class NIFTI::NRead

The NRead class parses the NIFTI data from a binary string.

Constants

MAGIC

Valid Magic codes for the NIFTI Header

Attributes

extended_header[R]

An array of nifti header extension hashes with keys esize, ecode and data.

hdr[R]

A hash containing header attributes.

image_narray[R]

A narray of image values reshapred to image dimensions

image_rubyarray[R]

An array of decoded image values

msg[R]

An array which records any status messages that are generated while parsing the DICOM string.

success[R]

A boolean which reports whether the NIFTI string was parsed successfully (true) or not (false).

Public Class Methods

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

Create a NRead object to parse a nifti file or binary string and set header and image info instance variables.

The nifti header will be checked for validity (header size and magic number) and will raise an IOError if invalid.

NIFTI header extensions are not yet supported and are not included in the header.

The header and image are accessible via the hdr and image instance variables. An optional narray matrix may also be available in image_narray if desired by passing in :narray => true as an option.

Parameters

  • source – A string which specifies either the path of a NIFTI file to be loaded, or a binary NIFTI string to be parsed.

  • options – A hash of parameters.

Options

  • :bin – Boolean. If set to true, string parameter will be interpreted as a binary NIFTI string, and not a path string, which is the default behaviour.

  • :image – Boolean. If set to true, automatically load the image into @image, otherwise only a header is collected and you can get an image

  • :narray – Boolean. If set to true, a properly shaped narray matrix will be set in the instance variable @image_narray. Automatically sets :image => true

# File lib/nifti/n_read.rb, line 42
def initialize(source=nil, options={})
  options[:image] = true if options[:narray]
  @msg = []
  @success = false
  set_stream(source, options)
  parse_header(options)

  return self
end

Public Instance Methods

get_image_narray(image_array, dim) click to toggle source

Create an narray if the NArray is available Tests if a file is readable, and if so, opens it.

Parameters

  • image_array – Array. A vector of image data.

  • dim – Array. The dim array from the nifti header, specifing number of dimensions (dim) and dimension length of other dimensions to reshape narray into.

# File lib/nifti/n_read.rb, line 76
def get_image_narray(image_array, dim)
  if Object.const_defined?('NArray')
    @image_narray = pixel_data = NArray.to_na(image_array).reshape!(*dim[1..dim[0]])
  else
    add_msg "Can't find NArray, no image_narray created.  Please `gem install narray`"
  end
end
read_image() click to toggle source

Unpack an image array from vox_offset to the end of a nifti file.

Parameters

There are no parameters - this reads from the binary string in the @string instance variable.

This sets @image_rubyarray to the image data vector and also returns it.

# File lib/nifti/n_read.rb, line 60
def read_image
  raw_image = []
  @stream.index = @hdr['vox_offset']
  type = NIFTI_DATATYPES[@hdr['datatype']]
  format = @stream.format[type]
  @image_rubyarray = @stream.decode(@stream.rest_length, type)
end

Private Instance Methods

add_msg(msg) click to toggle source

Add a message (TODO: and maybe print to screen if verbose)

# File lib/nifti/n_read.rb, line 265
def add_msg(msg)
  @msg << msg
end
check_header() click to toggle source

NIFTI uses the header length (first 4 bytes) to be 348 number of “ni10” or “n+10” as the last 4 bytes to be magic numbers to validate the header.

The header is usually checked before any data is read, but can be checked at any point in the process as the stream index is reset to its original position after validation.

There are no options - the method will raise an IOError if any of the magic numbers are not valid.

# File lib/nifti/n_read.rb, line 134
def check_header
  begin
    starting_index = @stream.index

    # Check sizeof_hdr
    @stream.index = 0;
    sizeof_hdr = @stream.decode(4, "UL")
    raise IOError, "Bad Header Length #{sizeof_hdr}" unless sizeof_hdr == 348

    # Check magic
    @stream.index = 344;
    magic = @stream.decode(4, "STR")
    raise IOError, "Bad Magic Code #{magic} (should be ni1 or n+1)" unless MAGIC.include?(magic)

  rescue IOError => e
    raise IOError, "Header appears to be malformed: #{e}"
  else
    @stream.index = starting_index
  end
end
dim_info_to_freq_dim(dim_info) click to toggle source

Bitwise Operator to extract Frequency Dimension

# File lib/nifti/n_read.rb, line 238
def dim_info_to_freq_dim(dim_info)
  extract_dim_info(dim_info, 0)
end
dim_info_to_phase_dim(dim_info) click to toggle source

Bitwise Operator to extract Phase Dimension

# File lib/nifti/n_read.rb, line 243
def dim_info_to_phase_dim(dim_info)
  extract_dim_info(dim_info, 2)
end
dim_info_to_slice_dim(dim_info) click to toggle source

Bitwise Operator to extract Slice Dimension

# File lib/nifti/n_read.rb, line 248
def dim_info_to_slice_dim(dim_info)
  extract_dim_info(dim_info, 4)
end
extract_dim_info(dim_info, offset = 0) click to toggle source

