Class: DICOM::Stream

Inherits:
Object
  • Object
show all
Defined in:
lib/dicom/stream.rb

Overview

Note:

In practice, this class is for internal library use. It is typically not accessed by the user, and can thus be considered a ‘private’ class.

The Stream class handles string operations (encoding to and decoding from binary strings). It is used by the various classes of ruby-dicom for tasks such as reading and writing from/to files or network packets.

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(binary, string_endian, options = {}) ⇒ Stream

Creates a Stream instance.

Parameters:

  • binary (String, NilClass)

    a binary string (or nil, if creating an empty instance)

  • string_endian (Boolean)

    the endianness of the instance string (true for big endian, false for small endian)

  • options (Hash) (defaults to: {})

    the options to use for creating the instance

Options Hash (options):

  • :index (Integer)

    a position (offset) in the instance string where reading will start



32
33
34
35
36
37
# File 'lib/dicom/stream.rb', line 32

def initialize(binary, string_endian, options={})
  @string = binary || ''
  @index = options[:index] || 0
  @errors = Array.new
  self.endian = string_endian
end

Instance Attribute Details

#equal_endianObject (readonly)

A boolean which reports the relationship between the endianness of the system and the instance string.



13
14
15
# File 'lib/dicom/stream.rb', line 13

def equal_endian
  @equal_endian
end

#errorsObject (readonly)

An array of warning/error messages that (may) have been accumulated.



21
22
23
# File 'lib/dicom/stream.rb', line 21

def errors
  @errors
end

#indexObject

Our current position in the instance string (used only for decoding).



15
16
17
# File 'lib/dicom/stream.rb', line 15

def index
  @index
end

#pad_byteObject (readonly)

A hash with vr as key and its corresponding pad byte as value.



23
24
25
# File 'lib/dicom/stream.rb', line 23

def pad_byte
  @pad_byte
end

#str_endianObject (readonly)

The endianness of the instance string.



19
20
21
# File 'lib/dicom/stream.rb', line 19

def str_endian
  @str_endian
end

#stringObject

The instance string.



17
18
19
# File 'lib/dicom/stream.rb', line 17

def string
  @string
end

Instance Method Details

#add_first(binary) ⇒ Object

Prepends a pre-encoded string to the instance string (inserts at the beginning).

Parameters:

  • binary (String)

    a binary string



43
44
45
# File 'lib/dicom/stream.rb', line 43

def add_first(binary)
  @string = binary + @string if binary
end

#add_last(binary) ⇒ Object

Appends a pre-encoded string to the instance string (inserts at the end).

Parameters:

  • binary (String)

    a binary string



51
52
53
# File 'lib/dicom/stream.rb', line 51

def add_last(binary)
  @string = @string + binary if binary
end

#decode(length, type) ⇒ String, ...

Note:

If multiple numbers are decoded, these are returned in an array.

Decodes a section of the instance string. The instance index is offset in accordance with the length read.

Parameters:

  • length (Integer)

    the string length to be decoded

  • type (String)

    the type (vr) of data to decode

Returns:

  • (String, Integer, Float, Array)

    the formatted (decoded) data

Raises:

  • (ArgumentError)


63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
# File 'lib/dicom/stream.rb', line 63

def decode(length, type)
  raise ArgumentError, "Invalid argument length. Expected Fixnum, got #{length.class}" unless length.is_a?(Fixnum)
  raise ArgumentError, "Invalid argument type. Expected string, got #{type.class}" unless type.is_a?(String)
  # Check if values are valid:
  if (@index + length) > @string.length
    # The index number is bigger then the length of the binary string.
    # We have reached the end and will return nil.
    value = nil
  else
    if type == 'AT'
      # We need to guard ourselves against the case where a string contains an invalid 'AT' value:
      if length == 4
        value = decode_tag
      else
        # Invalid. Just return nil.
        skip(length)
        value = nil
      end
    else
      # Decode the binary string and return value:
      value = @string.slice(@index, length).unpack(vr_to_str(type))
      # If the result is an array of one element, return the element instead of the array.
      # If result is contained in a multi-element array, the original array is returned.
      if value.length == 1
        value = value[0]
        # If value is a string, strip away possible trailing whitespace:
        value = value.rstrip if value.is_a?(String)
      end
      # Update our position in the string:
      skip(length)
    end
  end
  return value
end

#decode_all(type) ⇒ String, ...

Note:

If multiple numbers are decoded, these are returned in an array.

Decodes the entire instance string (typically used for decoding image data).

