National Imagery Transmission Format: Ruby parsing library

The NITF (National Image Transition Format) format is a file format developed by the U.S. Government for storing imagery, e.g. from satellites.

According to the foreword of the specification:

The National Imagery Transmission Format Standard (NITFS) is the suite of standards for formatting digital imagery and imagery-related products and exchanging them among members of the Intelligence Community (IC) as defined by the Executive Order 12333, and other United States Government departments and agencies."

This implementation is set to version format (file_version) of 02.10 and standard_type of BF01. It was implemented by River Loop Security.

File extension

["ntf", "nitf", "ntf.r0", "ntf.r1", "ntf.r2", "ntf.r3", "ntf.r4", "ntf.r5"]

KS implementation details

License: MIT
Minimal Kaitai Struct required: 0.8

References

This page hosts a formal specification of National Imagery Transmission Format using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Runtime library

All parsing code for Ruby generated by Kaitai Struct depends on the Ruby runtime library. You have to install it before you can parse data.

The Ruby runtime library can be installed from RubyGems:

gem install kaitai-struct

Code

Parse a local file and get structure in memory:

data = Nitf.from_file("path/to/local/file.ntf")

Or parse structure from a string of bytes:

bytes = "\x00\x01\x02..."
data = Nitf.new(Kaitai::Struct::Stream.new(bytes))

After that, one can get various attributes from the structure by invoking getter methods like:

data.header # => get header

Ruby source code to parse National Imagery Transmission Format

nitf.rb

# This is a generated file! Please edit source .ksy file and use kaitai-struct-compiler to rebuild

require 'kaitai/struct/struct'

unless Gem::Version.new(Kaitai::Struct::VERSION) >= Gem::Version.new('0.9')
  raise "Incompatible Kaitai Struct Ruby API: 0.9 or later is required, but you have #{Kaitai::Struct::VERSION}"
end


