7#include "focusalgorithms.h"
10#include "polynomialfit.h"
13#include "fitsviewer/fitsstardetector.h"
14#include "focusstars.h"
15#include <ekos_focus_debug.h>
26class LinearFocusAlgorithm :
public FocusAlgorithmInterface
35 LinearFocusAlgorithm(
const FocusParams ¶ms);
40 int initialPosition()
override
42 return requestedPosition;
48 int newMeasurement(
int position,
double value,
const double starWeight,
const QList<Edge*> *stars)
override;
50 FocusAlgorithmInterface *Copy()
override;
61 *pos = pass1Positions;
67 double latestValue()
const override
69 if (values.size() > 0)
74 bool isInFirstPass()
const override
76 if (params.focusAlgorithm == Focus::FOCUS_LINEAR || params.focusAlgorithm == Focus::FOCUS_LINEAR1PASS)
82 QString getTextStatus(
double R2 = 0)
const override;
84 int currentStep()
const override
92 int completeIteration(
int step,
bool foundFit,
double minPos,
double minVal);
95 int setupSolution(
int position,
double value,
double sigma);
98 int linearWalk(
int position,
double value,
const double starWeight);
101 CurveFitting::FittingGoal getGoal(
int numSteps);
104 int getCurveMinPoints();
110 int getNextStepSize();
115 bool setupPendingSolution(
int position);
118 void computeInitialPosition();
121 int getFirstPosition();
126 int setupSecondPass(
int position,
double value,
double margin = 2.0);
129 int finishFirstPass(
int position,
double value);
139 double peirce_criterion(
double N,
double n,
double m);
145 bool bestSamplesHeuristic();
152 double relativeHFR(
double origHFR,
const QList<Edge*> *stars);
155 double calculateStarSigma(
const bool useWeights,
const QList<Edge*> *stars);
173 int requestedPosition;
175 int passStartPosition;
180 double firstPassBestValue;
182 int firstPassBestPosition;
187 int minPositionLimit;
189 int maxPositionLimit;
192 int numPolySolutionsFound;
195 int numRestartSolutionsFound;
197 int secondPassStartIndex;
201 bool solutionPending;
207 double solutionPendingValue = 0;
208 int solutionPendingPosition = 0;
211 bool relativeHFREnabled =
false;
213 double bestRelativeHFR = 0;
214 int bestRelativeHFRIndex = 0;
219FocusAlgorithmInterface *LinearFocusAlgorithm::Copy()
221 LinearFocusAlgorithm *alg =
new LinearFocusAlgorithm(params);
223 return dynamic_cast<FocusAlgorithmInterface*
>(alg);
226FocusAlgorithmInterface *MakeLinearFocuser(
const FocusAlgorithmInterface::FocusParams ¶ms)
228 return new LinearFocusAlgorithm(params);
231LinearFocusAlgorithm::LinearFocusAlgorithm(
const FocusParams &focusParams)
232 : FocusAlgorithmInterface(focusParams)
237 maxPositionLimit = std::min(params.maxPositionAllowed, params.startPosition + params.maxTravel);
238 minPositionLimit = std::max(params.minPositionAllowed, params.startPosition - params.maxTravel);
239 computeInitialPosition();
242QString LinearFocusAlgorithm::getTextStatus(
double R2)
const
244 if (params.focusAlgorithm == Focus::FOCUS_LINEAR)
248 if (focusSolution > 0)
249 return QString(
"Solution: %1").
arg(focusSolution);
254 return QString(
"Pending: %1, %2").
arg(solutionPendingPosition).
arg(solutionPendingValue, 0,
'f', 2);
258 return QString(
"2nd Pass. 1st: %1, %2")
259 .
arg(firstPassBestPosition).
arg(firstPassBestValue, 0,
'f', 2);
264 if (params.filterName !=
"")
266 if (params.curveFit == CurveFitting::FOCUS_QUADRATIC)
267 text.
append(
": Quadratic");
268 else if (params.curveFit == CurveFitting::FOCUS_HYPERBOLA)
270 text.
append(
": Hyperbola");
271 text.
append(params.useWeights ?
" (W)" :
" (U)");
273 else if (params.curveFit == CurveFitting::FOCUS_PARABOLA)
275 text.
append(
": Parabola");
276 text.
append(params.useWeights ?
" (W)" :
" (U)");
278 else if (params.curveFit == CurveFitting::FOCUS_2DGAUSSIAN)
280 text.
append(
": Gaussian");
281 text.
append(params.useWeights ?
" (W)" :
" (U)");
287 return text.
append(
". Moving to Solution");
290 if (focusSolution > 0)
292 if (params.curveFit == CurveFitting::FOCUS_QUADRATIC)
293 return text.
append(
" Solution: %1").
arg(focusSolution);
296 return text.
append(
" Solution: %1, R²=%2").
arg(focusSolution).
arg(trunc(R2 * 100.0) / 100.0, 0,
'f', 2);
299 return text.
append(
" Failed");
304void LinearFocusAlgorithm::computeInitialPosition()
307 stepSize = params.initialStepSize;
309 solutionPending =
false;
311 firstPassBestValue = -1;
312 firstPassBestPosition = 0;
313 numPolySolutionsFound = 0;
314 numRestartSolutionsFound = 0;
315 secondPassStartIndex = -1;
317 qCDebug(KSTARS_EKOS_FOCUS)
318 <<
QString(
"Linear: Travel %1 initStep %2 pos %3 min %4 max %5 maxIters %6 tolerance %7 minlimit %8 maxlimit %9 init#steps %10 algo %11 backlash %12 curvefit %13 useweights %14")
319 .
arg(params.maxTravel).
arg(params.initialStepSize).
arg(params.startPosition).
arg(params.minPositionAllowed)
320 .
arg(params.maxPositionAllowed).
arg(params.maxIterations).
arg(params.focusTolerance).
arg(minPositionLimit)
321 .
arg(maxPositionLimit).
arg(params.initialOutwardSteps).
arg(params.focusAlgorithm).
arg(params.backlash)
322 .
arg(params.curveFit).
arg(params.useWeights);
324 requestedPosition = std::min(maxPositionLimit, getFirstPosition());
325 passStartPosition = requestedPosition;
326 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: initialPosition %1 sized %2")
327 .
arg(requestedPosition).
arg(params.initialStepSize);
334int LinearFocusAlgorithm::getFirstPosition()
336 if (params.focusAlgorithm != Focus::FOCUS_LINEAR1PASS)
337 return static_cast<int>(params.startPosition + params.initialOutwardSteps * params.initialStepSize);
340 double outSteps, numFullStepsOut, numHalfStepsOut;
342 switch (params.focusWalk)
344 case Focus::FOCUS_WALK_CLASSIC:
345 firstPosition =
static_cast<int>(params.startPosition + params.initialOutwardSteps * params.initialStepSize);
348 case Focus::FOCUS_WALK_FIXED_STEPS:
349 outSteps = (params.numSteps - 1) / 2.0f;
350 firstPosition = params.startPosition + (outSteps * params.initialStepSize);
353 case Focus::FOCUS_WALK_CFZ_SHUFFLE:
354 if (params.numSteps % 2)
357 numHalfStepsOut = 1.0f;
358 numFullStepsOut = ((params.numSteps - 1) / 2.0f) - numHalfStepsOut;
363 numHalfStepsOut = 1.5f;
364 numFullStepsOut = (params.numSteps / 2.0f) - 2.0f;
366 firstPosition = params.startPosition + (numFullStepsOut * params.initialStepSize) + (numHalfStepsOut *
367 (params.initialStepSize / 2.0f));
372 firstPosition =
static_cast<int>(params.startPosition + params.initialOutwardSteps * params.initialStepSize);
376 return firstPosition;
399double LinearFocusAlgorithm::relativeHFR(
double origHFR,
const QList<Edge*> *stars)
401 constexpr double minHFR = 0.3;
402 if (origHFR < minHFR)
return origHFR;
404 const int currentIndex = values.size();
405 const bool isFirstSample = (currentIndex == 0);
406 double relativeHFR = origHFR;
408 if (isFirstSample && stars !=
nullptr)
409 relativeHFREnabled =
true;
411 if (relativeHFREnabled && stars ==
nullptr)
414 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: Error: Inconsistent relativeHFR, disabling.");
415 relativeHFREnabled =
false;
418 if (!relativeHFREnabled)
421 if (starLists.size() != positions.size())
424 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: Error: Inconsistent relativeHFR data structures, disabling.");
425 relativeHFREnabled =
false;
432 constexpr int starDistanceThreshold = 5;
433 FocusStars fs(*stars, starDistanceThreshold);
434 starLists.push_back(fs);
435 auto ¤tStars = starLists.back();
439 relativeHFR = currentStars.getHFR();
440 if (relativeHFR <= 0)
443 relativeHFREnabled =
false;
447 bestRelativeHFR = relativeHFR;
448 bestRelativeHFRIndex = currentIndex;
453 auto &bestStars = starLists[bestRelativeHFRIndex];
454 double hfr = currentStars.relativeHFR(bestStars, bestRelativeHFR);
461 relativeHFR = currentStars.getHFR();
462 if (relativeHFR <= 0)
468 if (inFirstPass && relativeHFR <= bestRelativeHFR)
470 bestRelativeHFR = relativeHFR;
471 bestRelativeHFRIndex = currentIndex;
475 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"RelativeHFR: orig %1 computed %2 relative %3")
476 .
arg(origHFR).
arg(currentStars.getHFR()).
arg(relativeHFR);
481int LinearFocusAlgorithm::newMeasurement(
int position,
double value,
const double starWeight,
const QList<Edge*> *stars)
483 if (params.focusAlgorithm == Focus::FOCUS_LINEAR1PASS && (params.focusWalk == Focus::FOCUS_WALK_FIXED_STEPS
484 || params.focusWalk == Focus::FOCUS_WALK_CFZ_SHUFFLE))
485 return linearWalk(position, value, starWeight);
487 double minPos = -1.0, minVal = 0;
488 bool foundFit =
false;
490 double origValue = value;
494 if (params.curveFit == CurveFitting::FOCUS_QUADRATIC)
495 value = relativeHFR(value, stars);
499 if (params.focusAlgorithm == Focus::FOCUS_LINEAR1PASS && !inFirstPass)
501 return setupSolution(position, value, starWeight);
504 int thisStepSize = stepSize;
507 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: step %1, newMeasurement(%2, %3 -> %4, %5)")
509 .
arg(stars ==
nullptr ? 0 : stars->
size());
511 qCDebug(KSTARS_EKOS_FOCUS)
512 <<
QString(
"Linear: step %1, newMeasurement(%2, %3 -> %4, %5) 1stBest %6 %7").
arg(numSteps)
513 .
arg(position).
arg(origValue).
arg(value).
arg(stars ==
nullptr ? 0 : stars->
size())
514 .
arg(firstPassBestPosition).
arg(firstPassBestValue, 0,
'f', 3);
516 const int LINEAR_POSITION_TOLERANCE = params.initialStepSize;
517 if (abs(position - requestedPosition) > LINEAR_POSITION_TOLERANCE)
519 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: error didn't get the requested position");
520 return requestedPosition;
523 if (focusSolution != -1)
525 doneString =
i18n(
"Called newMeasurement after a solution was found.");
526 failCode = Ekos::FOCUS_FAIL_INTERNAL;
527 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: error %1").
arg(doneString);
534 values.push_back(value);
535 weights.push_back(starWeight);
538 pass1Positions.push_back(position);
539 pass1Values.push_back(value);
540 pass1Weights.push_back(starWeight);
541 pass1Outliers.push_back(
false);
548 if (solutionPending ||
549 (!inFirstPass && (value < firstPassBestValue * (1.0 + params.focusTolerance))))
551 if (setupPendingSolution(position))
553 return completeIteration(retryNumber > 0 ? 0 : stepSize,
false, -1.0f, -1.0f);
556 return setupSolution(position, value, starWeight);
561 constexpr int kNumPolySolutionsRequired = 2;
562 constexpr int kNumRestartSolutionsRequired = 3;
565 if (values.size() >= getCurveMinPoints())
567 if (params.curveFit == CurveFitting::FOCUS_QUADRATIC)
569 PolynomialFit fit(2, positions, values);
570 foundFit = fit.findMinimum(position, 0, params.maxPositionAllowed, &minPos, &minVal);
575 if (values.size() > getCurveMinPoints())
577 PolynomialFit fit2(2, positions.mid(1), values.mid(1));
578 foundFit = fit2.findMinimum(position, 0, params.maxPositionAllowed, &minPos, &minVal);
585 auto goal = getGoal(numSteps);
586 params.curveFitting->fitCurve(goal, positions, values, weights, pass1Outliers, params.curveFit, params.useWeights,
587 params.optimisationDirection);
589 foundFit = params.curveFitting->findMinMax(position, 0, params.maxPositionAllowed, &minPos, &minVal,
590 static_cast<CurveFitting::CurveFit
>(params.curveFit),
591 params.optimisationDirection);
596 const int distanceToMin =
static_cast<int>(position - minPos);
597 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: fit(%1): %2 = %3 @ %4 distToMin %5")
598 .
arg(positions.size()).
arg(minPos).
arg(minVal).
arg(position).
arg(distanceToMin);
599 if (distanceToMin >= 0)
602 numPolySolutionsFound = 0;
603 numRestartSolutionsFound = 0;
604 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: Solutions reset %1 = %2").
arg(minPos).
arg(minVal);
622 else if (!bestSamplesHeuristic())
626 if (minPos > passStartPosition)
628 numRestartSolutionsFound++;
629 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: RESTART Solution #%1 %2 = %3 @ %4")
630 .
arg(numRestartSolutionsFound).
arg(minPos).
arg(minVal).
arg(position);
634 numPolySolutionsFound++;
635 numRestartSolutionsFound = 0;
636 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: Solution #%1: %2 = %3 @ %4")
637 .
arg(numPolySolutionsFound).
arg(minPos).
arg(minVal).
arg(position);
647 if (params.focusAlgorithm == Focus::FOCUS_LINEAR)
648 howFarToGo = kNumPolySolutionsRequired;
650 howFarToGo = std::max(1,
static_cast<int> (params.initialOutwardSteps) - 1);
652 if (numPolySolutionsFound >= howFarToGo)
657 double minMeasurement = *std::min_element(values.begin(), values.end());
658 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: 1stPass solution @ %1: pos %2 val %3, min measurement %4")
659 .
arg(position).
arg(minPos).
arg(minVal).
arg(minMeasurement);
662 if (params.focusAlgorithm == Focus::FOCUS_LINEAR)
663 return setupSecondPass(
static_cast<int>(minPos), minMeasurement, 2.0);
669 return finishFirstPass(
static_cast<int>(round(minPos)), minMeasurement);
671 else if (numRestartSolutionsFound >= kNumRestartSolutionsRequired)
677 const double highestStartPosition = params.startPosition + params.initialOutwardSteps * params.initialStepSize;
678 params.startPosition = std::min(minPos, highestStartPosition);
679 computeInitialPosition();
680 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: Restart. Current pos %1, min pos %2, min val %3, re-starting at %4")
681 .
arg(position).
arg(minPos).
arg(minVal).
arg(requestedPosition);
682 return requestedPosition;
692 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: ******** No poly min: Poly must be inverted");
701 else if (gettingWorse())
705 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: getting worse, re-running 2nd pass");
706 return setupSecondPass(firstPassBestPosition, firstPassBestValue);
709 return completeIteration(thisStepSize, foundFit, minPos, minVal);
714CurveFitting::FittingGoal LinearFocusAlgorithm::getGoal(
int numSteps)
716 if (params.focusWalk == Focus::FOCUS_WALK_CLASSIC)
718 if (numSteps > 2.0 * params.initialOutwardSteps)
719 return CurveFitting::FittingGoal::BEST;
721 return CurveFitting::FittingGoal::STANDARD;
725 if (numSteps >= params.numSteps)
726 return CurveFitting::FittingGoal::BEST;
728 return CurveFitting::FittingGoal::STANDARD;
734int LinearFocusAlgorithm::getCurveMinPoints()
736 if (params.focusAlgorithm != Focus::FOCUS_LINEAR1PASS)
739 if (params.focusWalk == Focus::FOCUS_WALK_CLASSIC)
740 return params.initialOutwardSteps + 2;
743 if (params.donutBuster)
744 return params.numSteps;
746 return params.numSteps / 2 + 2;
750int LinearFocusAlgorithm::linearWalk(
int position,
double value,
const double starWeight)
754 return setupSolution(position, value, starWeight);
757 bool foundFit =
false;
758 double minPos = -1.0, minVal = 0;
760 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear1Pass: step %1, linearWalk %2, %3")
764 if (abs(position - requestedPosition) > params.initialStepSize)
766 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"linearWalk error: position %1, requested position %2").
arg(position)
767 .
arg(requestedPosition);
768 return requestedPosition;
773 values.push_back(value);
774 weights.push_back(starWeight);
775 pass1Positions.push_back(position);
776 pass1Values.push_back(value);
777 pass1Weights.push_back(starWeight);
778 pass1Outliers.push_back(
false);
780 if (values.size() < getCurveMinPoints())
787 if (params.curveFit == CurveFitting::FOCUS_QUADRATIC)
788 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"linearWalk called incorrectly for FOCUS_QUADRATIC");
792 auto goal = getGoal(numSteps);
794 if (params.donutBuster)
796 params.curveFitting->fitCurve(goal, positions, values, weights, pass1Outliers, params.curveFit, params.useWeights,
797 params.optimisationDirection);
799 foundFit = params.curveFitting->findMinMax(position, 0, params.maxPositionAllowed, &minPos, &minVal,
800 static_cast<CurveFitting::CurveFit
>(params.curveFit), params.optimisationDirection);
802 if (numSteps >= params.numSteps)
806 return finishFirstPass(
static_cast<int>(round(minPos)), minVal);
810 doneString =
i18n(
"Failed to fit curve to data.");
811 failCode = Ekos::FOCUS_FAIL_CURVEFIT;
812 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"LinearWalk: %1").
arg(doneString);
820 int nextStepSize = getNextStepSize();
821 return completeIteration(nextStepSize, foundFit, minPos, minVal);
831void LinearFocusAlgorithm::removeDonuts()
834 if (values.size() <= 7)
840 int centre = values.
size() / 2;
841 int minElement = centre;
842 double minValue = values[centre];
843 for (
int i = 1; i < 4; i++)
845 if (values[centre + i] < minValue)
847 minValue = values[centre + i];
848 minElement = centre + i;
850 if (values[centre - i] < minValue)
852 minValue = values[centre - i];
853 minElement = centre - i;
858 double threshold = values[minElement];
859 double nextValue, deltaValue;
860 double tolerance = 0.5;
861 for (
int i = minElement; i > 0; i--)
863 nextValue = values[i - 1];
864 if (nextValue < threshold)
866 queryOutliers[i - 1] =
true;
869 deltaValue = (nextValue - values[i]) * tolerance;
870 threshold = nextValue + deltaValue;
875 for (
int i = 0; i < minElement - 3; i++)
877 if (queryOutliers[i])
878 pass1Outliers[i] =
true;
885 threshold = values[minElement];
886 for (
int i = minElement; i < values.size() - 1; i++)
888 nextValue = values[i + 1];
889 if (nextValue < threshold)
891 queryOutliers[i + 1] =
true;
894 deltaValue = (nextValue - values[i]) * tolerance;
895 threshold = nextValue + deltaValue;
900 for (
int i = values.size() - 1; i > minElement + 3; i--)
902 if (queryOutliers[i])
903 pass1Outliers[i] =
true;
910 for (
int i = pass1Outliers.size() - 1; i >= 0; i--)
911 str.
append((pass1Outliers[i]) ?
'Y' :
'N');
912 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Donut analysis: %1").
arg(str);
917int LinearFocusAlgorithm::getNextStepSize()
919 int nextStepSize, lower, upper;
921 switch (params.focusWalk)
923 case Focus::FOCUS_WALK_CLASSIC:
924 nextStepSize = stepSize;
926 case Focus::FOCUS_WALK_FIXED_STEPS:
927 nextStepSize = stepSize;
929 case Focus::FOCUS_WALK_CFZ_SHUFFLE:
930 if (params.numSteps % 2)
933 lower = (params.numSteps - 3) / 2;
934 upper = params.numSteps - lower;
939 lower = (params.numSteps - 4) / 2;
940 upper = (params.numSteps - lower);
943 if (numSteps <= lower)
944 nextStepSize = stepSize;
945 else if (numSteps >= upper)
946 nextStepSize = stepSize;
948 nextStepSize = stepSize / 2;
951 nextStepSize = stepSize;
958int LinearFocusAlgorithm::setupSolution(
int position,
double value,
double weight)
960 focusSolution = position;
962 focusWeight = weight;
964 doneString =
i18n(
"Solution found.");
965 failCode = Ekos::FOCUS_FAIL_NONE;
966 if (params.focusAlgorithm == Focus::FOCUS_LINEAR)
967 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: solution @ %1 = %2 (best %3)")
968 .
arg(position).
arg(value).
arg(firstPassBestValue);
971 double delta = fabs(value - firstPassBestValue);
972 QString str(
"Linear: solution @ ");
973 str.
append(
QString(
"%1 = %2 (expected %3) delta=%4").arg(position).arg(value).arg(firstPassBestValue).arg(delta));
975 if (params.useWeights && weight > 0.0)
978 double numSigmas = delta * weight;
980 .arg(numSigmas).arg(value <= firstPassBestValue ?
"better" :
"worse"));
981 if (value <= firstPassBestValue || numSigmas < 1)
983 else if (numSigmas < 3)
986 str.
append(
QString(
". POOR result. If this happens repeatedly it may be a sign of poor backlash compensation."));
989 qCInfo(KSTARS_EKOS_FOCUS) << str;
995int LinearFocusAlgorithm::completeIteration(
int step,
bool foundFit,
double minPos,
double minVal)
997 if (params.focusAlgorithm == Focus::FOCUS_LINEAR && numSteps == params.maxIterations - 2)
1002 const int minIndex =
static_cast<int>(std::min_element(values.begin(), values.end()) - values.begin());
1003 return setupSecondPass(positions[minIndex], values[minIndex], 0.5);
1005 else if (numSteps > params.maxIterations)
1009 doneString =
i18n(
"Too many steps.");
1010 failCode = Ekos::FOCUS_FAIL_MAX_ITERS;
1011 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: error %1").
arg(doneString);
1017 requestedPosition = requestedPosition - step;
1020 if (requestedPosition < minPositionLimit)
1023 if (params.focusAlgorithm == Focus::FOCUS_LINEAR)
1025 const int minIndex =
static_cast<int>(std::min_element(values.begin(), values.end()) - values.begin());
1026 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: reached end without Vmin. Restarting %1 pos %2 value %3")
1027 .
arg(minIndex).
arg(positions[minIndex]).
arg(values[minIndex]);
1028 return setupSecondPass(positions[minIndex], values[minIndex]);
1032 if (foundFit && minPos >= minPositionLimit)
1034 return finishFirstPass(
static_cast<int>(round(minPos)), minVal);
1039 doneString =
i18n(
"Solution lies outside max travel.");
1040 failCode = Ekos::FOCUS_FAIL_FOCUSER_OOB;
1041 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: error %1").
arg(doneString);
1047 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: requesting position %1").
arg(requestedPosition);
1048 return requestedPosition;
1060bool LinearFocusAlgorithm::setupPendingSolution(
int position)
1062 const int length = values.
size();
1063 const int secondPassIndex = length - secondPassStartIndex;
1064 const double thisValue = values[length - 1];
1068 double referenceValue = (secondPassIndex <= 1) ? 1e6 : values[length - 2];
1069 if (retryNumber > 0 && length - (2 + retryNumber) >= 0)
1070 referenceValue = values[length - (2 + retryNumber)];
1073 const bool notGettingWorse = (secondPassIndex <= 1) || (thisValue <= referenceValue);
1076 const bool couldGoFather = (numSteps < params.maxIterations - 2) && (position - stepSize > minPositionLimit);
1079 int maxNumRetries = 3;
1080 if (position - stepSize / 2 < firstPassBestPosition)
1082 stepSize = std::max(2, params.initialStepSize / 4);
1086 if (notGettingWorse && couldGoFather)
1088 qCDebug(KSTARS_EKOS_FOCUS)
1089 <<
QString(
"Linear: %1: Position(%2) & HFR(%3) -- Pass1: %4 %5, solution pending, searching further")
1090 .
arg(length).
arg(position).
arg(thisValue, 0,
'f', 3).
arg(firstPassBestPosition).
arg(firstPassBestValue, 0,
'f', 3);
1091 solutionPending =
true;
1092 solutionPendingPosition = position;
1093 solutionPendingValue = thisValue;
1097 else if (solutionPending && couldGoFather && retryNumber < maxNumRetries &&
1098 (secondPassIndex > 1) && (thisValue >= referenceValue))
1100 qCDebug(KSTARS_EKOS_FOCUS)
1101 <<
QString(
"Linear: %1: Position(%2) & HFR(%3) -- Pass1: %4 %5, solution pending, got worse, retrying")
1102 .
arg(length).
arg(position).
arg(thisValue, 0,
'f', 3).
arg(firstPassBestPosition).
arg(firstPassBestValue, 0,
'f', 3);
1107 qCDebug(KSTARS_EKOS_FOCUS)
1108 <<
QString(
"Linear: %1: Position(%2) & HFR(%3) -- Pass1: %4 %5, finishing, can't go further")
1109 .
arg(length).
arg(position).
arg(thisValue, 0,
'f', 3).
arg(firstPassBestPosition).
arg(firstPassBestValue, 0,
'f', 3);
1114void LinearFocusAlgorithm::debugLog()
1116 QString str(
"Linear: points=[");
1117 for (
int i = 0; i < positions.size(); ++i)
1119 str.
append(
QString(
"(%1, %2, %3)").arg(positions[i]).arg(values[i]).arg(weights[i]));
1120 if (i < positions.size() - 1)
1124 str.
append(
QString(
";duration=%1").arg(stopWatch.elapsed() / 1000));
1128 str.
append(
QString(
";temperature=%1").arg(params.temperature));
1129 str.
append(
QString(
";focusalgorithm=%1").arg(params.focusAlgorithm));
1132 str.
append(
QString(
";useweights=%1").arg(params.useWeights));
1134 qCDebug(KSTARS_EKOS_FOCUS) << str;
1138int LinearFocusAlgorithm::setupSecondPass(
int position,
double value,
double margin)
1140 firstPassBestPosition = position;
1141 firstPassBestValue = value;
1142 inFirstPass =
false;
1143 solutionPending =
false;
1144 secondPassStartIndex = values.size();
1148 requestedPosition = std::min(
static_cast<int>(firstPassBestPosition + stepSize * margin), maxPositionLimit);
1149 stepSize = params.initialStepSize / 2;
1150 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: 2ndPass starting at %1 step %2").
arg(requestedPosition).
arg(stepSize);
1151 return requestedPosition;
1155int LinearFocusAlgorithm::finishFirstPass(
int position,
double value)
1157 firstPassBestPosition = position;
1158 firstPassBestValue = value;
1159 inFirstPass =
false;
1160 requestedPosition = position;
1162 if (params.refineCurveFit)
1166 std::vector<std::pair<int, double>> curveDeltas;
1168 params.curveFitting->calculateCurveDeltas(params.curveFit, curveDeltas);
1171 if (curveDeltas.size() > 6)
1174 std::vector<double> curveDeltasX2Vec;
1175 for (ulong i = 0; i < curveDeltas.size(); i++)
1176 curveDeltasX2Vec.push_back(pow(curveDeltas[i].second, 2.0));
1178 auto curveDeltasX2Mean = Mathematics::RobustStatistics::ComputeLocation(Mathematics::RobustStatistics::LOCATION_MEAN,
1183 double outlierRejection = params.donutBuster ? params.outlierRejection : 0.2;
1184 int maxOutliers = curveDeltas.size() * outlierRejection;
1187 double modelUnknowns = params.curveFit == CurveFitting::FOCUS_PARABOLA ? 3.0 : 4.0;
1191 double pc = peirce_criterion(
static_cast<double>(curveDeltas.size()),
static_cast<double>(maxOutliers), modelUnknowns);
1192 double pc_threshold = sqrt(pc * curveDeltasX2Mean);
1195 std::sort(curveDeltas.begin(), curveDeltas.end(),
1196 [](
const std::pair<int, double> &a,
const std::pair<int, double> &b)
1198 return ( a.second > b.second );
1202 auto bestPass1Outliers = pass1Outliers;
1206 for (ulong i = 0; i < curveDeltas.size(); i++)
1208 if(curveDeltas[i].second <= pc_threshold)
1212 pass1Outliers[curveDeltas[i].first] =
true;
1215 if (outliers >= maxOutliers)
1223 if (outliers > 0 && params.curveFitting->getCurveParams(params.curveFit, currentCoefficients))
1225 double currentR2 = params.curveFitting->calculateR2(
static_cast<CurveFitting::CurveFit
>(params.curveFit));
1228 params.curveFitting->fitCurve(CurveFitting::FittingGoal::BEST, pass1Positions, pass1Values, pass1Weights,
1229 pass1Outliers, params.curveFit, params.useWeights, params.optimisationDirection);
1230 double minPos, minVal;
1231 bool foundFit = params.curveFitting->findMinMax(position, 0, params.maxPositionAllowed, &minPos, &minVal,
1232 static_cast<CurveFitting::CurveFit
>(params.curveFit),
1233 params.optimisationDirection);
1236 double newR2 = params.curveFitting->calculateR2(
static_cast<CurveFitting::CurveFit
>(params.curveFit));
1237 if (newR2 > currentR2)
1240 requestedPosition = minPos;
1241 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Refine Curve Fit improved focus from %1 to %2. R2 improved from %3 to %4")
1247 qCDebug(KSTARS_EKOS_FOCUS) <<
1248 QString(
"Refine Curve Fit could not improve on the original solution");
1249 pass1Outliers = bestPass1Outliers;
1250 params.curveFitting->setCurveParams(params.curveFit, currentCoefficients);
1255 qCDebug(KSTARS_EKOS_FOCUS) <<
1256 QString(
"Refine Curve Fit failed to fit curve whilst refining curve fit. Running with original solution");
1257 pass1Outliers = bestPass1Outliers;
1258 params.curveFitting->setCurveParams(params.curveFit, currentCoefficients);
1263 return requestedPosition;
1276double LinearFocusAlgorithm::peirce_criterion(
double N,
double n,
double m)
1282 double Q = (pow(n, (n / N)) * pow((N - n), ((N - n) / N))) / N;
1289 while (abs(r_new - r_old) > (N * 2.0e-16))
1293 double ldiv = pow(r_new, n);
1297 double Lamda = pow((pow(Q, N) / (ldiv)), (1.0 / (N - n)));
1300 x2 = 1.0 + (N - m - n) / n * (1.0 - pow(Lamda, 2.0));
1312 r_new = exp((x2 - 1) / 2.0) * gsl_sf_erfc(sqrt(x2) / sqrt(2.0));
1321bool LinearFocusAlgorithm::bestSamplesHeuristic()
1323 const int length = values.size();
1324 if (length < 5)
return true;
1326 std::nth_element(tempValues.
begin(), tempValues.
begin() + 2, tempValues.
end());
1327 double secondBest = tempValues[1];
1328 if ((values[length - 1] <= secondBest) || (values[length - 2] <= secondBest))
1334bool LinearFocusAlgorithm::gettingWorse()
1337 constexpr int streak = 3;
1338 const int length = values.size();
1339 if (secondPassStartIndex < 0)
1341 if (length < streak + 1)
1344 if (length - secondPassStartIndex < streak + 1)
1346 for (
int i = length - 1; i >= length - streak; --i)
1347 if (values[i] <= values[i - 1])
QString i18n(const char *text, const TYPE &arg...)
Ekos is an advanced Astrophotography tool for Linux.
qsizetype size() const const
QString & append(QChar ch)
QString arg(Args &&... args) const const
qsizetype size() const const