class NIFTI::Stream

The Stream class handles string operations (encoding to and decoding from binary strings).

Attributes

equal_endian[R]

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

errors[R]

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

file[RW]

A File object to write to

format[R]

A hash of proper strings (based on endianess) to use for unpacking binary strings.

index[RW]

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

str_endian[R]

The endianness of the instance string.

string[RW]

The instance string.

Public Class Methods

new(binary, string_endian, options={}) click to toggle source

Creates a Stream instance.

Parameters

  • binary – A binary string.

  • string_endian – Boolean. The endianness of the instance string (true for big endian, false for small endian).

  • options – A hash of parameters.

Options

  • :index – Fixnum. A position (offset) in the instance string where reading will start.

# File lib/nifti/stream.rb, line 32
def initialize(binary, string_endian, options={})
  @string = binary || ""
  @index = options[:index] || 0
  @errors = Array.new
  self.endian = string_endian
end

Public Instance Methods

decode(length, type) click to toggle source

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

Notes

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

Parameters

  • length – Fixnum. The string length which will be decoded.

  • type – String. The type (vr) of data to decode.

# File lib/nifti/stream.rb, line 53
def decode(length, type)
  # 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"
      value = decode_tag
    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:
        # Do this using gsub instead of ruby-core #strip to keep trailing carriage
        # returns, etc., because that is valid whitespace that we want to have included
        # (i.e. in extended header data, AFNI writes a \n at the end of it's xml info,
        # and that must be kept in order to not change the file on writing back out).
        value.gsub!(/\000*$/, "") if value.respond_to? :gsub!
      end
      # Update our position in the string:
      skip(length)
    end
  end
  return value
end
encode(value, type) click to toggle source

Encodes a value and returns the resulting binary string.

Parameters

  • value – A custom value (String, Fixnum, etc..) or an array of numbers.

  • type – String. The type (vr) of data to encode.

# File lib/nifti/stream.rb, line 200
def encode(value, type)
  value = [value] unless value.is_a?(Array)
  return value.pack(vr_to_str(type))
end
encode_string_to_length(string, target_length, pad = :null) click to toggle source

Appends a string with trailling spaces to achieve a target length, and encodes it to a binary string. Returns the binary string. Raises an error if pad option is different than :null or :spaces

Parameters

  • string – A string to be processed.

  • target_length – Fixnum. The target length of the string that is created.

  • pad – Type of desired padding, either :null or :spaces

# File lib/nifti/stream.rb, line 214
def encode_string_to_length(string, target_length, pad = :null)
  if pad == :spaces
    template = "A#{target_length}"
  elsif pad == :null
    template = "a#{target_length}"
  else
    raise StandardError, "Could not identify padding type #{pad}"
  end
  
  length = string.length
  if length < target_length
    return [string].pack(template)
  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
length() click to toggle source

Returns the length of the binary instance string.

# File lib/nifti/stream.rb, line 85
def length
  return @string.length
end
reset() click to toggle source

Resets the instance string and index.

# File lib/nifti/stream.rb, line 105
def reset
  @string = ""
  @index = 0
end
reset_index() click to toggle source

Resets the instance index.

# File lib/nifti/stream.rb, line 112
def reset_index
  @index = 0
end
rest_length() click to toggle source

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

# File lib/nifti/stream.rb, line 91
def rest_length
  length = @string.length - @index
  return length
end
rest_string() click to toggle source

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

# File lib/nifti/stream.rb, line 98
def rest_string
  str = @string[@index..(@string.length-1)]
  return str
end
set_file(file) click to toggle source

Sets an instance file variable.

Notes

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.

Parameters

  • file – A File instance.

# File lib/nifti/stream.rb, line 127
def set_file(file)
  @file = file
end
set_string(binary) click to toggle source

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

Parameters

  • binary – A binary string.

# File lib/nifti/stream.rb, line 137
def set_string(binary)
  binary = binary[0] if binary.is_a?(Array)
  @string = binary
  @index = 0
end
skip(offset) click to toggle source

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

Parameters

  • offset – Fixnum. The length to skip (positive) or rewind (negative).

# File lib/nifti/stream.rb, line 149
def skip(offset)
  @index += offset
end
vr_to_str(vr) click to toggle source

Converts a data type/vr to an encode/decode string used by the pack/unpack methods, which is returned.

Parameters

  • vr – String. A data type (value representation).

# File lib/nifti/stream.rb, line 159
def vr_to_str(vr)
  unless @format[vr]
    errors << "Warning: Element type #{vr} does not have a reading method assigned to it. Something is not implemented correctly or the DICOM data analyzed is invalid."
    return @hex
  else
    return @format[vr]
  end