##
# The NITF (National Image Transition Format) format is a file format developed by the U.S. Government for
# storing imagery, e.g. from satellites.
# 
# According to the [foreword of the specification](https://web.archive.org/web/20181105050336/https://www.gwg.nga.mil/ntb/baseline/docs/2500c/2500C.pdf):
# > The National Imagery Transmission Format Standard (NITFS) is the suite of standards for formatting digital
# > imagery and imagery-related products and exchanging them among members of the Intelligence Community (IC) as
# > defined by the Executive Order 12333, and other United States Government departments and agencies."
# 
# This implementation is set to version format (`file_version`) of 02.10 and `standard_type` of `BF01`.
# It was implemented by [River Loop Security](https://www.riverloopsecurity.com/).
# @see https://web.archive.org/web/20181105050336/https://www.gwg.nga.mil/ntb/baseline/docs/2500c/2500C.pdf Source
class Nitf < Kaitai::Struct::Struct
  def initialize(_io, _parent = nil, _root = self)
    super(_io, _parent, _root)
    _read
  end

  def _read
    @header = Header.new(@_io, self, @_root)
    @image_segments = []
    (header.num_image_segments.to_i).times { |i|
      @image_segments << ImageSegment.new(@_io, self, @_root, i)
    }
    @graphics_segments = []
    (header.num_graphics_segments.to_i).times { |i|
      @graphics_segments << GraphicsSegment.new(@_io, self, @_root, i)
    }
    @text_segments = []
    (header.num_text_files.to_i).times { |i|
      @text_segments << TextSegment.new(@_io, self, @_root, i)
    }
    @data_extension_segments = []
    (header.num_data_extension.to_i).times { |i|
      @data_extension_segments << DataExtensionSegment.new(@_io, self, @_root, i)
    }
    @reserved_extension_segments = []
    (header.num_reserved_extension.to_i).times { |i|
      @reserved_extension_segments << ReservedExtensionSegment.new(@_io, self, @_root, i)
    }
    self
  end
  class ReservedExtensionSegment < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self, idx)
      super(_io, _parent, _root)
      @idx = idx
      _read
    end

    def _read
      @_raw_reserved_sub_header = @_io.read_bytes(_parent.header.lrnfo[idx].length_reserved_extension_subheader.to_i)
      _io__raw_reserved_sub_header = Kaitai::Struct::Stream.new(@_raw_reserved_sub_header)
      @reserved_sub_header = ReservedSubHeader.new(_io__raw_reserved_sub_header, self, @_root)
      @reserved_data_field = @_io.read_bytes(_parent.header.lrnfo[idx].length_reserved_extension_segment.to_i)
      self
    end
    attr_reader :reserved_sub_header
    attr_reader :reserved_data_field
    attr_reader :idx
    attr_reader :_raw_reserved_sub_header
  end
  class ImageComment < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @_unnamed0 = (@_io.read_bytes(80)).force_encoding("UTF-8")
      self
    end
    attr_reader :_unnamed0
  end
  class LengthReservedInfo < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @length_reserved_extension_subheader = (@_io.read_bytes(4)).force_encoding("UTF-8")
      @length_reserved_extension_segment = (@_io.read_bytes(7)).force_encoding("UTF-8")
      self
    end
    attr_reader :length_reserved_extension_subheader
    attr_reader :length_reserved_extension_segment
  end
  class Tre < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @extension_type_id = (@_io.read_bytes(6)).force_encoding("UTF-8")
      @edata_length = (@_io.read_bytes(5)).force_encoding("UTF-8")
      @edata = (@_io.read_bytes(edata_length.to_i)).force_encoding("UTF-8")
      self
    end

    ##
    # RETAG or CETAG
    attr_reader :extension_type_id

    ##
    # REL or CEL
    attr_reader :edata_length

    ##
    # REDATA or CEDATA
    attr_reader :edata
  end
  class BandInfo < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @representation = (@_io.read_bytes(2)).force_encoding("UTF-8")
      @subcategory = (@_io.read_bytes(6)).force_encoding("UTF-8")
      @img_filter_condition = @_io.read_bytes(1)
      raise Kaitai::Struct::ValidationNotEqualError.new([78].pack('C*'), img_filter_condition, _io, "/types/band_info/seq/2") if not img_filter_condition == [78].pack('C*')
      @img_filter_code = (@_io.read_bytes(3)).force_encoding("UTF-8")
      @num_luts = (@_io.read_bytes(1)).force_encoding("UTF-8")
      if num_luts.to_i != 0
        @num_lut_entries = (@_io.read_bytes(5)).force_encoding("UTF-8")
      end
      @luts = []
      (num_luts.to_i).times { |i|
        @luts << @_io.read_bytes(num_lut_entries.to_i)
      }
      self
    end

    ##
    # Indicates processing required to display the nth band of image w.r.t. the general image type recorded by IREP field
    attr_reader :representation
    attr_reader :subcategory
    attr_reader :img_filter_condition

    ##
    # Reserved
    attr_reader :img_filter_code
    attr_reader :num_luts

    ##
    # Number of entries in each of the LUTs for the nth image band
    attr_reader :num_lut_entries
    attr_reader :luts
  end
  class ImageSegment < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self, idx)
      super(_io, _parent, _root)
      @idx = idx
      _read
    end

    def _read
      @image_sub_header = ImageSubHeader.new(@_io, self, @_root)
      if has_mask
        @image_data_mask = ImageDataMask.new(@_io, self, @_root)
      end
      if has_mask
        @image_data_field = @_io.read_bytes((_parent.header.linfo[idx].length_image_segment.to_i - image_data_mask.total_size))
      end
      self
    end
    def has_mask
      return @has_mask unless @has_mask.nil?
      @has_mask = image_sub_header.img_compression[0..(2 - 1)] == "MM"
      @has_mask
    end
    attr_reader :image_sub_header
    attr_reader :image_data_mask
    attr_reader :image_data_field
    attr_reader :idx
  end
  class TextSegment < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self, idx)
      super(_io, _parent, _root)
      @idx = idx
      _read
    end

    def _read
      @text_sub_header = @_io.read_bytes(1)
      @text_data_field = @_io.read_bytes(_parent.header.ltnfo[idx].length_text_segment.to_i)
      self
    end
    attr_reader :text_sub_header
    attr_reader :text_data_field
    attr_reader :idx
  end
  class GraphicSubHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @file_part_type_sy = @_io.read_bytes(2)
      raise Kaitai::Struct::ValidationNotEqualError.new([83, 89].pack('C*'), file_part_type_sy, _io, "/types/graphic_sub_header/seq/0") if not file_part_type_sy == [83, 89].pack('C*')
      @graphic_id = (@_io.read_bytes(10)).force_encoding("UTF-8")
      @graphic_name = (@_io.read_bytes(20)).force_encoding("UTF-8")
      @graphic_classification = Clasnfo.new(@_io, self, @_root)
      @encryption = Encrypt.new(@_io, self, @_root)
      @graphic_type = @_io.read_bytes(1)
      raise Kaitai::Struct::ValidationNotEqualError.new([67].pack('C*'), graphic_type, _io, "/types/graphic_sub_header/seq/5") if not graphic_type == [67].pack('C*')
      @reserved1 = (@_io.read_bytes(13)).force_encoding("UTF-8")
      @graphic_display_level = (@_io.read_bytes(3)).force_encoding("UTF-8")
      @graphic_attachment_level = (@_io.read_bytes(3)).force_encoding("UTF-8")
      @graphic_location = (@_io.read_bytes(10)).force_encoding("UTF-8")
      @first_graphic_bound_loc = (@_io.read_bytes(10)).force_encoding("UTF-8")
      @graphic_color = (@_io.read_bytes(1)).force_encoding("UTF-8")
      @second_graphic_bound_loc = (@_io.read_bytes(10)).force_encoding("UTF-8")
      @reserved2 = (@_io.read_bytes(2)).force_encoding("UTF-8")
      @graphics_extended_sub_header = TreHeader.new(@_io, self, @_root)
      self
    end
    attr_reader :file_part_type_sy
    attr_reader :graphic_id
    attr_reader :graphic_name
    attr_reader :graphic_classification
    attr_reader :encryption
    attr_reader :graphic_type

    ##
    # Reserved
    attr_reader :reserved1
    attr_reader :graphic_display_level
    attr_reader :graphic_attachment_level
    attr_reader :graphic_location
    attr_reader :first_graphic_bound_loc
    attr_reader :graphic_color
    attr_reader :second_graphic_bound_loc

    ##
    # Reserved
    attr_reader :reserved2
    attr_reader :graphics_extended_sub_header
  end
  class Clasnfo < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @security_class = (@_io.read_bytes(1)).force_encoding("UTF-8")
      @security_system = (@_io.read_bytes(2)).force_encoding("UTF-8")
      @codewords = (@_io.read_bytes(11)).force_encoding("UTF-8")
      @control_and_handling = (@_io.read_bytes(2)).force_encoding("UTF-8")
      @releaseability = (@_io.read_bytes(20)).force_encoding("UTF-8")
      @declass_type = (@_io.read_bytes(2)).force_encoding("UTF-8")
      @declass_date = (@_io.read_bytes(8)).force_encoding("UTF-8")
      @declass_exemption = (@_io.read_bytes(4)).force_encoding("UTF-8")
      @downgrade = (@_io.read_bytes(1)).force_encoding("UTF-8")
      @downgrade_date = (@_io.read_bytes(8)).force_encoding("UTF-8")
      @class_text = (@_io.read_bytes(43)).force_encoding("UTF-8")
      @class_authority_type = (@_io.read_bytes(1)).force_encoding("UTF-8")
      @class_authority = (@_io.read_bytes(40)).force_encoding("UTF-8")
      @class_reason = (@_io.read_bytes(1)).force_encoding("UTF-8")
      @source_date = (@_io.read_bytes(8)).force_encoding("UTF-8")
      @control_number = (@_io.read_bytes(15)).force_encoding("UTF-8")
      self
    end
    attr_reader :security_class
    attr_reader :security_system
    attr_reader :codewords
    attr_reader :control_and_handling
    attr_reader :releaseability
    attr_reader :declass_type
    attr_reader :declass_date
    attr_reader :declass_exemption
    attr_reader :downgrade
    attr_reader :downgrade_date
    attr_reader :class_text
    attr_reader :class_authority_type
    attr_reader :class_authority
    attr_reader :class_reason
    attr_reader :source_date
    attr_reader :control_number
  end
  class LengthGraphicInfo < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @length_graphic_subheader = (@_io.read_bytes(4)).force_encoding("UTF-8")
      @length_graphic_segment = (@_io.read_bytes(6)).force_encoding("UTF-8")
      self
    end
    attr_reader :length_graphic_subheader
    attr_reader :length_graphic_segment
  end
  class Encrypt < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @_unnamed0 = (@_io.read_bytes(1)).force_encoding("UTF-8")
      self
    end
    attr_reader :_unnamed0
  end
  class ImageDataMask < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @blocked_img_data_offset = @_io.read_u4be
      @bmrlnth = @_io.read_u2be
      @tmrlnth = @_io.read_u2be
      @tpxcdlnth = @_io.read_u2be
      @tpxcd = @_io.read_bytes(tpxcd_size)
      if has_bmr
        @bmrbnd = []
        (bmrtmr_count).times { |i|
          @bmrbnd << @_io.read_u4be
        }
      end
      if has_tmr
        @tmrbnd = []
        (bmrtmr_count).times { |i|
          @tmrbnd << @_io.read_u4be
        }
      end
      self
    end
    def has_bmr
      return @has_bmr unless @has_bmr.nil?
      @has_bmr = bmrlnth != 0
      @has_bmr
    end
    def has_tmr
      return @has_tmr unless @has_tmr.nil?
      @has_tmr = tmrlnth != 0
      @has_tmr
    end
    def tmrbnd_size
      return @tmrbnd_size unless @tmrbnd_size.nil?
      @tmrbnd_size = (has_tmr ? (bmrtmr_count * 4) : 0)
      @tmrbnd_size
    end
    def tpxcd_size
      return @tpxcd_size unless @tpxcd_size.nil?
      @tpxcd_size = (((tpxcdlnth % 8) == 0 ? tpxcdlnth : (tpxcdlnth + (8 - (tpxcdlnth % 8)))) / 8)
      @tpxcd_size
    end
    def total_size
      return @total_size unless @total_size.nil?
      @total_size = ((((((4 + 2) + 2) + 2) + tpxcd_size) + bmrbnd_size) + tmrbnd_size)
      @total_size
    end
    def bmrbnd_size
      return @bmrbnd_size unless @bmrbnd_size.nil?
      @bmrbnd_size = (has_bmr ? (bmrtmr_count * 4) : 0)
      @bmrbnd_size
    end
    def bmrtmr_count
      return @bmrtmr_count unless @bmrtmr_count.nil?
      @bmrtmr_count = ((_parent.image_sub_header.num_blocks_per_row.to_i * _parent.image_sub_header.num_blocks_per_col.to_i) * (_parent.image_sub_header.img_mode != "S" ? 1 : (_parent.image_sub_header.num_bands.to_i != 0 ? _parent.image_sub_header.num_bands.to_i : _parent.image_sub_header.num_multispectral_bands.to_i)))
      @bmrtmr_count
    end
    attr_reader :blocked_img_data_offset

    ##
    # Block Mask Record Length
    attr_reader :bmrlnth

    ##
    # Pad Pixel Mask Record Length
    attr_reader :tmrlnth

    ##
    # Pad Output Pixel Code Length
    attr_reader :tpxcdlnth

    ##
    # Pad Output Pixel Code
    attr_reader :tpxcd

    ##
    # Block n, Band m Offset
    attr_reader :bmrbnd

    ##
    # Pad Pixel n, Band m
    attr_reader :tmrbnd
  end
  class GraphicsSegment < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self, idx)
      super(_io, _parent, _root)
      @idx = idx
      _read
    end

    def _read
      @graphic_sub_header = GraphicSubHeader.new(@_io, self, @_root)
      @graphic_data_field = @_io.read_bytes(_parent.header.lnnfo[idx].length_graphic_segment.to_i)
      self
    end
    attr_reader :graphic_sub_header
    attr_reader :graphic_data_field
    attr_reader :idx
  end
  class DataSubHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @des_base = DataSubHeaderBase.new(@_io, self, @_root)
      if tre_ofl
        @overflowed_header_type = (@_io.read_bytes(6)).force_encoding("UTF-8")
      end
      if tre_ofl
        @data_item_overflowed = (@_io.read_bytes(3)).force_encoding("UTF-8")
      end
      @des_defined_subheader_fields_len = (@_io.read_bytes(4)).force_encoding("UTF-8")
      @desshf = (@_io.read_bytes(des_defined_subheader_fields_len.to_i)).force_encoding("UTF-8")
      @des_defined_data_field = (@_io.read_bytes_full).force_encoding("UTF-8")
      self
    end
    def tre_ofl
      return @tre_ofl unless @tre_ofl.nil?
      @tre_ofl = des_base.desid == "TRE_OVERFLOW"
      @tre_ofl
    end
    attr_reader :des_base
    attr_reader :overflowed_header_type
    attr_reader :data_item_overflowed
    attr_reader :des_defined_subheader_fields_len
    attr_reader :desshf
    attr_reader :des_defined_data_field
  end
  class DataExtensionSegment < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self, idx)
      super(_io, _parent, _root)
      @idx = idx
      _read
    end

    def _read
      @_raw_data_sub_header = @_io.read_bytes(_parent.header.ldnfo[idx].length_data_extension_subheader.to_i)
      _io__raw_data_sub_header = Kaitai::Struct::Stream.new(@_raw_data_sub_header)
      @data_sub_header = DataSubHeader.new(_io__raw_data_sub_header, self, @_root)
      @data_data_field = @_io.read_bytes(_parent.header.ldnfo[idx].length_data_extension_segment.to_i)
      self
    end
    attr_reader :data_sub_header
    attr_reader :data_data_field
    attr_reader :idx
    attr_reader :_raw_data_sub_header
  end
  class DataSubHeaderTre < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @des_base = DataSubHeaderBase.new(@_io, self, @_root)
      if des_base.desid == "TRE_OVERFLOW"
        @overflowed_header_type = (@_io.read_bytes(6)).force_encoding("UTF-8")
      end
      if des_base.desid == "TRE_OVERFLOW"
        @data_item_overflowed = (@_io.read_bytes(3)).force_encoding("UTF-8")
      end
      @des_defined_subheader_fields_len = (@_io.read_bytes(4)).force_encoding("UTF-8")
      @des_defined_data_field = (@_io.read_bytes(des_defined_subheader_fields_len.to_i)).force_encoding("UTF-8")
      self
    end
    attr_reader :des_base
    attr_reader :overflowed_header_type
    attr_reader :data_item_overflowed
    attr_reader :des_defined_subheader_fields_len
    attr_reader :des_defined_data_field
  end
  class ImageSubHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @file_part_type = @_io.read_bytes(2)
      raise Kaitai::Struct::ValidationNotEqualError.new([73, 77].pack('C*'), file_part_type, _io, "/types/image_sub_header/seq/0") if not file_part_type == [73, 77].pack('C*')
      @image_id_1 = (@_io.read_bytes(10)).force_encoding("UTF-8")
      @image_date_time = DateTime.new(@_io, self, @_root)
      @target_id = (@_io.read_bytes(17)).force_encoding("UTF-8")
      @image_id_2 = (@_io.read_bytes(80)).force_encoding("UTF-8")
      @image_security_classification = Clasnfo.new(@_io, self, @_root)
      @encryption = Encrypt.new(@_io, self, @_root)
      @image_source = (@_io.read_bytes(42)).force_encoding("UTF-8")
      @num_sig_rows = (@_io.read_bytes(8)).force_encoding("UTF-8")
      @num_sig_cols = (@_io.read_bytes(8)).force_encoding("UTF-8")
      @pixel_value_type = (@_io.read_bytes(3)).force_encoding("UTF-8")
      @image_representation = (@_io.read_bytes(8)).force_encoding("UTF-8")
      @image_category = (@_io.read_bytes(8)).force_encoding("UTF-8")
      @actual_bits_per_pixel_per_band = (@_io.read_bytes(2)).force_encoding("UTF-8")
      @pixel_justification = (@_io.read_bytes(1)).force_encoding("UTF-8")
      @image_coordinate_rep = (@_io.read_bytes(1)).force_encoding("UTF-8")
      @image_geo_loc = (@_io.read_bytes(60)).force_encoding("UTF-8")
      @num_img_comments = (@_io.read_bytes(1)).force_encoding("UTF-8")
      @img_comments = []
      (num_img_comments.to_i).times { |i|
        @img_comments << ImageComment.new(@_io, self, @_root)
      }
      @img_compression = (@_io.read_bytes(2)).force_encoding("UTF-8")
      @compression_rate_code = (@_io.read_bytes(4)).force_encoding("UTF-8")
      @num_bands = (@_io.read_bytes(1)).force_encoding("UTF-8")
      if num_bands.to_i == 0
        @num_multispectral_bands = (@_io.read_bytes(5)).force_encoding("UTF-8")
      end
      @bands = []
      ((num_bands.to_i != 0 ? num_bands.to_i : num_multispectral_bands.to_i)).times { |i|
        @bands << BandInfo.new(@_io, self, @_root)
      }
      @img_sync_code = (@_io.read_bytes(1)).force_encoding("UTF-8")
      @img_mode = (@_io.read_bytes(1)).force_encoding("UTF-8")
      @num_blocks_per_row = (@_io.read_bytes(4)).force_encoding("UTF-8")
      @num_blocks_per_col = (@_io.read_bytes(4)).force_encoding("UTF-8")
      @num_pixels_per_block_horz = (@_io.read_bytes(4)).force_encoding("UTF-8")
      @num_pixels_per_block_vert = (@_io.read_bytes(4)).force_encoding("UTF-8")
      @num_pixels_per_band = (@_io.read_bytes(2)).force_encoding("UTF-8")
      @img_display_level = (@_io.read_bytes(3)).force_encoding("UTF-8")
      @attachment_level = (@_io.read_bytes(3)).force_encoding("UTF-8")
      @img_location = (@_io.read_bytes(10)).force_encoding("UTF-8")
      @img_magnification = (@_io.read_bytes(4)).force_encoding("UTF-8")
      @user_def_img_data_len = (@_io.read_bytes(5)).force_encoding("UTF-8")
      if user_def_img_data_len.to_i != 0
        @user_def_overflow = (@_io.read_bytes(3)).force_encoding("UTF-8")
      end
      if user_def_img_data_len.to_i > 2
        @user_def_img_data = []
        ((user_def_img_data_len.to_i - 3)).times { |i|
          @user_def_img_data << @_io.read_u1
        }
      end
      @image_extended_sub_header = TreHeader.new(@_io, self, @_root)
      self
    end
    attr_reader :file_part_type
    attr_reader :image_id_1
    attr_reader :image_date_time
    attr_reader :target_id
    attr_reader :image_id_2
    attr_reader :image_security_classification
    attr_reader :encryption
    attr_reader :image_source

    ##
    # Total number of rows of significant pixels in the image; only rows indexed 0 to (NROWS - 1) of the image contain significant data.
    attr_reader :num_sig_rows
    attr_reader :num_sig_cols
    attr_reader :pixel_value_type

    ##
    # MONO, RGB, RGB/LUT, MULTI, NODISPLY, NVECTOR, POLAR, VPH, YCbCr601
    attr_reader :image_representation

    ##
    # VIS, SL, TI, FL, RD, EO, OP, HR, HS,CP, BP, SAR, SARIQ, IR, MAP, MS, FP, MRI, XRAY, CAT, VD, PAT, LEG, DTEM, MATR, LOCG, BARO, CURRENT, DEPTH, WIND
    attr_reader :image_category
    attr_reader :actual_bits_per_pixel_per_band
    attr_reader :pixel_justification
    attr_reader :image_coordinate_rep
    attr_reader :image_geo_loc
    attr_reader :num_img_comments
    attr_reader :img_comments
    attr_reader :img_compression
    attr_reader :compression_rate_code
    attr_reader :num_bands
    attr_reader :num_multispectral_bands
    attr_reader :bands

    ##
    # Reserved for future use.
    attr_reader :img_sync_code

    ##
    # B = Band Interleaved by Block, P = Band Interleaved by Pixel, R = Band Interleaved by Row, S = Band Sequential
    attr_reader :img_mode
    attr_reader :num_blocks_per_row
    attr_reader :num_blocks_per_col
    attr_reader :num_pixels_per_block_horz
    attr_reader :num_pixels_per_block_vert
    attr_reader :num_pixels_per_band
    attr_reader :img_display_level
    attr_reader :attachment_level
    attr_reader :img_location
    attr_reader :img_magnification
    attr_reader :user_def_img_data_len
    attr_reader :user_def_overflow
    attr_reader :user_def_img_data
    attr_reader :image_extended_sub_header
  end
  class ReservedSubHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @file_part_type_re = @_io.read_bytes(2)
      raise Kaitai::Struct::ValidationNotEqualError.new([82, 69].pack('C*'), file_part_type_re, _io, "/types/reserved_sub_header/seq/0") if not file_part_type_re == [82, 69].pack('C*')
      @res_type_id = (@_io.read_bytes(25)).force_encoding("UTF-8")
      @res_version = (@_io.read_bytes(2)).force_encoding("UTF-8")
      @reclasnfo = Clasnfo.new(@_io, self, @_root)
      @res_user_defined_subheader_length = (@_io.read_bytes(4)).force_encoding("UTF-8")
      @res_user_defined_subheader_fields = (@_io.read_bytes(res_user_defined_subheader_length.to_i)).force_encoding("UTF-8")
      @res_user_defined_data = (@_io.read_bytes_full).force_encoding("UTF-8")
      self
    end
    attr_reader :file_part_type_re
    attr_reader :res_type_id
    attr_reader :res_version
    attr_reader :reclasnfo
    attr_reader :res_user_defined_subheader_length
    attr_reader :res_user_defined_subheader_fields
    attr_reader :res_user_defined_data
  end
  class DataSubHeaderBase < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @file_part_type_de = @_io.read_bytes(2)
      raise Kaitai::Struct::ValidationNotEqualError.new([68, 69].pack('C*'), file_part_type_de, _io, "/types/data_sub_header_base/seq/0") if not file_part_type_de == [68, 69].pack('C*')
      @desid = (@_io.read_bytes(25)).force_encoding("UTF-8")
      @data_definition_version = (@_io.read_bytes(2)).force_encoding("UTF-8")
      @declasnfo = Clasnfo.new(@_io, self, @_root)
      self
    end

    ##
    # File Part Type desigantor for Data Extension
    attr_reader :file_part_type_de
    attr_reader :desid
    attr_reader :data_definition_version
    attr_reader :declasnfo
  end
  class TextSubHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @text_date_time = (@_io.read_bytes(14)).force_encoding("UTF-8")
      @text_title = (@_io.read_bytes(80)).force_encoding("UTF-8")
      @text_security_class = Clasnfo.new(@_io, self, @_root)
      @encryp = Encrypt.new(@_io, self, @_root)
      @text_format = (@_io.read_bytes(3)).force_encoding("UTF-8")
      @text_extended_sub_header = TreHeader.new(@_io, self, @_root)
      self
    end
    attr_reader :text_date_time
    attr_reader :text_title
    attr_reader :text_security_class
    attr_reader :encryp

    ##
    # MTF (USMTF see MIL-STD-6040), STA (indicates BCS), UT1 (indicates ECS), U8S
    attr_reader :text_format
    attr_reader :text_extended_sub_header
  end
  class DateTime < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @_unnamed0 = (@_io.read_bytes(14)).force_encoding("UTF-8")
      self
    end

    ##
    # UTC time of image acquisition in the format CCYYMMDDhhmmss: CC century, YY last two digits of the year, MM month, DD day, hh hour, mm minute, ss second
    attr_reader :_unnamed0
  end
  class Header < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @file_profile_name = @_io.read_bytes(4)
      raise Kaitai::Struct::ValidationNotEqualError.new([78, 73, 84, 70].pack('C*'), file_profile_name, _io, "/types/header/seq/0") if not file_profile_name == [78, 73, 84, 70].pack('C*')
      @file_version = @_io.read_bytes(5)
      raise Kaitai::Struct::ValidationNotEqualError.new([48, 50, 46, 49, 48].pack('C*'), file_version, _io, "/types/header/seq/1") if not file_version == [48, 50, 46, 49, 48].pack('C*')
      @complexity_level = @_io.read_bytes(2)
      @standard_type = @_io.read_bytes(4)
      raise Kaitai::Struct::ValidationNotEqualError.new([66, 70, 48, 49].pack('C*'), standard_type, _io, "/types/header/seq/3") if not standard_type == [66, 70, 48, 49].pack('C*')
      @originating_station_id = (@_io.read_bytes(10)).force_encoding("UTF-8")
      @file_date_time = DateTime.new(@_io, self, @_root)
      @file_title = (@_io.read_bytes(80)).force_encoding("UTF-8")
      @file_security = Clasnfo.new(@_io, self, @_root)
      @file_copy_number = (@_io.read_bytes(5)).force_encoding("UTF-8")
      @file_num_of_copys = (@_io.read_bytes(5)).force_encoding("UTF-8")
      @encryption = Encrypt.new(@_io, self, @_root)
      @file_bg_color = @_io.read_bytes(3)
      @originator_name = (@_io.read_bytes(24)).force_encoding("UTF-8")
      @originator_phone = (@_io.read_bytes(18)).force_encoding("UTF-8")
      @file_length = (@_io.read_bytes(12)).force_encoding("UTF-8")
      @file_header_length = (@_io.read_bytes(6)).force_encoding("UTF-8")
      @num_image_segments = (@_io.read_bytes(3)).force_encoding("UTF-8")
      @linfo = []
      (num_image_segments.to_i).times { |i|
        @linfo << LengthImageInfo.new(@_io, self, @_root)
      }
      @num_graphics_segments = (@_io.read_bytes(3)).force_encoding("UTF-8")
      @lnnfo = []
      (num_graphics_segments.to_i).times { |i|
        @lnnfo << LengthGraphicInfo.new(@_io, self, @_root)
      }
      @reserved_numx = (@_io.read_bytes(3)).force_encoding("UTF-8")
      @num_text_files = (@_io.read_bytes(3)).force_encoding("UTF-8")
      @ltnfo = []
      (num_text_files.to_i).times { |i|
        @ltnfo << LengthTextInfo.new(@_io, self, @_root)
      }
      @num_data_extension = (@_io.read_bytes(3)).force_encoding("UTF-8")
      @ldnfo = []
      (num_data_extension.to_i).times { |i|
        @ldnfo << LengthDataInfo.new(@_io, self, @_root)
      }
      @num_reserved_extension = (@_io.read_bytes(3)).force_encoding("UTF-8")
      @lrnfo = []
      (num_reserved_extension.to_i).times { |i|
        @lrnfo << LengthReservedInfo.new(@_io, self, @_root)
      }
      @user_defined_header = TreHeader.new(@_io, self, @_root)
      @extended_header = TreHeader.new(@_io, self, @_root)
      self
    end
    attr_reader :file_profile_name
    attr_reader :file_version
    attr_reader :complexity_level

    ##
    # Value of BF01 indicates the file is formatted using ISO/IEC IS 12087-5.
    attr_reader :standard_type
    attr_reader :originating_station_id
    attr_reader :file_date_time
    attr_reader :file_title
    attr_reader :file_security
    attr_reader :file_copy_number
    attr_reader :file_num_of_copys
    attr_reader :encryption
    attr_reader :file_bg_color
    attr_reader :originator_name
    attr_reader :originator_phone
    attr_reader :file_length
    attr_reader :file_header_length
    attr_reader :num_image_segments
    attr_reader :linfo
    attr_reader :num_graphics_segments
    attr_reader :lnnfo
    attr_reader :reserved_numx
    attr_reader :num_text_files
    attr_reader :ltnfo
    attr_reader :num_data_extension
    attr_reader :ldnfo
    attr_reader :num_reserved_extension
    attr_reader :lrnfo
    attr_reader :user_defined_header
    attr_reader :extended_header
  end

  ##
  # Streaming file Header Data Extension Segment Subheader
  class DataSubHeaderStreaming < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @des_base = DataSubHeaderBase.new(@_io, self, @_root)
      @des_defined_subheader_fields_len = (@_io.read_bytes(4)).force_encoding("UTF-8")
      @sfh_l1 = (@_io.read_bytes(7)).force_encoding("UTF-8")
      @sfh_delim1 = @_io.read_u4be
      @sfh_dr = []
      (sfh_l1.to_i).times { |i|
        @sfh_dr << @_io.read_u1
      }
      @sfh_delim2 = @_io.read_u4be
      @sfh_l2 = (@_io.read_bytes(7)).force_encoding("UTF-8")
      self
    end
    attr_reader :des_base
    attr_reader :des_defined_subheader_fields_len

    ##
    # SFH Length 1: number of bytes in sfh_dr field
    attr_reader :sfh_l1

    ##
    # Shall contain the value 0x0A6E1D97.
    attr_reader :sfh_delim1
    attr_reader :sfh_dr

    ##
    # Shall contain the value 0x0ECA14BF.
    attr_reader :sfh_delim2

    ##
    # A repeat of sfh_l1.
    attr_reader :sfh_l2
  end
  class TreHeader < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @header_data_length = (@_io.read_bytes(5)).force_encoding("UTF-8")
      if header_data_length.to_i != 0
        @header_overflow = (@_io.read_bytes(3)).force_encoding("UTF-8")
      end
      if header_data_length.to_i > 2
        @header_data = []
        ((header_data_length.to_i - 3)).times { |i|
          @header_data << @_io.read_u1
        }
      end
      self
    end
    attr_reader :header_data_length
    attr_reader :header_overflow
    attr_reader :header_data
  end
  class LengthImageInfo < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @length_image_subheader = (@_io.read_bytes(6)).force_encoding("UTF-8")
      @length_image_segment = (@_io.read_bytes(10)).force_encoding("UTF-8")
      self
    end
    attr_reader :length_image_subheader
    attr_reader :length_image_segment
  end
  class LengthDataInfo < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @length_data_extension_subheader = (@_io.read_bytes(4)).force_encoding("UTF-8")
      @length_data_extension_segment = (@_io.read_bytes(9)).force_encoding("UTF-8")
      self
    end
    attr_reader :length_data_extension_subheader
    attr_reader :length_data_extension_segment
  end
  class LengthTextInfo < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @length_text_subheader = (@_io.read_bytes(4)).force_encoding("UTF-8")
      @length_text_segment = (@_io.read_bytes(5)).force_encoding("UTF-8")
      self
    end
    attr_reader :length_text_subheader
    attr_reader :length_text_segment
  end
  attr_reader :header
  attr_reader :image_segments
  attr_reader :graphics_segments
  attr_reader :text_segments
  attr_reader :data_extension_segments
  attr_reader :reserved_extension_segments
end