Kstars

gmath.cpp
1/*
2 SPDX-FileCopyrightText: 2012 Andrew Stepanenko
3
4 Modified by Jasem Mutlaq <mutlaqja@ikarustech.com> for KStars:
5 SPDX-FileCopyrightText: 2012 Jasem Mutlaq <mutlaqja@ikarustech.com>
6
7 SPDX-License-Identifier: GPL-2.0-or-later
8*/
9
10#include "gmath.h"
11
12#include "Options.h"
13#include "fitsviewer/fitsdata.h"
14#include "fitsviewer/fitsview.h"
15#include "auxiliary/kspaths.h"
16#include "ekos_guide_debug.h"
17#include "guidealgorithms.h"
18#include "guidelog.h"
19#include "../guideview.h"
20#include "linearguider.h"
21#include "hysteresisguider.h"
22#include "ekos/guide/opsguide.h"
23
24#include <QVector3D>
25#include <cmath>
26
27// Qt version calming
28#include <qtendl.h>
29
30GuiderUtils::Vector cgmath::findLocalStarPosition(QSharedPointer<FITSData> &imageData,
31 QSharedPointer<GuideView> &guideView, bool firstFrame)
32{
33 GuiderUtils::Vector position;
34 if (usingSEPMultiStar())
35 {
36 QRect trackingBox = guideView->getTrackingBox();
37 position = guideStars.findGuideStar(imageData, trackingBox, guideView, firstFrame);
38
39 }
40 else
41 position = GuideAlgorithms::findLocalStarPosition(
42 imageData, m_StarDetectionAlgorithm, video_width, video_height,
43 guideView->getTrackingBox());
44
45 if (position.x == -1 || position.y == -1)
46 setLostStar(true);
47 return position;
48}
49
50
51cgmath::cgmath() : QObject()
52{
53 // sky coord. system vars.
54 starPosition = GuiderUtils::Vector(0);
55 targetPosition = GuiderUtils::Vector(0);
56
57 // processing
58 in_params.reset();
59 out_params.reset();
60 driftUpto[GUIDE_RA] = driftUpto[GUIDE_DEC] = 0;
61 drift[GUIDE_RA] = new double[CIRCULAR_BUFFER_SIZE];
62 drift[GUIDE_DEC] = new double[CIRCULAR_BUFFER_SIZE];
63 memset(drift[GUIDE_RA], 0, sizeof(double) * CIRCULAR_BUFFER_SIZE);
64 memset(drift[GUIDE_DEC], 0, sizeof(double) * CIRCULAR_BUFFER_SIZE);
65 drift_integral[GUIDE_RA] = drift_integral[GUIDE_DEC] = 0;
66
67 logFile.setFileName(QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath("guide_log.txt"));
68 gpg.reset(new GPG());
69 m_RALinearGuider.reset( new LinearGuider("RA"));
70 m_DECLinearGuider.reset(new LinearGuider("DEC"));
71 m_RAHysteresisGuider.reset( new HysteresisGuider("RA"));
72 m_DECHysteresisGuider.reset(new HysteresisGuider("DEC"));
73}
74
75cgmath::~cgmath()
76{
77 delete[] drift[GUIDE_RA];
78 delete[] drift[GUIDE_DEC];
79}
80
81bool cgmath::setVideoParameters(int vid_wd, int vid_ht, int binX, int binY)
82{
83 if (vid_wd <= 0 || vid_ht <= 0)
84 return false;
85
86 video_width = vid_wd / binX;
87 video_height = vid_ht / binY;
88
89 calibration.setBinningUsed(binX, binY);
90 guideStars.setCalibration(calibration);
91
92 return true;
93}
94
95bool cgmath::setGuiderParameters(double guider_aperture)
96{
97 aperture = guider_aperture;
98 guideStars.setCalibration(calibration);
99
100 return true;
101}
102
103// This logging will be removed in favor of guidelog.h.
104void cgmath::createGuideLog()
105{
106 logFile.close();
108 QTextStream out(&logFile);
109
110 out << "Guiding rate,x15 arcsec/sec: " << Qt::endl;
111 out << "Focal,mm: " << calibration.getFocalLength() << Qt::endl;
112 out << "Aperture,mm: " << aperture << Qt::endl;
113 out << "F/D: " << calibration.getFocalLength() / aperture << Qt::endl;
114 out << "Frame #, Time Elapsed (ms), RA Error (arcsec), RA Correction (ms), RA Correction Direction, DEC Error "
115 "(arcsec), DEC Correction (ms), DEC Correction Direction"
116 << Qt::endl;
117
118 logTime.restart();
119}
120
121bool cgmath::setTargetPosition(double x, double y)
122{
123 // check frame ranges
124 if (x < 0)
125 x = 0;
126 if (y < 0)
127 y = 0;
128 if (x >= (double)video_width - 1)
129 x = (double)video_width - 1;
130 if (y >= (double)video_height - 1)
131 y = (double)video_height - 1;
132
133 targetPosition = GuiderUtils::Vector(x, y, 0);
134
135 guideStars.setCalibration(calibration);
136
137 return true;
138}
139
140bool cgmath::getTargetPosition(double *x, double *y) const
141{
142 *x = targetPosition.x;
143 *y = targetPosition.y;
144 return true;
145}
146
147void cgmath::getStarScreenPosition(double *dx, double *dy) const
148{
149 *dx = starPosition.x;
150 *dy = starPosition.y;
151}
152
153bool cgmath::reset()
154{
155 iterationCounter = 0;
156 driftUpto[GUIDE_RA] = driftUpto[GUIDE_DEC] = 0;
157 drift_integral[GUIDE_RA] = drift_integral[GUIDE_DEC] = 0;
158 out_params.reset();
159
160 memset(drift[GUIDE_RA], 0, sizeof(double) * CIRCULAR_BUFFER_SIZE);
161 memset(drift[GUIDE_DEC], 0, sizeof(double) * CIRCULAR_BUFFER_SIZE);
162
163 return true;
164}
165
166void cgmath::setStarDetectionAlgorithmIndex(int algorithmIndex)
167{
168 if (algorithmIndex < 0 || algorithmIndex > SEP_MULTISTAR)
169 return;
170
171 m_StarDetectionAlgorithm = algorithmIndex;
172}
173
174bool cgmath::usingSEPMultiStar() const
175{
176 return m_StarDetectionAlgorithm == SEP_MULTISTAR;
177}
178
179void cgmath::updateCircularBuffers(void)
180{
181 iterationCounter++;
182
183 driftUpto[GUIDE_RA]++;
184 driftUpto[GUIDE_DEC]++;
185 if (driftUpto[GUIDE_RA] >= CIRCULAR_BUFFER_SIZE)
186 driftUpto[GUIDE_RA] = 0;
187 if (driftUpto[GUIDE_DEC] >= CIRCULAR_BUFFER_SIZE)
188 driftUpto[GUIDE_DEC] = 0;
189}
190
191//-------------------- Processing ---------------------------
192void cgmath::start()
193{
194 iterationCounter = 0;
195 driftUpto[GUIDE_RA] = driftUpto[GUIDE_DEC] = 0;
196 drift_integral[GUIDE_RA] = drift_integral[GUIDE_DEC] = 0;
197 out_params.reset();
198
199 memset(drift[GUIDE_RA], 0, sizeof(double) * CIRCULAR_BUFFER_SIZE);
200 memset(drift[GUIDE_DEC], 0, sizeof(double) * CIRCULAR_BUFFER_SIZE);
201
202 if (calibration.getFocalLength() > 0 && aperture > 0)
203 createGuideLog();
204
205 gpg->reset();
206 m_RALinearGuider->reset();
207 m_DECLinearGuider->reset();
208 m_RAHysteresisGuider->reset();
209 m_DECHysteresisGuider->reset();
210}
211
212void cgmath::abort()
213{
214 guideStars.reset();
215 m_RALinearGuider->reset();
216 m_DECLinearGuider->reset();
217 m_RAHysteresisGuider->reset();
218 m_DECHysteresisGuider->reset();
219}
220
221void cgmath::suspend(bool mode)
222{
223 suspended = mode;
224 m_RALinearGuider->reset();
225 m_DECLinearGuider->reset();
226 m_RAHysteresisGuider->reset();
227 m_DECHysteresisGuider->reset();
228}
229
230bool cgmath::isSuspended() const
231{
232 return suspended;
233}
234
235bool cgmath::isStarLost() const
236{
237 return lost_star;
238}
239
240void cgmath::setLostStar(bool is_lost)
241{
242 lost_star = is_lost;
243}
244
245namespace
246{
247QString axisStr(int raDEC)
248{
249 if (raDEC == GUIDE_RA)
250 return "RA";
251 else if (raDEC == GUIDE_DEC)
252 return "DEC";
253 else
254 return "???";
255}
256
257const QString directionStr(GuideDirection dir)
258{
259 switch (dir)
260 {
261 case RA_DEC_DIR:
262 return "Decrease RA";
263 case RA_INC_DIR:
264 return "Increase RA";
265 case DEC_DEC_DIR:
266 return "Decrease DEC";
267 case DEC_INC_DIR:
268 return "Increase DEC";
269 default:
270 return "NO DIR";
271 }
272}
273} // namespace
274
275bool cgmath::configureInParams(Ekos::GuideState state)
276{
277 const bool dithering = state == Ekos::GuideState::GUIDE_DITHERING;
278
279 if (!dithering)
280 {
281 in_params.proportional_gain[0] = Options::rAProportionalGain();
282 in_params.proportional_gain[1] = Options::dECProportionalGain();
283
284 in_params.integral_gain[0] = Options::rAIntegralGain();
285 in_params.integral_gain[1] = Options::dECIntegralGain();
286
287 // Always pulse if we're dithering.
288 in_params.enabled[0] = Options::rAGuideEnabled();
289 in_params.enabled[1] = Options::dECGuideEnabled();
290
291 in_params.min_pulse_arcsec[0] = Options::rAMinimumPulseArcSec();
292 in_params.min_pulse_arcsec[1] = Options::dECMinimumPulseArcSec();
293
294 in_params.max_pulse_arcsec[0] = Options::rAMaximumPulseArcSec();
295 in_params.max_pulse_arcsec[1] = Options::dECMaximumPulseArcSec();
296
297 // RA W/E enable (but always pulse if dithering).
298 // East RA+ enabled?
299 in_params.enabled_axis1[0] = Options::eastRAGuideEnabled();
300 // West RA- enabled?
301 in_params.enabled_axis2[0] = Options::westRAGuideEnabled();
302
303 // DEC N/S enable (but always pulse if dithering).
304 // North DEC+ enabled?
305 in_params.enabled_axis1[1] = Options::northDECGuideEnabled();
306 // South DEC- enabled?
307 in_params.enabled_axis2[1] = Options::southDECGuideEnabled();
308 }
309 else
310 {
311 // If we're dithering, enable all axes and use full pulses.
312 in_params.proportional_gain[0] = 1.0;
313 in_params.proportional_gain[1] = 1.0;
314 in_params.integral_gain[0] = 0.0;
315 in_params.integral_gain[1] = 0.0;
316 in_params.min_pulse_arcsec[0] = 0.0;
317 in_params.min_pulse_arcsec[1] = 0.0;
318 in_params.max_pulse_arcsec[0] = Options::rAMaximumPulseArcSec();
319 in_params.max_pulse_arcsec[1] = Options::dECMaximumPulseArcSec();
320 in_params.enabled[0] = true;
321 in_params.enabled[1] = true;
322 in_params.enabled_axis1[0] = true;
323 in_params.enabled_axis2[0] = true;
324 in_params.enabled_axis1[1] = true;
325 in_params.enabled_axis2[1] = true;
326 }
327
328 return dithering;
329}
330
331void cgmath::updateOutParams(int k, const double arcsecDrift, int pulseLength, GuideDirection pulseDirection)
332{
333 out_params.pulse_dir[k] = pulseDirection;
334 out_params.pulse_length[k] = pulseLength;
335 out_params.delta[k] = arcsecDrift;
336}
337
338void cgmath::outputGuideLog()
339{
340 if (Options::guideLogging())
341 {
342 QTextStream out(&logFile);
343 out << iterationCounter << "," << logTime.elapsed() << "," << out_params.delta[0] << "," << out_params.pulse_length[0] <<
344 ","
345 << directionStr(out_params.pulse_dir[0]) << "," << out_params.delta[1] << ","
346 << out_params.pulse_length[1] << "," << directionStr(out_params.pulse_dir[1]) << Qt::endl;
347 }
348}
349
350void cgmath::processAxis(const int k, const bool dithering, const bool darkGuide, const Seconds &timeStep,
351 const QString &label)
352{
353 // zero all out commands
354 GuideDirection pulseDirection = NO_DIR;
355 int pulseLength = 0; // milliseconds
356 GuideDirection dir;
357
358 // Get the drift for this axis
359 const int idx = driftUpto[k];
360 const double arcsecDrift = drift[k][idx];
361
362 const double pulseConverter = (k == GUIDE_RA) ?
363 calibration.raPulseMillisecondsPerArcsecond() :
364 calibration.decPulseMillisecondsPerArcsecond();
365 const double maxPulseMilliseconds = in_params.max_pulse_arcsec[k] * pulseConverter;
366
367 if (dithering)
368 {
369 m_RALinearGuider->reset();
370 m_DECLinearGuider->reset();
371 m_RAHysteresisGuider->reset();
372 m_DECHysteresisGuider->reset();
373 }
374 LinearGuider *lGuider = nullptr;
375 HysteresisGuider *hGuider = nullptr;
376 bool useGPG = false;
377 if (!dithering)
378 {
379 if ((Options::rAGuidePulseAlgorithm() == Ekos::OpsGuide::HYSTERESIS_ALGORITHM) && k == GUIDE_RA)
380 hGuider = m_RAHysteresisGuider.get();
381 else if ((Options::dECGuidePulseAlgorithm() == Ekos::OpsGuide::HYSTERESIS_ALGORITHM) && k == GUIDE_DEC)
382 hGuider = m_DECHysteresisGuider.get();
383 else if ((Options::rAGuidePulseAlgorithm() == Ekos::OpsGuide::LINEAR_ALGORITHM) && k == GUIDE_RA)
384 lGuider = m_RALinearGuider.get();
385 else if ((Options::dECGuidePulseAlgorithm() == Ekos::OpsGuide::LINEAR_ALGORITHM) && k == GUIDE_DEC)
386 lGuider = m_DECLinearGuider.get();
387 else if ((Options::rAGuidePulseAlgorithm() == Ekos::OpsGuide::GPG_ALGORITHM) && (k == GUIDE_RA)
388 && in_params.enabled[k])
389 useGPG = true;
390 }
391
392 if (useGPG && darkGuide)
393 {
394 gpg->darkGuiding(&pulseLength, &dir, calibration, timeStep);
395 pulseDirection = dir;
396 }
397 else if (darkGuide)
398 {
399 // We should not be dark guiding without GPG
400 qCDebug(KSTARS_EKOS_GUIDE) << "Warning: dark guiding without GPG or while dithering.";
401 return;
402 }
403 else if (useGPG && gpg->computePulse(arcsecDrift,
404 usingSEPMultiStar() ? &guideStars : nullptr, &pulseLength, &dir, calibration, timeStep))
405 {
406 pulseDirection = dir;
407 pulseLength = std::min(pulseLength, static_cast<int>(maxPulseMilliseconds + 0.5));
408 }
409 else if (lGuider != nullptr)
410 {
411 // If we haven't been here in 2 exposures, should probably reset the linear guider.
412 // Similarly if we've dithered.
413 // Search for all gpg resets
414 // Also when it gets started or restarted...
415 //
416 // Also when I enable/disable the checkboxes, I should reset things appropriately
417
418 double pulse = 0;
419 if (k == GUIDE_RA)
420 {
421 lGuider->setGain(Options::rAProportionalGain());
422 lGuider->setMinMove(Options::rAMinimumPulseArcSec());
423 pulse = lGuider->guide(arcsecDrift) * calibration.raPulseMillisecondsPerArcsecond();
424 pulseDirection = pulse > 0 ? RA_DEC_DIR : RA_INC_DIR;
425 }
426 else
427 {
428 lGuider->setGain(Options::dECProportionalGain());
429 lGuider->setMinMove(Options::dECMinimumPulseArcSec());
430 pulse = lGuider->guide(arcsecDrift) * calibration.decPulseMillisecondsPerArcsecond();
431 pulseDirection = pulse > 0 ? DEC_DEC_DIR : DEC_INC_DIR;
432 }
433 pulseLength = std::min(fabs(pulse), maxPulseMilliseconds);
434 }
435 else if (hGuider != nullptr)
436 {
437 double pulse = 0;
438 if (k == GUIDE_RA)
439 {
440 hGuider->setGain(Options::rAProportionalGain());
441 hGuider->setMinMove(Options::rAMinimumPulseArcSec());
442 hGuider->setHysteresis(Options::rAHysteresis());
443 pulse = hGuider->guide(arcsecDrift) * calibration.raPulseMillisecondsPerArcsecond();
444 pulseDirection = pulse > 0 ? RA_DEC_DIR : RA_INC_DIR;
445 }
446 else
447 {
448 hGuider->setGain(Options::dECProportionalGain());
449 hGuider->setMinMove(Options::dECMinimumPulseArcSec());
450 hGuider->setHysteresis(Options::dECHysteresis());
451 pulse = hGuider->guide(arcsecDrift) * calibration.decPulseMillisecondsPerArcsecond();
452 pulseDirection = pulse > 0 ? DEC_DEC_DIR : DEC_INC_DIR;
453 }
454 pulseLength = std::min(fabs(pulse), maxPulseMilliseconds);
455 }
456 else
457 {
458 // This is the main non-GPG guide-pulse computation.
459 // Traditionally it was hardwired so that proportional_gain=133 was about a control gain of 1.0
460 // This is now in the 0.0 - 1.0 range, and multiplies the calibrated mount performance.
461
462 // Compute the average drift in the recent past for the integral control term.
463 drift_integral[k] = 0;
464 for (int i = 0; i < CIRCULAR_BUFFER_SIZE; ++i)
465 drift_integral[k] += drift[k][i];
466 drift_integral[k] /= (double)CIRCULAR_BUFFER_SIZE;
467
468 if (in_params.integral_gain[k] > 0)
469 qCDebug(KSTARS_EKOS_GUIDE) << label << "drift[" << axisStr(k) << "] = " << arcsecDrift
470 << " integral[" << axisStr(k) << "] = " << drift_integral[k];
471
472 const double arcsecPerMsPulse = k == GUIDE_RA ? calibration.raPulseMillisecondsPerArcsecond() :
473 calibration.decPulseMillisecondsPerArcsecond();
474 const double proportionalResponse = arcsecDrift * in_params.proportional_gain[k] * arcsecPerMsPulse;
475 const double integralResponse = drift_integral[k] * in_params.integral_gain[k] * arcsecPerMsPulse;
476 pulseLength = std::min(fabs(proportionalResponse + integralResponse), maxPulseMilliseconds);
477
478 // calculation of correcting mount pulse
479 // We do not send pulse if direction is disabled completely, or if direction in a specific axis (e.g. N or S) is disabled
480 if (!in_params.enabled[k] || // This axis not enabled
481 // Positive direction of this axis not enabled.
482 (arcsecDrift > 0 && !in_params.enabled_axis1[k]) ||
483 // Negative direction of this axis not enabled.
484 (arcsecDrift < 0 && !in_params.enabled_axis2[k]))
485 {
486 pulseDirection = NO_DIR;
487 pulseLength = 0;
488 }
489 else
490 {
491 // Check the min pulse value, and assign the direction.
492 const double pulseArcSec = pulseConverter > 0 ? pulseLength / pulseConverter : 0;
493 if (pulseArcSec >= in_params.min_pulse_arcsec[k])
494 {
495 // Star drifts are based on pixel differences of the star position in the camera sensor coordinate
496 // system: To correct a star drift > 0 the mount has to move in decreasing RA- & DEC-direction
497 if (k == GUIDE_RA)
498 pulseDirection = arcsecDrift > 0 ? RA_DEC_DIR : RA_INC_DIR;
499 else
500 pulseDirection = arcsecDrift > 0 ? DEC_DEC_DIR : DEC_INC_DIR;
501 }
502 else
503 pulseDirection = NO_DIR;
504 }
505
506 }
507 // Limit the pulse length in case of rediculous values.
508 constexpr int MAX_PULSE_MILLISECONDS = 5000;
509 if (pulseLength > MAX_PULSE_MILLISECONDS)
510 {
511 qCDebug(KSTARS_EKOS_GUIDE) << i18n("Limited long pulse of %1ms to %2ms", pulseLength, MAX_PULSE_MILLISECONDS);
512 pulseLength = MAX_PULSE_MILLISECONDS;
513 }
514 updateOutParams(k, arcsecDrift, pulseLength, pulseDirection);
515}
516
517void cgmath::calculatePulses(Ekos::GuideState state, const std::pair<Seconds, Seconds> &timeStep)
518{
519 const bool dithering = configureInParams(state);
520
521 processAxis(GUIDE_RA, dithering, false, timeStep.first, "Guiding:");
522 processAxis(GUIDE_DEC, dithering, false, timeStep.second, "Guiding:");
523
524 qCDebug(KSTARS_EKOS_GUIDE)
525 << QString("Guiding pulses: RA: %1ms %2 DEC: %3ms %4")
526 .arg(out_params.pulse_length[GUIDE_RA]).arg(directionStr(out_params.pulse_dir[GUIDE_RA]))
527 .arg(out_params.pulse_length[GUIDE_DEC]).arg(directionStr(out_params.pulse_dir[GUIDE_DEC]));
528
529 outputGuideLog();
530}
531
532void cgmath::performProcessing(Ekos::GuideState state, QSharedPointer<FITSData> &imageData,
533 QSharedPointer<GuideView> &guideView,
534 const std::pair<Seconds, Seconds> &timeStep, GuideLog * logger)
535{
536 if (suspended)
537 {
538 m_RALinearGuider->reset();
539 m_DECLinearGuider->reset();
540 m_RAHysteresisGuider->reset();
541 m_DECHysteresisGuider->reset();
542 if (Options::rAGuidePulseAlgorithm() == Ekos::OpsGuide::GPG_ALGORITHM)
543 {
544 GuiderUtils::Vector guideStarPosition = findLocalStarPosition(imageData, guideView, false);
545 if (guideStarPosition.x != -1 && !std::isnan(guideStarPosition.x))
546 {
547 gpg->suspended(guideStarPosition, targetPosition,
548 usingSEPMultiStar() ? &guideStars : nullptr, calibration);
549 }
550 }
551 // do nothing if suspended
552 return;
553 }
554
555 QElapsedTimer timer;
556 timer.start();
557 GuiderUtils::Vector starPositionArcSec, targetPositionArcSec;
558
559 // find guiding star location in the image
560 starPosition = findLocalStarPosition(imageData, guideView, false);
561
562 // If no star found, mark as lost star.
563 if (starPosition.x == -1 || std::isnan(starPosition.x))
564 {
565 setLostStar(true);
566 if (logger != nullptr && state == Ekos::GUIDE_GUIDING)
567 {
568 GuideLog::GuideData data;
569 data.code = GuideLog::GuideData::NO_STAR_FOUND;
570 data.type = GuideLog::GuideData::DROP;
571 logger->addGuideData(data);
572 }
573 return;
574 }
575 else
576 setLostStar(false);
577
578 // Emit the detected star center
579 QVector3D starCenter(starPosition.x, starPosition.y, 0);
580 emit newStarPosition(starCenter, true);
581
582 // If we're only calibrating, then we're done.
583 if (state == Ekos::GUIDE_CALIBRATING)
584 return;
585
586 if (state == Ekos::GUIDE_GUIDING && (targetPosition.x <= 0.0 || targetPosition.y <= 0.0))
587 {
588 qCDebug(KSTARS_EKOS_GUIDE) << "Guiding with target 0.0 -- something's wrong!!!!!!!!!!!";
589 for (int k = GUIDE_RA; k <= GUIDE_DEC; k++)
590 {
591 out_params.pulse_dir[k] = NO_DIR;
592 out_params.pulse_length[k] = 0;
593 out_params.delta[k] = 0;
594 setLostStar(true);
595 }
596 return;
597 }
598 // translate star coords into sky coord. system
599
600 // convert from pixels into arcsecs
601 starPositionArcSec = calibration.convertToArcseconds(starPosition);
602 targetPositionArcSec = calibration.convertToArcseconds(targetPosition);
603
604 // Compute RA & DEC drift in arcseconds.
605 const GuiderUtils::Vector star_xy_arcsec_drift = starPositionArcSec - targetPositionArcSec;
606 const GuiderUtils::Vector star_drift = calibration.rotateToRaDec(star_xy_arcsec_drift);
607
608 // both coords are ready for math processing
609 // put coord to drift list
610 // Note: if we're not guiding, these will be overwritten,
611 // as driftUpto is only incremented when guiding.
612 drift[GUIDE_RA][driftUpto[GUIDE_RA]] = star_drift.x;
613 drift[GUIDE_DEC][driftUpto[GUIDE_DEC]] = star_drift.y;
614
615 qCDebug(KSTARS_EKOS_GUIDE)
616 << QString("Star %1 %2 a-s %3 %4 Target %5 %6 a-s %7 %8 Drift: RA %9 DEC %10")
617 .arg(starPosition.x, 0, 'f', 1) .arg(starPosition.y, 0, 'f', 1)
618 .arg(starPositionArcSec.x, 0, 'f', 1) .arg(starPositionArcSec.y, 0, 'f', 1)
619 .arg(targetPosition.x, 0, 'f', 1) .arg(targetPosition.y, 0, 'f', 1)
620 .arg(targetPositionArcSec.x, 0, 'f', 1).arg(targetPositionArcSec.y, 0, 'f', 1)
621 .arg(star_drift.x, 0, 'f', 2) .arg(star_drift.y, 0, 'f', 2);
622
623 if (state == Ekos::GUIDE_GUIDING && usingSEPMultiStar())
624 {
625 double multiStarRADrift, multiStarDECDrift;
626 if (guideStars.getDrift(sqrt(star_drift.x * star_drift.x + star_drift.y * star_drift.y),
627 targetPosition.x, targetPosition.y,
628 &multiStarRADrift, &multiStarDECDrift))
629 {
630 qCDebug(KSTARS_EKOS_GUIDE) << QString("MultiStar drift: RA %1 DEC %2")
631 .arg(multiStarRADrift, 0, 'f', 2)
632 .arg(multiStarDECDrift, 0, 'f', 2);
633 drift[GUIDE_RA][driftUpto[GUIDE_RA]] = multiStarRADrift;
634 drift[GUIDE_DEC][driftUpto[GUIDE_DEC]] = multiStarDECDrift;
635 }
636 else
637 {
638 qCDebug(KSTARS_EKOS_GUIDE) << "MultiStar: failed, fell back to guide star";
639 }
640 }
641
642 // driftUpto will change when the circular buffer is updated,
643 // so save the values for logging.
644 const double raDrift = drift[GUIDE_RA][driftUpto[GUIDE_RA]];
645 const double decDrift = drift[GUIDE_DEC][driftUpto[GUIDE_DEC]];
646
647 // make decision by axes
648 calculatePulses(state, timeStep);
649
650 if (state == Ekos::GUIDE_GUIDING)
651 {
652 calculateRmsError();
653 emitStats();
654 updateCircularBuffers();
655 }
656 qCDebug(KSTARS_EKOS_GUIDE) << QString("performProcessing took %1s").arg(timer.elapsed() / 1000.0, 0, 'f', 3);
657
658 if (logger != nullptr)
659 {
660 GuideLog::GuideData data;
661 data.type = GuideLog::GuideData::MOUNT;
662 // These are distances in pixels.
663 // Note--these don't include the multistar algorithm, but the below ra/dec ones do.
664 data.dx = starPosition.x - targetPosition.x;
665 data.dy = starPosition.y - targetPosition.y;
666 // Above computes position - reticle (star drift), but we want the mount drift, so negate.
667 calibration.convertToPixels(-raDrift, -decDrift, &data.raDistance, &data.decDistance);
668
669 const double raGuideFactor = out_params.pulse_dir[GUIDE_RA] == NO_DIR ?
670 0 : (out_params.pulse_dir[GUIDE_RA] == RA_INC_DIR ? -1.0 : 1.0);
671 const double decGuideFactor = out_params.pulse_dir[GUIDE_DEC] == NO_DIR ?
672 0 : (out_params.pulse_dir[GUIDE_DEC] == DEC_INC_DIR ? 1.0 : -1.0);
673
674 // Phd2LogViewer wants these in pixels instead of arcseconds, so normalizing them, but
675 // that will be wrong for non-square pixels. They should really accept arcsecond units.
676 data.raGuideDistance = calibration.xPixelsPerArcsecond() * raGuideFactor * out_params.pulse_length[GUIDE_RA] /
677 calibration.raPulseMillisecondsPerArcsecond();
678 data.decGuideDistance = calibration.yPixelsPerArcsecond() * decGuideFactor * out_params.pulse_length[GUIDE_DEC] /
679 calibration.decPulseMillisecondsPerArcsecond();
680
681 data.raDuration = out_params.pulse_dir[GUIDE_RA] == NO_DIR ? 0 : out_params.pulse_length[GUIDE_RA];
682 data.raDirection = out_params.pulse_dir[GUIDE_RA];
683 data.decDuration = out_params.pulse_dir[GUIDE_DEC] == NO_DIR ? 0 : out_params.pulse_length[GUIDE_DEC];
684 data.decDirection = out_params.pulse_dir[GUIDE_DEC];
685 data.code = GuideLog::GuideData::NO_ERRORS;
686 data.snr = guideStars.getGuideStarSNR();
687 data.mass = guideStars.getGuideStarMass();
688 // Add SNR and MASS from SEP stars.
689 logger->addGuideData(data);
690 }
691}
692
693void cgmath::performDarkGuiding(Ekos::GuideState state, const std::pair<Seconds, Seconds> &timeStep)
694{
695
696 const bool dithering = configureInParams(state);
697 //out_params.sigma[GUIDE_RA] = 0;
698
699 processAxis(GUIDE_RA, dithering, true, timeStep.first, "Dark Guiding:");
700 qCDebug(KSTARS_EKOS_GUIDE)
701 << QString("Dark Guiding pulses: RA: %1ms %2")
702 .arg(out_params.pulse_length[GUIDE_RA]).arg(directionStr(out_params.pulse_dir[GUIDE_RA]));
703
704
705 // Don't guide in DEC when dark guiding
706 updateOutParams(GUIDE_DEC, 0, 0, NO_DIR);
707
708 outputGuideLog();
709}
710
711void cgmath::emitStats()
712{
713 double pulseRA = 0;
714 if (out_params.pulse_dir[GUIDE_RA] == RA_DEC_DIR)
715 pulseRA = out_params.pulse_length[GUIDE_RA];
716 else if (out_params.pulse_dir[GUIDE_RA] == RA_INC_DIR)
717 pulseRA = -out_params.pulse_length[GUIDE_RA];
718 double pulseDEC = 0;
719 if (out_params.pulse_dir[GUIDE_DEC] == DEC_DEC_DIR)
720 pulseDEC = -out_params.pulse_length[GUIDE_DEC];
721 else if (out_params.pulse_dir[GUIDE_DEC] == DEC_INC_DIR)
722 pulseDEC = out_params.pulse_length[GUIDE_DEC];
723
724 const bool hasGuidestars = usingSEPMultiStar();
725 const double snr = hasGuidestars ? guideStars.getGuideStarSNR() : 0;
726 const double skyBG = hasGuidestars ? guideStars.skybackground().mean : 0;
727 const int numStars = hasGuidestars ? guideStars.skybackground().starsDetected : 0; // wait for rob's release
728
729 // analyze.cpp uses only one RA/DEC-axis (up:+, down:-), hence RA is negated.
730 emit guideStats(-out_params.delta[GUIDE_RA], out_params.delta[GUIDE_DEC],
731 pulseRA, pulseDEC, snr, skyBG, numStars);
732}
733
734void cgmath::calculateRmsError(void)
735{
736 if (!do_statistics)
737 return;
738
739 if (iterationCounter == 0)
740 return;
741
742 int count = std::min(iterationCounter, static_cast<unsigned int>(CIRCULAR_BUFFER_SIZE));
743 for (int k = GUIDE_RA; k <= GUIDE_DEC; k++)
744 {
745 // Calculate RMS using PHD2's formula: sqrt((n * sumYSq - sumY * sumY) / (n * n))
746 double sumY = 0;
747 double sumYSq = 0;
748
749 for (int i = 0; i < count; ++i)
750 {
751 sumY += drift[k][i];
752 sumYSq += drift[k][i] * drift[k][i];
753 }
754
755 // Use PHD2's formula for population standard deviation
756 double variance = count < 2 ? 0 : (count * sumYSq - sumY * sumY) / (count * count);
757 if (variance >= 0.0)
758 out_params.sigma[k] = sqrt(variance);
759 else
760 out_params.sigma[k] = 0.0;
761 }
762}
763
764
765QVector3D cgmath::selectGuideStar(const QSharedPointer<FITSData> &imageData)
766{
767 return guideStars.selectGuideStar(imageData);
768}
769
770double cgmath::getGuideStarSNR()
771{
772 return guideStars.getGuideStarSNR();
773}
774
775//---------------------------------------------------------------------------------------
776cproc_in_params::cproc_in_params()
777{
778 reset();
779}
780
781void cproc_in_params::reset(void)
782{
783 average = true;
784
785 for (int k = GUIDE_RA; k <= GUIDE_DEC; k++)
786 {
787 enabled[k] = true;
788 integral_gain[k] = 0;
789 max_pulse_arcsec[k] = 5000;
790 min_pulse_arcsec[k] = 0;
791 }
792}
793
794cproc_out_params::cproc_out_params()
795{
796 reset();
797}
798
799void cproc_out_params::reset(void)
800{
801 for (int k = GUIDE_RA; k <= GUIDE_DEC; k++)
802 {
803 delta[k] = 0;
804 pulse_dir[k] = NO_DIR;
805 pulse_length[k] = 0;
806 sigma[k] = 0;
807 }
808}
QString i18n(const char *text, const TYPE &arg...)
KIOCORE_EXPORT QString dir(const QString &fileClass)
QString label(StandardShortcut id)
QCA_EXPORT Logger * logger()
qint64 elapsed() const const
QTextStream & endl(QTextStream &stream)
This file is part of the KDE documentation.
Documentation copyright © 1996-2025 The KDE developers.
Generated on Fri Apr 25 2025 11:58:35 by doxygen 1.13.2 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.