NT-MDT data: Ruby parsing library

A native file format of NT-MDT scientific software. Usually contains any of:

Some examples of mdt files can be downloaded at:

Application

["Nova", "Image Analysis", "NanoEducator", "Gwyddion", "Callisto"]

File extension

mdt

KS implementation details

License: GPL-3.0-or-later

This page hosts a formal specification of NT-MDT data 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 = NtMdt.from_file("path/to/local/file.mdt")

Or parse structure from a string of bytes:

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

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

data.size # => File size (w/o header)

Ruby source code to parse NT-MDT data

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


##
# A native file format of NT-MDT scientific software. Usually contains
# any of:
# 
# * [Scanning probe](https://en.wikipedia.org/wiki/Scanning_probe_microscopy) microscopy scans and spectra
# * [Raman spectra](https://en.wikipedia.org/wiki/Raman_spectroscopy)
# * results of their analysis
# 
# Some examples of mdt files can be downloaded at:
# 
# * <https://www.ntmdt-si.ru/resources/scan-gallery>
# * <http://callistosoft.narod.ru/Resources/Mdt.zip>
# @see https://svn.code.sf.net/p/gwyddion/code/trunk/gwyddion/modules/file/nt-mdt.c Source
class NtMdt < Kaitai::Struct::Struct

  ADC_MODE = {
    0 => :adc_mode_height,
    1 => :adc_mode_dfl,
    2 => :adc_mode_lateral_f,
    3 => :adc_mode_bias_v,
    4 => :adc_mode_current,
    5 => :adc_mode_fb_out,
    6 => :adc_mode_mag,
    7 => :adc_mode_mag_sin,
    8 => :adc_mode_mag_cos,
    9 => :adc_mode_rms,
    10 => :adc_mode_calc_mag,
    11 => :adc_mode_phase1,
    12 => :adc_mode_phase2,
    13 => :adc_mode_calc_phase,
    14 => :adc_mode_ex1,
    15 => :adc_mode_ex2,
    16 => :adc_mode_hv_x,
    17 => :adc_mode_hv_y,
    18 => :adc_mode_snap_back,
    255 => :adc_mode_false,
  }
  I__ADC_MODE = ADC_MODE.invert

  XML_SCAN_LOCATION = {
    0 => :xml_scan_location_hlt,
    1 => :xml_scan_location_hlb,
    2 => :xml_scan_location_hrt,
    3 => :xml_scan_location_hrb,
    4 => :xml_scan_location_vlt,
    5 => :xml_scan_location_vlb,
    6 => :xml_scan_location_vrt,
    7 => :xml_scan_location_vrb,
  }
  I__XML_SCAN_LOCATION = XML_SCAN_LOCATION.invert

  DATA_TYPE = {
    -65544 => :data_type_floatfix,
    -16138 => :data_type_float80,
    -13320 => :data_type_float64,
    -9990 => :data_type_float48,
    -5892 => :data_type_float32,
    -8 => :data_type_int64,
    -4 => :data_type_int32,
    -2 => :data_type_int16,
    -1 => :data_type_int8,
    0 => :data_type_unknown0,
    1 => :data_type_uint8,
    2 => :data_type_uint16,
    4 => :data_type_uint32,
    8 => :data_type_uint64,
  }
  I__DATA_TYPE = DATA_TYPE.invert

  XML_PARAM_TYPE = {
    0 => :xml_param_type_none,
    1 => :xml_param_type_laser_wavelength,
    2 => :xml_param_type_units,
    255 => :xml_param_type_data_array,
  }
  I__XML_PARAM_TYPE = XML_PARAM_TYPE.invert

  SPM_MODE = {
    0 => :spm_mode_constant_force,
    1 => :spm_mode_contact_constant_height,
    2 => :spm_mode_contact_error,
    3 => :spm_mode_lateral_force,
    4 => :spm_mode_force_modulation,
    5 => :spm_mode_spreading_resistance_imaging,
    6 => :spm_mode_semicontact_topography,
    7 => :spm_mode_semicontact_error,
    8 => :spm_mode_phase_contrast,
    9 => :spm_mode_ac_magnetic_force,
    10 => :spm_mode_dc_magnetic_force,
    11 => :spm_mode_electrostatic_force,
    12 => :spm_mode_capacitance_contrast,
    13 => :spm_mode_kelvin_probe,
    14 => :spm_mode_constant_current,
    15 => :spm_mode_barrier_height,
    16 => :spm_mode_constant_height,
    17 => :spm_mode_afam,
    18 => :spm_mode_contact_efm,
    19 => :spm_mode_shear_force_topography,
    20 => :spm_mode_sfom,
    21 => :spm_mode_contact_capacitance,
    22 => :spm_mode_snom_transmission,
    23 => :spm_mode_snom_reflection,
    24 => :spm_mode_snom_all,
    25 => :spm_mode_snom,
  }
  I__SPM_MODE = SPM_MODE.invert

  UNIT = {
    -10 => :unit_raman_shift,
    -9 => :unit_reserved0,
    -8 => :unit_reserved1,
    -7 => :unit_reserved2,
    -6 => :unit_reserved3,
    -5 => :unit_meter,
    -4 => :unit_centi_meter,
    -3 => :unit_milli_meter,
    -2 => :unit_micro_meter,
    -1 => :unit_nano_meter,
    0 => :unit_angstrom,
    1 => :unit_nano_ampere,
    2 => :unit_volt,
    3 => :unit_none,
    4 => :unit_kilo_hertz,
    5 => :unit_degrees,
    6 => :unit_percent,
    7 => :unit_celsius_degree,
    8 => :unit_volt_high,
    9 => :unit_second,
    10 => :unit_milli_second,
    11 => :unit_micro_second,
    12 => :unit_nano_second,
    13 => :unit_counts,
    14 => :unit_pixels,
    15 => :unit_reserved_sfom0,
    16 => :unit_reserved_sfom1,
    17 => :unit_reserved_sfom2,
    18 => :unit_reserved_sfom3,
    19 => :unit_reserved_sfom4,
    20 => :unit_ampere2,
    21 => :unit_milli_ampere,
    22 => :unit_micro_ampere,
    23 => :unit_nano_ampere2,
    24 => :unit_pico_ampere,
    25 => :unit_volt2,
    26 => :unit_milli_volt,
    27 => :unit_micro_volt,
    28 => :unit_nano_volt,
    29 => :unit_pico_volt,
    30 => :unit_newton,
    31 => :unit_milli_newton,
    32 => :unit_micro_newton,
    33 => :unit_nano_newton,
    34 => :unit_pico_newton,
    35 => :unit_reserved_dos0,
    36 => :unit_reserved_dos1,
    37 => :unit_reserved_dos2,
    38 => :unit_reserved_dos3,
    39 => :unit_reserved_dos4,
  }
  I__UNIT = UNIT.invert

  SPM_TECHNIQUE = {
    0 => :spm_technique_contact_mode,
    1 => :spm_technique_semicontact_mode,
    2 => :spm_technique_tunnel_current,
    3 => :spm_technique_snom,
  }
  I__SPM_TECHNIQUE = SPM_TECHNIQUE.invert

  CONSTS = {
    8 => :consts_frame_mode_size,
    22 => :consts_frame_header_size,
    30 => :consts_axis_scales_size,
    32 => :consts_file_header_size,
    38 => :consts_spectro_vars_min_size,
    77 => :consts_scan_vars_min_size,
  }
  I__CONSTS = CONSTS.invert
  def initialize(_io, _parent = nil, _root = self)
    super(_io, _parent, _root)
    _read
  end

  def _read
    @signature = @_io.read_bytes(4)
    raise Kaitai::Struct::ValidationNotEqualError.new([1, 176, 147, 255].pack('C*'), signature, _io, "/seq/0") if not signature == [1, 176, 147, 255].pack('C*')
    @size = @_io.read_u4le
    @reserved0 = @_io.read_bytes(4)
    @last_frame = @_io.read_u2le
    @reserved1 = @_io.read_bytes(18)
    @wrond_doc = @_io.read_bytes(1)
    @_raw_frames = @_io.read_bytes(size)
    _io__raw_frames = Kaitai::Struct::Stream.new(@_raw_frames)
    @frames = Framez.new(_io__raw_frames, self, @_root)
    self
  end
  class Uuid < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @data = []
      (16).times { |i|
        @data << @_io.read_u1
      }
      self
    end
    attr_reader :data
  end
  class Framez < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @frames = []
      ((_root.last_frame + 1)).times { |i|
        @frames << Frame.new(@_io, self, @_root)
      }
      self
    end
    attr_reader :frames
  end
  class Frame < Kaitai::Struct::Struct

    FRAME_TYPE = {
      0 => :frame_type_scanned,
      1 => :frame_type_spectroscopy,
      3 => :frame_type_text,
      105 => :frame_type_old_mda,
      106 => :frame_type_mda,
      107 => :frame_type_palette,
      190 => :frame_type_curves_new,
      201 => :frame_type_curves,
    }
    I__FRAME_TYPE = FRAME_TYPE.invert
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @size = @_io.read_u4le
      @_raw_main = @_io.read_bytes((size - 4))
      _io__raw_main = Kaitai::Struct::Stream.new(@_raw_main)
      @main = FrameMain.new(_io__raw_main, self, @_root)
      self
    end
    class Dots < Kaitai::Struct::Struct
      def initialize(_io, _parent = nil, _root = self)
        super(_io, _parent, _root)
        _read
      end

      def _read
        @fm_ndots = @_io.read_u2le
        if fm_ndots > 0
          @coord_header = DotsHeader.new(@_io, self, @_root)
        end
        @coordinates = []
        (fm_ndots).times { |i|
          @coordinates << DotsData.new(@_io, self, @_root)
        }
        @data = []
        (fm_ndots).times { |i|
          @data << DataLinez.new(@_io, self, @_root, i)
        }
        self
      end
      class DotsHeader < Kaitai::Struct::Struct
        def initialize(_io, _parent = nil, _root = self)
          super(_io, _parent, _root)
          _read
        end

        def _read
          @header_size = @_io.read_s4le
          @_raw_header = @_io.read_bytes(header_size)
          _io__raw_header = Kaitai::Struct::Stream.new(@_raw_header)
          @header = Header.new(_io__raw_header, self, @_root)
          self
        end
        class Header < Kaitai::Struct::Struct
          def initialize(_io, _parent = nil, _root = self)
            super(_io, _parent, _root)
            _read
          end

          def _read
            @coord_size = @_io.read_s4le
            @version = @_io.read_s4le
            @xyunits = Kaitai::Struct::Stream::resolve_enum(NtMdt::UNIT, @_io.read_s2le)
            self
          end
          attr_reader :coord_size
          attr_reader :version
          attr_reader :xyunits
        end
        attr_reader :header_size
        attr_reader :header
        attr_reader :_raw_header
      end
      class DotsData < Kaitai::Struct::Struct
        def initialize(_io, _parent = nil, _root = self)
          super(_io, _parent, _root)
          _read
        end

        def _read
          @coord_x = @_io.read_f4le
          @coord_y = @_io.read_f4le
          @forward_size = @_io.read_s4le
          @backward_size = @_io.read_s4le
          self
        end
        attr_reader :coord_x
        attr_reader :coord_y
        attr_reader :forward_size
        attr_reader :backward_size
      end
      class DataLinez < Kaitai::Struct::Struct
        def initialize(_io, _parent = nil, _root = self, index)
          super(_io, _parent, _root)
          @index = index
          _read
        end

        def _read
          @forward = []
          (_parent.coordinates[index].forward_size).times { |i|
            @forward << @_io.read_s2le
          }
          @backward = []
          (_parent.coordinates[index].backward_size).times { |i|
            @backward << @_io.read_s2le
          }
          self
        end
        attr_reader :forward
        attr_reader :backward
        attr_reader :index
      end
      attr_reader :fm_ndots
      attr_reader :coord_header
      attr_reader :coordinates
      attr_reader :data
    end
    class FrameMain < Kaitai::Struct::Struct
      def initialize(_io, _parent = nil, _root = self)
        super(_io, _parent, _root)
        _read
      end

      def _read
        @type = Kaitai::Struct::Stream::resolve_enum(NtMdt::Frame::FRAME_TYPE, @_io.read_u2le)
        @version = Version.new(@_io, self, @_root)
        @date_time = DateTime.new(@_io, self, @_root)
        @var_size = @_io.read_u2le
        case type
        when :frame_type_mda
          @_raw_frame_data = @_io.read_bytes_full
          _io__raw_frame_data = Kaitai::Struct::Stream.new(@_raw_frame_data)
          @frame_data = FdMetaData.new(_io__raw_frame_data, self, @_root)
        when :frame_type_curves_new
          @_raw_frame_data = @_io.read_bytes_full
          _io__raw_frame_data = Kaitai::Struct::Stream.new(@_raw_frame_data)
          @frame_data = FdCurvesNew.new(_io__raw_frame_data, self, @_root)
        when :frame_type_curves
          @_raw_frame_data = @_io.read_bytes_full
          _io__raw_frame_data = Kaitai::Struct::Stream.new(@_raw_frame_data)
          @frame_data = FdSpectroscopy.new(_io__raw_frame_data, self, @_root)
        when :frame_type_spectroscopy
          @_raw_frame_data = @_io.read_bytes_full
          _io__raw_frame_data = Kaitai::Struct::Stream.new(@_raw_frame_data)
          @frame_data = FdSpectroscopy.new(_io__raw_frame_data, self, @_root)
        when :frame_type_scanned
          @_raw_frame_data = @_io.read_bytes_full
          _io__raw_frame_data = Kaitai::Struct::Stream.new(@_raw_frame_data)
          @frame_data = FdScanned.new(_io__raw_frame_data, self, @_root)
        else
          @frame_data = @_io.read_bytes_full
        end
        self
      end

      ##
      # h_what
      attr_reader :type
      attr_reader :version
      attr_reader :date_time

      ##
      # h_am, v6 and older only
      attr_reader :var_size

      ##
      # 
      attr_reader :frame_data
      attr_reader :_raw_frame_data
    end
    class FdCurvesNew < Kaitai::Struct::Struct
      def initialize(_io, _parent = nil, _root = self)
        super(_io, _parent, _root)
        _read
      end

      def _read
        @block_count = @_io.read_u4le
        @blocks_headers = []
        (block_count).times { |i|
          @blocks_headers << BlockDescr.new(@_io, self, @_root)
        }
        @blocks_names = []
        (block_count).times { |i|
          @blocks_names << (@_io.read_bytes(blocks_headers[i].name_len)).force_encoding("UTF-8")
        }
        @blocks_data = []
        (block_count).times { |i|
          @blocks_data << @_io.read_bytes(blocks_headers[i].len)
        }
        self
      end
      class BlockDescr < Kaitai::Struct::Struct
        def initialize(_io, _parent = nil, _root = self)
          super(_io, _parent, _root)
          _read
        end

        def _read
          @name_len = @_io.read_u4le
          @len = @_io.read_u4le
          self
        end
        attr_reader :name_len
        attr_reader :len
      end
      attr_reader :block_count
      attr_reader :blocks_headers
      attr_reader :blocks_names
      attr_reader :blocks_data
    end
    class FdMetaData < Kaitai::Struct::Struct
      def initialize(_io, _parent = nil, _root = self)
        super(_io, _parent, _root)
        _read
      end

      def _read
        @head_size = @_io.read_u4le
        @tot_len = @_io.read_u4le
        @guids = []
        (2).times { |i|
          @guids << Uuid.new(@_io, self, @_root)
        }
        @frame_status = @_io.read_bytes(4)
        @name_size = @_io.read_u4le
        @comm_size = @_io.read_u4le
        @view_info_size = @_io.read_u4le
        @spec_size = @_io.read_u4le
        @source_info_size = @_io.read_u4le
        @var_size = @_io.read_u4le
        @data_offset = @_io.read_u4le
        @data_size = @_io.read_u4le
        @title = (@_io.read_bytes(name_size)).force_encoding("UTF-8")
        @xml = (@_io.read_bytes(comm_size)).force_encoding("UTF-8")
        @struct_len = @_io.read_u4le
        @array_size = @_io.read_u8le
        @cell_size = @_io.read_u4le
        @n_dimensions = @_io.read_u4le
        @n_mesurands = @_io.read_u4le
        @dimensions = []
        (n_dimensions).times { |i|
          @dimensions << Calibration.new(@_io, self, @_root)
        }
        @mesurands = []
        (n_mesurands).times { |i|
          @mesurands << Calibration.new(@_io, self, @_root)
        }
        self
      end
      class Image < Kaitai::Struct::Struct
        def initialize(_io, _parent = nil, _root = self)
          super(_io, _parent, _root)
          _read
        end

        def _read
          @image = []
          i = 0
          while not @_io.eof?
            @image << Vec.new(@_io, self, @_root)
            i += 1
          end
          self
        end
        class Vec < Kaitai::Struct::Struct
          def initialize(_io, _parent = nil, _root = self)
            super(_io, _parent, _root)
            _read
          end

          def _read
            @items = []
            (_parent._parent.n_mesurands).times { |i|
              case _parent._parent.mesurands[i].data_type
              when :data_type_uint64
                @items << @_io.read_u8le
              when :data_type_uint8
                @items << @_io.read_u1
              when :data_type_float32
                @items << @_io.read_f4le
              when :data_type_int8
                @items << @_io.read_s1
              when :data_type_uint16
                @items << @_io.read_u2le
              when :data_type_int64
                @items << @_io.read_s8le
              when :data_type_uint32
                @items << @_io.read_u4le
              when :data_type_float64
                @items << @_io.read_f8le
              when :data_type_int16
                @items << @_io.read_s2le
              when :data_type_int32
                @items << @_io.read_s4le
              end
            }
            self
          end
          attr_reader :items
        end
        attr_reader :image
      end
      class Calibration < Kaitai::Struct::Struct
        def initialize(_io, _parent = nil, _root = self)
          super(_io, _parent, _root)
          _read
        end

        def _read
          @len_tot = @_io.read_u4le
          @len_struct = @_io.read_u4le
          @len_name = @_io.read_u4le
          @len_comment = @_io.read_u4le
          @len_unit = @_io.read_u4le
          @si_unit = @_io.read_u8le
          @accuracy = @_io.read_f8le
          @function_id_and_dimensions = @_io.read_u8le
          @bias = @_io.read_f8le
          @scale = @_io.read_f8le
          @min_index = @_io.read_u8le
          @max_index = @_io.read_u8le
          @data_type = Kaitai::Struct::Stream::resolve_enum(NtMdt::DATA_TYPE, @_io.read_s4le)
          @len_author = @_io.read_u4le
          @name = (@_io.read_bytes(len_name)).force_encoding("utf-8")
          @comment = (@_io.read_bytes(len_comment)).force_encoding("utf-8")
          @unit = (@_io.read_bytes(len_unit)).force_encoding("utf-8")
          @author = (@_io.read_bytes(len_author)).force_encoding("utf-8")
          self
        end
        def count
          return @count unless @count.nil?
          @count = ((max_index - min_index) + 1)
          @count
        end
        attr_reader :len_tot
        attr_reader :len_struct
        attr_reader :len_name
        attr_reader :len_comment
        attr_reader :len_unit
        attr_reader :si_unit
        attr_reader :accuracy
        attr_reader :function_id_and_dimensions
        attr_reader :bias
        attr_reader :scale
        attr_reader :min_index
        attr_reader :max_index
        attr_reader :data_type
        attr_reader :len_author
        attr_reader :name
        attr_reader :comment
        attr_reader :unit
        attr_reader :author
      end
      def image
        return @image unless @image.nil?
        _pos = @_io.pos
        @_io.seek(data_offset)
        @_raw_image = @_io.read_bytes(data_size)
        _io__raw_image = Kaitai::Struct::Stream.new(@_raw_image)
        @image = Image.new(_io__raw_image, self, @_root)
        @_io.seek(_pos)
        @image
      end
      attr_reader :head_size
      attr_reader :tot_len
      attr_reader :guids
      attr_reader :frame_status
      attr_reader :name_size
      attr_reader :comm_size
      attr_reader :view_info_size
      attr_reader :spec_size
      attr_reader :source_info_size
      attr_reader :var_size
      attr_reader :data_offset
      attr_reader :data_size
      attr_reader :title
      attr_reader :xml
      attr_reader :struct_len
      attr_reader :array_size
      attr_reader :cell_size
      attr_reader :n_dimensions
      attr_reader :n_mesurands
      attr_reader :dimensions
      attr_reader :mesurands
      attr_reader :_raw_image
    end
    class FdSpectroscopy < Kaitai::Struct::Struct
      def initialize(_io, _parent = nil, _root = self)
        super(_io, _parent, _root)
        _read
      end

      def _read
        @_raw_vars = @_io.read_bytes(_parent.var_size)
        _io__raw_vars = Kaitai::Struct::Stream.new(@_raw_vars)
        @vars = Vars.new(_io__raw_vars, self, @_root)
        @fm_mode = @_io.read_u2le
        @fm_xres = @_io.read_u2le
        @fm_yres = @_io.read_u2le
        @dots = Dots.new(@_io, self, @_root)
        @data = []
        ((fm_xres * fm_yres)).times { |i|
          @data << @_io.read_s2le
        }
        @title = Title.new(@_io, self, @_root)
        @xml = Xml.new(@_io, self, @_root)
        self
      end
      class Vars < Kaitai::Struct::Struct
        def initialize(_io, _parent = nil, _root = self)
          super(_io, _parent, _root)
          _read
        end

        def _read
          @x_scale = AxisScale.new(@_io, self, @_root)
          @y_scale = AxisScale.new(@_io, self, @_root)
          @z_scale = AxisScale.new(@_io, self, @_root)
          @sp_mode = @_io.read_u2le
          @sp_filter = @_io.read_u2le
          @u_begin = @_io.read_f4le
          @u_end = @_io.read_f4le
          @z_up = @_io.read_s2le
          @z_down = @_io.read_s2le
          @sp_averaging = @_io.read_u2le
          @sp_repeat = @_io.read_u1
          @sp_back = @_io.read_u1
          @sp_4nx = @_io.read_s2le
          @sp_osc = @_io.read_u1
          @sp_n4 = @_io.read_u1
          @sp_4x0 = @_io.read_f4le
          @sp_4xr = @_io.read_f4le
          @sp_4u = @_io.read_s2le
          @sp_4i = @_io.read_s2le
          @sp_nx = @_io.read_s2le
          self
        end
        attr_reader :x_scale
        attr_reader :y_scale
        attr_reader :z_scale
        attr_reader :sp_mode
        attr_reader :sp_filter
        attr_reader :u_begin
        attr_reader :u_end
        attr_reader :z_up
        attr_reader :z_down
        attr_reader :sp_averaging
        attr_reader :sp_repeat
        attr_reader :sp_back
        attr_reader :sp_4nx
        attr_reader :sp_osc
        attr_reader :sp_n4
        attr_reader :sp_4x0
        attr_reader :sp_4xr
        attr_reader :sp_4u
        attr_reader :sp_4i
        attr_reader :sp_nx
      end
      attr_reader :vars
      attr_reader :fm_mode
      attr_reader :fm_xres
      attr_reader :fm_yres
      attr_reader :dots
      attr_reader :data
      attr_reader :title
      attr_reader :xml
      attr_reader :_raw_vars
    end
    class DateTime < Kaitai::Struct::Struct
      def initialize(_io, _parent = nil, _root = self)
        super(_io, _parent, _root)
        _read
      end

      def _read
        @date = Date.new(@_io, self, @_root)
        @time = Time.new(@_io, self, @_root)
        self
      end
      class Date < Kaitai::Struct::Struct
        def initialize(_io, _parent = nil, _root = self)
          super(_io, _parent, _root)
          _read
        end

        def _read
          @year = @_io.read_u2le
          @month = @_io.read_u2le
          @day = @_io.read_u2le
          self
        end

        ##
        # h_yea
        attr_reader :year

        ##
        # h_mon
        attr_reader :month

        ##
        # h_day
        attr_reader :day
      end
      class Time < Kaitai::Struct::Struct
        def initialize(_io, _parent = nil, _root = self)
          super(_io, _parent, _root)
          _read
        end

        def _read
          @hour = @_io.read_u2le
          @min = @_io.read_u2le
          @sec = @_io.read_u2le
          self
        end

        ##
        # h_h
        attr_reader :hour

        ##
        # h_m
        attr_reader :min

        ##
        # h_s
        attr_reader :sec
      end
      attr_reader :date
      attr_reader :time
    end
    class AxisScale < Kaitai::Struct::Struct
      def initialize(_io, _parent = nil, _root = self)
        super(_io, _parent, _root)
        _read
      end

      def _read
        @offset = @_io.read_f4le
        @step = @_io.read_f4le
        @unit = Kaitai::Struct::Stream::resolve_enum(NtMdt::UNIT, @_io.read_s2le)
        self
      end

      ##
      # x_scale->offset = gwy_get_gfloat_le(&p);# r0 (physical units)
      attr_reader :offset

      ##
      # x_scale->step = gwy_get_gfloat_le(&p); r (physical units) x_scale->step = fabs(x_scale->step); if (!x_scale->step) {
      #   g_warning("x_scale.step == 0, changing to 1");
      #   x_scale->step = 1.0;
      # }
      attr_reader :step

      ##
      # U
      attr_reader :unit
    end
    class FdScanned < Kaitai::Struct::Struct

      MODE = {
        0 => :mode_stm,
        1 => :mode_afm,
        2 => :mode_unknown2,
        3 => :mode_unknown3,
        4 => :mode_unknown4,
      }
      I__MODE = MODE.invert

      INPUT_SIGNAL = {
        0 => :input_signal_extension_slot,
        1 => :input_signal_bias_v,
        2 => :input_signal_ground,
      }
      I__INPUT_SIGNAL = INPUT_SIGNAL.invert

      LIFT_MODE = {
        0 => :lift_mode_step,
        1 => :lift_mode_fine,
        2 => :lift_mode_slope,
      }
      I__LIFT_MODE = LIFT_MODE.invert
      def initialize(_io, _parent = nil, _root = self)
        super(_io, _parent, _root)
        _read
      end

      def _read
        @_raw_vars = @_io.read_bytes(_parent.var_size)
        _io__raw_vars = Kaitai::Struct::Stream.new(@_raw_vars)
        @vars = Vars.new(_io__raw_vars, self, @_root)
        if false
          @orig_format = @_io.read_u4le
        end
        if false
          @tune = Kaitai::Struct::Stream::resolve_enum(LIFT_MODE, @_io.read_u4le)
        end
        if false
          @feedback_gain = @_io.read_f8le
        end
        if false
          @dac_scale = @_io.read_s4le
        end
        if false
          @overscan = @_io.read_s4le
        end
        @fm_mode = @_io.read_u2le
        @fm_xres = @_io.read_u2le
        @fm_yres = @_io.read_u2le
        @dots = Dots.new(@_io, self, @_root)
        @image = []
        ((fm_xres * fm_yres)).times { |i|
          @image << @_io.read_s2le
        }
        @title = Title.new(@_io, self, @_root)
        @xml = Xml.new(@_io, self, @_root)
        self
      end
      class Vars < Kaitai::Struct::Struct
        def initialize(_io, _parent = nil, _root = self)
          super(_io, _parent, _root)
          _read
        end

        def _read
          @x_scale = AxisScale.new(@_io, self, @_root)
          @y_scale = AxisScale.new(@_io, self, @_root)
          @z_scale = AxisScale.new(@_io, self, @_root)
          @channel_index = Kaitai::Struct::Stream::resolve_enum(NtMdt::ADC_MODE, @_io.read_u1)
          @mode = Kaitai::Struct::Stream::resolve_enum(NtMdt::Frame::FdScanned::MODE, @_io.read_u1)
          @xres = @_io.read_u2le
          @yres = @_io.read_u2le
          @ndacq = @_io.read_u2le
          @step_length = @_io.read_f4le
          @adt = @_io.read_u2le
          @adc_gain_amp_log10 = @_io.read_u1
          @adc_index = @_io.read_u1
          @input_signal_or_version = @_io.read_u1
          @substr_plane_order_or_pass_num = @_io.read_u1
          @scan_dir = ScanDir.new(@_io, self, @_root)
          @power_of_2 = @_io.read_u1
          @velocity = @_io.read_f4le
          @setpoint = @_io.read_f4le
          @bias_voltage = @_io.read_f4le
          @draw = @_io.read_u1
          @reserved = @_io.read_u1
          @xoff = @_io.read_s4le
          @yoff = @_io.read_s4le
          @nl_corr = @_io.read_u1
          self
        end
        attr_reader :x_scale
        attr_reader :y_scale
        attr_reader :z_scale

        ##
        # s_mode
        attr_reader :channel_index

        ##
        # s_dev
        attr_reader :mode

        ##
        # s_nx
        attr_reader :xres

        ##
        # s_ny
        attr_reader :yres

        ##
        # Step (DAC)
        attr_reader :ndacq

        ##
        # s_rs in Angstrom's (Angstrom*gwy_get_gfloat_le(&p))
        attr_reader :step_length

        ##
        # s_adt
        attr_reader :adt

        ##
        # s_adc_a
        attr_reader :adc_gain_amp_log10

        ##
        # ADC index
        attr_reader :adc_index

        ##
        # MDTInputSignal smp_in; s_smp_in (for signal) s_8xx (for version)
        attr_reader :input_signal_or_version

        ##
        # s_spl or z_03
        attr_reader :substr_plane_order_or_pass_num

        ##
        # s_xy TODO: interpretation
        attr_reader :scan_dir

        ##
        # s_2n (bool)
        attr_reader :power_of_2

        ##
        # s_vel (Angstrom/second)
        attr_reader :velocity

        ##
        # s_i0
        attr_reader :setpoint

        ##
        # s_ut
        attr_reader :bias_voltage

        ##
        # s_draw (bool)
        attr_reader :draw
        attr_reader :reserved

        ##
        # s_x00 (in DAC quants)
        attr_reader :xoff

        ##
        # s_y00 (in DAC quants)
        attr_reader :yoff

        ##
        # s_cor (bool)
        attr_reader :nl_corr
      end
      class Dot < Kaitai::Struct::Struct
        def initialize(_io, _parent = nil, _root = self)
          super(_io, _parent, _root)
          _read
        end

        def _read
          @x = @_io.read_s2le
          @y = @_io.read_s2le
          self
        end
        attr_reader :x
        attr_reader :y
      end
      class ScanDir < Kaitai::Struct::Struct
        def initialize(_io, _parent = nil, _root = self)
          super(_io, _parent, _root)
          _read
        end

        def _read
          @unkn = @_io.read_bits_int_be(4)
          @double_pass = @_io.read_bits_int_be(1) != 0
          @bottom = @_io.read_bits_int_be(1) != 0
          @left = @_io.read_bits_int_be(1) != 0
          @horizontal = @_io.read_bits_int_be(1) != 0
          self
        end
        attr_reader :unkn
        attr_reader :double_pass

        ##
        # Bottom - 1 Top - 0
        attr_reader :bottom

        ##
        # Left - 1 Right - 0
        attr_reader :left

        ##
        # Horizontal - 1 Vertical - 0
        attr_reader :horizontal
      end
      attr_reader :vars

      ##
      # s_oem
      attr_reader :orig_format

      ##
      # z_tune
      attr_reader :tune

      ##
      # s_fbg
      attr_reader :feedback_gain

      ##
      # s_s
      attr_reader :dac_scale

      ##
      # s_xov (in %)
      attr_reader :overscan

      ##
      # m_mode
      attr_reader :fm_mode

      ##
      # m_nx
      attr_reader :fm_xres

      ##
      # m_ny
      attr_reader :fm_yres
      attr_reader :dots
      attr_reader :image
      attr_reader :title
      attr_reader :xml
      attr_reader :_raw_vars
    end

    ##
    # h_sz
    attr_reader :size
    attr_reader :main
    attr_reader :_raw_main
  end
  class Version < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @minor = @_io.read_u1
      @major = @_io.read_u1
      self
    end
    attr_reader :minor
    attr_reader :major
  end
  class Xml < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @xml_len = @_io.read_u4le
      @xml = (@_io.read_bytes(xml_len)).force_encoding("UTF-16LE")
      self
    end
    attr_reader :xml_len
    attr_reader :xml
  end
  class Title < Kaitai::Struct::Struct
    def initialize(_io, _parent = nil, _root = self)
      super(_io, _parent, _root)
      _read
    end

    def _read
      @title_len = @_io.read_u4le
      @title = (@_io.read_bytes(title_len)).force_encoding("cp1251")
      self
    end
    attr_reader :title_len
    attr_reader :title
  end
  attr_reader :signature

  ##
  # File size (w/o header)
  attr_reader :size
  attr_reader :reserved0
  attr_reader :last_frame
  attr_reader :reserved1

  ##
  # documentation specifies 32 bytes long header, but zeroth frame starts at 33th byte in reality
  attr_reader :wrond_doc
  attr_reader :frames
  attr_reader :_raw_frames
end