end
write(binary) click to toggle source

Writes a binary string to the File instance.

Parameters

  • binary – A binary string.

# File lib/nifti/stream.rb, line 189
def write(binary)
  @file.write(binary)
end

Private Instance Methods

configure_endian() click to toggle source

Determines the relationship between system and string endianness, and sets the instance endian variable.

# File lib/nifti/stream.rb, line 239
def configure_endian
  if CPU_ENDIAN == @str_endian
    @equal_endian = true
  else
    @equal_endian = false
  end
end
endian=(string_endian) click to toggle source

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).

# File lib/nifti/stream.rb, line 363
def endian=(string_endian)
  @str_endian = string_endian
  configure_endian
  set_string_formats
  set_format_hash
  set_pad_byte
end
set_format_hash() click to toggle source

Sets the hash which is used to convert data element types (VR) to encode/decode strings accepted by the pack/unpack methods.

# File lib/nifti/stream.rb, line 282
def set_format_hash
  @format = {
    "BY" => @by, # Byte/Character (1-byte integers)
    "US" => @us, # Unsigned short (2 bytes)
    "SS" => @ss, # Signed short (2 bytes)
    "UL" => @ul, # Unsigned long (4 bytes)
    "SL" => @sl, # Signed long (4 bytes)
    "FL" => @fs, # Floating point single (4 bytes)
    "FD" => @fd, # Floating point double (8 bytes)
    "OB" => @by, # Other byte string (1-byte integers)
    "OF" => @fs, # Other float string (4-byte floating point numbers)
    "OW" => @us, # Other word string (2-byte integers)
    "AT" => @hex, # Tag reference (4 bytes) NB: This may need to be revisited at some point...
    "UN" => @hex, # Unknown information (header element is not recognized from local database)
    "HEX" => @hex, # HEX
    # We have a number of VRs that are decoded as string:
    "AE" => @str,
    "AS" => @str,
    "CS" => @str,
    "DA" => @str,
    "DS" => @str,
    "DT" => @str,
    "IS" => @str,
    "LO" => @str,
    "LT" => @str,
    "PN" => @str,
    "SH" => @str,
    "ST" => @str,
    "TM" => @str,
    "UI" => @str,
    "UT" => @str,
    "STR" => @str
  }
end
set_pad_byte() click to toggle source

Sets the hash which is used to keep track of which bytes to use for padding data elements of various vr which have an odd value length.

# File lib/nifti/stream.rb, line 320
def set_pad_byte
  @pad_byte = {
    # Space character:
    "AE" => "\x20",
    "AS" => "\x20",
    "CS" => "\x20",
    "DA" => "\x20",
    "DS" => "\x20",
    "DT" => "\x20",
    "IS" => "\x20",
    "LO" => "\x20",
    "LT" => "\x20",
    "PN" => "\x20",
    "SH" => "\x20",
    "ST" => "\x20",
    "TM" => "\x20",
    "UT" => "\x20",
    # Zero byte:
    "AT" => "\x00",
    "FL" => "\x00",
    "FD" => "\x00",
    "OB" => "\x00",
    "OF" => "\x00",
    "OW" => "\x00",
    "SL" => "\x00",
    "SQ" => "\x00",
    "SS" => "\x00",
    "UI" => "\x00",
    "UL" => "\x00",
    "UN" => "\x00",
    "US" => "\x00"
  }
  @pad_byte.default = "\20"
end
set_string_formats() click to toggle source

Sets the pack/unpack format strings that is used for encoding/decoding. Some of these depends on the endianness of the system and the String.

# File lib/nifti/stream.rb, line 255
def set_string_formats
  if @equal_endian
    # Native byte order:
    @us = "S*" # Unsigned short (2 bytes)
    @ss = "s*" # Signed short (2 bytes)
    @ul = "I*" # Unsigned long (4 bytes)
    @sl = "l*" # Signed long (4 bytes)
    @fs = "e*" # Floating point single (4 bytes)
    @fd = "E*" # Floating point double ( 8 bytes)
  else
    # Network byte order:
    @us = "n*"
    @ss = CUSTOM_SS # Custom string for our redefined pack/unpack.
    @ul = "N*"
    @sl = CUSTOM_SL # Custom string for our redefined pack/unpack.
    @fs = "g*"
    @fd = "G*"
  end
  # Format strings that are not dependent on endianness:
  @by = "C*" # Unsigned char (1 byte)
  @str = "a*"
  @hex = "H*" # (this may be dependent on endianness(?))
end