USGS

Isis 3.0 Object Programmers' Reference

Home

AutoReg.cpp

00001 
00019 #include "AutoReg.h"
00020 #include "Buffer.h"
00021 #include "Chip.h"
00022 #include "Filename.h"
00023 #include "Histogram.h"
00024 #include "LeastSquares.h"
00025 #include "iException.h"
00026 #include "Interpolator.h"
00027 #include "Matrix.h"
00028 #include "PixelType.h"
00029 #include "Plugin.h"
00030 #include "PolynomialBivariate.h"
00031 #include "Pvl.h"
00032 
00033 using namespace std;
00034 namespace Isis {
00081   AutoReg::AutoReg(Pvl &pvl) {
00082     p_template = pvl.FindObject("AutoRegistration");
00083 
00084     // Set default parameters
00085     p_patternChip.SetSize(3, 3);
00086     p_searchChip.SetSize(5, 5);
00087     p_fitChip.SetSize(5, 5);
00088     p_reducedPatternChip.SetSize(3, 3);
00089     p_reducedSearchChip.SetSize(5, 5);
00090     p_reducedFitChip.SetSize(5, 5);
00091     p_gradientFilterType = None;
00092 
00093     SetPatternValidPercent(50.0);
00094     SetSubsearchValidPercent(50.0);
00095     SetPatternZScoreMinimum(1.0);
00096     SetTolerance(Isis::Null);
00097 
00098     SetSubPixelAccuracy(true);
00099     SetEccentricityTesting(false);
00100     SetResidualTesting(false);
00101     SetSurfaceModelDistanceTolerance(1.5);
00102     SetSurfaceModelWindowSize(5);
00103 
00104     SetSurfaceModelEccentricityRatio(2);  // 2:1
00105     SetSurfaceModelResidualTolerance(0.1);
00106     SetReductionFactor(1);
00107 
00108     // Clear statistics
00109     //TODO: Delete these after control net refactor.
00110     p_totalRegistrations = 0;
00111     p_pixelSuccesses = 0;
00112     p_subpixelSuccesses = 0;
00113     p_patternChipNotEnoughValidDataCount = 0;
00114     p_patternZScoreNotMetCount = 0;
00115     p_fitChipNoDataCount = 0;
00116     p_fitChipToleranceNotMetCount = 0;
00117     p_surfaceModelNotEnoughValidDataCount = 0;
00118     p_surfaceModelSolutionInvalidCount = 0;
00119     p_surfaceModelDistanceInvalidCount = 0;
00120     p_surfaceModelEccentricityRatioNotMetCount = 0;
00121     p_surfaceModelResidualToleranceNotMetCount = 0;
00122 
00123     p_sampMovement = 0.;
00124     p_lineMovement = 0.;
00125 
00126     Init();
00127     Parse(pvl);
00128   }
00129 
00135   void AutoReg::Init() {
00136     // Set computed parameters to NULL so we don't use values from a previous
00137     // run
00138     p_zScoreMin = Isis::Null;
00139     p_zScoreMax = Isis::Null;
00140     p_goodnessOfFit = Isis::Null;
00141     p_wholePixelCorr = Isis::Null;
00142     p_subPixelCorr = Isis::Null;
00143     p_surfaceModelEccentricity = Isis::Null;
00144     p_surfaceModelEccentricityRatio = Isis::Null;
00145     p_surfaceModelAvgResidual = Isis::Null;
00146 
00147     p_bestSamp = 0;
00148     p_bestLine = 0;
00149     p_bestFit = Isis::Null;
00150 
00151     // --------------------------------------------------
00152     // Nulling out the fit chip
00153     // --------------------------------------------------
00154     for(int line = 1; line <= p_fitChip.Lines(); line++) {
00155       for(int samp = 1; samp <= p_fitChip.Samples(); samp++) {
00156         p_fitChip.SetValue(samp, line, Isis::Null);
00157       }
00158     }
00159     // --------------------------------------------------
00160     // Nulling out the reduced pattern chip
00161     // --------------------------------------------------
00162     for(int line = 1; line <= p_reducedPatternChip.Lines(); line++) {
00163       for(int samp = 1; samp <= p_reducedPatternChip.Samples(); samp++) {
00164         p_reducedPatternChip.SetValue(samp, line, Isis::Null);
00165       }
00166     }
00167     // --------------------------------------------------
00168     // Nulling out the reduced search chip
00169     // --------------------------------------------------
00170     for(int line = 1; line <= p_reducedSearchChip.Lines(); line++) {
00171       for(int samp = 1; samp <= p_reducedSearchChip.Samples(); samp++) {
00172         p_reducedSearchChip.SetValue(samp, line, Isis::Null);
00173       }
00174     }
00175 
00176   }
00177 
00179   AutoReg::~AutoReg() {
00180 
00181   }
00182 
00220   void AutoReg::Parse(Pvl &pvl) {
00221     try {
00222       // Get info from Algorithm group
00223       PvlGroup &algo = pvl.FindGroup("Algorithm", Pvl::Traverse);
00224       SetTolerance(algo["Tolerance"]);
00225       if(algo.HasKeyword("ChipInterpolator")) {
00226         SetChipInterpolator((string)algo["ChipInterpolator"]);
00227       }
00228 
00229       if(algo.HasKeyword("SubpixelAccuracy")) {
00230         SetSubPixelAccuracy((string)algo["SubpixelAccuracy"] == "True");
00231       }
00232 
00233       if(algo.HasKeyword("ReductionFactor")) {
00234         SetReductionFactor((int)algo["ReductionFactor"]);
00235       }
00236 
00237       if (algo.HasKeyword("Gradient")) {
00238         SetGradientFilterType((string)algo["Gradient"]);
00239       }
00240 
00241       // Setup the pattern chip
00242       PvlGroup &pchip = pvl.FindGroup("PatternChip", Pvl::Traverse);
00243       PatternChip()->SetSize((int)pchip["Samples"], (int)pchip["Lines"]);
00244 
00245       double minimum = Isis::ValidMinimum;
00246       double maximum = Isis::ValidMaximum;
00247       if(pchip.HasKeyword("ValidMinimum")) minimum = pchip["ValidMinimum"];
00248       if(pchip.HasKeyword("ValidMaximum")) maximum = pchip["ValidMaximum"];
00249       PatternChip()->SetValidRange(minimum, maximum);
00250 
00251       if(pchip.HasKeyword("MinimumZScore")) {
00252         SetPatternZScoreMinimum((double)pchip["MinimumZScore"]);
00253       }
00254       if(pchip.HasKeyword("ValidPercent")) {
00255         SetPatternValidPercent((double)pchip["ValidPercent"]);
00256       }
00257 
00258       // Setup the search chip
00259       PvlGroup &schip = pvl.FindGroup("SearchChip", Pvl::Traverse);
00260       SearchChip()->SetSize((int)schip["Samples"], (int)schip["Lines"]);
00261 
00262       minimum = Isis::ValidMinimum;
00263       maximum = Isis::ValidMaximum;
00264       if(schip.HasKeyword("ValidMinimum")) minimum = schip["ValidMinimum"];
00265       if(schip.HasKeyword("ValidMaximum")) maximum = schip["ValidMaximum"];
00266       SearchChip()->SetValidRange(minimum, maximum);
00267       if(schip.HasKeyword("SubchipValidPercent")) {
00268         SetSubsearchValidPercent((double)schip["SubchipValidPercent"]);
00269       }
00270 
00271       // Setup surface model
00272       PvlObject ar = pvl.FindObject("AutoRegistration");
00273       if(ar.HasGroup("SurfaceModel")) {
00274         PvlGroup &smodel = ar.FindGroup("SurfaceModel", Pvl::Traverse);
00275         if(smodel.HasKeyword("DistanceTolerance")) {
00276           SetSurfaceModelDistanceTolerance((double)smodel["DistanceTolerance"]);
00277         }
00278 
00279         if(smodel.HasKeyword("WindowSize")) {
00280           SetSurfaceModelWindowSize((int)smodel["WindowSize"]);
00281         }
00282 
00283         // What kind of eccentricity ratio will we tolerate?
00284         if(smodel.HasKeyword("EccentricityRatio")) {
00285           SetEccentricityTesting(true);
00286           SetSurfaceModelEccentricityRatio((double)smodel["EccentricityRatio"]);
00287         }
00288 
00289         // What kind of average residual will we tolerate?
00290         if(smodel.HasKeyword("ResidualTolerance")) {
00291           SetResidualTesting(true);
00292           SetSurfaceModelResidualTolerance((double)smodel["ResidualTolerance"]);
00293         }
00294       }
00295 
00296     }
00297     catch(iException &e) {
00298       string msg = "Improper format for AutoReg PVL [" + pvl.Filename() + "]";
00299       throw iException::Message(iException::User, msg, _FILEINFO_);
00300     }
00301     return;
00302   }
00303 
00311   void AutoReg::SetGradientFilterType(const iString& gradientFilterType) {
00312     if (gradientFilterType == "None") {
00313       p_gradientFilterType = None;
00314     }
00315     else if (gradientFilterType == "Sobel") {
00316       p_gradientFilterType = Sobel;
00317     }
00318     else {
00319       throw iException::Message(iException::User,
00320                                 "Invalid Gradient type.  Cannot use ["
00321                                 + gradientFilterType + "] to filter chip",
00322                                 _FILEINFO_);
00323     }
00324   }
00325 
00337   void AutoReg::SetSubPixelAccuracy(bool on) {
00338     p_subpixelAccuracy = on;
00339   }
00340 
00364   void AutoReg::SetPatternValidPercent(const double percent) {
00365     if((percent <= 0.0) || (percent > 100.0)) {
00366       string msg = "Invalid value for PatternChip ValidPercent [" 
00367         + iString(percent) 
00368         + "].  Must be greater than 0.0 and less than or equal to 100.0 (Default is 50.0).";
00369       throw iException::Message(iException::User, msg, _FILEINFO_);
00370     }
00371     p_patternValidPercent = percent;
00372   }
00373 
00374 
00392   void AutoReg::SetSubsearchValidPercent(const double percent) {
00393     if((percent <= 0.0) || (percent > 100.0)) {
00394       string msg = "Invalid value for SearchChip SubchipValidPercent [" 
00395         + iString(percent) + "]"
00396         + "].  Must be greater than 0.0 and less than or equal to 100.0 (Default is 50.0).";
00397       throw iException::Message(iException::User, msg, _FILEINFO_);
00398     }
00399     p_subsearchValidPercent = percent;
00400   }
00401 
00402 
00419   void AutoReg::SetPatternZScoreMinimum(double minimum) {
00420     if(minimum <= 0.0) {
00421       string msg = "Invalid value for PatternChip MinimumZScore ["
00422         + iString(minimum)
00423         + "].  Must be greater than 0.0. (Default is 1.0).";
00424       throw iException::Message(iException::User, msg, _FILEINFO_);
00425     }
00426     p_minimumPatternZScore = minimum;
00427   }
00428 
00429 
00441   void AutoReg::SetTolerance(const double tolerance) {
00442     p_tolerance = tolerance;
00443   }
00444 
00465   void AutoReg::SetChipInterpolator(const iString& interpolator) {
00466 
00467     Isis::Interpolator::interpType itype;
00468     if(interpolator == "NearestNeighborType") {
00469       itype = Isis::Interpolator::NearestNeighborType;
00470     }
00471     else if(interpolator == "BiLinearType") {
00472       itype = Isis::Interpolator::BiLinearType;
00473     }
00474     else if(interpolator == "CubicConvolutionType") {
00475       itype = Isis::Interpolator::CubicConvolutionType;
00476     }
00477     else {
00478       throw iException::Message(iException::User,
00479                                 "Invalid Interpolator type.  Cannot use ["
00480                                 + interpolator + "] to load chip",
00481                                 _FILEINFO_);
00482     }
00483 
00484     // Set pattern and search chips to use this interpolator type when reading data from cube
00485     p_patternChip.SetReadInterpolator(itype);
00486     p_searchChip.SetReadInterpolator(itype);
00487     p_reducedPatternChip.SetReadInterpolator(itype);
00488     p_reducedSearchChip.SetReadInterpolator(itype);
00489 
00490   }
00491 
00505   void AutoReg::SetSurfaceModelWindowSize(int size) {
00506     if(size % 2 != 1 || size < 3) {
00507       string msg = "Invalid value for SurfaceModel WindowSize ["
00508         + iString(size) + "].  Must be an odd number greater than or equal to 3";
00509       throw iException::Message(iException::User, msg, _FILEINFO_);
00510     }
00511     p_windowSize = size;
00512   }
00513 
00527   void AutoReg::SetSurfaceModelEccentricityRatio(double eccentricityRatio) {
00528     if(eccentricityRatio < 1) {
00529       string msg = "Invalid value for SurfaceModel EccentricityRatio [" 
00530         + iString(eccentricityRatio) + "].  Must greater than or equal to 1.0.";
00531       throw iException::Message(iException::User, msg, _FILEINFO_);
00532     }
00533     p_surfaceModelEccentricityRatioTolerance = eccentricityRatio;
00534     p_surfaceModelEccentricityTolerance = sqrt(eccentricityRatio * eccentricityRatio - 1) / eccentricityRatio;
00535   }
00536 
00553   void AutoReg::SetSurfaceModelResidualTolerance(double residualTolerance) {
00554     if(residualTolerance < 0) {
00555       string msg = "Invalid value for SurfaceModel ResidualTolerance [" 
00556         + iString(residualTolerance) + "].  Must greater than or equal to 0.0.";
00557       throw iException::Message(iException::User, msg, _FILEINFO_);
00558     }
00559     p_surfaceModelResidualTolerance = residualTolerance;
00560   }
00561 
00574   void AutoReg::SetSurfaceModelDistanceTolerance(double distance) {
00575     if(distance <= 0.0) {
00576       string msg = "Invalid value for SurfaceModel DistanceTolerance [" 
00577         + iString(distance) + "].  Must greater than 0.0.";
00578       throw iException::Message(iException::User, msg, _FILEINFO_);
00579     }
00580     p_distanceTolerance = distance;
00581   }
00582 
00583 
00594   void AutoReg::SetReductionFactor(int factor) {
00595     if(factor < 1) {
00596       string msg = "Invalid value for Algorithm ReductionFactor ["
00597         + iString(factor) + "].  Must greater than or equal to 1.";
00598       throw iException::Message(iException::User, msg, _FILEINFO_);
00599     }
00600     p_reduceFactor = factor;
00601   }
00602 
00612   Chip AutoReg::Reduce(Chip &chip, int reductionFactor) {
00613     Chip rChip((int)chip.Samples() / reductionFactor,
00614                (int)chip.Lines() / reductionFactor);
00615     if((int)rChip.Samples() < 1 || (int)rChip.Lines() < 1) {
00616       return chip;
00617     }
00618 
00619     // ----------------------------------
00620     // Fill the reduced Chip with nulls.
00621     // ----------------------------------
00622     for(int line = 1; line <= rChip.Lines(); line++) {
00623       for(int samp = 1; samp <= rChip.Samples(); samp++) {
00624         rChip.SetValue(samp, line, Isis::Null);
00625       }
00626     }
00627 
00628     Statistics stats;
00629     for(int l = 1; l <= rChip.Lines(); l++) {
00630       int istartLine = (l - 1) * reductionFactor + 1;
00631       int iendLine = istartLine + reductionFactor - 1;
00632       for(int s = 1; s <= rChip.Samples(); s++) {
00633 
00634         int istartSamp = (s - 1) * reductionFactor + 1;
00635         int iendSamp = istartSamp + reductionFactor - 1;
00636 
00637         stats.Reset();
00638         for(int line = istartLine; line < iendLine; line++) {
00639           for(int sample = istartSamp; sample < iendSamp; sample++) {
00640             stats.AddData(chip.GetValue(sample, line));
00641           }
00642         }
00643         rChip.SetValue(s, l, stats.Average());
00644       }
00645     }
00646     return rChip;
00647   }
00648 
00649 
00660   AutoReg::RegisterStatus AutoReg::Register() {
00661     // The search chip must be bigger than the pattern chip by N pixels in
00662     // both directions for a successful surface model
00663     int N = p_windowSize / 2 + 1;
00664 
00665     if(p_searchChip.Samples() < p_patternChip.Samples() + N) {
00666       string msg = "Search chips samples [";
00667       msg += iString(p_searchChip.Samples()) + "] must be at ";
00668       msg += "least [" + iString(N) + "] pixels wider than the pattern chip samples [";
00669       msg += iString(p_patternChip.Samples()) + "] for successful surface modeling";
00670       throw iException::Message(iException::User, msg, _FILEINFO_);
00671     }
00672 
00673     if(p_searchChip.Lines() < p_patternChip.Lines() + N) {
00674       string msg = "Search chips lines [";
00675       msg += iString(p_searchChip.Lines()) + "] must be at ";
00676       msg += "least [" + iString(N) + "] pixels taller than the pattern chip lines [";
00677       msg += iString(p_patternChip.Lines()) + "] for successful surface modeling";
00678       throw iException::Message(iException::User, msg, _FILEINFO_);
00679     }
00680 
00681     Init();
00682     p_totalRegistrations++;
00683 
00684     // Create copies of the search and pattern chips and run a gradient filter
00685     // over them before attempting to perform a match. We do this so that
00686     // multiple calls to this method won't result in having a gradient filter
00687     // applied multiple times to the same chip.
00688     Chip gradientPatternChip(p_patternChip);
00689     Chip gradientSearchChip(p_searchChip);
00690     ApplyGradientFilter(gradientPatternChip);
00691     ApplyGradientFilter(gradientSearchChip);
00692 
00693     // See if the pattern chip has enough good data
00694     if(!gradientPatternChip.IsValid(p_patternValidPercent)) {
00695       p_patternChipNotEnoughValidDataCount++;
00696       p_registrationStatus = PatternChipNotEnoughValidData;
00697       return PatternChipNotEnoughValidData;
00698     }
00699 
00700     if(!ComputeChipZScore(gradientPatternChip)) {
00701       p_patternZScoreNotMetCount++;
00702       p_registrationStatus = PatternZScoreNotMet;
00703       return PatternZScoreNotMet;
00704     }
00705 
00725     int startSamp = (gradientPatternChip.Samples() - 1) / 2 + 1;
00726     int startLine = (gradientPatternChip.Lines() - 1) / 2 + 1;
00727     int endSamp = gradientSearchChip.Samples() - startSamp + 1;
00728     int endLine = gradientSearchChip.Lines() - startLine + 1;
00729 
00730     // ----------------------------------------------------------------------
00731     // Before we attempt to apply the reduction factor, we need to make sure
00732     // we won't produce a chip of a bad size.
00733     // ----------------------------------------------------------------------
00734     if(gradientPatternChip.Samples() / p_reduceFactor < 2 || gradientPatternChip.Lines() / p_reduceFactor < 2) {
00735       string msg = "Reduction factor is too large";
00736       throw iException::Message(iException::User, msg, _FILEINFO_);
00737     }
00738 
00739     // Establish the center search tack point as best pixel to start for the
00740     // adaptive algorithm prior to reduction.
00741     int bestSearchSamp = gradientSearchChip.TackSample();
00742     int bestSearchLine = gradientSearchChip.TackLine();
00743 
00744     // ---------------------------------------------------------------------
00745     // if the reduction factor is still not equal to one, then we go ahead
00746     // with the reduction of the chips and call Match to get the first
00747     // estimate of the best line/sample.
00748     // ---------------------------------------------------------------------
00749     if(p_reduceFactor != 1) {
00750       p_reducedPatternChip.SetSize((int)gradientPatternChip.Samples() / p_reduceFactor,
00751           (int)gradientPatternChip.Lines() / p_reduceFactor);
00752 
00753       // ----------------------------------
00754       // Fill the reduced Chip with nulls.
00755       // ----------------------------------
00756       for(int line = 1; line <= p_reducedPatternChip.Lines(); line++) {
00757         for(int samp = 1; samp <= p_reducedPatternChip.Samples(); samp++) {
00758           p_reducedPatternChip.SetValue(samp, line, Isis::Null);
00759         }
00760       }
00761 
00762       p_reducedPatternChip = Reduce(gradientPatternChip, p_reduceFactor);
00763       if(!ComputeChipZScore(p_reducedPatternChip)) {
00764         p_patternZScoreNotMetCount++;
00765         p_registrationStatus = PatternZScoreNotMet;
00766         return PatternZScoreNotMet;
00767       }
00768 
00769       p_reducedSearchChip = Reduce(gradientSearchChip, p_reduceFactor);
00770       int reducedStartSamp = (p_reducedPatternChip.Samples() - 1) / 2 + 1;
00771       int reducedEndSamp = p_reducedSearchChip.Samples() - reducedStartSamp + 1;
00772       int reducedStartLine = (p_reducedPatternChip.Lines() - 1) / 2 + 1;
00773       int reducedEndLine = p_reducedSearchChip.Lines() - reducedStartLine + 1;
00774 
00775       Match(p_reducedSearchChip, p_reducedPatternChip, p_reducedFitChip,
00776           reducedStartSamp, reducedEndSamp, reducedStartLine, reducedEndLine);
00777 
00778       if(p_bestFit == Isis::Null) {
00779         p_fitChipNoDataCount++;
00780         p_registrationStatus = FitChipNoData;
00781         return FitChipNoData;
00782       }
00783 
00784       // ------------------------------------------------------
00785       // p_bestSamp and p_bestLine are set in Match() which is
00786       // called above.
00787       // -----------------------------------------------------
00788       int bs = (p_bestSamp - 1) * p_reduceFactor + ((p_reduceFactor - 1) / 2) + 1;
00789       int bl = (p_bestLine - 1) * p_reduceFactor + ((p_reduceFactor - 1) / 2) + 1;
00790 
00791       // ---------------------------------------------------------------
00792       // Now we grow our window size according to the reduction factor.
00793       // And we grow around where the first call Match() told us was the
00794       // best line/sample.
00795       // ---------------------------------------------------------------
00796       int newstartSamp = bs - p_reduceFactor - p_windowSize - 1;
00797       int newendSamp = bs + p_reduceFactor + p_windowSize + 1;
00798       int newstartLine = bl - p_reduceFactor - p_windowSize - 1;
00799       int newendLine = bl + p_reduceFactor + p_windowSize + 1;
00800 
00801       if(newstartLine < startLine) newstartLine = startLine;
00802       if(newendSamp > endSamp) newendSamp = endSamp;
00803       if(newstartSamp < startSamp) newstartSamp = startSamp;
00804       if(newendLine > endLine) newendLine = endLine;
00805 
00806       startSamp = newstartSamp;
00807       endSamp = newendSamp;
00808       startLine = newstartLine;
00809       endLine = newendLine;
00810       // We have found a good pixel in the reduction chip, but we
00811       // don't want to use its position, so reset in prep. for
00812       // non-adaptive registration.  Save it off for the adaptive algorithm.
00813       bestSearchSamp = bs;
00814       bestSearchLine = bl;
00815       p_bestSamp = 0;
00816       p_bestLine = 0;
00817       p_bestFit = Isis::Null;
00818     }
00819 
00820     // If the algorithm is adaptive then it expects the pattern and search chip
00821     // to be closely registered.  Within a few pixels.  So let it take over
00822     // doing the sub-pixel accuracy computation
00823     if(IsAdaptive()) {
00824       p_registrationStatus = AdaptiveRegistration(gradientSearchChip, gradientPatternChip, p_fitChip,
00825                                       startSamp, startLine, endSamp, endLine, bestSearchSamp,
00826                                       bestSearchLine);
00827       if(p_registrationStatus == AutoReg::SuccessSubPixel) {
00828         p_searchChip.SetChipPosition(p_chipSample, p_chipLine);
00829 
00830         // We need to get the cube position from the gradient search chip that
00831         // was modified by the adaptive registration.
00832         gradientSearchChip.SetChipPosition(p_chipSample, p_chipLine);
00833         p_cubeSample = gradientSearchChip.CubeSample();
00834         p_cubeLine   = gradientSearchChip.CubeLine();
00835         
00836         // Save off the gradient search and pattern chips if we used a gradient
00837         // filter.
00838         if (p_gradientFilterType != None) {
00839           p_gradientSearchChip = gradientSearchChip;
00840           p_gradientPatternChip = gradientPatternChip;
00841         }
00842 
00843         p_goodnessOfFit = p_bestFit;
00844         p_wholePixelCorr = p_bestFit;
00845         p_subpixelSuccesses++;
00846       }
00847       return p_registrationStatus;
00848     }
00849 
00850     // Not adaptive continue with slower search traverse
00851     Match(gradientSearchChip, gradientPatternChip, p_fitChip, startSamp, endSamp, startLine, endLine);
00852 
00853     // Check to see if we went through the fit chip and never got a fit at
00854     // any location.
00855     if(p_bestFit == Isis::Null) {
00856       p_fitChipNoDataCount++;
00857       p_registrationStatus = FitChipNoData;
00858       return FitChipNoData;
00859     }
00860 
00861     // -----------------------------------------------------------------
00862     // We had a location in the fit chip.  Save the values even if they
00863     // may not meet tolerances.  This is also saves the value in the
00864     // event the user does not want a surface model fit
00865     // ----------------------------------------------------------------
00866     p_goodnessOfFit = p_bestFit;
00867     p_wholePixelCorr = p_bestFit;
00868     p_searchChip.SetChipPosition(p_bestSamp, p_bestLine);
00869     gradientSearchChip.SetChipPosition(p_bestSamp, p_bestLine);
00870     p_cubeSample = p_searchChip.CubeSample();
00871     p_cubeLine   = p_searchChip.CubeLine();
00872 
00873     // Now see if we satisified the goodness of fit tolerance
00874     if(!CompareFits(p_bestFit, Tolerance())) {
00875       p_fitChipToleranceNotMetCount++;
00876       p_registrationStatus = FitChipToleranceNotMet;
00877       return FitChipToleranceNotMet;
00878     }
00879 
00880     // Try to fit a model for sub-pixel accuracy if necessary
00881     bool computedSubPixel = false;
00882     if(p_subpixelAccuracy && !IsIdeal(p_bestFit)) {
00883       vector<double> samps, lines, fits;
00884       for(int line = p_bestLine - p_windowSize / 2; line <= p_bestLine + p_windowSize / 2; line++) {
00885         if(line < 1) continue;
00886         if(line > p_fitChip.Lines()) continue;
00887         for(int samp = p_bestSamp - p_windowSize / 2; samp <= p_bestSamp + p_windowSize / 2; samp++) {
00888           if(samp < 1) continue;
00889           if(samp > p_fitChip.Samples()) continue;
00890           if(p_fitChip.GetValue(samp, line) == Isis::Null) continue;
00891           samps.push_back((double) samp);
00892           lines.push_back((double) line);
00893           fits.push_back(p_fitChip.GetValue(samp, line));
00894         }
00895       }
00896 
00897       // -----------------------------------------------------------
00898       // Make sure we have enough data for a surface fit.  That is,
00899       // we are not too close to the edge of the fit chip
00900       // -----------------------------------------------------------
00901       if((int)samps.size() < p_windowSize * p_windowSize * 2 / 3 + 1) {
00902         p_surfaceModelNotEnoughValidDataCount++;
00903         p_registrationStatus = SurfaceModelNotEnoughValidData;
00904         return SurfaceModelNotEnoughValidData;
00905       }
00906 
00907       // -------------------------------------------------------------------
00908       // Now that we know we have enough data to model the surface we call
00909       // ModelSurface to get the sub-pixel accuracy we are looking for.
00910       // -------------------------------------------------------------------
00911       computedSubPixel = ModelSurface(samps, lines, fits);
00912       if (!computedSubPixel) {
00913         return p_registrationStatus;
00914       }
00915 
00916       // ---------------------------------------------------------------------
00917       // See if the surface model solution moved too far from our whole pixel
00918       // solution
00919       // ---------------------------------------------------------------------
00920       p_sampMovement = std::fabs(p_bestSamp - p_chipSample);
00921       p_lineMovement = std::fabs(p_bestLine - p_chipLine);
00922       if(p_sampMovement > p_distanceTolerance ||
00923           p_lineMovement > p_distanceTolerance) {
00924         p_surfaceModelDistanceInvalidCount++;
00925         p_registrationStatus = SurfaceModelDistanceInvalid;
00926         return SurfaceModelDistanceInvalid;
00927       }
00928 
00929       // Ok we have subpixel fits in chip space so convert to cube space
00930       p_searchChip.SetChipPosition(p_chipSample, p_chipLine);
00931       gradientSearchChip.SetChipPosition(p_chipSample, p_chipLine);
00932       p_cubeSample = p_searchChip.CubeSample();
00933       p_cubeLine   = p_searchChip.CubeLine();
00934     }
00935 
00936     // Registration succeeded, but did it compute to sub-pixel accuracy?
00937     if (computedSubPixel) {
00938       p_subpixelSuccesses++;
00939       p_registrationStatus = SuccessSubPixel;
00940     }
00941     else {
00942       p_pixelSuccesses++;
00943       p_registrationStatus = SuccessPixel;
00944     }
00945 
00946     // Save off the gradient search and pattern chips if we used a gradient
00947     // filter.
00948     if (p_gradientFilterType != None) {
00949       p_gradientSearchChip = gradientSearchChip;
00950       p_gradientPatternChip = gradientPatternChip;
00951     }
00952 
00953     return p_registrationStatus;
00954   }
00955 
00956 
00967   bool AutoReg::ComputeChipZScore(Chip &chip) {
00968     Statistics patternStats;
00969     for(int i = 0; i < chip.Samples(); i++) {
00970       double pixels[chip.Lines()];
00971       for(int j = 0; j < chip.Lines(); j++) {
00972         pixels[j] = chip.GetValue(i + 1, j + 1);
00973       }
00974       patternStats.AddData(pixels, chip.Lines());
00975     }
00976 
00977     // If it does not pass, return
00978     p_zScoreMin = patternStats.ZScore(patternStats.Minimum());
00979     p_zScoreMax = patternStats.ZScore(patternStats.Maximum());
00980 
00981     // p_zScoreMin is made negative here so as to make it the equivalent of
00982     // taking the absolute value (because p_zScoreMin is guaranteed to be
00983     // negative)
00984     if (p_zScoreMax < p_minimumPatternZScore && -p_zScoreMin < p_minimumPatternZScore) {
00985       return false;
00986     }
00987     else {
00988       return true;
00989     }
00990   }
00991 
00999   void AutoReg::ApplyGradientFilter(Chip &chip) {
01000     if (p_gradientFilterType == None) {
01001       return;
01002     }
01003 
01004     // Use a different subchip size depending on which gradient filter is
01005     // being applied.
01006     int subChipWidth;
01007     if (p_gradientFilterType == Sobel) {
01008       subChipWidth = 3;
01009     }
01010     else {
01011       // Perform extra sanity check.
01012       string msg =
01013         "No rule to set sub-chip width for selected Gradient Filter Type.";
01014       throw iException::Message(iException::Programmer, msg, _FILEINFO_);
01015     }
01016 
01017     // Create a new chip to hold output during processing.
01018     Chip filteredChip(chip.Samples(), chip.Lines());
01019 
01020     // Move the subchip through the chip, extracting the contents into a buffer
01021     // of the same shape. This simulates the processing of a cube by boxcar,
01022     // but since that can only operate on cubes, this functionality had to be
01023     // replicated for use on chips.
01024     for (int line = 1; line <= chip.Lines(); line++) {
01025       for (int sample = 1; sample <= chip.Samples(); sample++) {
01026         Chip subChip = chip.Extract(subChipWidth, subChipWidth,
01027             sample, line);
01028 
01029         // Fill a buffer with subchip's contents. Since we'll never be storing
01030         // raw bytes in the buffer, we don't care about the pixel type.
01031         Buffer buffer(subChipWidth, subChipWidth, 1, Isis::None);
01032         double *doubleBuffer = buffer.DoubleBuffer();
01033         int bufferIndex = 0;
01034         for (int subChipLine = 1; subChipLine <= subChip.Lines();
01035             subChipLine++) {
01036           for (int subChipSample = 1; subChipSample <= subChip.Samples();
01037               subChipSample++) {
01038             doubleBuffer[bufferIndex] = subChip.GetValue(subChipSample,
01039                 subChipLine);
01040             bufferIndex++;
01041           }
01042         }
01043 
01044         // Calculate gradient based on contents in buffer and insert it into
01045         // output chip.
01046         double newPixelValue = 0;
01047         if (p_gradientFilterType == Sobel) {
01048           SobelGradient(buffer, newPixelValue);
01049         }
01050         filteredChip.SetValue(sample, line, newPixelValue);
01051       }
01052     }
01053 
01054     // Copy the data from the filtered chip back into the original chip.
01055     for (int line = 1; line <= filteredChip.Lines(); line++) {
01056       for (int sample = 1; sample <= filteredChip.Samples(); sample++) {
01057         chip.SetValue(sample, line, filteredChip.GetValue(sample, line));
01058       }
01059     }
01060   }
01061 
01062 
01072   void AutoReg::SobelGradient(Buffer &in, double &v) {
01073     bool specials = false;
01074     for(int i = 0; i < in.size(); ++i) {
01075       if(IsSpecial(in[i])) {
01076         specials = true;
01077       }
01078     }
01079     if(specials) {
01080       v = Isis::Null;
01081       return;
01082     }
01083     v = abs((in[0] + 2 * in[1] + in[2]) - (in[6] + 2 * in[7] + in[8])) +
01084         abs((in[2] + 2 * in[5] + in[8]) - (in[0] + 2 * in[3] + in[6]));
01085   }
01086 
01103   void AutoReg::Match(Chip &sChip, Chip &pChip, Chip &fChip, int startSamp, int endSamp, int startLine, int endLine) {
01104     // Sanity check.  Should have been caught by the two previous tests
01105     if(startSamp == endSamp && startLine == endLine) {
01106       string msg = "StartSample [" + iString(startSamp) + "] = EndSample ["
01107         + iString(endSamp) + "] and StartLine [" + iString(startLine) + " = EndLine ["
01108         + iString(endLine) + "].";
01109       throw iException::Message(iException::Programmer, msg, _FILEINFO_);
01110     }
01111 
01112     // Ok create a fit chip whose size is the same as the search chip
01113     // and then fill it with nulls
01114     fChip.SetSize(sChip.Samples(), sChip.Lines());
01115     for(int line = 1; line <= fChip.Lines(); line++) {
01116       for(int samp = 1; samp <= fChip.Samples(); samp++) {
01117         fChip.SetValue(samp, line, Isis::Null);
01118       }
01119     }
01120 
01121     // Create a chip the same size as the pattern chip.
01122     Chip subsearch(pChip.Samples(), pChip.Lines());
01123 
01124     for(int line = startLine; line <= endLine; line++) {
01125       for(int samp = startSamp; samp <= endSamp; samp++) {
01126         // Extract the subsearch chip and make sure it has enough valid data
01127         sChip.Extract(samp, line, subsearch);
01128 
01129 //        if(!subsearch.IsValid(p_patternValidPercent)) continue;
01130         if(!subsearch.IsValid(p_subsearchValidPercent)) continue;
01131 
01132         // Try to match the two subchips
01133         double fit = MatchAlgorithm(pChip, subsearch);
01134 
01135         // If we had a fit save off information about that fit
01136         if(fit != Isis::Null) {
01137           fChip.SetValue(samp, line, fit);
01138           if((p_bestFit == Isis::Null) || CompareFits(fit, p_bestFit)) {
01139             p_bestFit = fit;
01140             p_bestSamp = samp;
01141             p_bestLine = line;
01142           }
01143         }
01144       }
01145     }
01146   }
01147 
01148 
01173   bool AutoReg::ModelSurface(vector<double> &x,
01174                              vector<double> &y,
01175                              vector<double> &z) {
01176     PolynomialBivariate p(2);
01177     LeastSquares lsq(p);
01178     for(int i = 0; i < (int)x.size(); i++) {
01179       vector<double> xy;
01180       xy.push_back(x[i]);
01181       xy.push_back(y[i]);
01182       lsq.AddKnown(xy, z[i]);
01183     }
01184     try {
01185       lsq.Solve();
01186     }
01187     catch(iException &e) {
01188       e.Clear();
01189       p_registrationStatus = SurfaceModelSolutionInvalid;
01190       p_surfaceModelSolutionInvalidCount++;
01191       return false;
01192     }
01193 
01194     double a = p.Coefficient(0);
01195     double b = p.Coefficient(1);
01196     double c = p.Coefficient(2);
01197     double d = p.Coefficient(3);
01198     double e = p.Coefficient(4);
01199     double f = p.Coefficient(5);
01200 
01201     //----------------------------------------------------------
01202     // Compute eccentricity
01203     // For more information see:
01204     // http://mathworld.wolfram.com/Ellipse.html
01205     // Make sure delta matrix determinant is not equal to zero.
01206     // The general quadratic curve
01207     // dx^2+2exy+fy^2+2bx+2cy+a=0
01208     // is an ellipse when, after defining
01209     // Delta    =       |d    e/2   b|
01210     //          |e/2  f/2 c/2|
01211     //          |b    c/2   a|
01212     // J        =       |d   e/2|
01213     //      |e/2 f/e|
01214     // I        =       d + (f/2)
01215     // Delta!=0, J>0, and Delta/I<0. Also assume the ellipse is
01216     // nondegenerate (i.e., it is not a circle, so a!=c, and we have already
01217     // established is not a point, since J=ac-b^2!=0)
01218     // ---------------------------------------------------------
01219     if(p_testEccentricity) {
01220       bool canComputeEccentricity = true;
01221 
01222       Matrix delta(3, 3);
01223       delta[0][0] = d;
01224       delta[0][1] = e / 2;
01225       delta[0][2] = b / 2;
01226       delta[1][0] = e / 2;
01227       delta[1][1] = f;
01228       delta[1][2] = c / 2;
01229       delta[2][0] = b / 2;
01230       delta[2][1] = c / 2;
01231       delta[2][2] = a;
01232       if(delta.Determinant() == 0) {
01233         canComputeEccentricity = false;
01234       }
01235 
01236       //Make sure J matrix is greater than zero.
01237       Matrix J(2, 2);
01238       J[0][0] = d;
01239       J[0][1] = e / 2;
01240       J[1][0] = e / 2;
01241       J[1][1] = f;
01242       if(J.Determinant() <= 0) {
01243         canComputeEccentricity = false;
01244       }
01245 
01246       double I = d + (f);
01247       if(delta.Determinant() / I >= 0) {
01248         canComputeEccentricity = false;
01249       }
01250 
01251       if(canComputeEccentricity) {
01252         // If the eccentricity can be computed, go ahead and do so
01253         // Begin by calculating the semi-axis lengths
01254         double eA = sqrt((2 * (d * (c / 2) * (c / 2) + f * (b / 2) * (b / 2) + a * (e / 2) * (e / 2) - 2 * (e / 2) * (b / 2) * (c / 2) - d * f * a)) /
01255                          (((e / 2) * (e / 2) - d * f) * (sqrt((d - f) * (d - f) + 4 * (e / 2) * (e / 2)) - (d + f))));
01256 
01257         double eB = sqrt((2 * (d * (c / 2) * (c / 2) + f * (b / 2) * (b / 2) + a * (e / 2) * (e / 2) - 2 * (e / 2) * (b / 2) * (c / 2) - d * f * a)) /
01258                          (((e / 2) * (e / 2) - d * f) * (-sqrt((d - f) * (d - f) + 4 * (e / 2) * (e / 2)) - (d + f))));
01259 
01260         // Ensure that eA is always the semi-major axis, and eB the semi-minor
01261         if(eB > eA) {
01262           double tempVar = eB;
01263           eB = eA;
01264           eA = tempVar;
01265         }
01266 
01267         // Calculate eccentricity
01268         p_surfaceModelEccentricity = sqrt(eA * eA - eB * eB) / eA;
01269       }
01270       else {
01271         // If the eccentricity cannot be computed, assume it to be 0
01272         p_surfaceModelEccentricity = 0;
01273       }
01274 
01275       p_surfaceModelEccentricityRatio = sqrt(1.0 / (1.0 - p_surfaceModelEccentricity * p_surfaceModelEccentricity));
01276 
01277       // Ensure that the eccentricity is less than or equal to the tolerance
01278       if(p_surfaceModelEccentricity > p_surfaceModelEccentricityTolerance) {
01279         p_registrationStatus = SurfaceModelEccentricityRatioNotMet;
01280         p_surfaceModelEccentricityRatioNotMetCount++;
01281         return false;
01282       }
01283     }
01284 
01285     // Check if the user wants to test against the average residual
01286     if(p_testResidual) {
01287       // Compare the z computed with z actuals
01288       double meanAbsError = 0;
01289       for(int i = 0; i < (int) lsq.Residuals().size(); i++) {
01290         // Aggregate the absolute values of the residuals
01291         meanAbsError += fabs(lsq.Residual(i));
01292       }
01293 
01294       // The average residual, or mean absolute error
01295       meanAbsError = meanAbsError / lsq.Residuals().size();
01296 
01297       p_surfaceModelAvgResidual = meanAbsError;
01298 
01299       // Ensure the average residual is within the tolerance
01300       if(meanAbsError > p_surfaceModelResidualTolerance) {
01301         p_registrationStatus = SurfaceModelResidualToleranceNotMet;
01302         p_surfaceModelResidualToleranceNotMetCount++;
01303         return false;
01304       }
01305     }
01306 
01307     // Compute the determinant
01308     double det = 4.0 * d * f - e * e;
01309     if(det == 0.0) {
01310       p_registrationStatus = SurfaceModelSolutionInvalid;
01311       p_surfaceModelSolutionInvalidCount++;
01312       return false;
01313     }
01314 
01315     // Compute our chip position to sub-pixel accuracy
01316     p_chipSample = (c * e - 2.0 * b * f) / det;
01317     p_chipLine   = (b * e - 2.0 * c * d) / det;
01318     vector<double> temp;
01319     temp.push_back(p_chipSample);
01320     temp.push_back(p_chipLine);
01321     p_goodnessOfFit = lsq.Evaluate(temp);
01322     p_subPixelCorr = lsq.Evaluate(temp);
01323     return true;
01324   }
01325 
01326 
01336   bool AutoReg::CompareFits(double fit1, double fit2) {
01337     return(std::fabs(fit1 - IdealFit()) <= std::fabs(fit2 - IdealFit()));
01338   }
01339 
01346   bool AutoReg::IsIdeal(double fit) {
01347     return(std::fabs(IdealFit() - fit) < 0.00001);
01348   }
01349 
01350 #if 0
01351 
01355   AutoRegItem AutoReg::RegisterInformation() {
01356     AutoRegItem item;
01357     item.setSearchFile(p_searchChip.Filename());
01358     item.setPatternFile(p_patternChip.Filename());
01359     //item.setStatus(p_registrationStatus);
01360     item.setGoodnessOfFit(p_goodnessOfFit);
01361     item.setEccentricity(p_surfaceModelEccentricity);
01362     item.setZScoreOne(p_zScoreMin);
01363     item.setZScoreTwo(p_zScoreMax);
01364 
01365     /*if(p_goodnessOfFit != Isis::Null)item.setGoodnessOfFit(p_goodnessOfFit);
01366     if(p_surfaceModelEccentricity != Isis::Null) item.setEccentricity(p_surfaceModelEccentricity);
01367     if(p_zScoreMin != Isis::Null)item.setZScoreOne(p_zScoreMin);
01368     if(p_zScoreMax != Isis::Null)item.setZScoreTwo(p_zScoreMax);*/
01369 
01370     // Set the autoRegItem's change in line/sample numbers.
01371     if(p_registrationStatus == Success) {
01372       item.setDeltaSample(p_searchChip.TackSample() - p_searchChip.CubeSample());
01373       item.setDeltaLine(p_searchChip.TackLine() - p_searchChip.CubeLine());
01374     }
01375     else {
01376       item.setDeltaSample(Isis::Null);
01377       item.setDeltaLine(Isis::Null);
01378     }
01379 
01380     return item;
01381   }
01382 #endif
01383 
01384 
01395   Pvl AutoReg::RegistrationStatistics() {
01396     Pvl pvl;
01397     PvlGroup stats("AutoRegStatistics");
01398     stats += Isis::PvlKeyword("Total", p_totalRegistrations);
01399     stats += Isis::PvlKeyword("Successful", p_pixelSuccesses + p_subpixelSuccesses);
01400     stats += Isis::PvlKeyword("Failure", p_totalRegistrations - (p_pixelSuccesses + p_subpixelSuccesses));
01401     pvl.AddGroup(stats);
01402 
01403     PvlGroup successes("Successes");
01404     successes += PvlKeyword("SuccessPixel", p_pixelSuccesses);
01405     successes += PvlKeyword("SuccessSubPixel", p_subpixelSuccesses);
01406     pvl.AddGroup(successes);
01407 
01408     PvlGroup grp("PatternChipFailures");
01409     grp += PvlKeyword("PatternNotEnoughValidData", p_patternChipNotEnoughValidDataCount);
01410     grp += PvlKeyword("PatternZScoreNotMet", p_patternZScoreNotMetCount);
01411     pvl.AddGroup(grp);
01412 
01413     PvlGroup fit("FitChipFailures");
01414     fit += PvlKeyword("FitChipNoData", p_fitChipNoDataCount);
01415     fit += PvlKeyword("FitChipToleranceNotMet", p_fitChipToleranceNotMetCount);
01416     pvl.AddGroup(fit);
01417 
01418     PvlGroup model("SurfaceModelFailures");
01419     model += PvlKeyword("SurfaceModelNotEnoughValidData", p_surfaceModelNotEnoughValidDataCount);
01420     model += PvlKeyword("SurfaceModelSolutionInvalid", p_surfaceModelSolutionInvalidCount);
01421     model += PvlKeyword("SurfaceModelEccentricityRatioNotMet", p_surfaceModelEccentricityRatioNotMetCount);
01422     model += PvlKeyword("SurfaceModelDistanceInvalid", p_surfaceModelDistanceInvalidCount);
01423     model += PvlKeyword("SurfaceModelResidualToleranceNotMet", p_surfaceModelResidualToleranceNotMetCount);
01424     pvl.AddGroup(model);
01425 
01426     return (AlgorithmStatistics(pvl));
01427   }
01428 
01467   AutoReg::RegisterStatus AutoReg::AdaptiveRegistration(Chip &sChip,
01468       Chip &pChip,
01469       Chip &fChip,
01470       int startSamp,
01471       int startLine,
01472       int endSamp,
01473       int endLine,
01474       int bestSamp,
01475       int bestLine) {
01476     string msg = "Programmer needs to write their own virtual AdaptiveRegistration method";
01477     throw iException::Message(iException::Programmer, msg, _FILEINFO_);
01478     return SuccessSubPixel;
01479   }
01480 
01488   PvlGroup AutoReg::RegTemplate() {
01489     PvlGroup reg("AutoRegistration");
01490 
01491     PvlGroup &algo = p_template.FindGroup("Algorithm", Pvl::Traverse);
01492     reg += PvlKeyword("Algorithm", algo["Name"][0]);
01493     reg += PvlKeyword("Tolerance", algo["Tolerance"][0]);
01494     if(algo.HasKeyword("SubpixelAccuracy")) {
01495       reg += PvlKeyword("SubpixelAccuracy", algo["SubpixelAccuracy"][0]);
01496     }
01497     if(algo.HasKeyword("ReductionFactor")) {
01498       reg += PvlKeyword("ReductionFactor", algo["ReductionFactor"][0]);
01499     }
01500     if(algo.HasKeyword("Gradient")) {
01501       reg += PvlKeyword("Gradient", algo["Gradient"][0]);
01502     }
01503 
01504     PvlGroup &pchip = p_template.FindGroup("PatternChip", Pvl::Traverse);
01505     reg += PvlKeyword("PatternSamples", pchip["Samples"][0]);
01506     reg += PvlKeyword("PatternLines", pchip["Lines"][0]);
01507     if(pchip.HasKeyword("ValidMinimum")) {
01508       reg += PvlKeyword("PatternMinimum", pchip["ValidMinimum"][0]);
01509     }
01510     if(pchip.HasKeyword("ValidMaximum")) {
01511       reg += PvlKeyword("PatternMaximum", pchip["ValidMaximum"][0]);
01512     }
01513     if(pchip.HasKeyword("MinimumZScore")) {
01514       reg += PvlKeyword("MinimumZScore", pchip["MinimumZScore"][0]);
01515     }
01516     if(pchip.HasKeyword("ValidPercent")) {
01517       SetPatternValidPercent((double)pchip["ValidPercent"]);
01518       reg += PvlKeyword("ValidPercent", pchip["ValidPercent"][0]);
01519     }
01520 
01521     PvlGroup &schip = p_template.FindGroup("SearchChip", Pvl::Traverse);
01522     reg += PvlKeyword("SearchSamples", schip["Samples"][0]);
01523     reg += PvlKeyword("SearchLines", schip["Lines"][0]);
01524     if(schip.HasKeyword("ValidMinimum")) {
01525       reg += PvlKeyword("SearchMinimum", schip["ValidMinimum"][0]);
01526     }
01527     if(schip.HasKeyword("ValidMaximum")) {
01528       reg += PvlKeyword("SearchMaximum", schip["ValidMaximum"][0]);
01529     }
01530     if(schip.HasKeyword("SubchipValidPercent")) {
01531       SetSubsearchValidPercent((double)schip["SubchipValidPercent"]);
01532       reg += PvlKeyword("SubchipValidPercent", schip["SubchipValidPercent"][0]);
01533     }
01534 
01535     if(p_template.HasGroup("SurfaceModel")) {
01536       PvlGroup &smodel = p_template.FindGroup("SurfaceModel", Pvl::Traverse);
01537       if(smodel.HasKeyword("DistanceTolerance")) {
01538         reg += PvlKeyword("DistanceTolerance", smodel["DistanceTolerance"][0]);
01539       }
01540 
01541       if(smodel.HasKeyword("WindowSize")) {
01542         reg += PvlKeyword("WindowSize", smodel["WindowSize"][0]);
01543       }
01544 
01545       if(smodel.HasKeyword("EccentricityRatio")) {
01546         reg += PvlKeyword("EccentricityRatio", smodel["EccentricityRatio"][0]);
01547       }
01548 
01549       if(smodel.HasKeyword("ResidualTolerance")) {
01550         reg += PvlKeyword("ResidualTolerance", smodel["ResidualTolerance"][0]);
01551       }
01552     }
01553 
01554     return reg;
01555   }
01556 }