Parameters:

  • type (String)

    the type (vr) of data to decode

Returns:

  • (String, Integer, Float, Array)

    the formatted (decoded) data



104
105
106
107
108
109
# File 'lib/dicom/stream.rb', line 104

def decode_all(type)
  length = @string.length
  value = @string.slice(@index, length).unpack(vr_to_str(type))
  skip(length)
  return value
end

#decode_tagString, NilClass

Decodes 4 bytes of the instance string and formats it as a ruby-dicom tag string.

Returns:

  • (String, NilClass)

    a formatted tag string (‘GGGG,EEEE’), or nil (e.g. if at end of string)



115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
# File 'lib/dicom/stream.rb', line 115

def decode_tag
  length = 4
  # Check if values are valid:
  if (@index + length) > @string.length
    # The index number is bigger then the length of the binary string.
    # We have reached the end and will return nil.
    tag = nil
  else
    # Decode and process:
    string = @string.slice(@index, length).unpack(@hex)[0].upcase
    if @equal_endian
      tag = string[2..3] + string[0..1] + ',' + string[6..7] + string[4..5]
    else
      tag = string[0..3] + ',' + string[4..7]
    end
    # Update our position in the string:
    skip(length)
  end
  return tag
end

#encode(value, type) ⇒ String

Encodes a given value to a binary string.

Parameters:

  • value (String, Integer, Float, Array)

    a formatted value (String, Fixnum, etc..) or an array of numbers

  • type (String)

    the type (vr) of data to encode

Returns:

  • (String)

    an encoded binary string

Raises:

  • (ArgumentError)


142
143
144
145
146
# File 'lib/dicom/stream.rb', line 142

def encode(value, type)
  raise ArgumentError, "Invalid argument type. Expected string, got #{type.class}" unless type.is_a?(String)
  value = [value] unless value.is_a?(Array)
  return value.pack(vr_to_str(type))
end

#encode_first(value, type) ⇒ Object

Encodes a value to a binary string and prepends it to the instance string.

Parameters:

  • value (String, Integer, Float, Array)

    a formatted value (String, Fixnum, etc..) or an array of numbers

  • type (String)

    the type (vr) of data to encode



153
154
155
156
157
# File 'lib/dicom/stream.rb', line 153

def encode_first(value, type)
  value = [value] unless value.is_a?(Array)
  bin = value.pack(vr_to_str(type))
  @string = bin + @string
end

#encode_last(value, type) ⇒ Object

Encodes a value to a binary string and appends it to the instance string.

Parameters:

  • value (String, Integer, Float, Array)

    a formatted value (String, Fixnum, etc..) or an array of numbers

  • type (String)

    the type (vr) of data to encode



164
165
166
167
168
# File 'lib/dicom/stream.rb', line 164

def encode_last(value, type)
  value = [value] unless value.is_a?(Array)
  bin = value.pack(vr_to_str(type))
  @string = @string + bin
end

#encode_string_with_trailing_spaces(string, target_length) ⇒ String

Appends a string with trailling spaces to achieve a target length, and encodes it to a binary string.

Parameters:

  • string (String)

    a string to be padded

  • target_length (Integer)

    the target length of the string

Returns:

  • (String)

    an encoded binary string



176
177
178
179
180
181
182
183
184
185
# File 'lib/dicom/stream.rb', line 176

def encode_string_with_trailing_spaces(string, target_length)
  length = string.length
  if length < target_length
    return [string].pack(@str)+['20'*(target_length-length)].pack(@hex)
  elsif length == target_length
    return [string].pack(@str)
  else
    raise "The specified string is longer than the allowed maximum length (String: #{string}, Target length: #{target_length})."
  end
end

#encode_tag(tag) ⇒ String

Encodes a tag from the ruby-dicom format (‘GGGG,EEEE’) to a proper binary string.

Parameters:

  • tag (String)

    a ruby-dicom type tag string

Returns:

  • (String)

    an encoded binary string



192
193
194
195
196
197
198
199
# File 'lib/dicom/stream.rb', line 192

def encode_tag(tag)
  if @equal_endian
    clean_tag = tag[2..3] + tag[0..1] + tag[7..8] + tag[5..6]
  else
    clean_tag = tag[0..3] + tag[5..8]
  end
  return [clean_tag].pack(@hex)
end

#encode_value(value, vr) ⇒ String

Encodes a value, and if the the resulting binary string has an odd length, appends a proper padding byte to make it even length.

