Kstars

guidestars.cpp
1/*
2 SPDX-FileCopyrightText: 2020 Hy Murveit <hy@murveit.com>
3
4 SPDX-License-Identifier: GPL-2.0-or-later
5*/
6
7#include "guidestars.h"
8
9#include "ekos_guide_debug.h"
10#include "../guideview.h"
11#include "fitsviewer/fitsdata.h"
12#include "fitsviewer/fitssepdetector.h"
13#include "Options.h"
14
15#include <math.h>
16#include <stellarsolver.h>
17#include "ekos/auxiliary/stellarsolverprofileeditor.h"
18#include <QTime>
19
20#define DLOG if (false) qCDebug
21
22// Then when looking for the guide star, gets this many candidates.
23#define STARS_TO_SEARCH 250
24
25// Don't use stars with SNR lower than this when computing multi-star drift.
26#define MIN_DRIFT_SNR 8
27
28// If there are fewer than this many stars, don't use this multi-star algorithm.
29// It will instead back-off to a reticle-based algorithm.
30#define MIN_STAR_CORRESPONDENCE_SIZE 5
31
32// We limit the HFR for guide stars. When searching for the guide star, we relax this by the
33// margin below (e.g. if a guide star was selected that was near the max guide-star hfr, the later
34// the hfr increased a little, we still want to be able to find it.
35constexpr double HFR_MARGIN = 2.0;
36/*
37 Start with a set of reference (x,y) positions from stars, where one is designated a guide star.
38 Given these and a set of new input stars, determine a mapping of new stars to the references.
39 As the star positions should mostly change via translation, the correspondences are determined by
40 similar geometry. It is possible, though that there is noise in positions, and that
41 some reference stars may not be in the new star group, or that the new stars may contain some
42 stars not appearing in references. However, the guide star must appear in both for this to
43 be successful.
44 */
45
46namespace
47{
48
49QString logHeader(const QString &label)
50{
51 return QString("%1 %2 %3 %4 %5 %6 %7")
52 .arg(label, -9).arg("#", 3).arg("x ", 6).arg("y ", 6).arg("flux", 6)
53 .arg("HFR", 6).arg("SNR ", 5);
54}
55
56QString logStar(const QString &label, int index, const SkyBackground &bg, const Edge &star)
57{
58 double snr = bg.SNR(star.sum, star.numPixels);
59 return QString("%1 %2 %3 %4 %5 %6 %7")
60 .arg(label, -9)
61 .arg(index, 3)
62 .arg(star.x, 6, 'f', 1)
63 .arg(star.y, 6, 'f', 1)
64 .arg(star.sum, 6, 'f', 0)
65 .arg(star.HFR, 6, 'f', 2)
66 .arg(snr, 5, 'f', 1);
67}
68
69void logStars(const QString &label, const QString &label2, const SkyBackground &bg,
70 int size,
71 std::function<const Edge & (int index)> stars,
72 const QString &extraLabel,
73 std::function<QString (int index)> extras)
74{
75 QString header = logHeader(label);
76 if (extraLabel.size() > 0)
77 header.append(QString(" %1").arg(extraLabel));
78 qCDebug(KSTARS_EKOS_GUIDE) << header;
79 for (int i = 0; i < size; ++i)
80 {
81 const auto &star = stars(i);
82 QString line = logStar(label2, i, bg, star);
83 if (extraLabel.size() > 0)
84 line.append(QString(" %1").arg(extras(i)));
85 qCDebug(KSTARS_EKOS_GUIDE) << line;
86 }
87}
88} //namespace
89
90GuideStars::GuideStars()
91{
92}
93
94// It's possible that we don't map all the stars, if there are too many.
95int GuideStars::getStarMap(int index)
96{
97 if (index >= starMap.size() || (index < 0))
98 return -1;
99 return starMap[index];
100}
101
102void GuideStars::setupStarCorrespondence(const QList<Edge> &neighbors, int guideIndex)
103{
104 qCDebug(KSTARS_EKOS_GUIDE) << "setupStarCorrespondence: neighbors " << neighbors.size() << "guide index" << guideIndex;
105 if (neighbors.size() >= MIN_STAR_CORRESPONDENCE_SIZE)
106 {
107 starMap.clear();
108 for (int i = 0; i < neighbors.size(); ++i)
109 {
110 qCDebug(KSTARS_EKOS_GUIDE) << " adding ref: " << neighbors[i].x << neighbors[i].y;
111 // We need to initialize starMap, because findGuideStar(), which normally
112 // initializes starMap(), might call selectGuideStar() if it finds that
113 // the starCorrespondence is empty.
114 starMap.push_back(i);
115 }
116 starCorrespondence.initialize(neighbors, guideIndex);
117 }
118 else
119 starCorrespondence.reset();
120}
121
122// Calls SEP to generate a set of star detections and score them,
123// then calls selectGuideStars(detections, scores) to select the guide star.
124QVector3D GuideStars::selectGuideStar(const QSharedPointer<FITSData> &imageData)
125{
126 if (imageData == nullptr)
127 return QVector3D(-1, -1, -1);
128
129 QList<double> sepScores;
130 QList<double> minDistances;
131 const double maxHFR = Options::guideMaxHFR();
132 findTopStars(imageData, Options::maxMultistarReferenceStars(), &detectedStars, maxHFR,
133 nullptr, &sepScores, &minDistances);
134
135 int maxX = imageData->width();
136 int maxY = imageData->height();
137 return selectGuideStar(detectedStars, sepScores, maxX, maxY, minDistances);
138}
139
140// This selects the guide star by using the input scores, and
141// downgrading stars too close to the edges of the image (so that calibration won't fail).
142// It also removes from consideration stars with huge HFR values (most likely not stars).
143QVector3D GuideStars::selectGuideStar(const QList<Edge> &stars,
144 const QList<double> &sepScores,
145 int maxX, int maxY,
146 const QList<double> &minDistances)
147{
148 constexpr int maxStarDiameter = 32;
149
150 if ((uint) stars.size() < Options::minDetectionsSEPMultistar())
151 {
152 qCDebug(KSTARS_EKOS_GUIDE) << "Too few stars detected: " << stars.size();
153 return QVector3D(-1, -1, -1);
154 }
155
156 int maxIndex = Options::maxMultistarReferenceStars() < static_cast<uint>(stars.count()) ?
157 Options::maxMultistarReferenceStars() :
158 stars.count();
159 QVector<int> scores(Options::maxMultistarReferenceStars());
160
161 // Find the bottom 25% HFR value.
162 QVector<float> hfrs;
163 for (int i = 0; i < maxIndex; i++)
164 hfrs.push_back(stars[i].HFR);
165 std::sort(hfrs.begin(), hfrs.end());
166 float tooLowHFR = 0.0;
167 if (maxIndex / 4 > 0)
168 tooLowHFR = hfrs[maxIndex / 4];
169
170 QList<Edge> guideStarNeighbors;
171 for (int i = 0; i < maxIndex; i++)
172 {
173 int score = 100 + sepScores[i];
174 const Edge &center = stars.at(i);
175 guideStarNeighbors.append(center);
176
177 // Severely reject stars close to edges
178 // Worry about calibration? Make calibration distance parameter?
179 constexpr int borderGuard = 35;
180 if (center.x < borderGuard || center.y < borderGuard ||
181 center.x > (maxX - borderGuard) || center.y > (maxY - borderGuard))
182 score -= 1000;
183 // Reject stars bigger than square
184 if (center.HFR > float(maxStarDiameter) / 2)
185 score -= 1000;
186
187 // Try not to use a star with an HFR in bottom 25%.
188 if (center.HFR < tooLowHFR)
189 score -= 1000;
190
191 // Add advantage to stars with SNRs between 40-100.
192 auto bg = skybackground();
193 double snr = bg.SNR(center.sum, center.numPixels);
194 if (snr >= 40 && snr <= 100)
195 score += 75;
196 if (snr >= 100)
197 score -= 50;
198
199 // discourage stars that have close neighbors.
200 // This isn't perfect, as we're not detecting all stars, just top 100 or so.
201 const double neighborDistance = minDistances[i];
202 constexpr double closeNeighbor = 25;
203 constexpr double veryCloseNeighbor = 15;
204 if (neighborDistance < veryCloseNeighbor)
205 score -= 100;
206 else if (neighborDistance < closeNeighbor)
207 score -= 50;
208
209 scores[i] = score;
210 }
211
212 logStars("Select", "Star", skyBackground,
213 maxIndex,
214 [&](int i) -> const Edge&
215 {
216 return stars[i];
217 },
218 "score", [&](int i) -> QString
219 {
220 return QString("%1").arg(scores[i], 5);
221 });
222
223 int maxScore = -1;
224 int maxScoreIndex = -1;
225 for (int i = 0; i < maxIndex; i++)
226 {
227 if (scores[i] > maxScore)
228 {
229 maxScore = scores[i];
230 maxScoreIndex = i;
231 }
232 }
233
234 if (maxScoreIndex < 0)
235 {
236 qCDebug(KSTARS_EKOS_GUIDE) << "No suitable star detected.";
237 return QVector3D(-1, -1, -1);
238 }
239 setupStarCorrespondence(guideStarNeighbors, maxScoreIndex);
240 QVector3D newStarCenter(stars[maxScoreIndex].x, stars[maxScoreIndex].y, 0);
241 qCDebug(KSTARS_EKOS_GUIDE) << "new star center: " << maxScoreIndex << " x: "
242 << stars[maxScoreIndex].x << " y: " << stars[maxScoreIndex].y;
243 return newStarCenter;
244}
245
246// Find the current target positions for the guide-star neighbors, and add them
247// to the guideView.
248void GuideStars::plotStars(QSharedPointer<GuideView> &guideView, const QRect &trackingBox)
249{
250 if (guideView == nullptr) return;
251 guideView->clearNeighbors();
252 if (starCorrespondence.size() == 0) return;
253
254 const Edge gStar = starCorrespondence.reference(starCorrespondence.guideStar());
255
256 // Find the offset between the current reticle position and the original
257 // guide star position (e.g. it might have moved due to dithering).
258 double reticle_x = trackingBox.x() + trackingBox.width() / 2.0;
259 double reticle_y = trackingBox.y() + trackingBox.height() / 2.0;
260
261 // See which neighbor stars were associated with detected stars.
262 QVector<bool> found(starCorrespondence.size(), false);
263 QVector<int> detectedIndex(starCorrespondence.size(), -1);
264
265 for (int i = 0; i < detectedStars.size(); ++i)
266 {
267 int refIndex = getStarMap(i);
268 if (refIndex >= 0)
269 {
270 found[refIndex] = true;
271 detectedIndex[refIndex] = i;
272 }
273 }
274 // Add to the guideView the neighbor stars' target positions and whether they were found.
275 for (int i = 0; i < starCorrespondence.size(); ++i)
276 {
277 bool isGuideStar = i == starCorrespondence.guideStar();
278 const QVector2D offset = starCorrespondence.offset(i);
279 const double detected_x = found[i] ? detectedStars[detectedIndex[i]].x : 0;
280 const double detected_y = found[i] ? detectedStars[detectedIndex[i]].y : 0;
281 guideView->addGuideStarNeighbor(offset.x() + reticle_x, offset.y() + reticle_y, found[i],
282 detected_x, detected_y, isGuideStar);
283 }
284 guideView->updateNeighbors();
285}
286
287// Find the guide star using the starCorrespondence algorithm (looking for
288// the other reference stars in the same relative position as when the guide star was selected).
289// If this method fails, it backs off to looking in the tracking box for the highest scoring star.
290GuiderUtils::Vector GuideStars::findGuideStar(const QSharedPointer<FITSData> &imageData, const QRect &trackingBox,
291 QSharedPointer<GuideView> &guideView, bool firstFrame)
292{
293 // We fall-back to single star guiding if multistar isn't working,
294 // but limit this for a number of iterations.
295 // If a user doesn't have multiple stars available, the user shouldn't be using multistar.
296 constexpr int MAX_CONSECUTIVE_UNRELIABLE = 10;
297 if (firstFrame)
298 unreliableDectionCounter = 0;
299
300 // Don't accept reference stars whose position is more than this many pixels from expected.
301 constexpr double maxStarAssociationDistance = 10;
302
303 if (imageData == nullptr)
304 return GuiderUtils::Vector(-1, -1, -1);
305
306 // If the guide star has not yet been set up, then establish it here.
307 // Not thrilled doing this, but this is the way the internal guider is setup
308 // when guiding is restarted by the scheduler (the normal establish guide star
309 // methods are not called).
310 if (firstFrame && starCorrespondence.size() == 0)
311 {
312 QVector3D v = selectGuideStar(imageData);
313 qCDebug(KSTARS_EKOS_GUIDE) << QString("findGuideStar: Called without starCorrespondence. Refound guide star at %1 %2")
314 .arg(QString::number(v.x(), 'f', 1)).arg(QString::number(v.y(), 'f', 1));
315 return GuiderUtils::Vector(v.x(), v.y(), v.z());
316 }
317
318 // Allow a little margin above the max hfr for guide stars when searching for the guide star.
319 const double maxHFR = Options::guideMaxHFR() + HFR_MARGIN;
320 if (starCorrespondence.size() > 0)
321 {
322
323 findTopStars(imageData, STARS_TO_SEARCH, &detectedStars, maxHFR);
324 if (detectedStars.empty())
325 return GuiderUtils::Vector(-1, -1, -1);
326
327 // Allow it to guide even if the main guide star isn't detected (as long as enough reference stars are).
328 starCorrespondence.setAllowMissingGuideStar(allowMissingGuideStar);
329
330 // Star correspondence can run quicker if it knows the image size.
331 starCorrespondence.setImageSize(imageData->width(), imageData->height());
332
333 // When using large star-correspondence sets and filtering with a StellarSolver profile,
334 // the stars at the edge of detection can be lost. Best not to filter, but...
335 double minFraction = 0.5;
336 if (starCorrespondence.size() > 25) minFraction = 0.33;
337 else if (starCorrespondence.size() > 15) minFraction = 0.4;
338
339 Edge foundStar = starCorrespondence.find(detectedStars, maxStarAssociationDistance, &starMap, true, minFraction);
340
341 // Is there a correspondence to the guide star
342 // Should we also weight distance to the tracking box?
343 for (int i = 0; i < detectedStars.size(); ++i)
344 {
345 if (getStarMap(i) == starCorrespondence.guideStar())
346 {
347 auto &star = detectedStars[i];
348 double SNR = skyBackground.SNR(star.sum, star.numPixels);
349 guideStarSNR = SNR;
350 guideStarMass = star.sum;
351 unreliableDectionCounter = 0;
352 qCDebug(KSTARS_EKOS_GUIDE) << QString("StarCorrespondence found star %1 at %2 %3 SNR %4")
353 .arg(i).arg(star.x, 0, 'f', 1).arg(star.y, 0, 'f', 1).arg(SNR, 0, 'f', 1);
354
355 if (guideView != nullptr)
356 plotStars(guideView, trackingBox);
357 return GuiderUtils::Vector(star.x, star.y, 0);
358 }
359 }
360 // None of the stars matched the guide star, but it's possible star correspondence
361 // invented a guide star position.
362 if (foundStar.x >= 0 && foundStar.y >= 0)
363 {
364 guideStarSNR = skyBackground.SNR(foundStar.sum, foundStar.numPixels);
365 guideStarMass = foundStar.sum;
366 unreliableDectionCounter = 0; // debating this
367 qCDebug(KSTARS_EKOS_GUIDE) << "StarCorrespondence invented at" << foundStar.x << foundStar.y << "SNR" << guideStarSNR;
368 if (guideView != nullptr)
369 plotStars(guideView, trackingBox);
370 return GuiderUtils::Vector(foundStar.x, foundStar.y, 0);
371 }
372 }
373
374 qCDebug(KSTARS_EKOS_GUIDE) << "StarCorrespondence not used. It failed to find the guide star.";
375
376 if (++unreliableDectionCounter > MAX_CONSECUTIVE_UNRELIABLE)
377 return GuiderUtils::Vector(-1, -1, -1);
378
379 logStars("findGuide", "Star", skyBackground,
380 detectedStars.size(),
381 [&](int i) -> const Edge&
382 {
383 return detectedStars[i];
384 },
385 "assoc", [&](int i) -> QString
386 {
387 return QString("%1").arg(getStarMap(i));
388 });
389
390 if (trackingBox.isValid() == false)
391 return GuiderUtils::Vector(-1, -1, -1);
392
393 // If we didn't find a star that way, then fall back
394 findTopStars(imageData, 100, &detectedStars, maxHFR, &trackingBox);
395 if (detectedStars.size() > 0)
396 {
397 // Find the star closest to the guide star position, if we have a position.
398 // Otherwise use the center of the tracking box (which must be valid, see above if).
399 // 1. Get the guide star position
400 int best = 0;
401 double refX = trackingBox.x() + trackingBox.width() / 2;
402 double refY = trackingBox.y() + trackingBox.height() / 2;
403 if (starCorrespondence.size() > 0 && starCorrespondence.guideStar() >= 0)
404 {
405 const auto gStar = starCorrespondence.reference(starCorrespondence.guideStar());
406 refX = gStar.x;
407 refY = gStar.y;
408 }
409 // 2. Find the closest star to that position.
410 double minDistSq = 1e8;
411 for (int i = 0; i < detectedStars.size(); ++i)
412 {
413 const auto &dStar = detectedStars[i];
414 const double distSq = (dStar.x - refX) * (dStar.x - refX) + (dStar.y - refY) * (dStar.y - refY);
415 if (distSq < minDistSq)
416 {
417 best = i;
418 minDistSq = distSq;
419 }
420 }
421 // 3. Return that star.
422 auto &star = detectedStars[best];
423 double SNR = skyBackground.SNR(star.sum, star.numPixels);
424 guideStarSNR = SNR;
425 guideStarMass = star.sum;
426 qCDebug(KSTARS_EKOS_GUIDE) << "StarCorrespondence. Standard method found at " << star.x << star.y << "SNR" << SNR;
427 return GuiderUtils::Vector(star.x, star.y, 0);
428 }
429 return GuiderUtils::Vector(-1, -1, -1);
430}
431
432SSolver::Parameters GuideStars::getStarExtractionParameters(int num)
433{
434 SSolver::Parameters params;
435 params.listName = "Guider";
436 params.apertureShape = SSolver::SHAPE_CIRCLE;
437 params.minarea = 10; // changed from 5 --> 10
438 params.deblend_thresh = 32;
439 params.deblend_contrast = 0.005;
440 params.initialKeep = num * 2;
441 params.keepNum = num;
442 params.removeBrightest = 0;
443 params.removeDimmest = 0;
444 params.saturationLimit = 0;
445 return params;
446}
447
448// This is the interface to star detection.
449int GuideStars::findAllSEPStars(const QSharedPointer<FITSData> &imageData, QList<Edge *> *sepStars, int num)
450{
451 if (imageData == nullptr)
452 return 0;
453
454 QVariantMap settings;
455 settings["optionsProfileIndex"] = Options::guideOptionsProfile();
456 settings["optionsProfileGroup"] = static_cast<int>(Ekos::GuideProfiles);
457 imageData->setSourceExtractorSettings(settings);
458 imageData->findStars(ALGORITHM_SEP).waitForFinished();
459 skyBackground = imageData->getSkyBackground();
460
461 QList<Edge *> edges = imageData->getStarCenters();
462 // Let's sort edges, starting with widest
463 std::sort(edges.begin(), edges.end(), [](const Edge * edge1, const Edge * edge2) -> bool { return edge1->HFR > edge2->HFR;});
464
465 m_NumStarsDetected = edges.count();
466 // Take only the first num stars
467 {
468 int starCount = qMin(num, edges.count());
469 for (int i = 0; i < starCount; i++)
470 sepStars->append(edges[i]);
471 }
472 qCDebug(KSTARS_EKOS_GUIDE) << "Multistar: SEP detected" << edges.count() << "stars, keeping" << sepStars->size();
473
474 edges.clear();
475 return sepStars->count();
476}
477
478double GuideStars::findMinDistance(int index, const QList<Edge*> &stars)
479{
480 double closestDistanceSqr = 1e10;
481 const Edge &star = *stars[index];
482 for (int i = 0; i < stars.size(); ++i)
483 {
484 if (i == index) continue;
485 const Edge &thisStar = *stars[i];
486 const double xDist = star.x - thisStar.x;
487 const double yDist = star.y - thisStar.y;
488 const double distanceSqr = xDist * xDist + yDist * yDist;
489 if (distanceSqr < closestDistanceSqr)
490 closestDistanceSqr = distanceSqr;
491 }
492 return sqrt(closestDistanceSqr);
493}
494
495// Returns a list of 'num' stars, sorted according to evaluateSEPStars().
496// If the region-of-interest rectange is not null, it only returns scores in that area.
497void GuideStars::findTopStars(const QSharedPointer<FITSData> &imageData, int num, QList<Edge> *stars,
498 const double maxHFR, const QRect *roi,
499 QList<double> *outputScores, QList<double> *minDistances)
500{
501 if (roi == nullptr)
502 {
503 DLOG(KSTARS_EKOS_GUIDE) << "Multistar: findTopStars" << num;
504 }
505 else
506 {
507 DLOG(KSTARS_EKOS_GUIDE) << "Multistar: findTopStars" << num <<
508 QString("Roi(%1,%2 %3x%4)").arg(roi->x()).arg(roi->y()).arg(roi->width()).arg(roi->height());
509 }
510
511 stars->clear();
512 if (imageData == nullptr)
513 return;
514
515 QElapsedTimer timer;
516 timer.restart();
517 QList<Edge*> sepStars;
518 int count = findAllSEPStars(imageData, &sepStars, num * 2);
519 if (count == 0)
520 return;
521
522 if (sepStars.empty())
523 return;
524
525 QVector<double> scores;
526 evaluateSEPStars(sepStars, &scores, roi, maxHFR);
527 // Sort the sepStars by score, higher score to lower score.
529 for (int i = 0; i < scores.size(); ++i)
530 sc.push_back(std::pair<int, double>(i, scores[i]));
531 std::sort(sc.begin(), sc.end(), [](const std::pair<int, double> &a, const std::pair<int, double> &b)
532 {
533 return a.second > b.second;
534 });
535 // Copy the top num results.
536 for (int i = 0; i < std::min(num, scores.size()); ++i)
537 {
538 const int starIndex = sc[i].first;
539 const double starScore = sc[i].second;
540 if (starScore >= 0)
541 {
542 stars->append(*(sepStars[starIndex]));
543 if (outputScores != nullptr)
544 outputScores->append(starScore);
545 if (minDistances != nullptr)
546 minDistances->append(findMinDistance(starIndex, sepStars));
547 }
548 }
549 DLOG(KSTARS_EKOS_GUIDE)
550 << QString("Multistar: findTopStars returning: %1 stars, %2s")
551 .arg(stars->size()).arg(timer.elapsed() / 1000.0, 4, 'f', 2);
552}
553
554// Scores star detection relative to each other. Uses the star's SNR as the main measure.
555void GuideStars::evaluateSEPStars(const QList<Edge *> &starCenters, QVector<double> *scores,
556 const QRect *roi, const double maxHFR) const
557{
558 auto centers = starCenters;
559 scores->clear();
560 const int numDetections = centers.size();
561 for (int i = 0; i < numDetections; ++i) scores->push_back(0);
562 if (numDetections == 0) return;
563
564
565 // Rough constants used by this weighting.
566 // If the center pixel is above this, assume it's clipped and don't emphasize.
567 constexpr double snrWeight = 20; // Measure weight if/when multiple measures are used.
568 auto bg = skybackground();
569
570 // Sort by SNR in increasing order so the weighting goes up.
571 // Assign score based on the sorted position.
572 std::sort(centers.begin(), centers.end(), [&bg](const Edge * a, const Edge * b)
573 {
574 double snrA = bg.SNR(a->sum, a->numPixels);
575 double snrB = bg.SNR(b->sum, b->numPixels);
576 return snrA < snrB;
577 });
578
579 // See if the HFR restriction is too severe.
580 int numRejectedByHFR = 0;
581 for (int i = 0; i < numDetections; ++i)
582 {
583 if (centers.at(i)->HFR > maxHFR)
584 numRejectedByHFR++;
585 }
586 const int starsRemaining = numDetections - numRejectedByHFR;
587 const bool useHFRConstraint =
588 starsRemaining > 5 ||
589 (starsRemaining >= 3 && numDetections <= 6) ||
590 (starsRemaining >= 2 && numDetections <= 4) ||
591 (starsRemaining >= 1 && numDetections <= 2);
592
593 for (int i = 0; i < numDetections; ++i)
594 {
595 for (int j = 0; j < numDetections; ++j)
596 {
597 if (starCenters.at(j) == centers.at(i))
598 {
599 // Don't emphasize stars that are too wide.
600 if (useHFRConstraint && centers.at(i)->HFR > maxHFR)
601 (*scores)[j] = -1;
602 else
603 (*scores)[j] += snrWeight * i;
604 break;
605 }
606 }
607 }
608
609 // If we are insisting on a star in the tracking box.
610 if (roi != nullptr)
611 {
612 // Reset the score of anything outside the tracking box.
613 for (int j = 0; j < starCenters.size(); ++j)
614 {
615 const auto &star = starCenters.at(j);
616 if (star->x < roi->x() || star->x >= roi->x() + roi->width() ||
617 star->y < roi->y() || star->y >= roi->y() + roi->height())
618 (*scores)[j] = -1;
619 }
620 }
621}
622
623// Given a star detection and a reference star compute the RA and DEC drifts between the two.
624void GuideStars::computeStarDrift(const Edge &star, const Edge &reference, double *driftRA, double *driftDEC) const
625{
626 if (!calibrationInitialized) return;
627
628 GuiderUtils::Vector position(star.x, star.y, 0);
629 GuiderUtils::Vector reference_position(reference.x, reference.y, 0);
630 GuiderUtils::Vector arc_position, arc_reference_position;
631 arc_position = calibration.convertToArcseconds(position);
632 arc_reference_position = calibration.convertToArcseconds(reference_position);
633
634 // translate into sky coords.
635 GuiderUtils::Vector sky_coords = arc_position - arc_reference_position;
636 sky_coords = calibration.rotateToRaDec(sky_coords);
637
638 // Save the drifts in RA and DEC.
639 *driftRA = sky_coords.x;
640 *driftDEC = sky_coords.y;
641}
642
643// Computes the overall drift given the input image that was analyzed in findGuideStar().
644// Returns true if this can be done--there are already guide stars and the current detections
645// can be compared to them. In that case, fills in RADRift and DECDrift with arc-second drifts.
646// Reticle_x,y is the target position. It may be different from when we
647// originally selected the guide star, e.g. due to dithering, etc. We compute an offset from the
648// original guide-star position and the reticle position and offset all the reference star
649// positions by that.
650bool GuideStars::getDrift(double oneStarDrift, double reticle_x, double reticle_y,
651 double *RADrift, double *DECDrift)
652{
653 if (starCorrespondence.size() == 0)
654 return false;
655 const Edge gStar = starCorrespondence.reference(starCorrespondence.guideStar());
656 DLOG(KSTARS_EKOS_GUIDE) << "Multistar getDrift, reticle:" << reticle_x << reticle_y
657 << "guidestar" << gStar.x << gStar.y
658 << "so offsets:" << (reticle_x - gStar.x) << (reticle_y - gStar.y);
659 // Revoke multistar if we're that far away.
660 // Currently disabled by large constant.
661 constexpr double maxDriftForMultistar = 4000000.0; // arc-seconds
662
663 if (!calibrationInitialized)
664 return false;
665
666 // Don't run multistar if the 1-star approach is too far off.
667 if (oneStarDrift > maxDriftForMultistar)
668 {
669 qCDebug(KSTARS_EKOS_GUIDE) << "MultiStar: 1-star drift too high: " << oneStarDrift;
670 return false;
671 }
672 if (starCorrespondence.size() < 2)
673 {
674 qCDebug(KSTARS_EKOS_GUIDE) << "Multistar: no guide stars";
675 return false;
676 }
677
678 // The original guide star position has been offset.
679 double offset_x = reticle_x - gStar.x;
680 double offset_y = reticle_y - gStar.y;
681
682 int numStarsProcessed = 0;
683 bool guideStarProcessed = false;
684 double driftRASum = 0, driftDECSum = 0;
685 double guideStarRADrift = 0, guideStarDECDrift = 0;
686 QVector<double> raDrifts, decDrifts;
687
688 DLOG(KSTARS_EKOS_GUIDE)
689 << QString("%1 %2 dRA dDEC").arg(logHeader("")).arg(logHeader(" Ref:"));
690 for (int i = 0; i < detectedStars.size(); ++i)
691 {
692 const auto &star = detectedStars[i];
693 auto bg = skybackground();
694 double snr = bg.SNR(detectedStars[i].sum, detectedStars[i].numPixels);
695 // Probably should test SNR on the reference as well.
696 if (getStarMap(i) >= 0 && snr >= MIN_DRIFT_SNR)
697 {
698 auto ref = starCorrespondence.reference(getStarMap(i));
699 ref.x += offset_x;
700 ref.y += offset_y;
701 double driftRA, driftDEC;
702 computeStarDrift(star, ref, &driftRA, &driftDEC);
703 if (getStarMap(i) == starCorrespondence.guideStar())
704 {
705 guideStarRADrift = driftRA;
706 guideStarDECDrift = driftDEC;
707 guideStarProcessed = true;
708 }
709
710 raDrifts.push_back(driftRA);
711 decDrifts.push_back(driftDEC);
712 numStarsProcessed++;
713
714 DLOG(KSTARS_EKOS_GUIDE)
715 << QString("%1 %2 %3 %4").arg(logStar("MultiStar", i, bg, star))
716 .arg(logStar(" Ref:", getStarMap(i), bg, ref))
717 .arg(driftRA, 5, 'f', 2).arg(driftDEC, 5, 'f', 2);
718 }
719 }
720
721 if (numStarsProcessed == 0 || (!allowMissingGuideStar && !guideStarProcessed))
722 return false;
723
724 // Filter out reference star drifts that are too different from the guide-star drift.
725 QVector<double> raDriftsKeep, decDriftsKeep;
726 for (int i = 0; i < raDrifts.size(); ++i)
727 {
728 if ((allowMissingGuideStar && !guideStarProcessed) ||
729 ((fabs(raDrifts[i] - guideStarRADrift) < 2.0) &&
730 (fabs(decDrifts[i] - guideStarDECDrift) < 2.0)))
731 {
732 driftRASum += raDrifts[i];
733 driftDECSum += decDrifts[i];
734 raDriftsKeep.push_back(raDrifts[i]);
735 decDriftsKeep.push_back(decDrifts[i]);
736 }
737 }
738 if (raDriftsKeep.empty() || raDriftsKeep.empty())
739 return false; // Shouldn't happen.
740
741 numStarsProcessed = raDriftsKeep.size();
742
743 // Generate the drift either from the median or the average of the usable reference drifts.
744 bool useMedian = true;
745 if (useMedian)
746 {
747 std::sort(raDriftsKeep.begin(), raDriftsKeep.end(), [] (double d1, double d2)
748 {
749 return d1 < d2;
750 });
751 std::sort(decDriftsKeep.begin(), decDriftsKeep.end(), [] (double d1, double d2)
752 {
753 return d1 < d2;
754 });
755 if (numStarsProcessed % 2 == 0)
756 {
757 const int middle = numStarsProcessed / 2;
758 *RADrift = ( raDriftsKeep[middle - 1] + raDriftsKeep[middle]) / 2.0;
759 *DECDrift = (decDriftsKeep[middle - 1] + decDriftsKeep[middle]) / 2.0;
760 }
761 else
762 {
763 const int middle = numStarsProcessed / 2; // because it's 0-based
764 *RADrift = raDriftsKeep[middle];
765 *DECDrift = decDriftsKeep[middle];
766 }
767 DLOG(KSTARS_EKOS_GUIDE) << "MultiStar: Drift median " << *RADrift << *DECDrift << numStarsProcessed << " of " <<
768 detectedStars.size() << "#guide" << starCorrespondence.size();
769 }
770 else
771 {
772 *RADrift = driftRASum / raDriftsKeep.size();
773 *DECDrift = driftDECSum / decDriftsKeep.size();
774 DLOG(KSTARS_EKOS_GUIDE) << "MultiStar: Drift " << *RADrift << *DECDrift << numStarsProcessed << " of " <<
775 detectedStars.size() << "#guide" << starCorrespondence.size();
776 }
777 return true;
778}
779
780void GuideStars::setCalibration(const Calibration &cal)
781{
782 calibration = cal;
783 calibrationInitialized = true;
784}
qint64 restart()
void append(QList< T > &&value)
const_reference at(qsizetype i) const const
iterator begin()
void clear()
qsizetype count() const const
bool empty() const const
iterator end()
T & first()
void push_back(parameter_type value)
qsizetype size() const const
int height() const const
bool isValid() const const
int width() const const
int x() const const
int y() const const
QString & append(QChar ch)
QString arg(Args &&... args) const const
QString number(double n, char format, int precision)
qsizetype size() const const
QTextStream & center(QTextStream &stream)
float x() const const
float y() const const
float x() const const
float y() const const
float z() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 26 2024 11:59:51 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.