Bitwise Operator to extract Frequency, Phase & Slice Dimensions from 2byte diminfo

# File lib/nifti/n_read.rb, line 253
def extract_dim_info(dim_info, offset = 0)
  (dim_info >> offset) & 0x03
end
fps_into_dim_info(frequency_dim, phase_dim, slice_dim) click to toggle source

Bitwise Operator to encode Freq, Phase & Slice into Diminfo

# File lib/nifti/n_read.rb, line 258
def fps_into_dim_info(frequency_dim, phase_dim, slice_dim)
  ((frequency_dim & 0x03 ) << 0 ) |
  ((phase_dim & 0x03) << 2 ) |
  ((slice_dim & 0x03) << 4 )
end
open_file(file) click to toggle source

Tests if a file is readable, and if so, opens it.

Parameters

  • file – A path/file string.

# File lib/nifti/n_read.rb, line 213
def open_file(file)
  if File.exist?(file)
    if File.readable?(file)
      if not File.directory?(file)
        if File.size(file) > 8
          begin
            @file = Zlib::GzipReader.new(File.new(file, "rb"))
          rescue Zlib::GzipFile::Error
            @file = File.new(file, "rb")
          end
        else
          @msg << "Error! File is too small to contain DICOM information (#{file})."
        end
      else
        @msg << "Error! File is a directory (#{file})."
      end
    else
      @msg << "Error! File exists but I don't have permission to read it (#{file})."
    end
  else
    @msg << "Error! The file you have supplied does not exist (#{file})."
  end
end
parse_basic_header() click to toggle source

Read the nifti header according to its byte signature. The file stream will be left open and should be positioned at the end of the 348 byte header.

# File lib/nifti/n_read.rb, line 157
def parse_basic_header
  # The HEADER_SIGNATURE is defined in NIFTI::Constants and used for both reading and writing.
  header = {}
  HEADER_SIGNATURE.each do |header_item|
    name, length, type = *header_item
    header[name] = @stream.decode(length, type)
  end

  # Extract Freq, Phase & Slice Dimensions from diminfo
  if header['dim_info']
    header['freq_dim'] = dim_info_to_freq_dim(header['dim_info'])
    header['phase_dim'] = dim_info_to_phase_dim(header['dim_info'])
    header['slice_dim'] = dim_info_to_slice_dim(header['dim_info'])
  end
  header['sform_code_descr'] = XFORM_CODES[header['sform_code']]
  header['qform_code_descr'] = XFORM_CODES[header['qform_code']]

  return header
end
parse_extended_header() click to toggle source

Read any extended header information. The file stream will be left at imaging data itself, taking vox_offset into account for NIFTI Header Extended Attributes. Pass in the voxel offset so the extended header knows when to stop reading.

# File lib/nifti/n_read.rb, line 181
def parse_extended_header
  extended = []
  extension = @stream.decode(4, "BY")

  # "After the end of the 348 byte header (e.g., after the magic field),
  # the next 4 bytes are an byte array field named extension. By default,
  # all 4 bytes of this array should be set to zero. In a .nii file, these 4
  # bytes will always be present, since the earliest start point for the
  # image data is byte #352. In a separate .hdr file, these bytes may or may
  # not be present (i.e., a .hdr file may only be 348 bytes long). If not
  # present, then a NIfTI-1.1 compliant program should use the default value
  # of extension={0,0,0,0}. The first byte (extension[0]) is the only value
  # of this array that is specified at present. The other 3 bytes are
  # reserved for future use."
  if extension[0] != 0
    while @stream.index < @hdr['vox_offset']
      esize, ecode = *@stream.decode(8, "UL")
      data = @stream.decode(esize - 8, "STR")
      extended << {:esize => esize, :ecode => ecode, :data => data}
    end
    # stream.decode(header['vox_offset'] - stream.index, "STR")
    # stream.skip header['vox_offset'] - stream.index
  end
  return extended
end
parse_header(options = {}) click to toggle source

Parse the NIFTI Header.

# File lib/nifti/n_read.rb, line 113
def parse_header(options = {})
  check_header
  @hdr = parse_basic_header
  @extended_header = parse_extended_header

  # Optional image gathering
  read_image if options[:image]
  get_image_narray(@image_rubyarray, @hdr['dim']) if options[:narray]

  @success = true
end
set_stream(source, options) click to toggle source

Initializes @stream from a binary string or filename

# File lib/nifti/n_read.rb, line 87
def set_stream(source, options)
  # Are we going to read from a file, or read from a binary string?
  if options[:bin]
    # Read from the provided binary string:
    @str = source
  else
    # Read from file:
    open_file(source)
    # Read the initial header of the file:
    if @file == nil
      # File is not readable, so we return:
      @success = false
      return
    else
      # Extract the content of the file to a binary string:
      @str = @file.read
      @file.close
    end
  end

  # Create a Stream instance to handle the decoding of content from this binary string:
  @file_endian = false
  @stream = Stream.new(@str, false)
end