USGS

Isis 3.0 Application Source Code Reference

Home

mdiscal.cpp

Go to the documentation of this file.
00001 // $Id: mdiscal.cpp 2661 2011-06-16 22:40:56Z slambright@GS.DOI.NET $
00002 #include "Isis.h"
00003 
00004 #include <vector>
00005 #include <string>
00006 #include <algorithm>
00007 #include <memory>
00008 #include <cstdio>
00009 #include <cmath>
00010 
00011 #include "DarkModelPixel.h"
00012 #include "MdisCalUtils.h"
00013 #include "MultivariateStatistics.h"
00014 #include "ProcessByLine.h"
00015 #include "ProgramLauncher.h"
00016 #include "Spice.h"
00017 #include "Statistics.h"
00018 #include "TextFile.h"
00019 
00020 using namespace Isis;
00021 using namespace std;
00022 
00023 bool convertDarkToNull;
00024 enum MdisDarkCurrentMode {
00025   DarkCurrentNone,
00026   DarkCurrentStandard,
00027   DarkCurrentLinear,
00028   DarkCurrentModel
00029 };
00030 
00031 
00032 MdisDarkCurrentMode darkCurrentMode;
00033 vector<double> calibrationValues;
00034 bool isNarrowAngleCamera;
00035 bool isBinnedData;
00036 double exposureDuration;
00037 double ccdTemperature;
00038 int filterNumber;
00039 static int nDarkColumns(0);
00040 static int nValidDark(0);
00041 static int nSampsToNull(0);
00042 Statistics darkStrip;
00043 vector<double> prevLineData;
00044 vector<double> smearData;
00045 static double smearComponent(3.4);
00046 Pvl configFile;
00047 
00048 //  Limit functionality for aiding dark analysis
00049 static bool g_flatfield = true;
00050 static bool g_radiometric = true;
00051 
00052 //  Absolute coefficents
00053 static double abs_coef(1.0);
00054 
00055 //  I/F variables
00056 static double solarDist(1.0);
00057 static double F_f(1.0);
00058 static double iof(1.0);   //  I/F value for observation
00059 static DarkModelPixel *model(0);
00060 
00061 //  Local functions
00062 Filename DetermineFlatFieldFile();
00063 void GatherDarkStatistics(Buffer &in);
00064 void Calibrate(vector<Buffer *>&in, vector<Buffer *>&out);
00065 
00066 void IsisMain() {
00067 
00068   const string mdiscal_program = "mdiscal";
00069   const string mdiscal_version = "1.3";
00070   const string mdiscal_revision = "$Revision: 2661 $";
00071   string mdiscal_runtime = Application::DateTime();
00072 
00073   // Specify the version of the CDR generated
00074   const int cdr_version = 3;
00075 
00076   // We will be processing by column in case of a linear dark current fit. This will make the
00077   //   calibration a one pass system in this case, rather than two.
00078   ProcessByLine p;
00079   Filename calibFile("$messenger/calibration/mdisCalibration????.trn");
00080   calibFile.HighestVersion();
00081   configFile.Read(calibFile.Expanded());
00082 
00083   // Initialize variables
00084   calibrationValues.clear();
00085   prevLineData.clear();
00086   convertDarkToNull = true;
00087   isNarrowAngleCamera = true;
00088   isBinnedData = true;
00089   darkCurrentMode = (MdisDarkCurrentMode) - 1;
00090   exposureDuration = 0.0;
00091   ccdTemperature = 0.0; // This needs figured out!
00092   filterNumber = 1;  // Sufficent for the NAC!
00093   model = 0;
00094 
00095   Cube *icube = p.SetInputCube("FROM");
00096   PvlGroup &inst = icube->getGroup("Instrument");
00097   isNarrowAngleCamera = ((string)inst["InstrumentId"] == "MDIS-NAC");
00098   exposureDuration = inst["ExposureDuration"];
00099   exposureDuration /= 1000.0;  //  Convert ms to sec
00100 
00101   // Determine dark columns
00102   int fpuBin = inst["FpuBinningMode"];
00103   int pxlBin = inst["PixelBinningMode"];
00104 
00105   nDarkColumns = 4 / (fpuBin + 1);    //  DPU binning gives 2 dark cols
00106   if (pxlBin > 2) nDarkColumns = 0;   //  MP binning > 2x2 yields no darks
00107   else if (pxlBin > 0) nDarkColumns /= (pxlBin+1); // Might be 1 if wo/DPU + MP 2x2
00108 
00109   //  Determine number of valid darks.  For no binning will have 3.  All combos
00110   //  that have 2x2 total binning will give 1 valid dark.  All other options 
00111   //  have no valid dark columns.   
00112   nValidDark = MIN(nDarkColumns, 3);
00113   if(nValidDark < 3) {
00114     if((fpuBin + pxlBin) > 1) nValidDark = 0;
00115     else nValidDark = MIN(nValidDark, 1);
00116   }
00117 
00118 // Determine number of samples/columns to NULL.  For no binning it will yield 4
00119 // columns to NULL.  For DPU but no MP binning, 3;  For no DPU but MP binning,
00120 //  2x2 yields 3, 4x4 and 8x8 yields 1.
00121   nSampsToNull = (pxlBin < 2) ? 0 : ((pxlBin > 2) ? 1 : 3);  // Only MP here
00122   nSampsToNull = MIN(MAX(nDarkColumns+1,nSampsToNull), 4);  // No more than 4!
00123   darkStrip.Reset();
00124 
00125   ccdTemperature = icube->getGroup("Archive")["CCDTemperature"];
00126 
00127   // Binned data only applies to FPUBIN mode.  Pixel binning must be dealt
00128   // with specially in other calibration support components
00129   isBinnedData = (fpuBin == 1);
00130 
00131   // Get the trusted filter number
00132   if(!isNarrowAngleCamera) {
00133     filterNumber = ((int)(icube->getGroup("BandBin")["Number"])) - 1;
00134   }
00135   else {
00136     filterNumber = 1;  // For the NAC
00137   }
00138 
00139   UserInterface &ui = Application::GetUserInterface();
00140   convertDarkToNull = !ui.GetBoolean("KEEPDARK");
00141   if (!convertDarkToNull) nSampsToNull = 0;
00142     
00143 
00144   iString darkCurr = ui.GetString("DARKCURRENT");
00145   g_flatfield = ui.GetBoolean("FLATFIELD");
00146   g_radiometric = ui.GetBoolean("RADIOMETRIC");
00147 
00148   if(icube->getBandCount() != 1) {
00149     throw iException::Message(iException::User,
00150                               "MDIS images may only contain one band", _FILEINFO_);
00151   }
00152 
00153   if(icube->getSampleCount() < 3) {
00154     throw iException::Message(iException::User,
00155                               "Unable to obtain dark current data. Expected a sample dimension of at least 3", _FILEINFO_);
00156   }
00157 
00158   if((int)icube->getGroup("Instrument")["Unlutted"] == false) {
00159     throw iException::Message(iException::User,
00160                               "Calibration may not be performed on unlutted data.", _FILEINFO_);
00161   }
00162 
00163 
00164   //  Check for cases where certain models cannot be computed.
00165   //  These would be for cases where more than two factors of compression
00166   //  occur.  For this case, only the model can be used and only if the
00167   //  exposure time < 2 secs.
00168   if((darkCurr != "NONE") && (nValidDark <= 0)) {
00169     //  Both cases require dark pixels, model does not
00170     if((darkCurr == "STANDARD") || (darkCurr == "LINEAR")) {
00171       darkCurr = "MODEL";
00172       string mess = "There are no valid dark current pixels which are required"
00173                     " for " + darkCurr + " calibration... must use MODEL";
00174       iException &ie = iException::Message(iException::User, mess, _FILEINFO_);
00175       ie.Report();
00176       ie.Clear();
00177     }
00178 
00179     //  Model cannot be used for exposure times > 1.0 <sec>
00180     if((darkCurr == "MODEL") && (exposureDuration > 1.0)) {
00181       darkCurr = "NONE";
00182       string mess = "There are no valid dark current pixels and the dark model"
00183                     " correction can not be used when the exposure duration"
00184                     " exceeds 1000...image cannot be calibrated";
00185       iException &ie = iException::Message(iException::User, mess, _FILEINFO_);
00186       ie.Report();
00187       ie.Clear();
00188     }
00189   }
00190 
00191   auto_ptr<DarkModelPixel> darkModel;
00192   if(darkCurr == "NONE") {
00193     darkCurrentMode = DarkCurrentNone;
00194   }
00195   else if(darkCurr == "STANDARD") {
00196     darkCurrentMode = DarkCurrentStandard;
00197     calibrationValues.resize(icube->getLineCount());
00198   }
00199   else if(darkCurr == "LINEAR") {
00200     darkCurrentMode = DarkCurrentLinear;
00201     calibrationValues.resize(icube->getLineCount());
00202   }
00203   else if(darkCurr == "MODEL") {
00204     if(exposureDuration > 1.0) {
00205       string mess = "Dark model correction can not be used when the "
00206                     "exposure duration exceeds 1000...using LINEAR instead.";
00207       iException &ie = iException::Message(iException::User, mess, _FILEINFO_);
00208       ie.Report();
00209       ie.Clear();
00210 
00211       // set processing to standard
00212       darkCurrentMode = DarkCurrentLinear;
00213       calibrationValues.resize(icube->getLineCount());
00214       darkCurr = "STANDARD";
00215     }
00216     else {
00217       darkCurrentMode = DarkCurrentModel;
00218     }
00219   }
00220   else {
00221     // should never happen
00222     throw iException::Message(iException::Programmer,
00223                               "Invalid dark current mode [" +
00224                               darkCurr + "]", _FILEINFO_);
00225   }
00226 
00227   string darkCurrentFile("");
00228   if(darkCurrentMode != DarkCurrentNone) {
00229     if(darkCurrentMode != DarkCurrentModel) {
00230       p.Progress()->SetText("Gathering Dark Current Statistics");
00231       p.StartProcess(GatherDarkStatistics);
00232     }
00233     else {
00234       // read in dark current table variables and report the filename used
00235       darkModel = auto_ptr<DarkModelPixel> (new DarkModelPixel(pxlBin, ccdTemperature, exposureDuration));
00236       darkCurrentFile = darkModel->loadCoefficients(isNarrowAngleCamera, isBinnedData);
00237       model = darkModel.get();
00238     }
00239   }
00240 
00241   // We need to figure out our flat-field file
00242   if(darkCurrentMode == DarkCurrentLinear) {
00243     // We need to perform a linear regression with our data,
00244     //   convert statistics to a line.
00245     double *xdata = new double[calibrationValues.size()];
00246     double *ydata = new double[calibrationValues.size()];
00247 
00248     for(unsigned int x = 0; x < calibrationValues.size(); x++) {
00249       xdata[x] = x;
00250       ydata[x] = calibrationValues[x];
00251     }
00252 
00253     // Perform a regression
00254     MultivariateStatistics stats;
00255     stats.AddData(xdata, ydata, calibrationValues.size());
00256 
00257     // y = A + Bx
00258     double a, b;
00259     stats.LinearRegression(a, b);
00260     delete [] xdata;
00261     delete [] ydata;
00262 
00263     // Store a,b in calibration data instead of our line
00264     calibrationValues.resize(2);
00265     calibrationValues[0] = a;
00266     calibrationValues[1] = b;
00267   }
00268 
00269   //  Compute the (new) absolute calibration
00270   string respfile("");
00271   vector<double> rsp = loadResponsivity(isNarrowAngleCamera, isBinnedData,
00272                                         filterNumber + 1, respfile);
00273   // abs_coef = 1.0 / (rsp[0] * ((rsp[2] * ccdTemperature) + rsp[1]));
00274   double T = 1.0;
00275   double Rt = rsp[0];
00276   double Resp = 0;
00277   for(unsigned int i = 1 ; i < rsp.size() ; i++) {
00278     Resp += Rt * (rsp[i] * T);
00279     T *= ccdTemperature;
00280   }
00281   abs_coef = 1.0 / Resp;
00282 
00283 //  Retrieve filter dependant SMEAR component
00284   string smearfile("");
00285   smearComponent = loadSmearComponent(isNarrowAngleCamera, filterNumber + 1,
00286                                       smearfile);
00287 
00288   //  Compute I/F if requested by user
00289   iof = 1.0;
00290   bool do_iof = ui.GetBoolean("IOF");
00291   if (!g_radiometric) do_iof = false;
00292   bool iof_is_good = false;
00293   string solirrfile("");
00294   if (do_iof) {
00295     PvlGroup &inst = icube->getGroup("Instrument");
00296     string target = inst["TargetName"];
00297     string startTime = inst["SpacecraftClockCount"];
00298     if(sunDistanceAU(startTime, target, solarDist)) {
00299       vector<double> sol = loadSolarIrr(isNarrowAngleCamera, isBinnedData,
00300                                         filterNumber + 1, solirrfile);
00301       F_f = sol[2];
00302       iof = pi_c() * (solarDist * solarDist) / F_f;
00303       iof_is_good = true;
00304     }
00305     else {
00306       iof = 1.0;
00307       iof_is_good = false;
00308     }
00309   }
00310 
00311   //  Determine if we need to subsample the flat field should pixel binning
00312   //  occurred
00313   string reducedFlat("");
00314   Filename flatfield = DetermineFlatFieldFile();
00315   if(pxlBin > 0) {
00316     iString scale(pxlBin);
00317     Filename newflat;
00318     newflat.Temporary(flatfield.Basename() + "_reduced", "cub");
00319     reducedFlat = newflat.Expanded();
00320     string parameters = "FROM=" + flatfield.Expanded() +
00321                         " TO="   + newflat.Expanded() +
00322                         " MODE=SCALE" +
00323                         " LSCALE=" + scale +
00324                         " SSCALE=" + scale;
00325     try {
00326     //  iApp->Exec("reduce", parameters);
00327       ProgramLauncher::RunIsisProgram("reduce", parameters);
00328       reducedFlat = newflat.Expanded();
00329     }
00330     catch (iException &ie) {
00331       remove(reducedFlat.c_str());
00332       throw;
00333     }
00334     CubeAttributeInput att;
00335     p.SetInputCube(reducedFlat, att);
00336   }
00337   else {
00338     CubeAttributeInput att;
00339     p.SetInputCube(flatfield.Expanded(), att);
00340   }
00341 
00342   //  Set output file for processing
00343   Cube *ocube = p.SetOutputCube("TO");
00344 
00345   try {
00346     p.Progress()->SetText("Calibrating MDIS Cube");
00347     p.StartProcess(Calibrate);
00348   }
00349   catch(...) {
00350     if(!reducedFlat.empty()) remove(reducedFlat.c_str());
00351     throw;
00352   }
00353 
00354   //  Remove the temporary reduced input file if generated
00355   if(!reducedFlat.empty()) remove(reducedFlat.c_str());
00356 
00357   // Log calibration activity
00358   PvlGroup calibrationLog("RadiometricCalibration");
00359   calibrationLog.AddKeyword(PvlKeyword("SoftwareName", mdiscal_program));
00360   calibrationLog.AddKeyword(PvlKeyword("SoftwareVersion", mdiscal_version));
00361   calibrationLog.AddKeyword(PvlKeyword("ProcessDate", mdiscal_runtime));
00362   calibrationLog.AddKeyword(PvlKeyword("DarkCurrentModel", darkCurr));
00363 
00364   if(darkCurrentMode == DarkCurrentLinear) {
00365     iString equation = "Y = " + iString(calibrationValues[0]) + iString(" + ") + iString(calibrationValues[1]) + iString("x");
00366     calibrationLog.AddKeyword(PvlKeyword("DarkCurrentEquation", (string)equation));
00367   }
00368   else if(darkCurrentMode == DarkCurrentModel) {
00369     calibrationLog.AddKeyword(PvlKeyword("DarkCurrentFile", darkCurrentFile));
00370   }
00371 
00372   calibrationLog.AddKeyword(PvlKeyword("BinnedImage", isBinnedData));
00373   calibrationLog.AddKeyword(PvlKeyword("FilterNumber", filterNumber + 1));
00374   if (g_flatfield) {
00375     calibrationLog.AddKeyword(PvlKeyword("FlatFieldFile", flatfield.OriginalPath() + "/" + flatfield.Name()));
00376     calibrationLog.AddKeyword(PvlKeyword("CalibrationFile", calibFile.OriginalPath() + "/" + calibFile.Name()));
00377     calibrationLog.AddKeyword(PvlKeyword("ResponsivityFile", respfile));
00378     calibrationLog.AddKeyword(PvlKeyword("SmearCompFile", smearfile));
00379     PvlKeyword rspKey("Response", rsp[0]);
00380     for(unsigned int i = 1 ; i < rsp.size() ; i++) {
00381       rspKey.AddValue(rsp[i]);
00382     }
00383     calibrationLog.AddKeyword(rspKey);
00384     calibrationLog.AddKeyword(PvlKeyword("SmearComponent", smearComponent));
00385   }
00386   else {
00387     calibrationLog.AddKeyword(PvlKeyword("FlatFieldFile", "N/A"));
00388     calibrationLog.AddKeyword(PvlKeyword("CalibrationFile", "N/A"));
00389     calibrationLog.AddKeyword(PvlKeyword("ResponsivityFile", "N/A"));
00390     calibrationLog.AddKeyword(PvlKeyword("SmearCompFile", "N/A"));
00391     PvlKeyword rspKey("Response");
00392     calibrationLog.AddKeyword(PvlKeyword("Response", "N/A"));
00393     calibrationLog.AddKeyword(PvlKeyword("SmearComponent", "N/A"));
00394   }
00395 
00396   string calibType;
00397   if(do_iof  && iof_is_good) {
00398     calibrationLog.AddKeyword(PvlKeyword("Units", "I over F"));
00399     calibrationLog.AddKeyword(PvlKeyword("SolarDistance", solarDist, "AU"));
00400     calibrationLog.AddKeyword(PvlKeyword("SolarIrrFile", solirrfile));
00401     calibrationLog.AddKeyword(PvlKeyword("FilterIrradianceFactor", F_f));
00402     calibrationLog.AddKeyword(PvlKeyword("IOFFactor", iof));
00403     calibType = "IF";
00404   }
00405   else if (g_radiometric) {
00406     calibrationLog.AddKeyword(PvlKeyword("Units", "W / (m**2 micrometer sr )"));
00407     calibType = "RA";
00408   }
00409   else {
00410     calibrationLog.AddKeyword(PvlKeyword("Units", "DN"));
00411     calibType = "DN";
00412   }
00413 
00414   calibrationLog.AddKeyword(PvlKeyword("DarkStripColumns", nDarkColumns),
00415                             Pvl::Replace);
00416   calibrationLog.AddKeyword(PvlKeyword("ValidDarkColumns", nValidDark),
00417                             Pvl::Replace);
00418   if(darkStrip.TotalPixels() > 0) {
00419     calibrationLog.AddKeyword(PvlKeyword("DarkStripMean", darkStrip.Average()),
00420                               Pvl::Replace);
00421   }
00422 
00423   // Report nulled sample count
00424   calibrationLog.AddKeyword(PvlKeyword("LeftSamplesNulled", nSampsToNull));
00425 
00426   //  Handle updates of ProductId and SourceProduct Id keywords
00427   PvlGroup &archive = ocube->getGroup("Archive");
00428   PvlKeyword key = archive["ProductId"];
00429   iString orgProdId = key[0];
00430   iString newProdId = orgProdId + "_" + calibType + "_" + iString(cdr_version);
00431   newProdId[0] = 'C';
00432   key.SetValue(Quote(newProdId));
00433   archive.AddKeyword(key, Pvl::Replace);
00434 
00435   // Now SourceProductId
00436   if(archive.HasKeyword("SourceProductId")) {
00437     key = archive["SourceProductId"];
00438     for(int i = 0 ; i < key.Size() ; i++) {
00439       key[i] = Quote(key[i]);
00440     }
00441   }
00442   else {
00443     key = PvlKeyword("SourceProductId", Quote(orgProdId));
00444   }
00445 
00446   if(!darkCurrentFile.empty()) {
00447     key.AddValue(Quote(Filename(darkCurrentFile).Basename()));
00448   }
00449   key.AddValue(Quote(flatfield.Basename()));
00450   key.AddValue(Quote(Filename(respfile).Basename()));
00451   // key.AddValue(Quote(Filename(smearfile).Basename()));
00452   if(iof_is_good) {
00453     key.AddValue(Quote(Filename(solirrfile).Basename()));
00454   }
00455   archive.AddKeyword(key, Pvl::Replace);
00456 
00457   // Write Calibration group to file and log
00458   ocube->putGroup(calibrationLog);
00459   Application::Log(calibrationLog);
00460   configFile.Clear();
00461 }
00462 
00463 Filename DetermineFlatFieldFile() {
00464   string filename = "$messenger/calibration/FLAT/";
00465 
00466   // Filename consists of binned/notbinned, camera, and filter
00467   filename += "MDIS";
00468   filename += ((isNarrowAngleCamera) ? "NAC" : "WAC");
00469   filename += ((isBinnedData) ? "_BINNED_" : "_NOTBIN_");
00470   filename += "FLAT";
00471   if(isNarrowAngleCamera) {
00472     // NAC spec is simpler
00473     filename += "_?.cub";
00474   }
00475   else {
00476     // add a zero if the filter is 1-digit
00477     filename += "_FIL";
00478     if(filterNumber < 9) filename += "0";
00479     filename += iString(filterNumber + 1);
00480     filename += "_?.cub";
00481   }
00482 
00483   Filename final(filename);
00484   final.HighestVersion();
00485   return final;
00486 }
00487 
00488 
00489 void GatherDarkStatistics(Buffer &in) {
00490 
00491   // Some situations cannot use these processes for calibration
00492   calibrationValues[in.Line()-1] = Isis::Null;
00493 
00494   if (nValidDark > 0) {
00495     if (darkCurrentMode == DarkCurrentStandard) {
00496       // Figure out the median (Isis::Statistics wont do this for us)
00497       // because we have repeated numbers, put them into a sorted array size
00498       // of no more than 3 and take the middle
00499       vector<double> calibValues;
00500       int nDark = nValidDark;
00501       for(int i = 0 ; i < nDark ; i++) {
00502         calibValues.push_back(in[i]);
00503       }
00504       sort(calibValues.begin(), calibValues.end());
00505 
00506       // grab the middle element in the array for the median
00507       calibrationValues[in.Line()-1] = calibValues[nDark/2];
00508     }
00509     else if(darkCurrentMode == DarkCurrentLinear) {
00510       // Presently the linear regression only uses the first sample in the
00511       // dark current data
00512       calibrationValues[in.Line()-1] = in[0];
00513     }
00514   }
00515 }
00516 
00517 void Calibrate(vector<Buffer *>&in, vector<Buffer *>&out) {
00518   Buffer &imageIn   = *in[0];
00519   Buffer &flatField = *in[1];
00520   Buffer &imageOut  = *out[0];
00521 
00522   if(imageIn.Line() == 1) {
00523     prevLineData.resize(imageIn.SampleDimension());
00524     smearData.resize(imageIn.SampleDimension());
00525 
00526     for(unsigned int i = 0; i < prevLineData.size(); i++) {
00527       prevLineData[i] = 0.0;
00528       smearData[i] = 0.0;
00529     }
00530   }
00531 
00532   for(int i = 0; i < imageIn.size(); i++) {
00533 
00534     // Check for special pixel in input image and pass through
00535     if(Isis::IsSpecial(imageIn[i])) {
00536       imageOut[i] = imageIn[i];
00537       continue;
00538     }
00539 
00540     if (g_flatfield) {
00541       //  If the flat field pixel is special, can't calibrate so set to NULL
00542       //  and pass through (unlikely).
00543       if(Isis::IsSpecial(flatField[i])) {
00544         imageOut[i] = Isis::Null;
00545         continue;
00546       }
00547     }
00548 //462
00549 //if(i == 25 && imageIn.Line() == 25) std::cout <<  "In: " << imageIn[i] << std::endl;
00550 
00551     // Step 1: Perform dark current corrections
00552     if(darkCurrentMode == DarkCurrentNone) {
00553       imageOut[i] = imageIn[i];
00554     }
00555     else if(darkCurrentMode == DarkCurrentStandard) {
00556       imageOut[i] = imageIn[i] - calibrationValues[imageIn.Line()-1];
00557     }
00558     else if(darkCurrentMode == DarkCurrentLinear) {
00559       // Linear: out = in - bestfitline = in - (A + Bx)
00560       imageOut[i] = imageIn[i] - (calibrationValues[0] + calibrationValues[1] *
00561                                   (imageIn.Line() - 1));
00562     }
00563     else if(darkCurrentMode == DarkCurrentModel) {
00564       imageOut[i] = imageIn[i] - model->getDarkPixel(i, imageIn.Line() - 1);
00565     }
00566 
00567     if (g_flatfield) {
00568       // Step 2: Perform linearity correction
00569       if(isNarrowAngleCamera == true) {
00570         if(imageOut[i] <= 0.0) {
00571           imageOut[i] /= 0.912031;
00572         }
00573         else {
00574           imageOut[i] /= 0.011844 * log10(imageOut[i]) + 0.912031;
00575         }
00576       }
00577       else {
00578         // Wide angle camera
00579         if(imageOut[i] <= 0.0) {
00580           imageOut[i] /= 0.936321;
00581         }
00582         else {
00583           imageOut[i] /= 0.008760 * log10(imageOut[i]) + 0.936321;
00584         }
00585       }
00586   
00587       // Step 3: Readout Smear Correction (ms -> seconds)
00588       double t2 = smearComponent / imageIn.SampleDimension() / 1000.0;
00589   
00590       if(exposureDuration > 0.0 && imageIn.Line() > 1) {
00591         smearData[i] += t2 / exposureDuration * prevLineData[i];
00592         imageOut[i] -= smearData[i];
00593       }
00594   
00595       prevLineData[i] = imageOut[i];
00596   
00597       // Step 4: Uniformity (flat field)
00598       imageOut[i] /= flatField[i]; // divide by flat field
00599   
00600       // Step 5: Absolute coefficient
00601       if(exposureDuration > 0.0) {
00602         imageOut[i] = imageOut[i] / exposureDuration * abs_coef;
00603       }
00604     }  // End of flat field
00605 
00606     if (g_radiometric) {
00607     //  Step 6:  Convert to I/F units
00608       imageOut[i] *= iof;
00609     }
00610   }
00611 
00612   //  Compute dark current statistics
00613   for(int j = 0 ; j < nValidDark; j++) {
00614     darkStrip.AddData(imageOut[j]);
00615   }
00616 
00617   //  Null specified columns (2011-04-20 - KJB)
00618   for(int n = 0 ; n < nSampsToNull ; n++) {
00619     imageOut[n] = Isis::Null;
00620   }
00621 
00622 }
00623