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
# File 'lib/dicom/stream.rb', line 63

def decode(length, type)
  raise ArgumentError, "Invalid argument length. Expected Integer, got #{length.class}" unless length.is_a?(Integer)
  raise ArgumentError, "Invalid argument type. Expected string, got #{type.class}" unless type.is_a?(String)
  value = nil
  if (@index + length) <= @string.length
    # There are sufficient bytes remaining to extract the value:
    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)
      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
  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



100
101
102
103
104
105
# File 'lib/dicom/stream.rb', line 100

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)



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/dicom/stream.rb', line 111

def decode_tag
  length = 4
  tag = nil
  if (@index + length) <= @string.length
    # There are sufficient bytes remaining to extract a full tag:
    str = @string.slice(@index, length).unpack(@hex)[0].upcase
    if @equal_endian
      tag = "#{str[2..3]}#{str[0..1]},#{str[6..7]}#{str[4..5]}"
    else
      tag = "#{str[0..3]},#{str[4..7]}"
    end
    # Update our position in the string:
    skip(length)
  end
  tag
end

#encode(value, type) ⇒ String

Encodes a given value to a binary string.

Parameters:

  • value (String, Integer, Float, Array)

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

  • type (String)

    the type (vr) of data to encode

Returns:

  • (String)

    an encoded binary string

Raises:

  • (ArgumentError)


134
135
136
137
138
# File 'lib/dicom/stream.rb', line 134

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, Integer, etc..) or an array of numbers

  • type (String)

    the type (vr) of data to encode



145
146
147
148
# File 'lib/dicom/stream.rb', line 145

def encode_first(value, type)
  value = [value] unless value.is_a?(Array)
  @string = "#{value.pack(vr_to_str(type))}#{@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, Integer, etc..) or an array of numbers

  • type (String)

    the type (vr) of data to encode



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

def encode_last(value, type)
  value = [value] unless value.is_a?(Array)
  @string = "#{@string}#{value.pack(vr_to_str(type))}"
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



166
167
168
169
170
171
172
173
174
175
# File 'lib/dicom/stream.rb', line 166

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



182
183
184
185
186
# File 'lib/dicom/stream.rb', line 182

def encode_tag(tag)
  [
    @equal_endian ? "#{tag[2..3]}#{tag[0..1]}#{tag[7..8]}#{tag[5..6]}" : "#{tag[0..3]}#{tag[5..8]}"
  ].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, Integer, etc..) or an array of numbers

  • vr (String)

    the value representation of data to encode

Returns:

  • (String)

    the encoded binary string



195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/dicom/stream.rb', line 195

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)



216
217
218
219
220
221
222
# File 'lib/dicom/stream.rb', line 216

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)



231
232
233
234
235
236
237
238
239
# File 'lib/dicom/stream.rb', line 231

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



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

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



257
258
259
# File 'lib/dicom/stream.rb', line 257

def length
  return @string.length
end

#resetObject

Resets the instance string and index.



281
282
283
284
# File 'lib/dicom/stream.rb', line 281

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

#reset_indexObject

Resets the instance index.



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

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



265
266
267
268
# File 'lib/dicom/stream.rb', line 265

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



274
275
276
277
# File 'lib/dicom/stream.rb', line 274

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



299
300
301
# File 'lib/dicom/stream.rb', line 299

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



307
308
309
310
311
# File 'lib/dicom/stream.rb', line 307

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)



317
318
319
# File 'lib/dicom/stream.rb', line 317

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



325
326
327
# File 'lib/dicom/stream.rb', line 325

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