.roh file format: C++/STL parsing library

Avantes USB spectrometers are supplied with a Windows binary which generates one ROH and one RCM file when the user clicks "Save experiment". In the version of 6.0, the ROH file contains a header of 22 four-byte floats, then the spectrum as a float array and a footer of 3 floats. The first and last pixel numbers are specified in the header and determine the (length+1) of the spectral data. In the tested files, the length is (2032-211-1)=1820 pixels, but Kaitai determines this automatically anyway.

The wavelength calibration is stored as a polynomial with coefficients of 'wlintercept', 'wlx1', ... 'wlx4', the argument of which is the (pixel number + 1), as found out by comparing with the original Avantes converted data files. There is no intensity calibration saved, but it is recommended to do it in your program - the CCD in the spectrometer is so uneven that one should prepare exact pixel-to-pixel calibration curves to get reasonable spectral results.

The rest of the header floats is not known to the author. Note that the newer version of Avantes software has a different format, see also https://kr.mathworks.com/examples/matlab/community/20341-reading-spectra-from-avantes-binary-files-demonstration

The RCM file contains the user-specified comment, so it may be useful for automatic conversion of data.

Written and tested by Filip Dominec, 2017

File extension

roh

KS implementation details

License: CC0-1.0

This page hosts a formal specification of .roh file format using Kaitai Struct. This specification can be automatically translated into a variety of programming languages to get a parsing library.

Usage

Using Kaitai Struct in C++/STL usually consists of 3 steps.

  1. We need to create an STL input stream (std::istream).
    • One can open a stream for reading from a local file:
      #include <fstream>
      
      std::ifstream is("path/to/local/file.roh", std::ifstream::binary);
    • Or one can prepare a stream for reading from existing std::string str:
      #include <sstream>
      
      std::istringstream is(str);
    • Or one can parse arbitrary char* buffer in memory, given that we know its size:
      #include <sstream>
      
      const char buf[] = { ... };
      std::string str(buf, sizeof buf);
      std::istringstream is(str);
  2. We need to wrap our input stream into Kaitai stream:
    #include <kaitai/kaitaistream.h>
    
    kaitai::kstream ks(&is);
  3. And finally, we can invoke the parsing:
    avantes_roh60_t data(&ks);

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

data.unknown1() // => get unknown1

C++/STL source code to parse .roh file format

avantes_roh60.h

#ifndef AVANTES_ROH60_H_
#define AVANTES_ROH60_H_

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

#include "kaitai/kaitaistruct.h"

#include <stdint.h>
#include <vector>

#if KAITAI_STRUCT_VERSION < 7000L
#error "Incompatible Kaitai Struct C++/STL API: version 0.7 or later is required"
#endif

/**
 * Avantes USB spectrometers are supplied with a Windows binary which 
 * generates one ROH and one RCM file when the user clicks "Save
 * experiment". In the version of 6.0, the ROH file contains a header 
 * of 22 four-byte floats, then the spectrum as a float array and a 
 * footer of 3 floats. The first and last pixel numbers are specified in the 
 * header and determine the (length+1) of the spectral data. In the tested 
 * files, the length is (2032-211-1)=1820 pixels, but Kaitai determines this 
 * automatically anyway.
 * 
 * The wavelength calibration is stored as a polynomial with coefficients
 * of 'wlintercept', 'wlx1', ... 'wlx4', the argument of which is the
 * (pixel number + 1), as found out by comparing with the original 
 * Avantes converted data files. There is no intensity calibration saved,
 * but it is recommended to do it in your program - the CCD in the spectrometer 
 * is so uneven that one should prepare exact pixel-to-pixel calibration curves 
 * to get reasonable spectral results.
 * 
 * The rest of the header floats is not known to the author. Note that the 
 * newer version of Avantes software has a different format, see also
 * https://kr.mathworks.com/examples/matlab/community/20341-reading-spectra-from-avantes-binary-files-demonstration
 * 
 * The RCM file contains the user-specified comment, so it may be useful
 * for automatic conversion of data.
 * 
 * Written and tested by Filip Dominec, 2017
 */

class avantes_roh60_t : public kaitai::kstruct {

public:

    avantes_roh60_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent = 0, avantes_roh60_t* p__root = 0);

private:
    void _read();

public:
    ~avantes_roh60_t();

private:
    float m_unknown1;
    float m_wlintercept;
    float m_wlx1;
    float m_wlx2;
    float m_wlx3;
    float m_wlx4;
    std::vector<float>* m_unknown2;
    float m_ipixfirst;
    float m_ipixlast;
    std::vector<float>* m_unknown3;
    std::vector<float>* m_spectrum;
    std::vector<float>* m_unknown4;
    avantes_roh60_t* m__root;
    kaitai::kstruct* m__parent;

public:
    float unknown1() const { return m_unknown1; }
    float wlintercept() const { return m_wlintercept; }
    float wlx1() const { return m_wlx1; }
    float wlx2() const { return m_wlx2; }
    float wlx3() const { return m_wlx3; }
    float wlx4() const { return m_wlx4; }
    std::vector<float>* unknown2() const { return m_unknown2; }
    float ipixfirst() const { return m_ipixfirst; }
    float ipixlast() const { return m_ipixlast; }
    std::vector<float>* unknown3() const { return m_unknown3; }
    std::vector<float>* spectrum() const { return m_spectrum; }
    std::vector<float>* unknown4() const { return m_unknown4; }
    avantes_roh60_t* _root() const { return m__root; }
    kaitai::kstruct* _parent() const { return m__parent; }
};

#endif  // AVANTES_ROH60_H_

avantes_roh60.cpp

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

#include "avantes_roh60.h"



avantes_roh60_t::avantes_roh60_t(kaitai::kstream* p__io, kaitai::kstruct* p__parent, avantes_roh60_t* p__root) : kaitai::kstruct(p__io) {
    m__parent = p__parent;
    m__root = this;
    _read();
}

void avantes_roh60_t::_read() {
    m_unknown1 = m__io->read_f4le();
    m_wlintercept = m__io->read_f4le();
    m_wlx1 = m__io->read_f4le();
    m_wlx2 = m__io->read_f4le();
    m_wlx3 = m__io->read_f4le();
    m_wlx4 = m__io->read_f4le();
    int l_unknown2 = 9;
    m_unknown2 = new std::vector<float>();
    m_unknown2->reserve(l_unknown2);
    for (int i = 0; i < l_unknown2; i++) {
        m_unknown2->push_back(m__io->read_f4le());
    }
    m_ipixfirst = m__io->read_f4le();
    m_ipixlast = m__io->read_f4le();
    int l_unknown3 = 4;
    m_unknown3 = new std::vector<float>();
    m_unknown3->reserve(l_unknown3);
    for (int i = 0; i < l_unknown3; i++) {
        m_unknown3->push_back(m__io->read_f4le());
    }
    int l_spectrum = ((static_cast<int>(ipixlast()) - static_cast<int>(ipixfirst())) - 1);
    m_spectrum = new std::vector<float>();
    m_spectrum->reserve(l_spectrum);
    for (int i = 0; i < l_spectrum; i++) {
        m_spectrum->push_back(m__io->read_f4le());
    }
    int l_unknown4 = 3;
    m_unknown4 = new std::vector<float>();
    m_unknown4->reserve(l_unknown4);
    for (int i = 0; i < l_unknown4; i++) {
        m_unknown4->push_back(m__io->read_f4le());
    }
}

avantes_roh60_t::~avantes_roh60_t() {
    delete m_unknown2;
    delete m_unknown3;
    delete m_spectrum;
    delete m_unknown4;
}