Warning, file /education/kstars/kstars/ekos/focus/focusstars.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 #include "focusstars.h"
0002 #include "ekos/guide/internalguide/starcorrespondence.h"
0003 #include <ekos_focus_debug.h>
0004 
0005 namespace Ekos
0006 {
0007 
0008 namespace
0009 {
0010 
0011 using Ekos::FocusStars;
0012 
0013 // Debug code to print out how well the star correspondence matched.
0014 // Compares the star correspondence offsets from the detected "guide star position"
0015 // with the actual detected stars. Note, those offsets were just taken from the
0016 // first star set's detected stars.
0017 // Move to star correspondence?
0018 double correspondenceRmsError(const StarCorrespondence &corr, const QList<Edge> &stars2,
0019                               const QVector<int> &starMap, GuiderUtils::Vector gsPosition)
0020 {
0021     const int s2Size = starMap.size();
0022     double sumSq2 = 0;
0023     int num = 0;
0024     for (int s2 = 0; s2 < s2Size; s2++)
0025     {
0026         const int s1 = starMap[s2];
0027         if (s1 >= 0)
0028         {
0029             // Compute the RMS association distance.
0030             double x2 = stars2[s2].x;
0031             double y2 = stars2[s2].y;
0032 
0033             QVector2D offset = corr.offset(s1);
0034             double x1 = gsPosition.x + offset.x();
0035             double y1 = gsPosition.y + offset.y();
0036             double dSq = (x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2);
0037             sumSq2 += dSq;
0038             num++;
0039         }
0040     }
0041     return sqrt(sumSq2 / num);
0042 }
0043 
0044 // Use StarCorrespondence to find a common set of stars between stars1 and stars2.
0045 // Use mainStar1 as the "guideStar" or really the central star. We look for the pattern
0046 // of stars around that star. It is an index into stars1. If this star is missing from stars2,
0047 // StarCorrespondence could recover, but ideally it exists in both.
0048 // Returns two FocusStars objects containing the sets of corresponding stars.
0049 int matchStars(const QList<Edge> &stars1, const QList<Edge> &stars2, int mainStar1,
0050                double maxDistance, FocusStars *stars1Filtered, FocusStars *stars2Filtered)
0051 {
0052     // minFraction is the fraction of the stars passed into StarCorrespondence (i.e. in stars1)
0053     // that must be found in stars2. Ideally, star1 would be a smaller list, but instead we simply
0054     // adjust minFraction.
0055     // Goal is to get 45% of the stars in the smaller star set (or more).
0056     int minStars = std::min(stars1.size(), stars2.size());
0057     int desiredStars = 0.45 * minStars;
0058     const double minFraction = static_cast<double>(desiredStars) / stars1.size();
0059 
0060     // Do the star correspondence
0061     QVector<int> starMap;
0062     StarCorrespondence corr(stars1, mainStar1);
0063     corr.setAllowMissingGuideStar(true);
0064     Edge gStar = corr.find(stars2, maxDistance, &starMap, false, minFraction);
0065     GuiderUtils::Vector gsPosition(gStar.x, gStar.y, 0);
0066 
0067     // Grab the two sets of stars.
0068     const int s2Size = starMap.size();
0069     int numAssociated = 0;
0070     QList<Edge> edges1, edges2;
0071     for (int s2 = 0; s2 < s2Size; s2++)
0072     {
0073         const int s1 = starMap[s2];
0074         if (s1 >= 0)
0075         {
0076             numAssociated++;
0077             edges1.append(stars1[s1]);
0078             edges2.append(stars2[s2]);
0079         }
0080     }
0081     *stars1Filtered = FocusStars(edges1);
0082     *stars2Filtered = FocusStars(edges2);
0083 
0084     // debug
0085     double corrError = correspondenceRmsError(corr, stars2, starMap, gsPosition);
0086     QString debugStr = QString("matchStars: Inputs sized %1 %2 found %3 matches, RMS dist %4")
0087                        .arg(stars1.size()).arg(stars2.size()).arg(numAssociated).arg(corrError, 0, 'f', 1);
0088     qCDebug(KSTARS_EKOS_FOCUS) << debugStr;
0089 
0090     return numAssociated;
0091 }
0092 
0093 bool filterFocusStars(const FocusStars &stars1, const FocusStars &stars2, double maxDistance, FocusStars *filtered1,
0094                       FocusStars *filtered2)
0095 {
0096     int size1 = stars1.getStars().size();
0097     int size2 = stars2.getStars().size();
0098 
0099     // This section limits the process to the first 100 stars, for computational reasons,
0100     // just in case there is an input with a large number of stars.
0101     constexpr int maxNumStars = 100;
0102     QList<Edge> s1;
0103     const QList<Edge> *s1ptr = &(stars1.getStars());
0104     if (size1 > maxNumStars)
0105     {
0106         for (int i = 0; i < maxNumStars; i++)
0107             s1.append(stars1.getStars()[i]);
0108         s1ptr = &s1;
0109         size1 = maxNumStars;
0110     }
0111     QList<Edge> s2;
0112     const QList<Edge> *s2ptr = &(stars2.getStars());
0113     if (size2 > maxNumStars)
0114     {
0115         for (int i = 0; i < maxNumStars; i++)
0116             s2.append(stars2.getStars()[i]);
0117         s2ptr = &s2;
0118         size2 = maxNumStars;
0119     }
0120 
0121     // If we have at least 10 matching stars, or 45% of the smallest star set, that's fine.
0122     const int sufficientMatches = std::min(10, static_cast<int>(0.45 * std::min(size1, size2)));
0123 
0124     // Pick upto 5 different seed stars, if we need them.
0125     // Those are the stars from stars1 that are used in the StarCorrespondence algorithm.
0126     int maxMatches = 0;
0127     srand(time(0));
0128     FocusStars f1, f2;
0129     for (int i = 0; i < 5; ++i)
0130     {
0131         const int seed = rand() % size1;
0132         int numMatches = matchStars(*s1ptr, *s2ptr, seed, maxDistance, &f1, &f2);
0133         if (numMatches > maxMatches)
0134         {
0135             *filtered1 = f1;
0136             *filtered2 = f2;
0137             maxMatches = numMatches;
0138             if (numMatches >= sufficientMatches) break;
0139         }
0140     }
0141     return (maxMatches > 1);
0142 }
0143 
0144 }  // namespace
0145 
0146 
0147 FocusStars::FocusStars(const QList<Edge *> edges, double maxDist)
0148     : maxDistance(maxDist), initialized(true)
0149 {
0150     // Copy the memory--can't depend on it sticking around.
0151     for (const auto &edge : edges)
0152         stars.append(*edge);
0153 }
0154 
0155 FocusStars::FocusStars(const QList<Edge> edges, double maxDist)
0156     : maxDistance(maxDist), initialized(true)
0157 {
0158     // Copy the memory--can't depend on it sticking around.
0159     for (const auto &edge : edges)
0160         stars.append(edge);
0161 }
0162 
0163 // Fitsdata does a bit of processing, removing saturated stars,
0164 // removing extremes, but all this is now being done in stellarsolver.
0165 // Just computing the median HFR.
0166 double FocusStars::getHFR() const
0167 {
0168     if (!initialized)
0169         return -1;
0170 
0171     if (computedHFR >= 0)
0172         return computedHFR;
0173     if (stars.size() == 0) return -1;
0174     std::vector<double> values;
0175     for (auto &star : stars)
0176         values.push_back(star.HFR);
0177     const int middle = values.size() / 2;
0178     std::nth_element(values.begin(), values.begin() + middle, values.end());
0179     computedHFR = values[middle];
0180     return computedHFR;
0181 }
0182 
0183 bool FocusStars::commonHFR(const FocusStars &referenceStars, double *thisHFR, double *referenceHFR) const
0184 {
0185     if (!initialized)
0186         return -1;
0187 
0188     FocusStars commonStars, commonReferenceStars;
0189     bool ok = filterFocusStars(*this, referenceStars, maxDistance, &commonStars, &commonReferenceStars);
0190     if (!ok) return false;
0191     *thisHFR  = commonStars.getHFR();
0192     *referenceHFR = commonReferenceStars.getHFR();
0193 
0194     if (*thisHFR < 0 || *referenceHFR < 0 || commonStars.getStars().size() == 0 ||
0195             commonReferenceStars.getStars().size() == 0)
0196         return false;
0197 
0198     return true;
0199 }
0200 
0201 void FocusStars::printStars(const QString &label) const
0202 {
0203     if (label.size() > 0) fprintf(stderr, "%s\n{", label.toLatin1().data());
0204     bool first = true;
0205     for (const auto &star : getStars())
0206     {
0207         fprintf(stderr, "%s{%6.1f,%6.1f, %5.2f}", first ? "" : ", ", star.x, star.y, star.HFR);
0208         first = false;
0209     }
0210     fprintf(stderr, "}\n");
0211 }
0212 
0213 double FocusStars::relativeHFR(const FocusStars &referenceStars, double referenceHFR) const
0214 {
0215     if (!initialized)
0216         return -1;
0217 
0218     double hfrCommon, refHfrCommon;
0219     if (!commonHFR(referenceStars, &hfrCommon, &refHfrCommon))
0220     {
0221         qCDebug(KSTARS_EKOS_FOCUS) << "Couldn't get a relative HFR, punted!";
0222         // Scale the reference hfr by the HFR ratio when we can't find common stars.
0223         return  (getHFR() / referenceStars.getHFR()) * referenceHFR;
0224     }
0225 
0226     if (hfrCommon < 0 || refHfrCommon < 0) return -1;
0227 
0228     QString debugStr = QString("RelativeHFR: sizes: %7 %8 hfr: (%1 (%2) / %3 (%4)) * %5 = %6")
0229                        .arg(hfrCommon, 0, 'f', 2).arg(getHFR(), 0, 'f', 2).arg(refHfrCommon, 0, 'f', 2)
0230                        .arg(referenceStars.getHFR(), 0, 'f', 2).arg(referenceHFR, 0, 'f', 2)
0231                        .arg((hfrCommon / refHfrCommon) * referenceHFR, 0, 'f', 2)
0232                        .arg(getStars().size()).arg(referenceStars.getStars().size());
0233     qCDebug(KSTARS_EKOS_FOCUS) << debugStr;
0234 
0235     return (hfrCommon / refHfrCommon) * referenceHFR;
0236 }
0237 
0238 }  // namespace