Commit 9d1f754b authored by Stuart Sides's avatar Stuart Sides
Browse files

First add of jitterfit application

parent e960e645
Loading
Loading
Loading
Loading
+226 −0
Original line number Diff line number Diff line
<?xml version="1.0" encoding="UTF-8"?>

<application name="jitterfit" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://isis.astrogeology.usgs.gov/Schemas/Application/application.xsd">
  <brief>
    Registers lines from a normal image with lines from the check image.
  </brief>
  <description>
    <p>
      This program co-regsisters lines in the main cube (FROM) with the corrisponding line in
      the checkline cube (FROM2).
      The sensor line used to collect the checkline is used to center the registration around
      the same area in the main cube.
    </p>
    <p>
      The main input cube (FROM) is expected to be a cube from a rolling shutter instrument.
      The checkline cube (FROM2) is expected to contain lines of data read from the same rolling shutter
      instrument, but read at different times than the coorsponding line in the main cube. The check 
      cube must also have an associated table containing the sensor line number and time the line was read. 
      The algorithum co-registers these individual checklines to the main cube and calculates an offset
      in the line and sample directions. It then fits an Nth order polynomial through the offsets and time to
      characterize the jitter. The coefficients are then written to the cube labels so the rolling shutter
      camera model can adjust the look direction of each pixel in the model's detector map.
    </p>
  </description>

  <groups>
    <group name="Registration">
      <parameter name="FROM">
        <type>cube</type>
        <fileMode>input</fileMode>
        <brief>
          Input cube containing an image that need jitter correction.
        </brief>
        <description>
          This input cube is the cube that needs to be de-jittered. The algorithms jitterfit uses
          are designed for a rolling shutter instrument with an accompanying check line cube
          file (see main description). This cube file must be writable. Coefficients to adjust
          the jitter will be written to the cube labels for use by the rolling shutter cammera model.
        </description>
        <filter>
          *.cub
        </filter>
      </parameter>
      
      <parameter name="FROM2">
        <type>cube</type>
        <fileMode>input</fileMode>
        <brief>
          Input cube containing the check lines.
        </brief>
        <description>
          This input cube is the check line cube. It contains a set of lines that were 
          repetedly read from the sensor array intersperesed with the readout of the main
          image data.
          EXPAND
        </description>
        <filter>
          *.cub
        </filter>
      </parameter>

      <parameter name="DEFFILE">
        <type>filename</type>
        <fileMode>input</fileMode>
        <brief>
          The Auto Registration template
        </brief>
        <description>
          The parameter template to use for the Autoregistration process. The default template
          calls the Maximum Correlation pattern matching algorithm with predefined parameter values.
          There are other templates available in the system autoreg/template directory.  Also, the user
          can use the 'autoregtemplate' application to create a new template file.

          Example minimal autoregistration definition file:
          <pre>
             Object = AutoRegistration
               Group = Algorithm
                 Name = MaximumCorrelation
                 Tolerance = 0.7
                 SubPixelAccuracy = True
               EndGroup
 
               Group = PatternChip
                 Samples = 1391
                 Lines = 1 

               EndGroup
 
               Group = SearchChip
                 Samples = 1399
                 Lines = 7
               EndGroup

               Group = SurfaceModel
                 WindowSize = 7
               EndGroup
             EndObject
          </pre>

        </description>

        <filter>
          *.def
        </filter>
      </parameter>
      
      <parameter name="SCALE">
        <type>double</type>
        <default><item>4.0</item></default>
        <brief>
          The scale of the image.
        </brief>
        <description>
          This is the scale of the image. Only use this parameter if you have enlarged the main cube.
          This will allow the program to access the correct normal cube line number based on the 
          un-enlarged checktable values.
          Example: For a 4x enlargement set this value to 4.0.
          ADD WHY
        </description>
      </parameter>
      
      <parameter name="TO">
        <type>filename</type>
        <fileMode>output</fileMode>
        <internalDefault>None</internalDefault>
        <brief>
          Output file where the registration results will be written.
        </brief>
        <description>
          SOMETHING
        </description>
        <filter>
          *.csv
        </filter>
      </parameter>
      
      <parameter name="TO2">
        <type>filename</type>
        <fileMode>output</fileMode>
        <internalDefault>None</internalDefault>
        <brief>
          Output file where the registration statistics will be written.
        </brief>
        <description>
          SOMETHING 
        </description>
        <filter>
          *.csv
        </filter>
      </parameter>
    </group>

    <group name="Fitting">
      <parameter name="TOLERANCE">
        <type>double</type>
        <brief>
          The tolerance that determines which points will be excluded based on the goodness of fit 
          of the registration.
        </brief>
        <description>
          SOMETHING
        </description>
        <default><item>.7</item></default>
        <minimum inclusive="yes">0.0</minimum>
        
      </parameter>
      <parameter name="DEGREE">
        <type>integer</type>
        <brief>
          The order of polynomial to solve for.
        </brief>
        <description>
          SOMETHING
        </description>
        <default><item>3</item></default>
        <minimum inclusive="yes">1</minimum>
        
      </parameter>
      <parameter name="MAXTIME">
        <type>double</type>
        <brief>
          The time, in seconds, that the last line was taken. The minimum is .0026 seconds it takes for 
          a 2250 line image to be taken plus the time it takes to take 60 checklines.
        </brief>
        <description>
          SOMETHING
          WHAT IS THIS USED FOR
        </description>
        <default><item>0.0027114285714286</item></default>
        <minimum inclusive="no">0.0</minimum>
      </parameter>
      
      <parameter name="COEFFICIENTTO">
        <type>filename</type>
        <fileMode>output</fileMode>
        <brief>
          Output file that holds the coefficients.
        </brief>
        <description>
          SOMETHING
          FORMAT
        </description>
        <filter>
          *.csv
        </filter>
      </parameter>
      
      <parameter name="RESIDUALTO">
        <type>filename</type>
        <fileMode>output</fileMode>
        <internalDefault>None</internalDefault>
        <brief>
          Output file that holds the residuals.
        </brief>
        <description>
          SOMETHING
        </description>
        <filter>
          *.csv
        </filter>
      </parameter>
      
    </group>
  </groups>

