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