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

KDE's Doxygen guidelines are available online.