|
Isis 3.0 Object Programmers' Reference |
Home |
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 }