19.4. Tutorials

19.4.1. Using Interactive Mode with Binary Output

This tutorial walks through the process of creating a simple parser for the binary output functionality of Interactive Mode. It gives a simple C++ source example which parses path radiance for a known number of queries. This tutorial requires some knowledge of both programming and binary data representation.

When using Interactive Mode with binary output, it is the user's responsibility to craft a Collector Configuration File which can actually be parsed. For instance, the number of surfaces intersected by a ray can vary depending on the scene location. In order to know how many surfaces are in the binary output, the Collector Configuration File would need to include the pathsurfacecount collector. Without this solution-level collector's information, the parser would not know how much of the following binary data represents surface collections.

This tutorial will simply ask DIRSIG for the path radiance using a fixed number of queries. The Collector Configuration File looks as follows:

<?xml version="1.0" standalone="no"?>
<!DOCTYPE interactive SYSTEM "http://www.dirsig.org/dtds/interactive.dtd">
<interactive format="binary" pausepolicy="never" 
             startupoutputredirect="startup.log">

  <simulationcollectors>
  </simulationcollectors>

  <solutioncollectors>
    <pathradiance/>
  </solutioncollectors>

  <surfacecollectors>
  </surfacecollectors>

</interactive>
  

Note that the format has been specified as "binary". Also, the startup output from DIRSIG which gives diagnostic information as a scene loads has been redirected to a file called "startup.log". Only the "pathradiance" solution-level collector is in use.

For the tutorial, the following simple bandpass XML file is used:

<bandpasslist>
  <bandpass spectralunits="microns">
    <minimum>0.3</minimum>
    <maximum>0.5</maximum>
    <delta>0.1</delta>
  </bandpass>  
</bandpasslist>
  

With this bandpass, DIRSIG will compute radiance values for 0.3, 0.4, and 0.5 microns. The output from the "pathradiance" collector is a Stokes Vector (which may or may not be polarized).

The interactive mode queries will be supplied via a textfile that looks as follows:

0 1E-6 0 0 800 0 0 -1
0 1E-6 0 1 800 0 0 -1
  

Here we have two queries. Both have a time offset of zero relative to the simulation base time. Both specify a solid angle of 1E-6 steradians. Both queries specify a ray shooting straight down from an altitude of 800 meters. The rays are separated by 1 meter along the y-axis.

Given an appropriate DIRSIG scene and atmosphere, the pieces are put together as follows:

    prompt> dirsig-4.1 -interactive collector.xml bandpass.xml \
                demo.cfg < queries > queryoutput 
  

This example uses shell redirection to read the queries and feed them to DIRSIG via stdin. It takes the output from DIRSIG and redirects it to "queryoutput". The contents of this queryoutput file will be binary data. Remember, there is a "startup.log" file available with plaintext output of the startup process. The next step in the tutorial is to write a simple program to parse this binary output. First the pseudo code:

  1. For each query...

    1. Parse the radiance spectral vector in to memory

    2. Print the radiance spectral vector to the console in human-readable ASCII

  1. To parse a spectral vector...

    1. Parse the number of stokes vectors. This is a four-byte, big-endian integer.

    2. Attempt to parse the number of specified stokes vectors. Each stokes vector is preceeded by an eight-byte, big-endian double describing the spectral location in microns. After the spectral location comes the actual stokes vector.

  1. To parse a stokes vector...

    1. Parse the polarization status byte. A zero value here indicates an unpolarized stokes vector, meaning just one double of data will follow. A value of one indicates polarized data, meaning four doubles of data will follow.

    2. Parse either one or four eight-byte, big endian doubles, as specified by the polarization status byte.

Note that Interactive Mode will always output data in big-endian format, regardless of the native architecture's endianness. The following demonstration code, for simplicity reasons, does not any endian checking or conversions. This means that the as-is version of the following code will only work on big-endian processors such as SPARC. The x86 processors for Intel and AMD and little-endian, and the following code will not work as-is on these platforms. The following code also assumes that a "double" is eight bytes and an "int" is four bytes.

#include <fstream>
#include <iostream>
#include <istream>
#include <utility>
#include <vector>

typedef std::vector<double> StokesVector;
typedef std::pair<double,StokesVector> SpectralData;
typedef std::vector<SpectralData> SpectralVector;
  
/* Parse a stokes vector.  */
void parseSV( std::istream& is, StokesVector& stokes ) {

    // read polarization status
    char polStatus = -1;
    is.read( &polStatus, 1 );

    // number of data elements in stokes depends on polarization state
    const int numElements = ( polStatus == 1 ) ? 4 : 1;

    // read all stokes elements
    for ( int i = 0; i < numElements; i++ ) {

        // read current stokes vector element
        double stokesData = -1.0;
        is.read( reinterpret_cast<char*>( &stokesData ), sizeof( double ) );

        // stash parsed data value
        stokes.push_back( stokesData );
    }

}
  
/* Parse spectral vector of stokes vectors */
void parseSpecVecSV( std::istream& is, SpectralVector& specvec ) {

    // read number of stokes vectors in spectral vector
    int vectorCount = -1;
    is.read( reinterpret_cast<char*>( &vectorCount ), sizeof( int ) );

    // read each wavelength / stokes vector pair
    for ( int i = 0; i < vectorCount; i++ ) {

        double wavelength = -1.0;
        is.read( reinterpret_cast<char*>( &wavelength ), sizeof( double ) );

        StokesVector stokes;
        parseSV( is, stokes );

        specvec.push_back( SpectralData( wavelength, stokes ) );
    }
}
  
int main( int argc, char* argv ) {

    // Name of query output data file
    std::string dataFilename = "queryoutput";

    // Number of queries we are parsing data for
    const int numQueries = 2;

    // Open binary data file
    std::ifstream is( dataFilename.c_str(), std::ios::in | std::ios::binary );
    if ( !is ) {
        std::cerr << "Error: unable to open query result file" << std::endl;
        return 1; 
    }

    // Drive through each query result
    for ( int i = 0; i < numQueries; i++ ) {

        // Parse query info
        SpectralVector pathRadiance;
        parseSpecVecSV( is, pathRadiance );
        
        // Pretty print query info
        std::cout << "Query # " << (i+1) << std::endl;
        for ( int l = 0; l < pathRadiance.size(); l++ ) {
            // dump wavelength
            std::cout << "  [" << pathRadiance.at(l).first << "] ";

            // dump stokes
            for ( int stokesIdx = 0; 
                  stokesIdx < pathRadiance.at(l).second.size(); 
                  stokesIdx++ ) {
                std::cout << pathRadiance.at(l).second.at(stokesIdx) << " ";
            }
            std::cout << std::endl;
        }
        std::cout << std::endl;
    }

    return 0;
}
  

Assuming that the assumptions about endianness and type sizes are correct for the platform this program is compiled on, the following output will result:

Query # 1
  [0.3] 0.0236427 
  [0.4] 0.0235044 
  [0.5] 0.00549655 

Query # 2
  [0.3] 0.0236427 
  [0.4] 0.0234982 
  [0.5] 0.00516551 
  

It is possible to verify these results by rerunning the queries using the "plain" output format in the collector configuration file.

This tutorial has demonstrated that it is possible to bypass much of the traditional DIRSIG "front-end" by using Interactive Mode. The binary output allows for high performance interaction with the model. For many cases the XML output of Interactive Mode is preferable because it is easier to debug and robust XML parsers are available for almost all major programming languages.