Parameters:

  • value (String, Integer, Float, Array)

    a formatted value (String, Fixnum, etc..) or an array of numbers

  • vr (String)

    the value representation of data to encode

Returns:

  • (String)

    the encoded binary string



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/dicom/stream.rb', line 208

def encode_value(value, vr)
  if vr == 'AT'
    bin = encode_tag(value)
  else
    # Make sure the value is in an array:
    value = [value] unless value.is_a?(Array)
    # Get the proper pack string:
    type = vr_to_str(vr)
    # Encode:
    bin = value.pack(type)
    # Add an empty byte if the resulting binary has an odd length:
    bin = bin + @pad_byte[vr] if bin.length.odd?
  end
  return bin
end

#endian=(string_endian) ⇒ Object

Sets the endianness of the instance string. The relationship between the string endianness and the system endianness determines which encoding/decoding flags to use.

Parameters:

  • string_endian (Boolean)

    the endianness of the instance string (true for big endian, false for small endian)



229
230
231
232
233
234
235
# File 'lib/dicom/stream.rb', line 229

def endian=(string_endian)
  @str_endian = string_endian
  configure_endian
  set_pad_byte
  set_string_formats
  set_format_hash
end

#export(length = nil) ⇒ String

Note:

The exported string is removed from the instance string.

Extracts the entire instance string, or optionally, just the first part of it if a length is specified.

Parameters:

  • length (Integer) (defaults to: nil)

    the length of the string to cut out (if nil, the entire string is exported)

Returns:

  • (String)

    the instance string (or part of it)



244
245
246
247
248
249
250
251
252
# File 'lib/dicom/stream.rb', line 244

def export(length=nil)
  if length
    string = @string.slice!(0, length)
  else
    string = @string
    reset
  end
  return string
end

#extract(length) ⇒ String

Extracts and returns a binary string of the given length, starting at the index position. The instance index is then offset in accordance with the length read.

Parameters:

  • length (Integer)

    the length of the string to be extracted

Returns:

  • (String)

    a part of the instance string



260
261
262
263
264
# File 'lib/dicom/stream.rb', line 260

def extract(length)
  str = @string.slice(@index, length)
  skip(length)
  return str
end

#lengthInteger

Gives the length of the instance string.

Returns:

  • (Integer)

    the instance string’s length



270
271
272
# File 'lib/dicom/stream.rb', line 270

def length
  return @string.length
end

#resetObject

Resets the instance string and index.



294
295
296
297
# File 'lib/dicom/stream.rb', line 294

def reset
  @string = ''
  @index = 0
end

#reset_indexObject

Resets the instance index.



301
302
303
# File 'lib/dicom/stream.rb', line 301

def reset_index
  @index = 0
end

#rest_lengthInteger

Calculates the remaining length of the instance string (from the index position).

Returns:

  • (Integer)

    the remaining length of the instance string



278
279
280
281
# File 'lib/dicom/stream.rb', line 278

def rest_length
  length = @string.length - @index
  return length
end

#rest_stringString

Extracts the remaining part of the instance string (from the index position to the end of the string).

Returns:

  • (String)

    the remaining part of the instance string



287
288
289
290
# File 'lib/dicom/stream.rb', line 287

def rest_string
  str = @string[@index..(@string.length-1)]
  return str
end

#set_file(file) ⇒ Object

Note:

For performance reasons, we enable the Stream instance to write directly to file, to avoid expensive string operations which will otherwise slow down the write performance.

Sets the instance file variable.

Parameters:

  • file (File)

    a File object



312
313
314
# File 'lib/dicom/stream.rb', line 312

def set_file(file)
  @file = file
end

#set_string(binary) ⇒ Object

Sets a new instance string, and resets the index variable.

Parameters:

  • binary (String)

    an encoded string



320
321
322
323
324
# File 'lib/dicom/stream.rb', line 320

def set_string(binary)
  binary = binary[0] if binary.is_a?(Array)
  @string = binary
  @index = 0
end

#skip(offset) ⇒ Object

Applies an offset (positive or negative) to the instance index.

Parameters:

  • offset (Integer)

    the length to skip (positive) or rewind (negative)



330
331
332
# File 'lib/dicom/stream.rb', line 330

def skip(offset)
  @index += offset
end

#write(binary) ⇒ Object

Writes a binary string to the File object of this instance.

Parameters:

  • binary (String)

    a binary string



338
339
340
# File 'lib/dicom/stream.rb', line 338

def write(binary)
  @file.write(binary)
end