</application>
+234 −0
Original line number Diff line number Diff line
#include "Isis.h"

#include <iostream>

#include <QList>
#include <QString>

#include "AutoReg.h"
#include "AutoRegFactory.h"
#include "BasisFunction.h"
#include "Chip.h"
#include "Cube.h"
#include "CSVReader.h"
#include "FileName.h"
#include "LeastSquares.h"
#include "NthOrderPolynomial.h"
#include "Pvl.h"
#include "Table.h"
#include "UserInterface.h"

using namespace std;
using namespace Isis;

struct RegistrationData {
  int checkLine; //0
  int checkSample; //1
  double checkTime; //2
  double matchedLine; //3
  double matchedSample; //4
  double matchedTime; //5
  double deltaLine; //6
  double deltaSample; //7
  double goodness; //8 Goodness of fit
  int success; //9
};

void IsisMain() {
  
  bool registrationFileSpecified = false;
  
  UserInterface &ui = Application::GetUserInterface();
  
  Cube jitterCube;
  jitterCube.open(ui.GetFileName("FROM"), "rw");
  
  Cube checkCube;
  checkCube.open(ui.GetFileName("FROM2"), "r");
  
  Pvl defFile;
  defFile.read(ui.GetFileName("DEFFILE"));
  AutoReg *ar = AutoRegFactory::Create(defFile);

  double scale = ui.GetDouble("SCALE");

  int pointSpacing = jitterCube.sampleCount();

  // Setup the registration results file
  ofstream outputFile;
  if (ui.WasEntered("TO")) {
    registrationFileSpecified = true;
    QString to(FileName(ui.GetFileName("TO")).expanded());
    outputFile.open(to.toLatin1().data());
    outputFile << "# checkline line, checkline sample, checkline time taken, "
                  "matched jittered image line, matched jittered image "
                  "sample, matched jittered image time taken, delta line, "
                  "delta sample, goodness of fit, registration success " << endl;
  }

  // Question: Why use a file name here? Can't Table read from an open Cube?
  Table mainReadouts(QString("Normalized Main Readout Line Times"), jitterCube.fileName());
  Table checklineReadouts(QString("Normalized Checkline Readout Line Times"), checkCube.fileName());
  
  // Register each check line to the area near the corrisponding main image line using the 
  // registration definition file
  QList<RegistrationData> registrationData;
  for (int k = 0; k < checkCube.lineCount(); k++) {
    
    int checklineLine = checklineReadouts[k][0];
    int mainLine = mainReadouts[checklineLine][0];
    
    int sample = (int)(pointSpacing / 2.0 + 0.5);
    
    ar->PatternChip()->TackCube(sample, k + 1); 
    ar->PatternChip()->Load(checkCube);
    
    ar->SearchChip()->TackCube(sample, checklineLine * scale); // The checkline will correspond to the line number that the checkCube was taken at
    ar->SearchChip()->Load(jitterCube);
    
    ar->Register();
    
    if (registrationFileSpecified) {
      outputFile << checklineLine << "," << sample/scale << "," << 
                    std::setprecision(14) << double(checklineReadouts[k][1]) << "," << 
                    ar->CubeLine()/scale << "," << ar->CubeSample()/scale << ","  << 
                    double(mainReadouts[mainLine][1]) << "," << 
                    checklineLine - ar->CubeLine()/scale << "," << 
                    sample/scale - ar->CubeSample()/scale << "," << 
                    ar->GoodnessOfFit() << "," << ar->Success() << endl;
    }
    
    RegistrationData checkLineRegistration;
    checkLineRegistration.checkLine = checklineLine;
    checkLineRegistration.checkSample = sample/scale;
    checkLineRegistration.checkTime = double(checklineReadouts[k][1]);
    checkLineRegistration.matchedLine = ar->CubeLine()/scale;
    checkLineRegistration.matchedSample = ar->CubeSample()/scale;
    checkLineRegistration.matchedTime = double(mainReadouts[mainLine][1]);
    checkLineRegistration.deltaLine = checklineLine - ar->CubeLine()/scale;
    checkLineRegistration.deltaSample = sample/scale - ar->CubeSample()/scale;
    checkLineRegistration.goodness = ar->GoodnessOfFit();
    checkLineRegistration.success = ar->Success();
    registrationData.append(checkLineRegistration);
  }

  //!!!!!!!!FOR INTERNAL STORAGE BETWEEN THE REGISTRATION STEP AND THE FITTING STEP !!!!!!!
  //!!!!!!!! We are going to use a QList of registrationData structs !!!!!!!

  if (ui.WasEntered("TO2")) {
    ofstream regStatsFile;
    QString to(FileName(ui.GetFileName("TO2")).expanded());
    regStatsFile.open(to.toLatin1().data());
    Pvl regStats = ar->RegistrationStatistics();
    regStatsFile << regStats << endl;
    regStatsFile << endl;
    regStatsFile.close();
  }

  if (registrationFileSpecified) {
    outputFile.close();
  }

  // Solve for the coefficients of the Nth order polynomial
  double tolerance = ui.GetDouble("TOLERANCE");
  int degree = ui.GetInteger("DEGREE");
  double maxTime = ui.GetDouble("MAXTIME");

  BasisFunction *lineFunction = new NthOrderPolynomial(degree);
  BasisFunction *sampleFunction = new NthOrderPolynomial(degree);

  LeastSquares lsqLine(*lineFunction, false, false, false, false);
  LeastSquares lsqSample(*sampleFunction, false, false, false, false);

  std::vector<double> known(2);

  for (int i = 0; i < registrationData.size(); i++) {
    RegistrationData checkLineRow = registrationData[i];

    if (checkLineRow.goodness >= tolerance) {

      /* Normalization Equation
       * 
       * a = min of scale 
       * b = max of scale
       * 
       * ((b - a)(x - min(x)) / (max(x) - min(x))) + a 
       * 
       * We're normalizing from -1 to 1 so the equation below is simplified
       */

      known[0] = ((2 * checkLineRow.matchedTime) / maxTime) - 1;
      known[1] = ((2 * checkLineRow.checkTime) / maxTime) - 1;

      lsqLine.AddKnown(known, checkLineRow.deltaLine);
      lsqSample.AddKnown(known, checkLineRow.deltaSample);
    }
  }

  lsqLine.Solve();
  lsqSample.Solve();


  // Write the coefficients to COEFFICIENTTO file and the main cube label
  ofstream outputCoefficientFile;
  QString coefficientTo(FileName(ui.GetFileName("COEFFICIENTTO")).expanded());
  outputCoefficientFile.open(coefficientTo.toLatin1().data());
  outputCoefficientFile << "# Line, Sample" << endl;

  PvlKeyword &jitterLineCoefficients = 
      jitterCube.label()->findKeyword("JitterLineCoefficients", PvlObject::Traverse);
  PvlKeyword &jitterSampleCoefficients = 
      jitterCube.label()->findKeyword("JitterSampleCoefficients", PvlObject::Traverse);

  for (int i = 0; i < degree; i++) {
    outputCoefficientFile << std::setprecision(14) << lineFunction->Coefficient(i) << "," <<
                             std::setprecision(14) << sampleFunction->Coefficient(i) << endl;
    if (i == 0) {
      jitterLineCoefficients.setValue(toString(lineFunction->Coefficient(i)));
      jitterSampleCoefficients.setValue(toString(sampleFunction->Coefficient(i)));
    }
    else {
      jitterLineCoefficients += toString(lineFunction->Coefficient(i));
      jitterSampleCoefficients += toString(sampleFunction->Coefficient(i));
    }
  }

  outputCoefficientFile.close();


  // Write the registered line/samp, solved line/samp, residual line/samp, time
  if (ui.WasEntered("RESIDUALTO")) {
    ofstream outputResidualFile;
    QString residualTo(FileName(ui.GetFileName("RESIDUALTO")).expanded());
    outputResidualFile.open(residualTo.toLatin1().data());  

    outputResidualFile << "# Registered Line, Solved Line, Registered Line Residual, "
                          "Registered Sample, Solved Sample, Sample Residual, Time Taken" << endl;

    for (unsigned int i = 0; i < lsqLine.Residuals().size(); i++) {
      RegistrationData checkLineRow = registrationData[i];

      double solvedLine = 0;
      double solvedSample = 0;

      for (int k = 0; k < degree; k++) {
        solvedLine = solvedLine + lineFunction->Coefficient(k) * pow(checkLineRow.matchedTime, k+1);
        solvedSample = solvedSample + sampleFunction->Coefficient(k) * pow(checkLineRow.matchedTime, k+1);
      }

      outputResidualFile << std::setprecision(14) << checkLineRow.matchedLine << "," << 
                            std::setprecision(14) << checkLineRow.checkLine - solvedLine << "," <<
                            std::setprecision(14) << lsqLine.Residual(i) << "," << 
                            std::setprecision(14) << checkLineRow.matchedSample << "," << 
                            std::setprecision(14) << checkLineRow.checkSample - solvedSample << "," << 
                            std::setprecision(14) << lsqSample.Residual(i) << "," << 
                            std::setprecision(14) << checkLineRow.matchedTime << endl;
    }

    outputResidualFile.close();
  }

  delete lineFunction;
  delete sampleFunction;

}
+15 −0
Original line number Diff line number Diff line
APPNAME = jitterfit

include $(ISISROOT)/make/isismake.tsts

commands:
	# To test proper run
	cp $(INPUT)/simulated_clipper_eis_nac_rolling_shutter.cub $(OUTPUT) > /dev/null;
	$(APPNAME) \
		from=$(OUTPUT)/simulated_clipper_eis_nac_rolling_shutter.cub \
		from2=$(INPUT)/simulated_clipper_eis_nac_rolling_shutter_checkline.cub \
		deffile=$(INPUT)/S046mos1400x2250.def \
		coefficientto=$(OUTPUT)/coefficients.txt \
		scale=1.0 > /dev/null;
	catlab from=$(OUTPUT)/simulated_clipper_eis_nac_rolling_shutter.cub \
		to=$(OUTPUT)/simulated_clipper_eis_nac_rolling_shutter_labels.pvl > /dev/null;