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)");
282 return text.
append(
". Moving to Solution");
285 if (focusSolution > 0)
287 if (params.curveFit == CurveFitting::FOCUS_QUADRATIC)
288 return text.
append(
" Solution: %1").
arg(focusSolution);
291 return text.
append(
" Solution: %1, R²=%2").
arg(focusSolution).
arg(trunc(R2 * 100.0) / 100.0, 0,
'f', 2);
294 return text.
append(
" Failed");
299void LinearFocusAlgorithm::computeInitialPosition()
302 stepSize = params.initialStepSize;
304 solutionPending =
false;
306 firstPassBestValue = -1;
307 firstPassBestPosition = 0;
308 numPolySolutionsFound = 0;
309 numRestartSolutionsFound = 0;
310 secondPassStartIndex = -1;
312 qCDebug(KSTARS_EKOS_FOCUS)
313 <<
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")
314 .
arg(params.maxTravel).
arg(params.initialStepSize).
arg(params.startPosition).
arg(params.minPositionAllowed)
315 .
arg(params.maxPositionAllowed).
arg(params.maxIterations).
arg(params.focusTolerance).
arg(minPositionLimit)
316 .
arg(maxPositionLimit).
arg(params.initialOutwardSteps).
arg(params.focusAlgorithm).
arg(params.backlash)
317 .
arg(params.curveFit).
arg(params.useWeights);
319 requestedPosition = std::min(maxPositionLimit, getFirstPosition());
320 passStartPosition = requestedPosition;
321 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: initialPosition %1 sized %2")
322 .
arg(requestedPosition).
arg(params.initialStepSize);
329int LinearFocusAlgorithm::getFirstPosition()
331 if (params.focusAlgorithm != Focus::FOCUS_LINEAR1PASS)
332 return static_cast<int>(params.startPosition + params.initialOutwardSteps * params.initialStepSize);
335 double outSteps, numFullStepsOut, numHalfStepsOut;
337 switch (params.focusWalk)
339 case Focus::FOCUS_WALK_CLASSIC:
340 firstPosition =
static_cast<int>(params.startPosition + params.initialOutwardSteps * params.initialStepSize);
343 case Focus::FOCUS_WALK_FIXED_STEPS:
344 outSteps = (params.numSteps - 1) / 2.0f;
345 firstPosition = params.startPosition + (outSteps * params.initialStepSize);
348 case Focus::FOCUS_WALK_CFZ_SHUFFLE:
349 if (params.numSteps % 2)
352 numHalfStepsOut = 1.0f;
353 numFullStepsOut = ((params.numSteps - 1) / 2.0f) - numHalfStepsOut;
358 numHalfStepsOut = 1.5f;
359 numFullStepsOut = (params.numSteps / 2.0f) - 2.0f;
361 firstPosition = params.startPosition + (numFullStepsOut * params.initialStepSize) + (numHalfStepsOut *
362 (params.initialStepSize / 2.0f));
367 firstPosition =
static_cast<int>(params.startPosition + params.initialOutwardSteps * params.initialStepSize);
371 return firstPosition;
394double LinearFocusAlgorithm::relativeHFR(
double origHFR,
const QList<Edge*> *stars)
396 constexpr double minHFR = 0.3;
397 if (origHFR < minHFR)
return origHFR;
399 const int currentIndex = values.size();
400 const bool isFirstSample = (currentIndex == 0);
401 double relativeHFR = origHFR;
403 if (isFirstSample && stars !=
nullptr)
404 relativeHFREnabled =
true;
406 if (relativeHFREnabled && stars ==
nullptr)
409 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: Error: Inconsistent relativeHFR, disabling.");
410 relativeHFREnabled =
false;
413 if (!relativeHFREnabled)
416 if (starLists.size() != positions.size())
419 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: Error: Inconsistent relativeHFR data structures, disabling.");
420 relativeHFREnabled =
false;
427 constexpr int starDistanceThreshold = 5;
428 FocusStars fs(*stars, starDistanceThreshold);
429 starLists.push_back(fs);
430 auto ¤tStars = starLists.back();
434 relativeHFR = currentStars.getHFR();
435 if (relativeHFR <= 0)
438 relativeHFREnabled =
false;
442 bestRelativeHFR = relativeHFR;
443 bestRelativeHFRIndex = currentIndex;
448 auto &bestStars = starLists[bestRelativeHFRIndex];
449 double hfr = currentStars.relativeHFR(bestStars, bestRelativeHFR);
456 relativeHFR = currentStars.getHFR();
457 if (relativeHFR <= 0)
463 if (inFirstPass && relativeHFR <= bestRelativeHFR)
465 bestRelativeHFR = relativeHFR;
466 bestRelativeHFRIndex = currentIndex;
470 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"RelativeHFR: orig %1 computed %2 relative %3")
471 .
arg(origHFR).
arg(currentStars.getHFR()).
arg(relativeHFR);
476int LinearFocusAlgorithm::newMeasurement(
int position,
double value,
const double starWeight,
const QList<Edge*> *stars)
478 if (params.focusAlgorithm == Focus::FOCUS_LINEAR1PASS && (params.focusWalk == Focus::FOCUS_WALK_FIXED_STEPS
479 || params.focusWalk == Focus::FOCUS_WALK_CFZ_SHUFFLE))
480 return linearWalk(position, value, starWeight);
482 double minPos = -1.0, minVal = 0;
483 bool foundFit =
false;
485 double origValue = value;
489 if (params.curveFit == CurveFitting::FOCUS_QUADRATIC)
490 value = relativeHFR(value, stars);
494 if (params.focusAlgorithm == Focus::FOCUS_LINEAR1PASS && !inFirstPass)
496 return setupSolution(position, value, starWeight);
499 int thisStepSize = stepSize;
502 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: step %1, newMeasurement(%2, %3 -> %4, %5)")
504 .
arg(stars ==
nullptr ? 0 : stars->
size());
506 qCDebug(KSTARS_EKOS_FOCUS)
507 <<
QString(
"Linear: step %1, newMeasurement(%2, %3 -> %4, %5) 1stBest %6 %7").
arg(numSteps)
508 .
arg(position).
arg(origValue).
arg(value).
arg(stars ==
nullptr ? 0 : stars->
size())
509 .
arg(firstPassBestPosition).
arg(firstPassBestValue, 0,
'f', 3);
511 const int LINEAR_POSITION_TOLERANCE = params.initialStepSize;
512 if (abs(position - requestedPosition) > LINEAR_POSITION_TOLERANCE)
514 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: error didn't get the requested position");
515 return requestedPosition;
518 if (focusSolution != -1)
520 doneString =
i18n(
"Called newMeasurement after a solution was found.");
521 failCode = Ekos::FOCUS_FAIL_INTERNAL;
522 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: error %1").
arg(doneString);
529 values.push_back(value);
530 weights.push_back(starWeight);
533 pass1Positions.push_back(position);
534 pass1Values.push_back(value);
535 pass1Weights.push_back(starWeight);
536 pass1Outliers.push_back(
false);
543 if (solutionPending ||
544 (!inFirstPass && (value < firstPassBestValue * (1.0 + params.focusTolerance))))
546 if (setupPendingSolution(position))
548 return completeIteration(retryNumber > 0 ? 0 : stepSize,
false, -1.0f, -1.0f);
551 return setupSolution(position, value, starWeight);
556 constexpr int kNumPolySolutionsRequired = 2;
557 constexpr int kNumRestartSolutionsRequired = 3;
560 if (values.size() >= getCurveMinPoints())
562 if (params.curveFit == CurveFitting::FOCUS_QUADRATIC)
564 PolynomialFit fit(2, positions, values);
565 foundFit = fit.findMinimum(position, 0, params.maxPositionAllowed, &minPos, &minVal);
570 if (values.size() > getCurveMinPoints())
572 PolynomialFit fit2(2, positions.mid(1), values.mid(1));
573 foundFit = fit2.findMinimum(position, 0, params.maxPositionAllowed, &minPos, &minVal);
580 auto goal = getGoal(numSteps);
581 params.curveFitting->fitCurve(goal, positions, values, weights, pass1Outliers, params.curveFit, params.useWeights,
582 params.optimisationDirection);
584 foundFit = params.curveFitting->findMinMax(position, 0, params.maxPositionAllowed, &minPos, &minVal,
585 static_cast<CurveFitting::CurveFit
>(params.curveFit),
586 params.optimisationDirection);
591 const int distanceToMin =
static_cast<int>(position - minPos);
592 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: fit(%1): %2 = %3 @ %4 distToMin %5")
593 .
arg(positions.size()).
arg(minPos).
arg(minVal).
arg(position).
arg(distanceToMin);
594 if (distanceToMin >= 0)
597 numPolySolutionsFound = 0;
598 numRestartSolutionsFound = 0;
599 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: Solutions reset %1 = %2").
arg(minPos).
arg(minVal);
617 else if (!bestSamplesHeuristic())
621 if (minPos > passStartPosition)
623 numRestartSolutionsFound++;
624 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: RESTART Solution #%1 %2 = %3 @ %4")
625 .
arg(numRestartSolutionsFound).
arg(minPos).
arg(minVal).
arg(position);
629 numPolySolutionsFound++;
630 numRestartSolutionsFound = 0;
631 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: Solution #%1: %2 = %3 @ %4")
632 .
arg(numPolySolutionsFound).
arg(minPos).
arg(minVal).
arg(position);
642 if (params.focusAlgorithm == Focus::FOCUS_LINEAR)
643 howFarToGo = kNumPolySolutionsRequired;
645 howFarToGo = std::max(1,
static_cast<int> (params.initialOutwardSteps) - 1);
647 if (numPolySolutionsFound >= howFarToGo)
652 double minMeasurement = *std::min_element(values.begin(), values.end());
653 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: 1stPass solution @ %1: pos %2 val %3, min measurement %4")
654 .
arg(position).
arg(minPos).
arg(minVal).
arg(minMeasurement);
657 if (params.focusAlgorithm == Focus::FOCUS_LINEAR)
658 return setupSecondPass(
static_cast<int>(minPos), minMeasurement, 2.0);
664 return finishFirstPass(
static_cast<int>(round(minPos)), minMeasurement);
666 else if (numRestartSolutionsFound >= kNumRestartSolutionsRequired)
672 const double highestStartPosition = params.startPosition + params.initialOutwardSteps * params.initialStepSize;
673 params.startPosition = std::min(minPos, highestStartPosition);
674 computeInitialPosition();
675 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: Restart. Current pos %1, min pos %2, min val %3, re-starting at %4")
676 .
arg(position).
arg(minPos).
arg(minVal).
arg(requestedPosition);
677 return requestedPosition;
687 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: ******** No poly min: Poly must be inverted");
696 else if (gettingWorse())
700 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: getting worse, re-running 2nd pass");
701 return setupSecondPass(firstPassBestPosition, firstPassBestValue);
704 return completeIteration(thisStepSize, foundFit, minPos, minVal);
709CurveFitting::FittingGoal LinearFocusAlgorithm::getGoal(
int numSteps)
711 if (params.focusWalk == Focus::FOCUS_WALK_CLASSIC)
713 if (numSteps > 2.0 * params.initialOutwardSteps)
714 return CurveFitting::FittingGoal::BEST;
716 return CurveFitting::FittingGoal::STANDARD;
720 if (numSteps >= params.numSteps)
721 return CurveFitting::FittingGoal::BEST;
723 return CurveFitting::FittingGoal::STANDARD;
729int LinearFocusAlgorithm::getCurveMinPoints()
731 if (params.focusAlgorithm != Focus::FOCUS_LINEAR1PASS)
734 if (params.focusWalk == Focus::FOCUS_WALK_CLASSIC)
735 return params.initialOutwardSteps + 2;
738 if (params.donutBuster)
739 return params.numSteps;
741 return params.numSteps / 2 + 2;
745int LinearFocusAlgorithm::linearWalk(
int position,
double value,
const double starWeight)
749 return setupSolution(position, value, starWeight);
752 bool foundFit =
false;
753 double minPos = -1.0, minVal = 0;
755 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear1Pass: step %1, linearWalk %2, %3")
759 if (abs(position - requestedPosition) > params.initialStepSize)
761 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"linearWalk error: position %1, requested position %2").
arg(position)
762 .
arg(requestedPosition);
763 return requestedPosition;
768 values.push_back(value);
769 weights.push_back(starWeight);
770 pass1Positions.push_back(position);
771 pass1Values.push_back(value);
772 pass1Weights.push_back(starWeight);
773 pass1Outliers.push_back(
false);
775 if (values.size() < getCurveMinPoints())
782 if (params.curveFit == CurveFitting::FOCUS_QUADRATIC)
783 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"linearWalk called incorrectly for FOCUS_QUADRATIC");
787 auto goal = getGoal(numSteps);
789 if (params.donutBuster)
791 params.curveFitting->fitCurve(goal, positions, values, weights, pass1Outliers, params.curveFit, params.useWeights,
792 params.optimisationDirection);
794 foundFit = params.curveFitting->findMinMax(position, 0, params.maxPositionAllowed, &minPos, &minVal,
795 static_cast<CurveFitting::CurveFit
>(params.curveFit), params.optimisationDirection);
797 if (numSteps >= params.numSteps)
801 return finishFirstPass(
static_cast<int>(round(minPos)), minVal);
805 doneString =
i18n(
"Failed to fit curve to data.");
806 failCode = Ekos::FOCUS_FAIL_CURVEFIT;
807 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"LinearWalk: %1").
arg(doneString);
815 int nextStepSize = getNextStepSize();
816 return completeIteration(nextStepSize, foundFit, minPos, minVal);
826void LinearFocusAlgorithm::removeDonuts()
829 if (values.size() <= 7)
835 int centre = values.
size() / 2;
836 int minElement = centre;
837 double minValue = values[centre];
838 for (
int i = 1; i < 4; i++)
840 if (values[centre + i] < minValue)
842 minValue = values[centre + i];
843 minElement = centre + i;
845 if (values[centre - i] < minValue)
847 minValue = values[centre - i];
848 minElement = centre - i;
853 double threshold = values[minElement];
854 double nextValue, deltaValue;
855 double tolerance = 0.5;
856 for (
int i = minElement; i > 0; i--)
858 nextValue = values[i - 1];
859 if (nextValue < threshold)
861 queryOutliers[i - 1] =
true;
864 deltaValue = (nextValue - values[i]) * tolerance;
865 threshold = nextValue + deltaValue;
870 for (
int i = 0; i < minElement - 3; i++)
872 if (queryOutliers[i])
873 pass1Outliers[i] =
true;
880 threshold = values[minElement];
881 for (
int i = minElement; i < values.size() - 1; i++)
883 nextValue = values[i + 1];
884 if (nextValue < threshold)
886 queryOutliers[i + 1] =
true;
889 deltaValue = (nextValue - values[i]) * tolerance;
890 threshold = nextValue + deltaValue;
895 for (
int i = values.size() - 1; i > minElement + 3; i--)
897 if (queryOutliers[i])
898 pass1Outliers[i] =
true;
905 for (
int i = pass1Outliers.size() - 1; i >= 0; i--)
906 str.
append((pass1Outliers[i]) ?
'Y' :
'N');
907 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Donut analysis: %1").
arg(str);
912int LinearFocusAlgorithm::getNextStepSize()
914 int nextStepSize, lower, upper;
916 switch (params.focusWalk)
918 case Focus::FOCUS_WALK_CLASSIC:
919 nextStepSize = stepSize;
921 case Focus::FOCUS_WALK_FIXED_STEPS:
922 nextStepSize = stepSize;
924 case Focus::FOCUS_WALK_CFZ_SHUFFLE:
925 if (params.numSteps % 2)
928 lower = (params.numSteps - 3) / 2;
929 upper = params.numSteps - lower;
934 lower = (params.numSteps - 4) / 2;
935 upper = (params.numSteps - lower);
938 if (numSteps <= lower)
939 nextStepSize = stepSize;
940 else if (numSteps >= upper)
941 nextStepSize = stepSize;
943 nextStepSize = stepSize / 2;
946 nextStepSize = stepSize;
953int LinearFocusAlgorithm::setupSolution(
int position,
double value,
double weight)
955 focusSolution = position;
957 focusWeight = weight;
959 doneString =
i18n(
"Solution found.");
960 failCode = Ekos::FOCUS_FAIL_NONE;
961 if (params.focusAlgorithm == Focus::FOCUS_LINEAR)
962 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: solution @ %1 = %2 (best %3)")
963 .
arg(position).
arg(value).
arg(firstPassBestValue);
966 double delta = fabs(value - firstPassBestValue);
967 QString str(
"Linear: solution @ ");
968 str.
append(
QString(
"%1 = %2 (expected %3) delta=%4").arg(position).arg(value).arg(firstPassBestValue).arg(delta));
970 if (params.useWeights && weight > 0.0)
973 double numSigmas = delta * weight;
975 .arg(numSigmas).arg(value <= firstPassBestValue ?
"better" :
"worse"));
976 if (value <= firstPassBestValue || numSigmas < 1)
978 else if (numSigmas < 3)
981 str.
append(
QString(
". POOR result. If this happens repeatedly it may be a sign of poor backlash compensation."));
984 qCInfo(KSTARS_EKOS_FOCUS) << str;
990int LinearFocusAlgorithm::completeIteration(
int step,
bool foundFit,
double minPos,
double minVal)
992 if (params.focusAlgorithm == Focus::FOCUS_LINEAR && numSteps == params.maxIterations - 2)
997 const int minIndex =
static_cast<int>(std::min_element(values.begin(), values.end()) - values.begin());
998 return setupSecondPass(positions[minIndex], values[minIndex], 0.5);
1000 else if (numSteps > params.maxIterations)
1004 doneString =
i18n(
"Too many steps.");
1005 failCode = Ekos::FOCUS_FAIL_MAX_ITERS;
1006 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: error %1").
arg(doneString);
1012 requestedPosition = requestedPosition - step;
1015 if (requestedPosition < minPositionLimit)
1018 if (params.focusAlgorithm == Focus::FOCUS_LINEAR)
1020 const int minIndex =
static_cast<int>(std::min_element(values.begin(), values.end()) - values.begin());
1021 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: reached end without Vmin. Restarting %1 pos %2 value %3")
1022 .
arg(minIndex).
arg(positions[minIndex]).
arg(values[minIndex]);
1023 return setupSecondPass(positions[minIndex], values[minIndex]);
1027 if (foundFit && minPos >= minPositionLimit)
1029 return finishFirstPass(
static_cast<int>(round(minPos)), minVal);
1034 doneString =
i18n(
"Solution lies outside max travel.");
1035 failCode = Ekos::FOCUS_FAIL_FOCUSER_OOB;
1036 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: error %1").
arg(doneString);
1042 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: requesting position %1").
arg(requestedPosition);
1043 return requestedPosition;
1055bool LinearFocusAlgorithm::setupPendingSolution(
int position)
1057 const int length = values.
size();
1058 const int secondPassIndex = length - secondPassStartIndex;
1059 const double thisValue = values[length - 1];
1063 double referenceValue = (secondPassIndex <= 1) ? 1e6 : values[length - 2];
1064 if (retryNumber > 0 && length - (2 + retryNumber) >= 0)
1065 referenceValue = values[length - (2 + retryNumber)];
1068 const bool notGettingWorse = (secondPassIndex <= 1) || (thisValue <= referenceValue);
1071 const bool couldGoFather = (numSteps < params.maxIterations - 2) && (position - stepSize > minPositionLimit);
1074 int maxNumRetries = 3;
1075 if (position - stepSize / 2 < firstPassBestPosition)
1077 stepSize = std::max(2, params.initialStepSize / 4);
1081 if (notGettingWorse && couldGoFather)
1083 qCDebug(KSTARS_EKOS_FOCUS)
1084 <<
QString(
"Linear: %1: Position(%2) & HFR(%3) -- Pass1: %4 %5, solution pending, searching further")
1085 .
arg(length).
arg(position).
arg(thisValue, 0,
'f', 3).
arg(firstPassBestPosition).
arg(firstPassBestValue, 0,
'f', 3);
1086 solutionPending =
true;
1087 solutionPendingPosition = position;
1088 solutionPendingValue = thisValue;
1092 else if (solutionPending && couldGoFather && retryNumber < maxNumRetries &&
1093 (secondPassIndex > 1) && (thisValue >= referenceValue))
1095 qCDebug(KSTARS_EKOS_FOCUS)
1096 <<
QString(
"Linear: %1: Position(%2) & HFR(%3) -- Pass1: %4 %5, solution pending, got worse, retrying")
1097 .
arg(length).
arg(position).
arg(thisValue, 0,
'f', 3).
arg(firstPassBestPosition).
arg(firstPassBestValue, 0,
'f', 3);
1102 qCDebug(KSTARS_EKOS_FOCUS)
1103 <<
QString(
"Linear: %1: Position(%2) & HFR(%3) -- Pass1: %4 %5, finishing, can't go further")
1104 .
arg(length).
arg(position).
arg(thisValue, 0,
'f', 3).
arg(firstPassBestPosition).
arg(firstPassBestValue, 0,
'f', 3);
1109void LinearFocusAlgorithm::debugLog()
1111 QString str(
"Linear: points=[");
1112 for (
int i = 0; i < positions.size(); ++i)
1114 str.
append(
QString(
"(%1, %2, %3)").arg(positions[i]).arg(values[i]).arg(weights[i]));
1115 if (i < positions.size() - 1)
1119 str.
append(
QString(
";duration=%1").arg(stopWatch.elapsed() / 1000));
1123 str.
append(
QString(
";temperature=%1").arg(params.temperature));
1124 str.
append(
QString(
";focusalgorithm=%1").arg(params.focusAlgorithm));
1127 str.
append(
QString(
";useweights=%1").arg(params.useWeights));
1129 qCDebug(KSTARS_EKOS_FOCUS) << str;
1133int LinearFocusAlgorithm::setupSecondPass(
int position,
double value,
double margin)
1135 firstPassBestPosition = position;
1136 firstPassBestValue = value;
1137 inFirstPass =
false;
1138 solutionPending =
false;
1139 secondPassStartIndex = values.size();
1143 requestedPosition = std::min(
static_cast<int>(firstPassBestPosition + stepSize * margin), maxPositionLimit);
1144 stepSize = params.initialStepSize / 2;
1145 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Linear: 2ndPass starting at %1 step %2").
arg(requestedPosition).
arg(stepSize);
1146 return requestedPosition;
1150int LinearFocusAlgorithm::finishFirstPass(
int position,
double value)
1152 firstPassBestPosition = position;
1153 firstPassBestValue = value;
1154 inFirstPass =
false;
1155 requestedPosition = position;
1157 if (params.refineCurveFit)
1161 std::vector<std::pair<int, double>> curveDeltas;
1163 params.curveFitting->calculateCurveDeltas(params.curveFit, curveDeltas);
1166 if (curveDeltas.size() > 6)
1169 std::vector<double> curveDeltasX2Vec;
1170 for (ulong i = 0; i < curveDeltas.size(); i++)
1171 curveDeltasX2Vec.push_back(pow(curveDeltas[i].second, 2.0));
1173 auto curveDeltasX2Mean = Mathematics::RobustStatistics::ComputeLocation(Mathematics::RobustStatistics::LOCATION_MEAN,
1178 double outlierRejection = params.donutBuster ? params.outlierRejection : 0.2;
1179 int maxOutliers = curveDeltas.size() * outlierRejection;
1181 double modelUnknowns = params.curveFit == CurveFitting::FOCUS_PARABOLA ? 3.0 : 4.0;
1185 double pc = peirce_criterion(
static_cast<double>(curveDeltas.size()),
static_cast<double>(maxOutliers), modelUnknowns);
1186 double pc_threshold = sqrt(pc * curveDeltasX2Mean);
1189 std::sort(curveDeltas.begin(), curveDeltas.end(),
1190 [](
const std::pair<int, double> &a,
const std::pair<int, double> &b)
1192 return ( a.second > b.second );
1196 auto bestPass1Outliers = pass1Outliers;
1200 for (ulong i = 0; i < curveDeltas.size(); i++)
1202 if(curveDeltas[i].second <= pc_threshold)
1206 pass1Outliers[curveDeltas[i].first] =
true;
1209 if (outliers >= maxOutliers)
1217 if (outliers > 0 && params.curveFitting->getCurveParams(params.curveFit, currentCoefficients))
1219 double currentR2 = params.curveFitting->calculateR2(
static_cast<CurveFitting::CurveFit
>(params.curveFit));
1222 params.curveFitting->fitCurve(CurveFitting::FittingGoal::BEST, pass1Positions, pass1Values, pass1Weights,
1223 pass1Outliers, params.curveFit, params.useWeights, params.optimisationDirection);
1224 double minPos, minVal;
1225 bool foundFit = params.curveFitting->findMinMax(position, 0, params.maxPositionAllowed, &minPos, &minVal,
1226 static_cast<CurveFitting::CurveFit
>(params.curveFit),
1227 params.optimisationDirection);
1230 double newR2 = params.curveFitting->calculateR2(
static_cast<CurveFitting::CurveFit
>(params.curveFit));
1231 if (newR2 > currentR2)
1234 requestedPosition = minPos;
1235 qCDebug(KSTARS_EKOS_FOCUS) <<
QString(
"Refine Curve Fit improved focus from %1 to %2. R2 improved from %3 to %4")
1241 qCDebug(KSTARS_EKOS_FOCUS) <<
1242 QString(
"Refine Curve Fit could not improve on the original solution");
1243 pass1Outliers = bestPass1Outliers;
1244 params.curveFitting->setCurveParams(params.curveFit, currentCoefficients);
1249 qCDebug(KSTARS_EKOS_FOCUS) <<
1250 QString(
"Refine Curve Fit failed to fit curve whilst refining curve fit. Running with original solution");
1251 pass1Outliers = bestPass1Outliers;
1252 params.curveFitting->setCurveParams(params.curveFit, currentCoefficients);
1257 return requestedPosition;
1270double LinearFocusAlgorithm::peirce_criterion(
double N,
double n,
double m)
1276 double Q = (pow(n, (n / N)) * pow((N - n), ((N - n) / N))) / N;
1283 while (abs(r_new - r_old) > (N * 2.0e-16))
1287 double ldiv = pow(r_new, n);
1291 double Lamda = pow((pow(Q, N) / (ldiv)), (1.0 / (N - n)));
1294 x2 = 1.0 + (N - m - n) / n * (1.0 - pow(Lamda, 2.0));
1306 r_new = exp((x2 - 1) / 2.0) * gsl_sf_erfc(sqrt(x2) / sqrt(2.0));
1315bool LinearFocusAlgorithm::bestSamplesHeuristic()
1317 const int length = values.size();
1318 if (length < 5)
return true;
1320 std::nth_element(tempValues.
begin(), tempValues.
begin() + 2, tempValues.
end());
1321 double secondBest = tempValues[1];
1322 if ((values[length - 1] <= secondBest) || (values[length - 2] <= secondBest))
1328bool LinearFocusAlgorithm::gettingWorse()
1331 constexpr int streak = 3;
1332 const int length = values.size();
1333 if (secondPassStartIndex < 0)
1335 if (length < streak + 1)
1338 if (length - secondPassStartIndex < streak + 1)
1340 for (
int i = length - 1; i >= length - streak; --i)
1341 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