1 /*
2 * guider.cpp
3 * PHD Guiding
4 *
5 * Created by Bret McKee
6 * Copyright (c) 2012 Bret McKee
7 * All rights reserved.
8 *
9 * This source code is distributed under the following "BSD" license
10 * Redistribution and use in source and binary forms, with or without
11 * modification, are permitted provided that the following conditions are met:
12 * Redistributions of source code must retain the above copyright notice,
13 * this list of conditions and the following disclaimer.
14 * Redistributions in binary form must reproduce the above copyright notice,
15 * this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * Neither the name of Craig Stark, Stark Labs nor the names of its
18 * contributors may be used to endorse or promote products derived from
19 * this software without specific prior written permission.
20 *
21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
22 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
23 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
25 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
26 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
27 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
29 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
30 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31 * POSSIBILITY OF SUCH DAMAGE.
32 *
33 */
34 #include "phd.h"
35 #include "nudge_lock.h"
36 #include "comet_tool.h"
37 #include "polardrift_tool.h"
38 #include "staticpa_tool.h"
39 #include "guiding_assistant.h"
40
41 // un-comment to log star deflections to a file
42 //#define CAPTURE_DEFLECTIONS
43
44 struct DeflectionLogger
45 {
46 #ifdef CAPTURE_DEFLECTIONS
47 wxFFile *m_file;
48 PHD_Point m_lastPos;
49 #endif
50
51 void Init();
52 void Uninit();
53 void Log(const PHD_Point& pos);
54 };
55 static DeflectionLogger s_deflectionLogger;
56
57 #ifdef CAPTURE_DEFLECTIONS
58
Init()59 void DeflectionLogger::Init()
60 {
61 m_file = new wxFFile();
62 wxDateTime now = wxDateTime::UNow();
63 wxString pathname = Debug.GetLogDir() + PATHSEPSTR + now.Format(_T("star_displacement_%Y-%m-%d_%H%M%S.csv"));
64 m_file->Open(pathname, "w");
65 m_lastPos.Invalidate();
66 }
67
Uninit()68 void DeflectionLogger::Uninit()
69 {
70 delete m_file;
71 m_file = 0;
72 }
73
Log(const PHD_Point & pos)74 void DeflectionLogger::Log(const PHD_Point& pos)
75 {
76 if (m_lastPos.IsValid())
77 {
78 PHD_Point mountpt;
79 pMount->TransformCameraCoordinatesToMountCoordinates(pos - m_lastPos, mountpt);
80 m_file->Write(wxString::Format("%0.2f,%0.2f\n", mountpt.X, mountpt.Y));
81 }
82 else
83 {
84 m_file->Write(wxString::Format("DeltaRA, DeltaDec, Scale=%0.2f\n", pFrame->GetCameraPixelScale()));
85 if (pMount->GetGuidingEnabled())
86 pFrame->Alert("GUIDING IS ACTIVE!!! Star displacements will be useless!");
87 }
88
89 m_lastPos = pos;
90 }
91
92 #else // CAPTURE_DEFLECTIONS
93
Init()94 inline void DeflectionLogger::Init() { }
Uninit()95 inline void DeflectionLogger::Uninit() { }
Log(const PHD_Point &)96 inline void DeflectionLogger::Log(const PHD_Point&) { }
97
98 #endif // CAPTURE_DEFLECTIONS
99
100 static const int DefaultOverlayMode = OVERLAY_NONE;
101 static const bool DefaultScaleImage = true;
102
BEGIN_EVENT_TABLE(Guider,wxWindow)103 BEGIN_EVENT_TABLE(Guider, wxWindow)
104 EVT_PAINT(Guider::OnPaint)
105 EVT_CLOSE(Guider::OnClose)
106 EVT_ERASE_BACKGROUND(Guider::OnErase)
107 END_EVENT_TABLE()
108
109 static void SaveBookmarks(const std::vector<wxRealPoint>& vec)
110 {
111 std::ostringstream os;
112 os.setf(std::ios::fixed);
113 os.precision(5);
114 for (auto it = vec.begin(); it != vec.end(); ++it)
115 os << it->x << ' ' << it->y << ' ';
116 pConfig->Profile.SetString("/guider/bookmarks", os.str().c_str());
117 }
118
LoadBookmarks(std::vector<wxRealPoint> * vec)119 static void LoadBookmarks(std::vector<wxRealPoint> *vec)
120 {
121 wxString s(pConfig->Profile.GetString("/guider/bookmarks", wxEmptyString));
122 std::istringstream is(static_cast<const char *>(s.c_str()));
123
124 vec->clear();
125
126 while (true)
127 {
128 double x, y;
129 is >> x >> y;
130 if (!is.good())
131 break;
132 vec->push_back(wxRealPoint(x, y));
133 }
134 }
135
StateStr(GUIDER_STATE st)136 static const wxStringCharType *StateStr(GUIDER_STATE st)
137 {
138 switch (st) {
139 case STATE_UNINITIALIZED: return wxS("UNINITIALIZED");
140 case STATE_SELECTING: return wxS("SELECTING");
141 case STATE_SELECTED: return wxS("SELECTED");
142 case STATE_CALIBRATING_PRIMARY: return wxS("CALIBRATING_PRIMARY");
143 case STATE_CALIBRATING_SECONDARY: return wxS("CALIBRATING_SECONDARY");
144 case STATE_CALIBRATED: return wxS("CALIBRATED");
145 case STATE_GUIDING: return wxS("GUIDING");
146 case STATE_STOP: return wxS("STOP");
147 default: return wxS("??");
148 }
149 }
150
Guider(wxWindow * parent,int xSize,int ySize)151 Guider::Guider(wxWindow *parent, int xSize, int ySize) :
152 wxWindow(parent, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxFULL_REPAINT_ON_RESIZE)
153 {
154 m_state = STATE_UNINITIALIZED;
155 Debug.Write(wxString::Format("guider state => %s\n", StateStr(m_state)));
156 m_scaleFactor = 1.0;
157 m_showBookmarks = true;
158 m_displayedImage = new wxImage(XWinSize,YWinSize,true);
159 m_paused = PAUSE_NONE;
160 m_starFoundTimestamp = 0;
161 m_avgDistanceNeedReset = false;
162 m_avgDistanceCnt = 0;
163 m_lockPosShift.shiftEnabled = false;
164 m_lockPosShift.shiftRate.SetXY(0., 0.);
165 m_lockPosShift.shiftUnits = UNIT_ARCSEC;
166 m_lockPosShift.shiftIsMountCoords = true;
167 m_lockPosIsSticky = false;
168 m_ignoreLostStarLooping = false;
169 m_forceFullFrame = false;
170 m_measurementMode = false;
171 m_searchRegion = 0;
172 m_pCurrentImage = new usImage(); // so we always have one
173
174 SetOverlayMode(DefaultOverlayMode);
175
176 wxPoint center;
177 center.x = pConfig->Profile.GetInt("/overlay/slit/center.x", 752 / 2);
178 center.y = pConfig->Profile.GetInt("/overlay/slit/center.y", 580 / 2);
179 wxSize size;
180 size.x = pConfig->Profile.GetInt("/overlay/slit/width", 8);
181 size.y = pConfig->Profile.GetInt("/overlay/slit/height", 100);
182 int angle = pConfig->Profile.GetInt("/overlay/slit/angle", 0);
183 SetOverlaySlitCoords(center, size, angle);
184
185 m_defectMapPreview = 0;
186
187 m_polarAlignCircleRadius = 0.0;
188 m_polarAlignCircleCorrection = 1.0;
189
190 SetBackgroundStyle(wxBG_STYLE_CUSTOM);
191 SetBackgroundColour(wxColour((unsigned char) 30, (unsigned char) 30,(unsigned char) 30));
192
193 s_deflectionLogger.Init();
194 }
195
~Guider()196 Guider::~Guider()
197 {
198 delete m_displayedImage;
199 delete m_pCurrentImage;
200
201 s_deflectionLogger.Uninit();
202 }
203
LoadProfileSettings()204 void Guider::LoadProfileSettings()
205 {
206 bool enableFastRecenter = pConfig->Profile.GetBoolean("/guider/FastRecenter", true);
207 EnableFastRecenter(enableFastRecenter);
208
209 bool scaleImage = pConfig->Profile.GetBoolean("/guider/ScaleImage", DefaultScaleImage);
210 SetScaleImage(scaleImage);
211
212 double minHFD = pConfig->Profile.GetDouble("/guider/StarMinHFD", 0.);
213 SetMinStarHFD(minHFD);
214
215 double minSNR = pConfig->Profile.GetDouble("/guider/StarMinSNR", 6.0);
216 SetMinStarSNR(minSNR);
217
218 unsigned int autoSelDownsample = wxMax(0, pConfig->Profile.GetInt("/guider/AutoSelDownsample", 0));
219 SetAutoSelDownsample(autoSelDownsample);
220
221 LoadBookmarks(&m_bookmarks);
222
223 // clear the display
224 if (m_pCurrentImage->ImageData)
225 {
226 delete m_displayedImage;
227 m_displayedImage = new wxImage(XWinSize, YWinSize, true);
228 DisplayImage(new usImage());
229 }
230 }
231
SetPaused(PauseType pause)232 PauseType Guider::SetPaused(PauseType pause)
233 {
234 Debug.Write(wxString::Format("Guider::SetPaused(%d)\n", pause));
235
236 PauseType prev = m_paused;
237 m_paused = pause;
238
239 if (prev == PAUSE_FULL && pause != prev)
240 {
241 Debug.Write("Guider::SetPaused: resetting avg dist filter\n");
242 m_avgDistanceNeedReset = true;
243 }
244
245 if (pause != prev)
246 {
247 Refresh();
248 Update();
249 }
250
251 return prev;
252 }
253
ForceFullFrame()254 void Guider::ForceFullFrame()
255 {
256 if (!m_forceFullFrame)
257 {
258 Debug.Write("setting force full frames = true\n");
259 m_forceFullFrame = true;
260 }
261 }
262
SetIgnoreLostStarLooping(bool ignore)263 void Guider::SetIgnoreLostStarLooping(bool ignore)
264 {
265 if (m_ignoreLostStarLooping != ignore)
266 {
267 Debug.Write(wxString::Format("setting ignore lost star looping = %s\n", ignore ? "true" : "false"));
268 m_ignoreLostStarLooping = ignore;
269 }
270 }
271
SetOverlayMode(int overlayMode)272 bool Guider::SetOverlayMode(int overlayMode)
273 {
274 bool bError = false;
275
276 try
277 {
278 switch (overlayMode)
279 {
280 case OVERLAY_NONE:
281 case OVERLAY_BULLSEYE:
282 case OVERLAY_GRID_FINE:
283 case OVERLAY_GRID_COARSE:
284 case OVERLAY_RADEC:
285 case OVERLAY_SLIT:
286 break;
287 default:
288 throw ERROR_INFO("invalid overlayMode");
289 }
290
291 m_overlayMode = (OVERLAY_MODE) overlayMode;
292 }
293 catch (const wxString& Msg)
294 {
295 POSSIBLY_UNUSED(Msg);
296 m_overlayMode = OVERLAY_NONE;
297 bError = true;
298 }
299
300 Refresh();
301 Update();
302
303 return bError;
304 }
305
GetOverlaySlitCoords(wxPoint * center,wxSize * size,int * angle)306 void Guider::GetOverlaySlitCoords(wxPoint *center, wxSize *size, int *angle)
307 {
308 *center = m_overlaySlitCoords.center;
309 *size = m_overlaySlitCoords.size;
310 *angle = m_overlaySlitCoords.angle;
311 }
312
EnableMeasurementMode(bool enable)313 void Guider::EnableMeasurementMode(bool enable)
314 {
315 if (enable)
316 {
317 if (m_state == STATE_GUIDING)
318 m_measurementMode = true;
319 }
320 else
321 m_measurementMode = false;
322 }
323
SetOverlaySlitCoords(const wxPoint & center,const wxSize & size,int angle)324 void Guider::SetOverlaySlitCoords(const wxPoint& center, const wxSize& size, int angle)
325 {
326 m_overlaySlitCoords.center = center;
327 m_overlaySlitCoords.size = size;
328 m_overlaySlitCoords.angle = angle;
329
330 pConfig->Profile.SetInt("/overlay/slit/center.x", center.x);
331 pConfig->Profile.SetInt("/overlay/slit/center.y", center.y);
332 pConfig->Profile.SetInt("/overlay/slit/width", size.x);
333 pConfig->Profile.SetInt("/overlay/slit/height", size.y);
334 pConfig->Profile.SetInt("/overlay/slit/angle", angle);
335
336 if (size.GetWidth() > 0 && size.GetHeight() > 0)
337 {
338 if (angle != 0)
339 {
340 double a = -radians((double)angle);
341 double s = sin(a);
342 double c = cos(a);
343 double cx = (double)center.x;
344 double cy = (double)center.y;
345 double x, y;
346
347 x = +(double)size.GetWidth() / 2.0;
348 y = +(double)size.GetHeight() / 2.0;
349 m_overlaySlitCoords.corners[0].x = cx + (int)floor(x * c - y * s);
350 m_overlaySlitCoords.corners[0].y = cy + (int)floor(x * s + y * c);
351
352 x = -(double)size.GetWidth() / 2.0;
353 y = +(double)size.GetHeight() / 2.0;
354 m_overlaySlitCoords.corners[1].x = cx + (int)floor(x * c - y * s);
355 m_overlaySlitCoords.corners[1].y = cy + (int)floor(x * s + y * c);
356
357 x = -(double)size.GetWidth() / 2.0;
358 y = -(double)size.GetHeight() / 2.0;
359 m_overlaySlitCoords.corners[2].x = cx + (int)floor(x * c - y * s);
360 m_overlaySlitCoords.corners[2].y = cy + (int)floor(x * s + y * c);
361
362 x = +(double)size.GetWidth() / 2.0;
363 y = -(double)size.GetHeight() / 2.0;
364 m_overlaySlitCoords.corners[3].x = cx + (int)floor(x * c - y * s);
365 m_overlaySlitCoords.corners[3].y = cy + (int)floor(x * s + y * c);
366 }
367 else
368 {
369 m_overlaySlitCoords.corners[0] = wxPoint(center.x + size.GetWidth() / 2, center.y + size.GetHeight() / 2);
370 m_overlaySlitCoords.corners[1] = wxPoint(center.x - size.GetWidth() / 2, center.y + size.GetHeight() / 2);
371 m_overlaySlitCoords.corners[2] = wxPoint(center.x - size.GetWidth() / 2, center.y - size.GetHeight() / 2);
372 m_overlaySlitCoords.corners[3] = wxPoint(center.x + size.GetWidth() / 2, center.y - size.GetHeight() / 2);
373 }
374
375 m_overlaySlitCoords.corners[4] = m_overlaySlitCoords.corners[0];
376 }
377
378 Refresh();
379 Update();
380 }
381
EnableFastRecenter(bool enable)382 void Guider::EnableFastRecenter(bool enable)
383 {
384 m_fastRecenterEnabled = enable;
385 pConfig->Profile.SetInt("/guider/FastRecenter", m_fastRecenterEnabled);
386 }
387
SetPolarAlignCircle(const PHD_Point & pt,double radius)388 void Guider::SetPolarAlignCircle(const PHD_Point& pt, double radius)
389 {
390 m_polarAlignCircleRadius = radius;
391 m_polarAlignCircleCenter = pt;
392 }
393
SetScaleImage(bool newScaleValue)394 bool Guider::SetScaleImage(bool newScaleValue)
395 {
396 bool bError = false;
397
398 try
399 {
400 m_scaleImage = newScaleValue;
401 }
402 catch (const wxString& Msg)
403 {
404 POSSIBLY_UNUSED(Msg);
405 bError = true;
406 }
407
408 pConfig->Profile.SetBoolean("/guider/ScaleImage", m_scaleImage);
409 return bError;
410 }
411
OnErase(wxEraseEvent & evt)412 void Guider::OnErase(wxEraseEvent& evt)
413 {
414 evt.Skip();
415 }
416
OnClose(wxCloseEvent & evt)417 void Guider::OnClose(wxCloseEvent& evt)
418 {
419 Destroy();
420 }
421
PaintHelper(wxAutoBufferedPaintDCBase & dc,wxMemoryDC & memDC)422 bool Guider::PaintHelper(wxAutoBufferedPaintDCBase& dc, wxMemoryDC& memDC)
423 {
424 bool bError = false;
425
426 try
427 {
428 GUIDER_STATE state = GetState();
429 GetSize(&XWinSize, &YWinSize);
430
431 if (m_pCurrentImage->ImageData)
432 {
433 int blevel = m_pCurrentImage->FiltMin;
434 int wlevel = m_pCurrentImage->FiltMax;
435 m_pCurrentImage->CopyToImage(&m_displayedImage, blevel, wlevel, pFrame->Stretch_gamma);
436 }
437
438 int imageWidth = m_displayedImage->GetWidth();
439 int imageHeight = m_displayedImage->GetHeight();
440
441 // scale the image if necessary
442
443 if (imageWidth != XWinSize || imageHeight != YWinSize)
444 {
445 // The image is not the exact right size -- figure out what to do.
446 double xScaleFactor = imageWidth / (double)XWinSize;
447 double yScaleFactor = imageHeight / (double)YWinSize;
448 int newWidth = imageWidth;
449 int newHeight = imageHeight;
450
451 double newScaleFactor = (xScaleFactor > yScaleFactor) ?
452 xScaleFactor :
453 yScaleFactor;
454
455 // Debug.Write(wxString::Format("xScaleFactor=%.2f, yScaleFactor=%.2f, newScaleFactor=%.2f\n", xScaleFactor,
456 // yScaleFactor, newScaleFactor));
457
458 // we rescale the image if:
459 // - The image is either too big
460 // - The image is so small that at least one dimension is less
461 // than half the width of the window or
462 // - The user has requsted rescaling
463
464 if (xScaleFactor > 1.0 || yScaleFactor > 1.0 ||
465 xScaleFactor < 0.45 || yScaleFactor < 0.45 || m_scaleImage)
466 {
467
468 newWidth /= newScaleFactor;
469 newHeight /= newScaleFactor;
470
471 newScaleFactor = 1.0 / newScaleFactor;
472
473 m_scaleFactor = newScaleFactor;
474
475 if (imageWidth != newWidth || imageHeight != newHeight)
476 {
477 // Debug.Write(wxString::Format("Resizing image to %d,%d\n", newWidth, newHeight));
478
479 if (newWidth > 0 && newHeight > 0)
480 {
481 m_displayedImage->Rescale(newWidth, newHeight, wxIMAGE_QUALITY_HIGH);
482 }
483 }
484 }
485 else
486 {
487 m_scaleFactor = 1.0;
488 }
489 }
490
491 // important to provide explicit color for r,g,b, optional args to Size().
492 // If default args are provided wxWidgets performs some expensive histogram
493 // operations.
494 wxBitmap DisplayedBitmap(m_displayedImage->Size(wxSize(XWinSize, YWinSize), wxPoint(0, 0), 0, 0, 0));
495 memDC.SelectObject(DisplayedBitmap);
496
497 dc.Blit(0, 0, DisplayedBitmap.GetWidth(), DisplayedBitmap.GetHeight(), &memDC, 0, 0, wxCOPY, false);
498
499 int XImgSize = m_displayedImage->GetWidth();
500 int YImgSize = m_displayedImage->GetHeight();
501
502 if (m_overlayMode)
503 {
504 dc.SetPen(wxPen(wxColor(200,50,50)));
505 dc.SetBrush(*wxTRANSPARENT_BRUSH);
506
507 switch (m_overlayMode)
508 {
509 case OVERLAY_BULLSEYE:
510 {
511 int cx = XImgSize / 2;
512 int cy = YImgSize / 2;
513 dc.DrawCircle(cx,cy,25);
514 dc.DrawCircle(cx,cy,50);
515 dc.DrawCircle(cx,cy,100);
516 dc.DrawLine(0, cy, XImgSize, cy);
517 dc.DrawLine(cx, 0, cx, YImgSize);
518 break;
519 }
520 case OVERLAY_GRID_FINE:
521 case OVERLAY_GRID_COARSE:
522 {
523 int i;
524 int size = (m_overlayMode - 1) * 20;
525 for (i=size; i<XImgSize; i+=size)
526 dc.DrawLine(i,0,i,YImgSize);
527 for (i=size; i<YImgSize; i+=size)
528 dc.DrawLine(0,i,XImgSize,i);
529 break;
530 }
531
532 case OVERLAY_RADEC:
533 {
534 Mount *mount = TheScope();
535 if (mount)
536 {
537 double StarX = CurrentPosition().X;
538 double StarY = CurrentPosition().Y;
539
540 double r = 15.0;
541 double rlabel = r + 9.0;
542
543 double wAngle = mount->IsCalibrated() ? mount->xAngle() : 0.0;
544 double eAngle = wAngle + M_PI;
545 GuideParity raParity = mount->RAParity();
546 if (raParity == GUIDE_PARITY_ODD)
547 {
548 // odd parity => West calibration pulses move scope East
549 // => star moves West
550 // => East vector is opposite direction from X calibration vector (West calibration direction)
551 eAngle += M_PI;
552 }
553 double cos_eangle = cos(eAngle);
554 double sin_eangle = sin(eAngle);
555 dc.SetPen(wxPen(pFrame->pGraphLog->GetRaOrDxColor(), 2, wxPENSTYLE_DOT));
556 dc.DrawLine(ROUND(StarX * m_scaleFactor + r * cos_eangle), ROUND(StarY * m_scaleFactor + r * sin_eangle),
557 ROUND(StarX * m_scaleFactor - r * cos_eangle), ROUND(StarY * m_scaleFactor - r * sin_eangle));
558 if (raParity != GUIDE_PARITY_UNKNOWN)
559 {
560 dc.SetTextForeground(pFrame->pGraphLog->GetRaOrDxColor());
561 dc.DrawText(_("E"),
562 ROUND(StarX * m_scaleFactor + rlabel * cos_eangle) - 4, ROUND(StarY * m_scaleFactor + rlabel * sin_eangle) - 6);
563 }
564
565 double nAngle = mount->IsCalibrated() ? mount->yAngle() : M_PI / 2.0;
566 GuideParity decParity = mount->DecParity();
567 if (decParity == GUIDE_PARITY_EVEN)
568 {
569 // even parity => North calibration pulses move scope North
570 // => star moves South
571 // => North vector is opposite direction from Y calibration vector (North calibration direction)
572 nAngle += M_PI;
573 }
574 double cos_nangle = cos(nAngle);
575 double sin_nangle = sin(nAngle);
576 dc.SetPen(wxPen(pFrame->pGraphLog->GetDecOrDyColor(), 2, wxPENSTYLE_DOT));
577 dc.DrawLine(ROUND(StarX * m_scaleFactor + r * cos_nangle), ROUND(StarY * m_scaleFactor + r * sin_nangle),
578 ROUND(StarX * m_scaleFactor - r * cos_nangle), ROUND(StarY * m_scaleFactor - r * sin_nangle));
579 if (decParity != GUIDE_PARITY_UNKNOWN)
580 {
581 dc.SetTextForeground(pFrame->pGraphLog->GetDecOrDyColor());
582 dc.DrawText(_("N"),
583 ROUND(StarX * m_scaleFactor + rlabel * cos_nangle) - 4, ROUND(StarY * m_scaleFactor + rlabel * sin_nangle) - 6);
584 }
585
586 wxGraphicsContext *gc = wxGraphicsContext::Create(dc);
587 gc->SetPen(wxPen(pFrame->pGraphLog->GetRaOrDxColor(), 1, wxPENSTYLE_DOT));
588 double step = (double) YImgSize / 10.0;
589
590 double MidX = (double) XImgSize / 2.0;
591 double MidY = (double) YImgSize / 2.0;
592 gc->Rotate(eAngle);
593 gc->GetTransform().TransformPoint(&MidX, &MidY);
594 gc->Rotate(-eAngle);
595 gc->Translate((double) XImgSize / 2.0 - MidX, (double) YImgSize / 2.0 - MidY);
596 gc->Rotate(eAngle);
597 for (int i = -2; i < 12; i++) {
598 gc->StrokeLine(0.0,step * (double) i,
599 (double) XImgSize, step * (double) i);
600 }
601
602 MidX = (double) XImgSize / 2.0;
603 MidY = (double) YImgSize / 2.0;
604 gc->Rotate(-eAngle);
605 gc->Rotate(nAngle);
606 gc->GetTransform().TransformPoint(&MidX, &MidY);
607 gc->Rotate(-nAngle);
608 gc->Translate((double) XImgSize / 2.0 - MidX, (double) YImgSize / 2.0 - MidY);
609 gc->Rotate(nAngle);
610 gc->SetPen(wxPen(pFrame->pGraphLog->GetDecOrDyColor(),1,wxPENSTYLE_DOT));
611 for (int i = -2; i < 12; i++) {
612 gc->StrokeLine(0.0,step * (double) i,
613 (double) XImgSize, step * (double) i);
614 }
615 delete gc;
616 }
617 break;
618 }
619
620 case OVERLAY_SLIT:
621 if (m_overlaySlitCoords.size.GetWidth() > 0 && m_overlaySlitCoords.size.GetHeight() > 0)
622 {
623 if (m_scaleFactor == 1.0)
624 {
625 dc.DrawLines(5, m_overlaySlitCoords.corners);
626 }
627 else
628 {
629 wxPoint pt[5];
630 for (int i = 0; i < 5; i++)
631 {
632 pt[i].x = (int) floor(m_overlaySlitCoords.corners[i].x * m_scaleFactor);
633 pt[i].y = (int) floor(m_overlaySlitCoords.corners[i].y * m_scaleFactor);
634 }
635 dc.DrawLines(5, pt);
636 }
637 }
638 break;
639
640 case OVERLAY_NONE:
641 break;
642 }
643 }
644
645 if (m_defectMapPreview)
646 {
647 dc.SetPen(wxPen(wxColor(255, 0, 0), 1, wxPENSTYLE_SOLID));
648 for (DefectMap::const_iterator it = m_defectMapPreview->begin(); it != m_defectMapPreview->end(); ++it)
649 {
650 const wxPoint& pt = *it;
651 dc.DrawPoint((int)(pt.x * m_scaleFactor), (int)(pt.y * m_scaleFactor));
652 }
653 }
654
655 // draw the lockpoint of there is one
656 if (state > STATE_SELECTED)
657 {
658 double LockX = LockPosition().X;
659 double LockY = LockPosition().Y;
660
661 switch (state)
662 {
663 case STATE_UNINITIALIZED:
664 case STATE_SELECTING:
665 case STATE_SELECTED:
666 case STATE_STOP:
667 break;
668 case STATE_CALIBRATING_PRIMARY:
669 case STATE_CALIBRATING_SECONDARY:
670 dc.SetPen(wxPen(wxColor(255,255,0),1, wxPENSTYLE_DOT));
671 break;
672 case STATE_CALIBRATED:
673 case STATE_GUIDING:
674 dc.SetPen(wxPen(wxColor(0,255,0)));
675 break;
676 }
677
678 dc.DrawLine(0, int(LockY * m_scaleFactor), XImgSize, int(LockY * m_scaleFactor));
679 dc.DrawLine(int(LockX * m_scaleFactor), 0, int(LockX * m_scaleFactor), YImgSize);
680 }
681
682 // draw a polar alignment circle
683 if (m_polarAlignCircleRadius)
684 {
685 dc.SetBrush(*wxTRANSPARENT_BRUSH);
686 wxPenStyle penStyle = m_polarAlignCircleCorrection == 1.0 ? wxPENSTYLE_DOT : wxPENSTYLE_SOLID;
687 dc.SetPen(wxPen(wxColor(255,0,255), 1, penStyle));
688 int radius = ROUND(m_polarAlignCircleRadius * m_polarAlignCircleCorrection * m_scaleFactor);
689 dc.DrawCircle(m_polarAlignCircleCenter.X * m_scaleFactor,
690 m_polarAlignCircleCenter.Y * m_scaleFactor, radius);
691 }
692
693 // draw static polar align stuff
694 PolarDriftTool::PaintHelper(dc, m_scaleFactor);
695 StaticPaTool::PaintHelper(dc, m_scaleFactor);
696
697 if (IsPaused())
698 {
699 dc.SetTextForeground(*wxYELLOW);
700 dc.DrawText(_("PAUSED"), 10, YWinSize - 20);
701 }
702 else if (pMount && !pMount->GetGuidingEnabled())
703 {
704 dc.SetTextForeground(*wxYELLOW);
705 dc.DrawText(_("Guide output DISABLED"), 10, YWinSize - 20);
706 }
707 }
708 catch (const wxString& Msg)
709 {
710 POSSIBLY_UNUSED(Msg);
711 bError = true;
712 }
713
714 return bError;
715 }
716
UpdateImageDisplay(usImage * pImage)717 void Guider::UpdateImageDisplay(usImage *pImage)
718 {
719 if (!pImage)
720 {
721 pImage = m_pCurrentImage;
722 }
723
724 Debug.Write(wxString::Format("UpdateImageDisplay: Size=(%d,%d) min=%u, max=%u, med=%u, FiltMin=%u, FiltMax=%u, Gamma=%.3f\n",
725 pImage->Size.x, pImage->Size.y, pImage->MinADU, pImage->MaxADU, pImage->MedianADU,
726 pImage->FiltMin, pImage->FiltMax, pFrame->Stretch_gamma));
727
728 Refresh();
729 Update();
730 }
731
SetDefectMapPreview(const DefectMap * defectMap)732 void Guider::SetDefectMapPreview(const DefectMap *defectMap)
733 {
734 m_defectMapPreview = defectMap;
735 Refresh();
736 Update();
737 }
738
SaveCurrentImage(const wxString & fileName)739 bool Guider::SaveCurrentImage(const wxString& fileName)
740 {
741 return m_pCurrentImage->Save(fileName);
742 }
743
InvalidateLockPosition()744 void Guider::InvalidateLockPosition()
745 {
746 if (m_lockPosition.IsValid())
747 EvtServer.NotifyLockPositionLost();
748 m_lockPosition.Invalidate();
749 NudgeLockTool::UpdateNudgeLockControls();
750 }
751
SetLockPosition(const PHD_Point & position)752 bool Guider::SetLockPosition(const PHD_Point& position)
753 {
754 bool bError = false;
755
756 try
757 {
758 if (!position.IsValid())
759 {
760 throw ERROR_INFO("Point is not valid");
761 }
762
763 double x = position.X;
764 double y = position.Y;
765 Debug.Write(wxString::Format("setting lock position to (%.2f, %.2f)\n", x, y));
766
767 if ((x < 0.0) || (x >= m_pCurrentImage->Size.x))
768 {
769 throw ERROR_INFO("invalid x value");
770 }
771
772 if ((y < 0.0) || (y >= m_pCurrentImage->Size.y))
773 {
774 throw ERROR_INFO("invalid y value");
775 }
776
777 if (!m_lockPosition.IsValid() || position.X != m_lockPosition.X || position.Y != m_lockPosition.Y)
778 {
779 EvtServer.NotifySetLockPosition(position);
780 if (m_state == STATE_GUIDING)
781 {
782 // let guide algorithms react to the updated lock pos
783 pMount->NotifyGuidingDithered(position.X - m_lockPosition.X, position.Y - m_lockPosition.Y, false);
784 GuideLog.NotifySetLockPosition(this);
785 }
786 NudgeLockTool::UpdateNudgeLockControls();
787 }
788
789 m_lockPosition.SetXY(x, y);
790 }
791 catch (const wxString& Msg)
792 {
793 POSSIBLY_UNUSED(Msg);
794 bError = true;
795 }
796
797 return bError;
798 }
799
SetLockPosToStarAtPosition(const PHD_Point & starPosHint)800 bool Guider::SetLockPosToStarAtPosition(const PHD_Point& starPosHint)
801 {
802 bool error = SetCurrentPosition(m_pCurrentImage, starPosHint);
803
804 if (!error && CurrentPosition().IsValid())
805 {
806 SetLockPosition(CurrentPosition());
807 }
808
809 return error;
810 }
811
812 // distance to nearest edge
edgeDist(const PHD_Point & pt,const wxSize & size)813 static double edgeDist(const PHD_Point& pt, const wxSize& size)
814 {
815 return wxMin(pt.X, wxMin(size.GetWidth() - pt.X, wxMin(pt.Y, size.GetHeight() - pt.Y)));
816 }
817
MoveLockPosition(const PHD_Point & mountDeltaArg)818 bool Guider::MoveLockPosition(const PHD_Point& mountDeltaArg)
819 {
820 bool err = false;
821
822 try
823 {
824 if (!mountDeltaArg.IsValid())
825 {
826 throw ERROR_INFO("Point is not valid");
827 }
828
829 if (!pMount || !pMount->IsCalibrated())
830 {
831 throw ERROR_INFO("No mount");
832 }
833
834 const usImage *image = CurrentImage();
835 if (!image)
836 {
837 throw ERROR_INFO("cannot move lock pos without an image");
838 }
839
840 // This loop is to handle dithers when the star is near the edge of the frame. The strategy
841 // is to try reflecting the requested dither in 4 directions along the RA/Dec axes; if any
842 // of the projections results in a valid lock position, use it. Otherwise, choose the
843 // direction that moves farthest from the edge of the frame.
844
845 PHD_Point cameraDelta, mountDelta;
846 double dbest;
847
848 for (int q = 0; q < 4; q++)
849 {
850 int sx = 1 - ((q & 1) << 1);
851 int sy = 1 - (q & 2);
852
853 PHD_Point tmpMount(mountDeltaArg.X * sx, mountDeltaArg.Y * sy);
854 PHD_Point tmpCamera;
855
856 if (pMount->TransformMountCoordinatesToCameraCoordinates(tmpMount, tmpCamera))
857 {
858 throw ERROR_INFO("Transform failed");
859 }
860
861 PHD_Point tmpLockPosition = m_lockPosition + tmpCamera;
862
863 if (IsValidLockPosition(tmpLockPosition))
864 {
865 cameraDelta = tmpCamera;
866 mountDelta = tmpMount;
867 break;
868 }
869
870 Debug.Write("dither produces an invalid lock position, try a variation\n");
871
872 double d = edgeDist(tmpLockPosition, image->Size);
873 if (q == 0 || d > dbest)
874 {
875 cameraDelta = tmpCamera;
876 mountDelta = tmpMount;
877 dbest = d;
878 }
879 }
880
881 PHD_Point newLockPosition = m_lockPosition + cameraDelta;
882 if (SetLockPosition(newLockPosition))
883 {
884 throw ERROR_INFO("SetLockPosition failed");
885 }
886
887 // update average distance right away so GetCurrentDistance reflects the increased distance from the dither
888 double dist = cameraDelta.Distance(), distRA = fabs(mountDelta.X);
889 m_avgDistance += dist;
890 m_avgDistanceLong += dist;
891 m_avgDistanceRA += distRA;
892 m_avgDistanceLongRA += distRA;
893
894 if (IsFastRecenterEnabled())
895 {
896 m_ditherRecenterRemaining.SetXY(fabs(mountDelta.X), fabs(mountDelta.Y));
897 m_ditherRecenterDir.x = mountDelta.X < 0.0 ? 1 : -1;
898 m_ditherRecenterDir.y = mountDelta.Y < 0.0 ? 1 : -1;
899 // make each step a bit less than the full search region distance to avoid losing the star
900 double f = ((double) GetMaxMovePixels() * 0.7) / m_ditherRecenterRemaining.Distance();
901 m_ditherRecenterStep.SetXY(f * m_ditherRecenterRemaining.X, f * m_ditherRecenterRemaining.Y);
902 }
903 }
904 catch (const wxString& Msg)
905 {
906 POSSIBLY_UNUSED(Msg);
907 err = true;
908 }
909
910 return err;
911 }
912
SetState(GUIDER_STATE newState)913 void Guider::SetState(GUIDER_STATE newState)
914 {
915 try
916 {
917 Debug.Write(wxString::Format("Changing from state %s to %s\n", StateStr(m_state), StateStr(newState)));
918
919 if (newState == STATE_STOP)
920 {
921 // we are going to stop looping exposures. We should put
922 // ourselves into a good state to restart looping later
923 switch (m_state)
924 {
925 case STATE_UNINITIALIZED:
926 case STATE_SELECTING:
927 case STATE_SELECTED:
928 newState = m_state;
929 break;
930 case STATE_CALIBRATING_PRIMARY:
931 case STATE_CALIBRATING_SECONDARY:
932 // because we have done some moving here, we need to just
933 // start over...
934 newState = STATE_UNINITIALIZED;
935 break;
936 case STATE_CALIBRATED:
937 case STATE_GUIDING:
938 newState = STATE_SELECTED;
939 break;
940 case STATE_STOP:
941 break;
942 }
943 }
944
945 assert(newState != STATE_STOP);
946
947 if (newState > m_state + 1)
948 {
949 Debug.Write(wxString::Format("Cannot transition from %s to newState=%s\n", StateStr(m_state), StateStr(newState)));
950 throw ERROR_INFO("Illegal state transition");
951 }
952
953 GUIDER_STATE requestedState = newState;
954
955 switch (requestedState)
956 {
957 case STATE_UNINITIALIZED:
958 InvalidateLockPosition();
959 InvalidateCurrentPosition();
960 newState = STATE_SELECTING;
961 break;
962 case STATE_SELECTED:
963 break;
964 case STATE_CALIBRATING_PRIMARY:
965 if (!pMount->IsCalibrated())
966 {
967 pMount->ResetErrorCount();
968 if (pMount->BeginCalibration(CurrentPosition()))
969 {
970 newState = STATE_UNINITIALIZED;
971 Debug.Write(ERROR_INFO("pMount->BeginCalibration failed"));
972 }
973 else
974 {
975 GuideLog.StartCalibration(pMount);
976 EvtServer.NotifyStartCalibration(pMount);
977 }
978 break;
979 }
980 // fall through
981 case STATE_CALIBRATING_SECONDARY:
982 if (!pSecondaryMount || !pSecondaryMount->IsConnected())
983 {
984 newState = STATE_CALIBRATED;
985 }
986 else if (!pSecondaryMount->IsCalibrated())
987 {
988 pSecondaryMount->ResetErrorCount();
989 if (pSecondaryMount->BeginCalibration(CurrentPosition()))
990 {
991 newState = STATE_UNINITIALIZED;
992 Debug.Write(ERROR_INFO("pSecondaryMount->BeginCalibration failed"));
993 }
994 else
995 {
996 GuideLog.StartCalibration(pSecondaryMount);
997 EvtServer.NotifyStartCalibration(pSecondaryMount);
998 }
999 }
1000 break;
1001 case STATE_GUIDING:
1002 assert(pMount);
1003
1004 m_ditherRecenterRemaining.Invalidate(); // reset dither fast recenter state
1005
1006 pMount->AdjustCalibrationForScopePointing();
1007 if (pSecondaryMount)
1008 {
1009 pSecondaryMount->AdjustCalibrationForScopePointing();
1010 }
1011
1012 pFrame->UpdateStatusBarCalibrationStatus();
1013
1014 if (m_lockPosition.IsValid() && m_lockPosIsSticky)
1015 {
1016 Debug.Write("keeping sticky lock position\n");
1017 }
1018 else
1019 {
1020 SetLockPosition(CurrentPosition());
1021 }
1022 break;
1023
1024 case STATE_SELECTING:
1025 case STATE_CALIBRATED:
1026 case STATE_STOP:
1027 break;
1028 }
1029
1030 if (newState >= requestedState)
1031 {
1032 m_state = newState;
1033 Debug.Write(wxString::Format("guider state => %s\n", StateStr(m_state)));
1034 }
1035 else
1036 {
1037 Debug.Write(wxString::Format("SetState recurses newState %s\n", StateStr(newState)));
1038 SetState(newState);
1039 }
1040 }
1041 catch (const wxString& Msg)
1042 {
1043 POSSIBLY_UNUSED(Msg);
1044 }
1045 }
1046
UpdateCurrentDistance(double distance,double distanceRA)1047 void Guider::UpdateCurrentDistance(double distance, double distanceRA)
1048 {
1049 m_starFoundTimestamp = wxDateTime::GetTimeNow();
1050
1051 if (IsGuiding())
1052 {
1053 // update moving average distance
1054 static double const alpha = .3; // moderately high weighting for latest sample
1055 m_avgDistance += alpha * (distance - m_avgDistance);
1056 m_avgDistanceRA += alpha * (distanceRA - m_avgDistanceRA);
1057
1058 ++m_avgDistanceCnt;
1059
1060 if (m_avgDistanceCnt < 10)
1061 {
1062 // initialize smoothed running avg with mean of first 10 pts
1063 m_avgDistanceLong += (distance - m_avgDistanceLong) / m_avgDistanceCnt;
1064 m_avgDistanceLongRA += (distance - m_avgDistanceLongRA) / m_avgDistanceCnt;
1065 }
1066 else
1067 {
1068 static double const alpha_long = .045; // heavy smoothing, low weighting for latest sample .045 => 15 frame half-life
1069 m_avgDistanceLong += alpha_long * (distance - m_avgDistanceLong);
1070 m_avgDistanceLongRA += alpha_long * (distance - m_avgDistanceLongRA);
1071 }
1072 }
1073 else
1074 {
1075 // not yet guiding, reinitialize average distance
1076 m_avgDistance = m_avgDistanceLong = distance;
1077 m_avgDistanceRA = m_avgDistanceLongRA = distanceRA;
1078 m_avgDistanceCnt = 1;
1079 }
1080
1081 if (m_avgDistanceNeedReset)
1082 {
1083 // avg distance history invalidated
1084 m_avgDistance = m_avgDistanceLong = distance;
1085 m_avgDistanceRA = m_avgDistanceLongRA = distanceRA;
1086 m_avgDistanceCnt = 1;
1087 m_avgDistanceNeedReset = false;
1088 }
1089 }
1090
CurrentError(time_t starFoundTimestamp,double avgDist)1091 inline static double CurrentError(time_t starFoundTimestamp, double avgDist)
1092 {
1093 enum { THRESHOLD_SECONDS = 20 };
1094 static double const LARGE_DISTANCE = 100.0;
1095
1096 if (!starFoundTimestamp)
1097 {
1098 return LARGE_DISTANCE;
1099 }
1100
1101 if (wxDateTime::GetTimeNow() - starFoundTimestamp > THRESHOLD_SECONDS)
1102 {
1103 return LARGE_DISTANCE;
1104 }
1105
1106 return avgDist;
1107 }
1108
CurrentError(bool raOnly)1109 double Guider::CurrentError(bool raOnly)
1110 {
1111 return ::CurrentError(m_starFoundTimestamp, raOnly ? m_avgDistanceRA : m_avgDistance);
1112 }
1113
CurrentErrorSmoothed(bool raOnly)1114 double Guider::CurrentErrorSmoothed(bool raOnly)
1115 {
1116 return ::CurrentError(m_starFoundTimestamp, raOnly ? m_avgDistanceLongRA : m_avgDistanceLong);
1117 }
1118
StartGuiding()1119 void Guider::StartGuiding()
1120 {
1121 // we set the state to calibrating. The state machine will
1122 // automatically move from calibrating->calibrated->guiding
1123 // when it can
1124 SetState(STATE_CALIBRATING_PRIMARY);
1125 }
1126
StopGuiding()1127 void Guider::StopGuiding()
1128 {
1129 // first, send a notification that we stopped
1130 switch (m_state)
1131 {
1132 case STATE_UNINITIALIZED:
1133 case STATE_SELECTING:
1134 case STATE_SELECTED:
1135 break;
1136 case STATE_CALIBRATING_PRIMARY:
1137 case STATE_CALIBRATING_SECONDARY:
1138 case STATE_CALIBRATED:
1139 EvtServer.NotifyCalibrationFailed(m_state == STATE_CALIBRATING_SECONDARY ? pSecondaryMount : pMount,
1140 _("Calibration manually stopped"));
1141 // fall through to notify guiding stopped
1142 case STATE_GUIDING:
1143 if ((!pMount || !pMount->IsBusy()) && (!pSecondaryMount || !pSecondaryMount->IsBusy()))
1144 {
1145 // Notify guiding stopped if there are no outstanding guide steps. The Guiding
1146 // Stopped notification must come after the final GuideStep notification otherwise
1147 // event server clients and the guide log will show the guide step happening after
1148 // guiding stopped.
1149
1150 pFrame->NotifyGuidingStopped();
1151 }
1152 break;
1153 case STATE_STOP:
1154 break;
1155 }
1156
1157 SetState(STATE_STOP);
1158 }
1159
Reset(bool fullReset)1160 void Guider::Reset(bool fullReset)
1161 {
1162 SetState(STATE_UNINITIALIZED);
1163 if (fullReset)
1164 {
1165 InvalidateCurrentPosition(true);
1166 }
1167 }
1168
1169 // Called from the alert to offer auto-restore calibration
SetAutoLoad(long param)1170 static void SetAutoLoad(long param)
1171 {
1172 pFrame->SetAutoLoadCalibration(true);
1173 pFrame->m_infoBar->Dismiss();
1174 }
1175
1176 // Generate an alert if the user is likely to be missing the opportunity for auto-restore of
1177 // the just-completed calibration
CheckCalibrationAutoLoad()1178 static void CheckCalibrationAutoLoad()
1179 {
1180 int autoLoadProfileVal = pConfig->Profile.GetInt("/AutoLoadCalibration", -1);
1181 bool shouldAutoLoad = pPointingSource && pPointingSource->CanReportPosition();
1182
1183 if (autoLoadProfileVal == -1)
1184 {
1185 // new profile, assert appropriate choice as default
1186 pFrame->SetAutoLoadCalibration(shouldAutoLoad);
1187 }
1188 else if (autoLoadProfileVal == 0 && shouldAutoLoad)
1189 {
1190 bool alreadyAsked = pConfig->Profile.GetBoolean("/AlreadyAskedCalibAutoload", false);
1191
1192 if (!alreadyAsked)
1193 {
1194 wxString msg = wxString::Format(_("Do you want to automatically restore this calibration whenever the profile is used?"));
1195 pFrame->Alert(msg, 0, "Auto-restore", &SetAutoLoad, 0, false, 0);
1196 pConfig->Profile.SetBoolean("/AlreadyAskedCalibAutoload", true);
1197 }
1198 }
1199 }
1200
DisplayImage(usImage * img)1201 void Guider::DisplayImage(usImage *img)
1202 {
1203 if (IsCalibratingOrGuiding())
1204 return;
1205
1206 // switch in the new image
1207 usImage *prev = m_pCurrentImage;
1208 m_pCurrentImage = img;
1209
1210 ImageLogger::SaveImage(prev);
1211
1212 UpdateImageDisplay();
1213 }
1214
1215 inline static bool
IsLoopingState(GUIDER_STATE state)1216 IsLoopingState(GUIDER_STATE state)
1217 {
1218 // returns true for looping, but non-guiding states
1219 switch (state)
1220 {
1221 case STATE_UNINITIALIZED:
1222 case STATE_SELECTING:
1223 case STATE_SELECTED:
1224 return true;
1225
1226 case STATE_CALIBRATING_PRIMARY:
1227 case STATE_CALIBRATING_SECONDARY:
1228 case STATE_CALIBRATED:
1229 case STATE_GUIDING:
1230 case STATE_STOP:
1231 return false;
1232 }
1233
1234 return false;
1235 }
1236
1237 /************* A new image is ready ************************/
1238
UpdateGuideState(usImage * pImage,bool bStopping)1239 void Guider::UpdateGuideState(usImage *pImage, bool bStopping)
1240 {
1241 wxString statusMessage;
1242 bool someException = false;
1243
1244 try
1245 {
1246 Debug.Write(wxString::Format("UpdateGuideState(): m_state=%d\n", m_state));
1247
1248 if (pImage)
1249 {
1250 // switch in the new image
1251
1252 usImage *pPrevImage = m_pCurrentImage;
1253 m_pCurrentImage = pImage;
1254
1255 ImageLogger::SaveImage(pPrevImage);
1256 }
1257 else
1258 {
1259 pImage = m_pCurrentImage;
1260 }
1261
1262 if (pFrame && pFrame->pStatsWin)
1263 pFrame->pStatsWin->UpdateImageSize(pImage->Size);
1264
1265 if (bStopping)
1266 {
1267 StopGuiding();
1268 statusMessage = _("Stopped Guiding");
1269 throw THROW_INFO("Stopped Guiding");
1270 }
1271
1272 assert(!pMount || !pMount->IsBusy());
1273
1274 // shift lock position
1275 if (LockPosShiftEnabled() && IsGuiding())
1276 {
1277 if (ShiftLockPosition())
1278 {
1279 EvtServer.NotifyLockShiftLimitReached();
1280 pFrame->Alert(_("Shifted lock position outside allowable area. Lock Position Shift disabled."));
1281 EnableLockPosShift(false);
1282 }
1283 NudgeLockTool::UpdateNudgeLockControls();
1284 }
1285
1286 GuiderOffset ofs;
1287 FrameDroppedInfo info;
1288
1289 if (UpdateCurrentPosition(pImage, &ofs, &info)) // true means error
1290 {
1291 info.frameNumber = pImage->FrameNum;
1292 info.time = pFrame->TimeSinceGuidingStarted();
1293 info.avgDist = pFrame->CurrentGuideError();
1294
1295 switch (m_state)
1296 {
1297 case STATE_UNINITIALIZED:
1298 case STATE_SELECTING:
1299 EvtServer.NotifyLooping(pImage->FrameNum, nullptr, &info);
1300 break;
1301 case STATE_SELECTED:
1302 // we had a current position and lost it
1303 EvtServer.NotifyLooping(pImage->FrameNum, nullptr, &info);
1304 if (!m_ignoreLostStarLooping)
1305 {
1306 SetState(STATE_UNINITIALIZED);
1307 EvtServer.NotifyStarLost(info);
1308 }
1309 StaticPaTool::NotifyStarLost();
1310 break;
1311 case STATE_CALIBRATING_PRIMARY:
1312 case STATE_CALIBRATING_SECONDARY:
1313 GuideLog.CalibrationFrameDropped(info);
1314 Debug.Write("Star lost during calibration... blundering on\n");
1315 EvtServer.NotifyStarLost(info);
1316 pFrame->StatusMsg(_("star lost"));
1317 break;
1318 case STATE_GUIDING:
1319 {
1320 GuideLog.FrameDropped(info);
1321 EvtServer.NotifyStarLost(info);
1322 GuidingAssistant::NotifyFrameDropped(info);
1323 pFrame->pGraphLog->AppendData(info);
1324
1325 // allow guide algorithms to attempt dead reckoning
1326 static GuiderOffset ZERO_OFS;
1327 pFrame->SchedulePrimaryMove(pMount, ZERO_OFS, MOVEOPTS_DEDUCED_MOVE);
1328
1329 wxColor prevColor = GetBackgroundColour();
1330 SetBackgroundColour(wxColour(64,0,0));
1331 ClearBackground();
1332 if (pFrame->GetBeepForLostStar())
1333 wxBell();
1334 wxMilliSleep(100);
1335 SetBackgroundColour(prevColor);
1336 break;
1337 }
1338
1339 case STATE_CALIBRATED:
1340 case STATE_STOP:
1341 break;
1342 }
1343
1344 statusMessage = info.status;
1345 throw THROW_INFO("unable to update current position");
1346 }
1347
1348 statusMessage = info.status;
1349
1350 if (IsLoopingState(m_state))
1351 EvtServer.NotifyLooping(pImage->FrameNum, &PrimaryStar(), nullptr);
1352
1353 // we have a star selected, so re-enable subframes
1354 if (m_forceFullFrame)
1355 {
1356 Debug.Write("setting force full frames = false\n");
1357 m_forceFullFrame = false;
1358 }
1359
1360 if (IsPaused())
1361 {
1362 if (m_state == STATE_GUIDING)
1363 {
1364 // allow guide algorithms to attempt dead reckoning
1365 static GuiderOffset ZERO_OFS;
1366 pFrame->SchedulePrimaryMove(pMount, ZERO_OFS, MOVEOPTS_DEDUCED_MOVE);
1367 }
1368
1369 statusMessage = _("Paused") + (GetPauseType() == PAUSE_FULL ? _("/full") : _("/looping"));
1370 throw THROW_INFO("Skipping frame - guider is paused");
1371 }
1372
1373 switch (m_state)
1374 {
1375 case STATE_SELECTING:
1376 assert(CurrentPosition().IsValid());
1377 SetLockPosition(CurrentPosition());
1378 Debug.Write("CurrentPosition() valid, moving to STATE_SELECTED\n");
1379 EvtServer.NotifyStarSelected(CurrentPosition());
1380 SetState(STATE_SELECTED);
1381 break;
1382 case STATE_SELECTED:
1383 if (!StaticPaTool::UpdateState())
1384 {
1385 SetState(STATE_UNINITIALIZED);
1386 statusMessage = _("Static PA rotation failed");
1387 throw ERROR_INFO("Static PA rotation failed");
1388 }
1389 if (!PolarDriftTool::UpdateState())
1390 {
1391 SetState(STATE_UNINITIALIZED);
1392 statusMessage = _("Polar Drift PA drift failed");
1393 throw ERROR_INFO("Polar Drift PA drift failed");
1394 }
1395 break;
1396 case STATE_CALIBRATING_PRIMARY:
1397 if (!pMount->IsCalibrated())
1398 {
1399 if (pMount->UpdateCalibrationState(CurrentPosition()))
1400 {
1401 SetState(STATE_UNINITIALIZED);
1402 statusMessage = pMount->IsStepGuider() ? _("AO calibration failed") : _("calibration failed");
1403 throw ERROR_INFO("Calibration failed");
1404 }
1405
1406 if (!pMount->IsCalibrated())
1407 {
1408 break;
1409 }
1410 }
1411
1412 SetState(STATE_CALIBRATING_SECONDARY);
1413
1414 if (m_state == STATE_CALIBRATING_SECONDARY)
1415 {
1416 // if we really have a secondary mount, and it isn't calibrated,
1417 // we need to take another exposure before falling into the code
1418 // below. If we don't have one, or it is calibrated, we can fall
1419 // through. If we don't fall through, we end up displaying a frame
1420 // which has the lockpoint in the wrong place, and while I thought I
1421 // could live with it when I originally wrote the code, it bothered
1422 // me so I did this. Ick.
1423 break;
1424 }
1425
1426 // Fall through
1427 case STATE_CALIBRATING_SECONDARY:
1428 if (pSecondaryMount && pSecondaryMount->IsConnected())
1429 {
1430 if (!pSecondaryMount->IsCalibrated())
1431 {
1432 if (pSecondaryMount->UpdateCalibrationState(CurrentPosition()))
1433 {
1434 SetState(STATE_UNINITIALIZED);
1435 statusMessage = _("calibration failed");
1436 throw ERROR_INFO("Calibration failed");
1437 }
1438 }
1439
1440 if (!pSecondaryMount->IsCalibrated())
1441 {
1442 break;
1443 }
1444 }
1445 assert(!pSecondaryMount || !pSecondaryMount->IsConnected() || pSecondaryMount->IsCalibrated());
1446
1447 SetState(STATE_CALIBRATED);
1448 // fall through
1449 case STATE_CALIBRATED:
1450 assert(m_state == STATE_CALIBRATED);
1451 SetState(STATE_GUIDING);
1452
1453 pFrame->NotifyGuidingStarted();
1454
1455 // camera angle is known, so ok to calculate shift rate camera coords
1456 UpdateLockPosShiftCameraCoords();
1457 if (LockPosShiftEnabled())
1458 GuideLog.NotifyLockShiftParams(m_lockPosShift, m_lockPosition.ShiftRate());
1459
1460 CheckCalibrationAutoLoad();
1461 break;
1462 case STATE_GUIDING:
1463 if (m_ditherRecenterRemaining.IsValid())
1464 {
1465 // fast recenter after dither taking large steps and bypassing
1466 // guide algorithms
1467
1468 PHD_Point step(wxMin(m_ditherRecenterRemaining.X, m_ditherRecenterStep.X),
1469 wxMin(m_ditherRecenterRemaining.Y, m_ditherRecenterStep.Y));
1470
1471 Debug.Write(wxString::Format("dither recenter: remaining=(%.1f,%.1f) step=(%.1f,%.1f)\n",
1472 m_ditherRecenterRemaining.X * m_ditherRecenterDir.x,
1473 m_ditherRecenterRemaining.Y * m_ditherRecenterDir.y,
1474 step.X * m_ditherRecenterDir.x, step.Y * m_ditherRecenterDir.y));
1475
1476 m_ditherRecenterRemaining -= step;
1477 if (m_ditherRecenterRemaining.X < 0.5 && m_ditherRecenterRemaining.Y < 0.5)
1478 {
1479 // fast recenter is done
1480 m_ditherRecenterRemaining.Invalidate();
1481 // reset distance tracker
1482 m_avgDistanceNeedReset = true;
1483 }
1484
1485 ofs.mountOfs.SetXY(step.X * m_ditherRecenterDir.x, step.Y * m_ditherRecenterDir.y);
1486 pMount->TransformMountCoordinatesToCameraCoordinates(ofs.mountOfs, ofs.cameraOfs);
1487 pFrame->SchedulePrimaryMove(pMount, ofs, MOVEOPTS_RECOVERY_MOVE);
1488 // let guide algorithms know about the direct move
1489 pMount->NotifyDirectMove(ofs.mountOfs);
1490 }
1491 else if (m_measurementMode)
1492 {
1493 GuidingAssistant::NotifyBacklashStep(CurrentPosition());
1494 }
1495 else
1496 {
1497 // ordinary guide step
1498 s_deflectionLogger.Log(CurrentPosition());
1499 pFrame->SchedulePrimaryMove(pMount, ofs, MOVEOPTS_GUIDE_STEP);
1500 }
1501 break;
1502
1503 case STATE_UNINITIALIZED:
1504 case STATE_STOP:
1505 break;
1506 }
1507 }
1508 catch (const wxString& Msg)
1509 {
1510 POSSIBLY_UNUSED(Msg);
1511 someException = true;
1512 }
1513
1514 // during calibration, the mount is responsible for updating the status message
1515 if (someException && m_state != STATE_CALIBRATING_PRIMARY && m_state != STATE_CALIBRATING_SECONDARY)
1516 {
1517 pFrame->StatusMsg(statusMessage);
1518 }
1519
1520 if (m_measurementMode && m_state != STATE_GUIDING)
1521 {
1522 GuidingAssistant::NotifyBacklashError();
1523 m_measurementMode = false;
1524 }
1525
1526 pFrame->UpdateButtonsStatus();
1527
1528 UpdateImageDisplay(pImage);
1529
1530 Debug.AddLine("UpdateGuideState exits: " + statusMessage);
1531 }
1532
ShiftLockPosition()1533 bool Guider::ShiftLockPosition()
1534 {
1535 m_lockPosition.UpdateShift();
1536 bool isValid = IsValidLockPosition(m_lockPosition);
1537 Debug.Write(wxString::Format("ShiftLockPos: new pos = %.2f, %.2f valid=%d\n",
1538 m_lockPosition.X, m_lockPosition.Y, isValid));
1539 return !isValid;
1540 }
1541
SetLockPosShiftRate(const PHD_Point & rate,GRAPH_UNITS units,bool isMountCoords,bool updateToolWin)1542 void Guider::SetLockPosShiftRate(const PHD_Point& rate, GRAPH_UNITS units, bool isMountCoords, bool updateToolWin)
1543 {
1544 Debug.Write(wxString::Format("SetLockPosShiftRate: rate = %.2f,%.2f units = %d isMountCoords = %d\n",
1545 rate.X, rate.Y, units, isMountCoords));
1546
1547 m_lockPosShift.shiftRate = rate;
1548 m_lockPosShift.shiftUnits = units;
1549 m_lockPosShift.shiftIsMountCoords = isMountCoords;
1550
1551 CometTool::UpdateCometToolControls(updateToolWin);
1552
1553 if (m_state == STATE_CALIBRATED || m_state == STATE_GUIDING)
1554 {
1555 UpdateLockPosShiftCameraCoords();
1556 if (LockPosShiftEnabled())
1557 {
1558 GuideLog.NotifyLockShiftParams(m_lockPosShift, m_lockPosition.ShiftRate());
1559 }
1560 }
1561 }
1562
EnableLockPosShift(bool enable)1563 void Guider::EnableLockPosShift(bool enable)
1564 {
1565 if (enable != m_lockPosShift.shiftEnabled)
1566 {
1567 Debug.Write(wxString::Format("EnableLockPosShift: enable = %d\n", enable));
1568 m_lockPosShift.shiftEnabled = enable;
1569 if (enable)
1570 {
1571 m_lockPosition.BeginShift();
1572 }
1573 if (m_state == STATE_CALIBRATED || m_state == STATE_GUIDING)
1574 {
1575 GuideLog.NotifyLockShiftParams(m_lockPosShift, m_lockPosition.ShiftRate());
1576 }
1577
1578 CometTool::UpdateCometToolControls(false);
1579 }
1580 }
1581
UpdateLockPosShiftCameraCoords()1582 void Guider::UpdateLockPosShiftCameraCoords()
1583 {
1584 if (!m_lockPosShift.shiftRate.IsValid())
1585 {
1586 Debug.Write("UpdateLockPosShiftCameraCoords: no shift rate set\n");
1587 m_lockPosition.DisableShift();
1588 return;
1589 }
1590
1591 PHD_Point rate(0., 0.);
1592
1593 // convert shift rate to camera coordinates
1594 if (m_lockPosShift.shiftIsMountCoords)
1595 {
1596 PHD_Point radec_rates = m_lockPosShift.shiftRate;
1597
1598 Debug.Write(wxString::Format("UpdateLockPosShiftCameraCoords: shift rate mount coords = %.2f,%.2f\n",
1599 radec_rates.X, radec_rates.Y));
1600
1601 Mount *scope = TheScope();
1602 if (scope)
1603 {
1604 if (m_lockPosShift.shiftUnits == UNIT_ARCSEC)
1605 {
1606 // if rates are RA/Dec arc-seconds, assume they are ephemeris rates
1607
1608 // account for parity if known
1609 GuideParity raParity = scope->RAParity();
1610 GuideParity decParity = scope->DecParity();
1611 if (raParity != GUIDE_PARITY_UNKNOWN || decParity != GUIDE_PARITY_UNKNOWN)
1612 {
1613 if (raParity == GUIDE_PARITY_ODD)
1614 radec_rates.X = -radec_rates.X;
1615 if (decParity == GUIDE_PARITY_ODD)
1616 radec_rates.Y = -radec_rates.Y;
1617
1618 Debug.Write(wxString::Format("UpdateLockPosShiftCameraCoords: after parity adjustment: %.2f,%.2f\n", radec_rates.X, radec_rates.Y));
1619 }
1620
1621 // account for scope declination
1622 if (pPointingSource)
1623 {
1624 double dec = pPointingSource->GetDeclination();
1625 if (dec != UNKNOWN_DECLINATION)
1626 {
1627 radec_rates.X *= cos(dec);
1628 Debug.Write(wxString::Format("UpdateLockPosShiftCameraCoords: RA shift rate adjusted for declination %.1f\n", degrees(dec)));
1629 }
1630 }
1631 }
1632
1633 scope->TransformMountCoordinatesToCameraCoordinates(radec_rates, rate);
1634 }
1635 }
1636 else
1637 {
1638 rate = m_lockPosShift.shiftRate;
1639 }
1640
1641 Debug.Write(wxString::Format("UpdateLockPosShiftCameraCoords: shift rate camera coords = %.2f,%.2f %s/hr\n",
1642 rate.X, rate.Y, m_lockPosShift.shiftUnits == UNIT_ARCSEC ? "arcsec" : "pixels"));
1643
1644 // convert arc-seconds to pixels
1645 if (m_lockPosShift.shiftUnits == UNIT_ARCSEC)
1646 {
1647 rate /= pFrame->GetCameraPixelScale();
1648 }
1649 rate /= 3600.0; // per hour => per second
1650
1651 Debug.Write(wxString::Format("UpdateLockPosShiftCameraCoords: shift rate %.2g,%.2g px/sec\n",
1652 rate.X, rate.Y));
1653
1654 m_lockPosition.SetShiftRate(rate.X, rate.Y);
1655 }
1656
GetSettingsSummary() const1657 wxString Guider::GetSettingsSummary() const
1658 {
1659 // return a loggable summary of current global configs managed by MyFrame
1660 return wxEmptyString;
1661 }
1662
GetConfigDialogPane(wxWindow * pParent)1663 Guider::GuiderConfigDialogPane *Guider::GetConfigDialogPane(wxWindow *pParent)
1664 {
1665 return new GuiderConfigDialogPane(pParent, this);
1666 }
1667
GuiderConfigDialogPane(wxWindow * pParent,Guider * pGuider)1668 Guider::GuiderConfigDialogPane::GuiderConfigDialogPane(wxWindow *pParent, Guider *pGuider)
1669 : ConfigDialogPane(_("Guider Settings"), pParent)
1670 {
1671 m_pGuider = pGuider;
1672 }
1673
LayoutControls(Guider * pGuider,BrainCtrlIdMap & CtrlMap)1674 void Guider::GuiderConfigDialogPane::LayoutControls(Guider *pGuider, BrainCtrlIdMap& CtrlMap)
1675 {
1676 wxSizerFlags def_flags = wxSizerFlags(0).Border(wxALL, 5).Expand();
1677
1678 wxStaticBoxSizer *pStarTrack = new wxStaticBoxSizer(wxVERTICAL, m_pParent, _("Guide star tracking"));
1679 wxStaticBoxSizer *pCalib = new wxStaticBoxSizer(wxVERTICAL, m_pParent, _("Calibration"));
1680 wxStaticBoxSizer *pShared = new wxStaticBoxSizer(wxVERTICAL, m_pParent, _("Shared Parameters"));
1681 wxFlexGridSizer *pCalibSizer = new wxFlexGridSizer(3, 2, 10, 10);
1682 wxFlexGridSizer *pSharedSizer = new wxFlexGridSizer(2, 2, 10, 10);
1683
1684 pStarTrack->Add(GetSizerCtrl(CtrlMap, AD_szStarTracking), def_flags);
1685 pStarTrack->Layout();
1686
1687 pCalibSizer->Add(GetSizerCtrl(CtrlMap, AD_szFocalLength));
1688 pCalibSizer->Add(GetSizerCtrl(CtrlMap, AD_szCalibrationDuration), wxSizerFlags(0).Border(wxLEFT, 90));
1689 pCalibSizer->Add(GetSingleCtrl(CtrlMap, AD_cbAutoRestoreCal));
1690 pCalibSizer->Add(GetSingleCtrl(CtrlMap, AD_cbAssumeOrthogonal), wxSizerFlags(0).Border(wxLEFT, 90));
1691 CondAddCtrl(pCalibSizer, CtrlMap, AD_cbClearCalibration);
1692 CondAddCtrl(pCalibSizer, CtrlMap, AD_cbUseDecComp, wxSizerFlags(0).Border(wxLEFT, 90));
1693 pCalib->Add(pCalibSizer, def_flags);
1694 pCalib->Layout();
1695
1696 // Minor ordering to have "no-mount" condition look ok
1697 pSharedSizer->Add(GetSingleCtrl(CtrlMap, AD_cbScaleImages));
1698 pSharedSizer->Add(GetSingleCtrl(CtrlMap, AD_cbFastRecenter), wxSizerFlags(0).Border(wxLEFT, 35));
1699 CondAddCtrl(pSharedSizer, CtrlMap, AD_cbReverseDecOnFlip);
1700 CondAddCtrl(pSharedSizer, CtrlMap, AD_cbEnableGuiding, wxSizerFlags(0).Border(wxLEFT, 35));
1701 CondAddCtrl(pSharedSizer, CtrlMap, AD_cbSlewDetection);
1702 pShared->Add(pSharedSizer, def_flags);
1703 pShared->Layout();
1704
1705 this->Add(pStarTrack, def_flags);
1706 this->Add(pCalib, def_flags);
1707 this->Add(pShared, def_flags);
1708 Fit(m_pParent);
1709
1710 }
1711
GetConfigDialogCtrlSet(wxWindow * pParent,Guider * pGuider,AdvancedDialog * pAdvancedDialog,BrainCtrlIdMap & CtrlMap)1712 GuiderConfigDialogCtrlSet *Guider::GetConfigDialogCtrlSet(wxWindow *pParent, Guider *pGuider, AdvancedDialog *pAdvancedDialog, BrainCtrlIdMap& CtrlMap)
1713 {
1714 return new GuiderConfigDialogCtrlSet(pParent, pGuider, pAdvancedDialog, CtrlMap);
1715 }
1716
GuiderConfigDialogCtrlSet(wxWindow * pParent,Guider * pGuider,AdvancedDialog * pAdvancedDialog,BrainCtrlIdMap & CtrlMap)1717 GuiderConfigDialogCtrlSet::GuiderConfigDialogCtrlSet(wxWindow *pParent, Guider *pGuider, AdvancedDialog* pAdvancedDialog, BrainCtrlIdMap& CtrlMap) :
1718 ConfigDialogCtrlSet(pParent, pAdvancedDialog, CtrlMap)
1719 {
1720 assert(pGuider);
1721
1722 m_pGuider = pGuider;
1723
1724 m_pScaleImage = new wxCheckBox(GetParentWindow(AD_cbScaleImages), wxID_ANY, _("Always scale images"));
1725 AddCtrl(CtrlMap, AD_cbScaleImages, m_pScaleImage, _("Always scale images to fill window"));
1726
1727 m_pEnableFastRecenter = new wxCheckBox(GetParentWindow(AD_cbFastRecenter), wxID_ANY, _("Fast recenter after calibration or dither"));
1728 AddCtrl(CtrlMap, AD_cbFastRecenter, m_pEnableFastRecenter, _("Speed up calibration and dithering by using larger guide pulses to return the star to the center position. Un-check to use the old, slower method of recentering after calibration or dither."));
1729 }
1730
LoadValues()1731 void GuiderConfigDialogCtrlSet::LoadValues()
1732 {
1733 m_pEnableFastRecenter->SetValue(m_pGuider->IsFastRecenterEnabled());
1734 m_pScaleImage->SetValue(m_pGuider->GetScaleImage());
1735 }
1736
UnloadValues()1737 void GuiderConfigDialogCtrlSet::UnloadValues()
1738 {
1739 m_pGuider->EnableFastRecenter(m_pEnableFastRecenter->GetValue());
1740 m_pGuider->SetScaleImage(m_pScaleImage->GetValue());
1741 }
1742
GetExposedState()1743 EXPOSED_STATE Guider::GetExposedState()
1744 {
1745 EXPOSED_STATE rval;
1746 Guider *guider = pFrame->pGuider;
1747
1748 if (!guider)
1749 rval = EXPOSED_STATE_NONE;
1750
1751 else if (guider->IsPaused())
1752 rval = EXPOSED_STATE_PAUSED;
1753
1754 else if (!pFrame->CaptureActive)
1755 rval = EXPOSED_STATE_NONE;
1756
1757 else
1758 {
1759 // map the guider internal state into a server reported state
1760
1761 switch (guider->GetState())
1762 {
1763 case STATE_UNINITIALIZED:
1764 case STATE_STOP:
1765 default:
1766 rval = EXPOSED_STATE_NONE;
1767 break;
1768
1769 case STATE_SELECTING:
1770 // only report "looping" if no star is selected
1771 if (guider->CurrentPosition().IsValid())
1772 rval = EXPOSED_STATE_SELECTED;
1773 else
1774 rval = EXPOSED_STATE_LOOPING;
1775 break;
1776
1777 case STATE_SELECTED:
1778 case STATE_CALIBRATED:
1779 rval = EXPOSED_STATE_SELECTED;
1780 break;
1781
1782 case STATE_CALIBRATING_PRIMARY:
1783 case STATE_CALIBRATING_SECONDARY:
1784 rval = EXPOSED_STATE_CALIBRATING;
1785 break;
1786
1787 case STATE_GUIDING:
1788 if (guider->IsLocked())
1789 rval = EXPOSED_STATE_GUIDING_LOCKED;
1790 else
1791 rval = EXPOSED_STATE_GUIDING_LOST;
1792 }
1793
1794 Debug.Write(wxString::Format("case statement mapped state %d to %d\n", guider->GetState(), rval));
1795 }
1796
1797 return rval;
1798 }
1799
SetMinStarHFD(double val)1800 void Guider::SetMinStarHFD(double val)
1801 {
1802 Debug.Write(wxString::Format("Setting StarMinHFD = %.2f\n", val));
1803 pConfig->Profile.SetDouble("/guider/StarMinHFD", val);
1804 m_minStarHFD = val;
1805 }
1806
SetMinStarSNR(double val)1807 void Guider::SetMinStarSNR(double val)
1808 {
1809 Debug.Write(wxString::Format("Setting StarMinSNR = %0.1f\n", val));
1810 pConfig->Profile.SetDouble("/guider/StarMinSNR", val);
1811 m_minStarSNR = val;
1812 }
1813
SetAutoSelDownsample(unsigned int val)1814 void Guider::SetAutoSelDownsample(unsigned int val)
1815 {
1816 Debug.Write(wxString::Format("Setting AutoSelDownsample = %u\n", val));
1817 pConfig->Profile.SetInt("/guider/AutoSelDownsample", val);
1818 m_autoSelDownsample = val;
1819 }
1820
SetBookmarksShown(bool show)1821 void Guider::SetBookmarksShown(bool show)
1822 {
1823 bool prev = m_showBookmarks;
1824 m_showBookmarks = show;
1825 if (prev != show && m_bookmarks.size())
1826 {
1827 Refresh();
1828 Update();
1829 }
1830 }
1831
ToggleShowBookmarks()1832 void Guider::ToggleShowBookmarks()
1833 {
1834 SetBookmarksShown(!m_showBookmarks);
1835 }
1836
DeleteAllBookmarks()1837 void Guider::DeleteAllBookmarks()
1838 {
1839 if (m_bookmarks.size())
1840 {
1841 bool confirmed = ConfirmDialog::Confirm(_("Are you sure you want to delete all Bookmarks?"),
1842 "/delete_all_bookmarks_ok");
1843 if (confirmed)
1844 {
1845 m_bookmarks.clear();
1846 SaveBookmarks(m_bookmarks);
1847 if (m_showBookmarks)
1848 {
1849 Refresh();
1850 Update();
1851 }
1852 }
1853 }
1854 }
1855
IsClose(const wxRealPoint & p1,const wxRealPoint & p2,double tolerance)1856 static bool IsClose(const wxRealPoint& p1, const wxRealPoint& p2, double tolerance)
1857 {
1858 return fabs(p1.x - p2.x) <= tolerance &&
1859 fabs(p1.y - p2.y) <= tolerance;
1860 }
1861
FindBookmark(const wxRealPoint & pos,std::vector<wxRealPoint> & vec)1862 static std::vector<wxRealPoint>::iterator FindBookmark(const wxRealPoint& pos, std::vector<wxRealPoint>& vec)
1863 {
1864 static const double TOLERANCE = 6.0;
1865 std::vector<wxRealPoint>::iterator it;
1866 for (it = vec.begin(); it != vec.end(); ++it)
1867 if (IsClose(*it, pos, TOLERANCE))
1868 break;
1869 return it;
1870 }
1871
ToggleBookmark(const wxRealPoint & pos)1872 void Guider::ToggleBookmark(const wxRealPoint& pos)
1873 {
1874 std::vector<wxRealPoint>::iterator it = FindBookmark(pos, m_bookmarks);
1875
1876 if (it == m_bookmarks.end())
1877 m_bookmarks.push_back(pos);
1878 else
1879 m_bookmarks.erase(it);
1880
1881 SaveBookmarks(m_bookmarks);
1882 }
1883
BookmarkPos(const PHD_Point & pos,std::vector<wxRealPoint> & vec)1884 static bool BookmarkPos(const PHD_Point& pos, std::vector<wxRealPoint>& vec)
1885 {
1886 if (pos.IsValid())
1887 {
1888 wxRealPoint pt(pos.X, pos.Y);
1889 std::vector<wxRealPoint>::iterator it = FindBookmark(pt, vec);
1890 if (it != vec.end())
1891 vec.erase(it);
1892 vec.push_back(pt);
1893 SaveBookmarks(vec);
1894 return true;
1895 }
1896 return false;
1897 }
1898
BookmarkLockPosition()1899 void Guider::BookmarkLockPosition()
1900 {
1901 if (BookmarkPos(LockPosition(), m_bookmarks) && m_showBookmarks)
1902 {
1903 Refresh();
1904 Update();
1905 }
1906 }
1907
BookmarkCurPosition()1908 void Guider::BookmarkCurPosition()
1909 {
1910 if (BookmarkPos(CurrentPosition(), m_bookmarks) && m_showBookmarks)
1911 {
1912 Refresh();
1913 Update();
1914 }
1915 }
1916