Commit 88ddf26f authored by Kaitlyn Lee's avatar Kaitlyn Lee Committed by Adam Paquette
Browse files

Cleaned up hyb2onccal to match the JAXA paper (#3952)



* Added logging for testing

* Added couts to test code

* Cleaned up calibration function to match JAXA paper

* Added history comment

* Better comment for why we create a temp file

Co-authored-by: default avatarStuart Sides <ssides@usgs.gov>
parent b6a1c8a2
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -99,6 +99,17 @@ xsi:noNamespaceSchemaLocation="http://isis.astrogeology.usgs.gov/Schemas/Applica
      process FIT files using the old keywords.  Also set TARGET=RYUGU in the event the user
      does not specify a target, and the target in the fit label is set to SKY.
    </change>
    <change name="Kaitlyn Lee" date="2020-07-21">
      Updated calibration method to match JAXAs calibration by:
      1. moving linearity correction to after smear correction,
      2. commenting out dark current and linearity correction,
      3. changing an addition to subtraction in the g_bias calculation,
      4. refactoring the smear correction to be before the main loop to speed up the program
         and to be dependent on the bit-corrected input image DNs instead of the already corrected
         output image DNs,
      5. and removing `*g_sensitivity*g_texp` in the flat-field correction so that the output
         aligns with l2b data.
    </change>
  </history>

  <category>
+73 −137
Original line number Diff line number Diff line
#ifndef Hyb2OncCalUtils_h
#define Hyb2OncCalUtils_h




#include "CSVReader.h"
#include "IException.h"
#include "LineManager.h"
#include "NaifStatus.h"



#include <string>
#include <vector>
#include <algorithm>
@@ -28,10 +18,12 @@

#include "AlphaCube.h"
#include "Buffer.h"
#include "CSVReader.h"
#include "FileName.h"
#include "IException.h"
#include "iTime.h"
#include "LineManager.h"
#include "NaifStatus.h"
#include "Pixel.h"
#include "ProcessByLine.h"
#include "ProcessBySample.h"
@@ -44,18 +36,11 @@
#include "Statistics.h"
#include "TextFile.h"





// OpenCV libraries
#include <opencv2/opencv.hpp>


/**
 * @author 2016-04-04 Tyler Wilson
 *
 *
 */
using namespace cv;
using namespace std;
@@ -120,27 +105,19 @@ static double g_compfactor(1.0); // Default if OutputMode = LOSS-LESS; 16.0 for
static QString g_iofCorrection("IOF");  //!< Is I/F correction to be applied?

// I/F variables

//static double g_iof(1.0);



static double g_iofScale(1.0);
static double g_solarFlux(1.0);  //!< The solar flux (used to calculate g_iof).
static double g_sensitivity(1.0);
static double g_effectiveBandwidth(1.0);

static double g_J(1.0);



namespace Isis {

// Temporary cube file pointer deleter
struct TemporaryCubeDeleter {
  static inline void cleanup(Cube *cube) {
    if (cube) {

      FileName filename(cube->fileName());
      delete cube;
      remove(filename.expanded().toLatin1().data());
@@ -152,7 +129,6 @@ enum InstrumentType{ONCW1,ONCW2,ONCT};
InstrumentType g_instrument;

static AlphaCube *alpha(0);

static Pvl g_configFile;


@@ -166,8 +142,7 @@ static Pvl g_configFile;
 * @return The value of the function at the point x.
 */
double linearFun(double Iobs,double x, double g[3]) {
  return Iobs - g[0]*x -g[1]*pow(x,2.0) -g[2]*pow(x,3.0);

  return Iobs - (g[0] * x) - (g[1] * pow(x, 2.0)) - (g[2] * pow(x, 3.0));
}

/**
@@ -179,7 +154,7 @@ double linearFun(double Iobs,double x, double g[3]) {
 * @return
 */
double dFun(double x, double g[3]) {
  return -g[0] - 2*g[1]*x -3*g[2]*pow(x,2.0);
  return -g[0] - (2 * g[1] * x) - (3 * g[2] * pow(x, 2.0));

}

@@ -201,14 +176,13 @@ bool newton_rapheson(double Iobs,double x0, double g[3],double &result, double e
   int iter = 0;
   int maxIterations = 500;
   x[0] = x0;
   while (dx > epsilon)  {

   while (dx > epsilon) {
     x[1] = x[0] - linearFun(Iobs, x[0], g) / dFun(x[0], g);
     dx = fabs(x[1] - x[0]);
     x[0] = x[1];
     iter++;
     if (iter > maxIterations) {

       return false;
     }
   }
@@ -227,7 +201,6 @@ bool newton_rapheson(double Iobs,double x0, double g[3],double &result, double e
*   @history 2019-02-12 Tyler Wilson - Modified to support new calibration settings/formulas.
*/
void Calibrate(vector<Buffer *>& in, vector<Buffer *>& out) {

  Buffer& imageIn   = *in[0];
  Buffer& flatField = *in[1];
  Buffer& imageOut  = *out[0];
@@ -240,13 +213,19 @@ void Calibrate(vector<Buffer *>& in, vector<Buffer *>& out) {
  int alphaSample = alpha->AlphaSample(currentSample);

  if ( (alphaSample <= pixelsToNull) || (alphaSample >= (1024 - pixelsToNull)) ) {

    for (int i = 0; i < imageIn.size(); i++) {
      imageOut[i] = Isis::Null;
    }
    return;
  }

  double smear = 0;
  for (int j = 0; j < imageIn.size(); j++) {
    // Left out g_darkCurrent subtraction for now.
    smear += ((imageIn[j] * pow(2.0, 12 - g_bitDepth) - g_bias) / imageIn.size());
  }
  smear *= g_timeRatio;

  // Iterate over the line space
  for (int i = 0; i < imageIn.size(); i++) {
    imageOut[i] = imageIn[i] * pow(2.0, 12 - g_bitDepth);
@@ -260,11 +239,8 @@ void Calibrate(vector<Buffer *>& in, vector<Buffer *>& out) {
    // Apply compression factor here to raise LOSSY dns to proper response


    // 1) BIAS Removal - Only needed if not on-board corrected

    // BIAS Removal - Only needed if not on-board corrected
    if (!g_onBoardSmearCorrection) {


      if ( (imageOut[i] - g_bias) <= 0.0) {
        imageOut[i] = Null;
        continue;
@@ -275,29 +251,23 @@ void Calibrate(vector<Buffer *>& in, vector<Buffer *>& out) {
      }
    }


    double dn = imageOut[i];    
    double result = 1.0;
    double x0 = 1.0;
    newton_rapheson(dn,x0, g_L,result );   
    imageOut[i] = result;


    // DARK Current
    imageOut[i] = imageOut[i] - g_darkCurrent;    

    // Smear correction
    if (!g_onBoardSmearCorrection) {

      double smear = 0;
      for (int j=0;j < imageIn.size();j++) {
        smear += (imageOut[j]/imageIn.size() );
      }
      smear*=g_timeRatio;
      imageOut[i] = imageOut[i] - smear;

    }

    // Both dark current and linearity correction are commented out for now since the JAXA team
    // does not currently do these steps.

    // DARK Current
    // imageOut[i] = imageOut[i] - g_darkCurrent;    
    
    // Linearity Correction
    // double dn = imageOut[i];    
    // double result = 1.0;
    // double x0 = 1.0;
    // newton_rapheson(dn,x0, g_L,result );   
    // imageOut[i] = result;

    // FLATFIELD correction
    //  Check for any special pixels in the flat field (unlikely)
@@ -311,20 +281,15 @@ void Calibrate(vector<Buffer *>& in, vector<Buffer *>& out) {
      }
      else {
        if (flatField[i] != 0) {
          imageOut[i] /= (flatField[i]*g_sensitivity*g_texp);
          imageOut[i] /= (flatField[i]);
        }
      }
    }


  }


  return;
}



/**
 * @brief Translates a 1-banded Isis::Cube to an OpenMat object
 *
@@ -335,12 +300,10 @@ void Calibrate(vector<Buffer *>& in, vector<Buffer *>& out) {
 * @return @b Mat A pointer to the OpenMat object
 */
Mat *isis2mat(Cube *icube) {

  int nlines = icube->lineCount();
  int nsamples = icube->sampleCount();
  Mat *matrix = new Mat(nlines, nsamples, CV_64F);


  // Set up line manager and read in the data
  LineManager linereader(*icube);
  for (int line = 0; line < nlines; line++) {
@@ -350,10 +313,7 @@ Mat * isis2mat(Cube *icube) {
      matrix->at<double>(line, samp) = (double)linereader[samp];
    }
 }


return matrix;

}


@@ -365,7 +325,6 @@ return matrix;
 * @param matrix A pointer to the OpenMat object
 *
 * @param cubeName The name of the Isis::Cube that is being created.
 *
 */
void mat2isis(Mat *matrix, QString cubeName) {

@@ -386,14 +345,10 @@ void mat2isis(Mat *matrix, QString cubeName) {
    for ( int samp=0; samp < nsamples; samp++ ) {

      linewriter[samp] = matrix->at<double>(linewriter.Line() - 1,samp);

    }
    ocube.write(linewriter);

  }

  ocube.close();

}


@@ -405,12 +360,9 @@ void mat2isis(Mat *matrix, QString cubeName) {
 * @param matrix A pointer to the OpenMat object
 *
 * @param cubeName The name of the ISIS::Cube that is being created.
 *
 */

void translate(Cube *flatField,double *transform, QString fname) {


  Mat *originalMat = isis2mat(flatField);
  double scale = transform[0];

@@ -428,7 +380,6 @@ void translate(Cube *flatField,double *transform, QString fname) {

  Mat temp = *originalMat;


  Mat originalCropped = temp(Rect(startsample, startline, width, height));

  if (scale == 1) {
@@ -453,13 +404,11 @@ void translate(Cube *flatField,double *transform, QString fname) {
*   @history 2019-02-12 Tyler Wilson - Modified to support new calibration settings/formulas.
*/
FileName DetermineFlatFieldFile(const QString &filter) {

  QString fileName = "$hayabusa2/calibration/flatfield/";

  // FileName consists of binned/notbinned, camera, and filter
  fileName += "flat_" + filter.toLower() + "_norm.cub";

  FileName final(fileName);

  return final;
}

@@ -469,15 +418,8 @@ FileName DetermineFlatFieldFile(const QString &filter) {
* @param config QString Name of the calibration file to load.
*
* Loads g_b0-g_b2,g_bae0-g_bae1,g_d0-g_d1
*
*
*/
QString loadCalibrationVariables(const QString &config)  {



  //  UserInterface& ui = Application::GetUserInterface();

  FileName calibFile(config);
  if ( config.contains("?") ) calibFile = calibFile.highestVersion();

@@ -497,8 +439,6 @@ QString loadCalibrationVariables(const QString &config) {
  // Load Smear Removal Variables
  g_Tvct = Smear["Tvct"];



  // Load DarkCurrent variables and calculate the dark current
  g_d0 = DarkCurrent["D"][0].toDouble();
  g_d1 = DarkCurrent["D"][1].toDouble();
@@ -518,7 +458,7 @@ QString loadCalibrationVariables(const QString &config) {
      CCDTemp = g_CCD_T_temperature;
  }

   g_darkCurrent = g_texp*exp(0.10*CCDTemp+0.52);
   g_darkCurrent = g_texp * exp(g_d0 * (CCDTemp + g_d1));

  // Load Bias variables
  g_b0 = Bias["B"][0].toDouble();
@@ -527,12 +467,10 @@ QString loadCalibrationVariables(const QString &config) {
  g_bae0 = Bias["B_AE"][0].toDouble();
  g_bae1 = Bias["B_AE"][1].toDouble();



  // Compute BIAS correction factor (it's a constant so do it once!)
  g_bias = g_b0+g_b1*g_CCD_T_temperature+g_b2*g_ECT_T_temperature;
  g_bias = g_b0 + (g_b1 * g_CCD_T_temperature) + (g_b2 * g_ECT_T_temperature);

  g_bias *= (g_bae0 + g_bae1*g_AEtemperature); //bias correction factor
  g_bias *= (g_bae0 - (g_bae1 * g_AEtemperature)); //bias correction factor
  
  // Load the Solar Flux for the specific filter
  g_solarFlux = solar[g_filter.toLower()];
@@ -541,15 +479,13 @@ QString loadCalibrationVariables(const QString &config) {

  g_J = g_solarFlux / (g_effectiveBandwidth * .0001);



  // Load the linearity variables
  g_L[0] = linearity["L"][0].toDouble();
  g_L[1] = linearity["L"][1].toDouble();
  g_L[2] = linearity["L"][2].toDouble();


  return ( calibFile.original() );
  return calibFile.original();
}


+276 −329
Original line number Diff line number Diff line
@@ -34,19 +34,11 @@
#include "Statistics.h"
#include "TextFile.h"



using namespace std;





namespace Isis {

  void hyb2onccal(UserInterface &ui) {


    // g_iofCorrection = ui.GetString("UNITS");

    const QString hyb2cal_program = "hyb2onccal";
@@ -55,7 +47,6 @@ void hyb2onccal(UserInterface &ui) {
    QString hyb2cal_runtime = Application::DateTime();

    ProcessBySample p;

    Cube *icube = p.SetInputCube("FROM");

    // Basic assurances...
@@ -70,12 +61,10 @@ void hyb2onccal(UserInterface &ui) {
    try {
      g_filter = bandbin["FilterName"][0];
    }

    catch(IException &e) {
      QString msg = "Unable to read FilterName keyword in the BandBin group "
                    "from input file [" + icube->fileName() + "]";
      throw IException(e, IException::Io, msg, _FILEINFO_);

    }

    QString instrument("");
@@ -87,7 +76,6 @@ void hyb2onccal(UserInterface &ui) {
      QString msg = "Unable to read InstrumentId keyword in the Instrument group "
                    "from input file [" + icube->fileName() + "]";
      throw IException(e, IException::Io, msg, _FILEINFO_);

    }

    if (instrument=="ONC-W1") {
@@ -105,8 +93,6 @@ void hyb2onccal(UserInterface &ui) {
      throw IException(IException::Io, msg, _FILEINFO_);
    }



    //Set up binning and image subarea mapping
    binning = inst["Binning"];
    int startLine = inst["SelectedImageAreaY1"];
@@ -127,8 +113,6 @@ void hyb2onccal(UserInterface &ui) {

    }



    if (g_bitDepth < 0) {
      g_bitDepth = 12;  //Correpsonds to no correction being done for bit depth
    }
@@ -144,21 +128,17 @@ void hyb2onccal(UserInterface &ui) {
      throw IException(e, IException::Io, msg, _FILEINFO_);
    }


    try {
      g_AEtemperature = inst["ONCAETemperature"][0].toDouble();

    }
    catch(IException &e) {
      QString msg = "Unable to read [ONCAETemperature] keyword in the Instrument group "
                    "from input file [" + icube->fileName() + "]";
      throw IException(e, IException::Io, msg, _FILEINFO_);

    }

    try {
      g_CCD_T_temperature = inst["ONCTCCDTemperature"][0].toDouble();

    }
    catch(IException &e) {
      QString msg = "Unable to read [ONCTCCDTemperature] keyword in the Instrument group "
@@ -168,7 +148,6 @@ void hyb2onccal(UserInterface &ui) {

    try {
      g_ECT_T_temperature = inst["ONCTElectricCircuitTemperature"][0].toDouble();

    }
    catch(IException &e) {
      QString msg = "Unable to read [ONCTElectricCircuitTemperature] keyword in the Instrument group "
@@ -176,25 +155,19 @@ void hyb2onccal(UserInterface &ui) {
      throw IException(e, IException::Io, msg, _FILEINFO_);
    }


    try {

      g_startTime = inst["SpacecraftClockStartCount"][0];
    }

    catch (IException &e) {
      QString msg = "Unable to read [SpacecraftClockStartCount] keyword in the Instrument group "
                    "from input file [" + icube->fileName() + "]";
      throw IException(e, IException::Io, msg, _FILEINFO_);
    }


    QString smearCorrection;

    try {
      smearCorrection = inst["SmearCorrection"][0];
    }

    catch (IException &e) {
      QString msg = "Unable to read [SmearCorrection] keyword in the Instrument group "
                    "from input file [" + icube->fileName() + "]";
@@ -206,18 +179,15 @@ void hyb2onccal(UserInterface &ui) {
    }
    else {
      qDebug() << icube->fileName();

    }

    QString compmode = inst["Compression"];
    // TODO: verify that the compression factor/scale is actually 16 for compressed Hayabusa2 images.
    g_compfactor = ("lossy" == compmode.toLower()) ? 16.0 : 1.0;


    QString target = inst["TargetName"];
    g_target = target;


    // NOTE we do not have a valid flat-field for the W1 or W2 images.
    FileName flatfile = "NONE";
    PvlGroup alphaCube;
@@ -237,7 +207,6 @@ void hyb2onccal(UserInterface &ui) {

      // Image is not cropped
      if (!g_cropped) {

        // Determine if we need to subsample the flat field if pixel binning occurred
        // TODO: test a binned image (add test case).
        if (binning > 1) {
@@ -258,23 +227,19 @@ void hyb2onccal(UserInterface &ui) {
            remove(reducedFlat.toLatin1().data());
            throw ie;
          }
        //QScopedPointer<Cube, TemporaryCubeDeleter> reduced(new Cube(reducedFlat, "r"));
        //flatcube.swap( reduced );
        }

        // Set up processing for flat field as a second input file
        CubeAttributeInput att;
        p.SetInputCube(reducedFlat, att);
      }
      // The image was cropped, so pull the same subarea from the flat file into a temp file
      else {

      // Image is cropped so we have to deal with it
        FileName transFlat =
        FileName::createTempFile("$TEMPORARY/" + flatfile.baseName() + "_translated.cub");

        Cube *flatOriginal = new Cube(flatfile.expanded() );


        double alphaStartSample = alphaCube["AlphaStartingSample"][0].toDouble();
        double alphaStartLine = alphaCube["AlphaStartingLine"][0].toDouble();
        double alphaEndSample = alphaCube["AlphaEndingSample"][0].toDouble();
@@ -284,32 +249,21 @@ void hyb2onccal(UserInterface &ui) {

        // Translates and scales the flatfield image.  Scaling
        // might be necessary in the event that the raw image was also binned.

        translate(flatOriginal,transform,transFlat.expanded());

        QScopedPointer<Cube, TemporaryCubeDeleter> translated(new Cube(transFlat.expanded(), "r"));
        flatcube.swap(translated);

        CubeAttributeInput att;

        p.SetInputCube(transFlat.expanded(),att);

      }  //Finished setting flatfield file for ONC-T

    }



    Cube *ocube  = p.SetOutputCube("TO");


    QString calfile = loadCalibrationVariables(ui.GetAsString("CONFIG"));



    g_timeRatio = g_Tvct/(g_texp + g_Tvct);


    QString g_units = "DN";
    // if ( "radiance" == g_iofCorrection.toLower() ) {
    //   // Units of RADIANCE
@@ -368,9 +322,7 @@ void hyb2onccal(UserInterface &ui) {
    calibrationLog.addKeyword(bnae);  
    calibrationLog.addKeyword(PvlKeyword("Bias_AETemp", toString(g_AEtemperature, 16)));


    switch (g_instrument) {

      case InstrumentType::ONCT:
         calibrationLog.addKeyword(PvlKeyword("Bias_CCDTemp", toString(g_CCD_T_temperature, 16)));
         calibrationLog.addKeyword(PvlKeyword("Bias_ECTTemp", toString(g_ECT_T_temperature, 16)));
@@ -412,15 +364,10 @@ void hyb2onccal(UserInterface &ui) {
    darkCurrent.addValue(toString(g_darkCurrent, 16));
    calibrationLog.addKeyword(darkCurrent);


    // Write Calibration group to output file
    ocube->putGroup(calibrationLog);
    Application::Log(calibrationLog);
  //configFile.clear();
    p.EndProcess();

  }



}