1 /*
2 *  guiding_assistant.cpp
3 *  PHD Guiding
4 *
5 *  Created by Andy Galasso and Bruce Waddington
6 *  Copyright (c) 2015 Andy Galasso and Bruce Waddington
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 
35 #include "phd.h"
36 #include "guiding_assistant.h"
37 #include "backlash_comp.h"
38 #include "guiding_stats.h"
39 #include "optionsbutton.h"
40 
41 #include <wx/textwrapper.h>
42 #include <wx/tokenzr.h>
43 
44 struct GADetails
45 {
46     wxString TimeStamp;
47     wxString SNR;
48     wxString StarMass;
49     wxString SampleCount;
50     wxString ElapsedTime;
51     wxString ExposureTime;
52     wxString RA_HPF_RMS;
53     wxString Dec_HPF_RMS;
54     wxString Total_HPF_RMS;
55     wxString RAPeak;
56     wxString RAPeak_Peak;
57     wxString RADriftRate;
58     wxString RAMaxDriftRate;
59     wxString DriftLimitingExposure;
60     wxString DecDriftRate;
61     wxString DecPeak;
62     wxString PAError;
63     wxString BackLashInfo;
64     wxString Dec_LF_DriftRate;
65     wxString DecCorrectedRMS;
66     wxString RecRAMinMove;
67     wxString RecDecMinMove;
68     std::vector<double> BLTNorthMoves;
69     std::vector<double> BLTSouthMoves;
70     int BLTMsmtPulse;
71     wxString BLTAmount;
72     wxString Recommendations;
73 };
74 
StartRow(int & row,int & column)75 inline static void StartRow(int& row, int& column)
76 {
77     ++row;
78     column = 0;
79 }
80 
MakeBold(wxControl * ctrl)81 static void MakeBold(wxControl *ctrl)
82 {
83     wxFont font = ctrl->GetFont();
84     font.SetWeight(wxFONTWEIGHT_BOLD);
85     ctrl->SetFont(font);
86 }
87 
88 // Dialog for making sure sampling period is adequate for decent measurements
89 struct SampleWait : public wxDialog
90 {
91     wxStaticText *m_CountdownAmount;
92     wxTimer m_SecondsTimer;
93     int m_SecondsLeft;
94 
95     SampleWait(int SamplePeriod, bool BltNeeded);
96     void OnTimer(wxTimerEvent& evt);
97     void OnCancel(wxCommandEvent& event);
98 };
99 
SampleWait(int SecondsLeft,bool BltNeeded)100 SampleWait::SampleWait(int SecondsLeft, bool BltNeeded) : wxDialog(pFrame, wxID_ANY, _("Extended Sampling"))
101 {
102     wxBoxSizer* vSizer = new wxBoxSizer(wxVERTICAL);
103     wxBoxSizer* amtSizer = new wxBoxSizer(wxHORIZONTAL);
104     wxStaticText* explanation = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
105     wxString msg;
106     if (BltNeeded)
107         msg = _("Additional data sampling is being done to better meaure Dec drift. Backlash testing \nwill start automatically when sampling is completed.");
108     else
109         msg = _("Additional sampling is being done for accurate measurements.  Results will be shown when sampling is complete.");
110     explanation->SetLabelText(msg);
111     MakeBold(explanation);
112     wxStaticText* countDownLabel = new wxStaticText(this, wxID_ANY, _("Seconds remaining: "), wxDefaultPosition, wxDefaultSize, wxALIGN_LEFT);
113     m_SecondsLeft = SecondsLeft;
114     m_CountdownAmount = new wxStaticText(this, wxID_ANY, std::to_string(wxMax(0, m_SecondsLeft)));
115     amtSizer->Add(countDownLabel, wxSizerFlags(0).Border(wxALL, 8));
116     amtSizer->Add(m_CountdownAmount, wxSizerFlags(0).Border(wxALL, 8));
117     wxButton* cancelBtn = new wxButton(this, wxID_ANY, _("Cancel"));
118     cancelBtn->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(SampleWait::OnCancel), nullptr, this);
119     wxBoxSizer* btnSizer = new wxBoxSizer(wxHORIZONTAL);
120     btnSizer->Add(cancelBtn, wxSizerFlags(0).Border(wxALL, 8).Center());
121 
122     vSizer->Add(explanation, wxSizerFlags(0).Border(wxALL, 8).Center());
123     vSizer->Add(amtSizer, wxSizerFlags(0).Border(wxALL, 8).Center());
124     vSizer->Add(btnSizer, wxSizerFlags(0).Border(wxALL, 8).Center());
125 
126     SetAutoLayout(true);
127     SetSizerAndFit(vSizer);
128 
129     m_SecondsTimer.Connect(wxEVT_TIMER, wxTimerEventHandler(SampleWait::OnTimer), nullptr, this);
130     m_SecondsTimer.Start(1000);
131 }
132 
OnTimer(wxTimerEvent & evt)133 void SampleWait::OnTimer(wxTimerEvent& evt)
134 {
135     m_SecondsLeft -= 1;
136     if (m_SecondsLeft > 0)
137     {
138         m_CountdownAmount->SetLabelText(std::to_string(m_SecondsLeft));
139         m_CountdownAmount->Update();
140     }
141     else
142     {
143         m_SecondsTimer.Stop();
144         EndDialog(wxOK);
145     }
146 }
147 
OnCancel(wxCommandEvent & event)148 void SampleWait::OnCancel(wxCommandEvent& event)
149 {
150     m_SecondsTimer.Stop();
151     if (wxGetKeyState(WXK_CONTROL))
152         EndDialog(wxOK);
153     else
154         EndDialog(wxCANCEL);
155 }
156 
157 // Encapsulated struct for implementing the dialog box
158 struct GuidingAsstWin : public wxDialog
159 {
160     enum DialogState
161     {
162         STATE_NO_STAR = 0,
163         STATE_START_READY = 1,
164         STATE_MEASURING = 2,
165         STATE_STOPPED = 3
166     };
167     enum DlgConstants {MAX_BACKLASH_COMP = 3000, GA_MIN_SAMPLING_PERIOD = 120};
168 
169     wxButton *m_start;
170     wxButton *m_stop;
171     OptionsButton* btnReviewPrev;
172     wxTextCtrl *m_report;
173     wxStaticText *m_instructions;
174     wxGrid *m_statusgrid;
175     wxGrid *m_displacementgrid;
176     wxGrid *m_othergrid;
177     wxFlexGridSizer *m_recommendgrid;
178     wxBoxSizer *m_vSizer;
179     wxStaticBoxSizer *m_recommend_group;
180     wxCheckBox *m_backlashCB;
181     wxStaticText *m_gaStatus;
182     wxButton *m_graphBtn;
183 
184     wxGridCellCoords m_timestamp_loc;
185     wxGridCellCoords m_starmass_loc;
186     wxGridCellCoords m_samplecount_loc;
187     wxGridCellCoords m_snr_loc;
188     wxGridCellCoords m_elapsedtime_loc;
189     wxGridCellCoords m_exposuretime_loc;
190     wxGridCellCoords m_hfcutoff_loc;
191     wxGridCellCoords m_ra_rms_loc;
192     wxGridCellCoords m_dec_rms_loc;
193     wxGridCellCoords m_total_rms_loc;
194     wxGridCellCoords m_ra_peak_loc;
195     wxGridCellCoords m_dec_peak_loc;
196     wxGridCellCoords m_ra_peakpeak_loc;
197     wxGridCellCoords m_ra_drift_loc;
198     wxGridCellCoords m_ra_drift_exp_loc;
199     wxGridCellCoords m_dec_drift_loc;
200     wxGridCellCoords m_pae_loc;
201     wxGridCellCoords m_ra_peak_drift_loc;
202     wxGridCellCoords m_backlash_loc;
203     wxButton *m_raMinMoveButton;
204     wxButton *m_decMinMoveButton;
205     wxButton *m_decBacklashButton;
206     wxButton *m_decAlgoButton;
207     wxStaticText *m_ra_msg;
208     wxStaticText *m_dec_msg;
209     wxStaticText *m_snr_msg;
210     wxStaticText *m_pae_msg;
211     wxStaticText *m_hfd_msg;
212     wxStaticText *m_backlash_msg;
213     wxStaticText *m_exposure_msg;
214     wxStaticText *m_calibration_msg;
215     wxStaticText *m_binning_msg;
216     wxStaticText *m_decAlgo_msg;
217     double m_ra_minmove_rec;  // recommended value
218     double m_dec_minmove_rec; // recommended value
219     double m_min_exp_rec;
220     double m_max_exp_rec;
221 
222     DialogState m_dlgState;
223     bool m_measuring;
224     wxLongLong_t m_startTime;
225     long m_elapsedSecs;
226     PHD_Point m_startPos;
227     wxString startStr;
228     DescriptiveStats m_hpfRAStats;
229     DescriptiveStats m_lpfRAStats;
230     DescriptiveStats m_hpfDecStats;
231     AxisStats m_decAxisStats;
232     AxisStats m_raAxisStats;
233     long m_axisTimebase;
234     HighPassFilter m_raHPF;
235     LowPassFilter m_raLPF;
236     HighPassFilter m_decHPF;
237     double sumSNR;
238     double sumMass;
239     double m_lastTime;
240     double maxRateRA;               // arc-sec per second
241     double decDriftPerMin;          // px per minute
242     double decCorrectedRMS;         // RMS of drift-corrected Dec dataset
243     double alignmentError;          // arc-minutes
244     double m_backlashPx;
245     int m_backlashMs;
246     double m_backlashSigmaMs;
247     int m_backlashRecommendedMs;
248 
249     bool m_guideOutputDisabled;
250     bool m_savePrimaryMountEnabled;
251     bool m_saveSecondaryMountEnabled;
252     bool m_measurementsTaken;
253     int  m_origSubFrames;
254     bool m_suspectCalibration;
255     bool inBLTWrapUp = false;
256     bool origMultistarMode;
257 
258     bool m_measuringBacklash;
259     BacklashTool *m_backlashTool;
260     bool reviewMode;
261     GADetails gaDetails;
262     bool m_flushConfig;
263 
264     GuidingAsstWin();
265     ~GuidingAsstWin();
266 
267     void OnClose(wxCloseEvent& event);
268     void OnMouseMove(wxMouseEvent&);
269     void OnAppStateNotify(wxCommandEvent& event);
270     void OnStart(wxCommandEvent& event);
271     void DoStop(const wxString& status = wxEmptyString);
272     void OnStop(wxCommandEvent& event);
273     void OnRAMinMove(wxCommandEvent& event);
274     void OnDecMinMove(wxCommandEvent& event);
275     void OnDecBacklash(wxCommandEvent& event);
276     void OnDecAlgoChange(wxCommandEvent& event);
277     void OnGraph(wxCommandEvent& event);
278     void OnHelp(wxCommandEvent& event);
279     void OnReviewPrevious(wxCommandEvent& event);
280     void OnGAReviewSelection(wxCommandEvent& evt);
281 
282     wxStaticText *AddRecommendationBtn(const wxString& msg,
283                                        void (GuidingAsstWin::* handler)(wxCommandEvent&),
284                                        wxButton **ppButton);
285     wxStaticText *AddRecommendationMsg(const wxString& msg);
286     void FillResultCell(wxGrid *pGrid, const wxGridCellCoords& loc, double pxVal,
287                         double asVal, const wxString& units1, const wxString& units2,
288                         const wxString& extraInfo = wxEmptyString);
289     void UpdateInfo(const GuideStepInfo& info);
290     void DisplayStaticResults(const GADetails& details);
291     void FillInstructions(DialogState eState);
292     void MakeRecommendations();
293     void DisplayStaticRecommendations(const GADetails& details);
294     void LogResults();
295     void BacklashStep(const PHD_Point& camLoc);
296     void EndBacklashTest(bool completed);
297     void BacklashError();
298     void StatsReset();
299     void LoadGAResults(const wxString& TimeStamp, GADetails* Details);
300     void SaveGAResults(const wxString* AllRecommendations);
301     int GetGAHistoryCount();
302     void GetMinMoveRecs(double& RecRA, double& RecDec);
303     bool LikelyBacklash(const CalibrationDetails& calDetails);
304     const int MAX_GA_HISTORY = 3;
305 };
306 
HighlightCell(wxGrid * pGrid,wxGridCellCoords where)307 static void HighlightCell(wxGrid *pGrid, wxGridCellCoords where)
308 {
309     pGrid->SetCellBackgroundColour(where.GetRow(), where.GetCol(), "DARK SLATE GREY");
310     pGrid->SetCellTextColour(where.GetRow(), where.GetCol(), "white");
311 }
312 
313 struct GridTooltipInfo : public wxObject
314 {
315     wxGrid *grid;
316     int gridNum;
317     wxGridCellCoords prevCoords;
GridTooltipInfoGridTooltipInfo318     GridTooltipInfo(wxGrid *g, int i) : grid(g), gridNum(i) { }
319 };
320 
321 struct TextWrapper
322 {
323     wxWindow *win;
324     int width;
TextWrapperTextWrapper325     TextWrapper(wxWindow *win_, int width_) : win(win_), width(width_) { }
WrapTextWrapper326     wxString Wrap(const wxString& text) const
327     {
328         struct Wrapper : public wxTextWrapper
329         {
330             wxString str;
331             Wrapper(wxWindow *win_, const wxString& text, int width_) { Wrap(win_, text, width_); }
332             void OnOutputLine(const wxString& line) { str += line; }
333             void OnNewLine() { str += '\n'; }
334         };
335         return Wrapper(win, text, width).str;
336     }
337 };
338 
339 // Constructor
GuidingAsstWin()340 GuidingAsstWin::GuidingAsstWin()
341     : wxDialog(pFrame, wxID_ANY, wxGetTranslation(_("Guiding Assistant"))),
342       m_measuring(false),
343       m_guideOutputDisabled(false),
344       m_measurementsTaken(false),
345       m_origSubFrames(-1),
346       m_backlashTool(nullptr),
347       m_flushConfig(false)
348 {
349     // Sizer hierarchy:
350     // m_vSizer has {instructions, vResultsSizer, m_gaStatus, btnSizer}
351     // vResultsSizer has {hTopSizer, hBottomSizer}
352     // hTopSizer has {status_group, displacement_group}
353     // hBottomSizer has {other_group, m_recommendation_group}
354     m_vSizer = new wxBoxSizer(wxVERTICAL);
355     wxBoxSizer* vResultsSizer = new wxBoxSizer(wxVERTICAL);
356     wxBoxSizer* hTopSizer = new wxBoxSizer(wxHORIZONTAL);       // Measurement status and high-frequency results
357     wxBoxSizer* hBottomSizer = new wxBoxSizer(wxHORIZONTAL);             // Low-frequency results and recommendations
358 
359     m_instructions = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(700, 50), wxALIGN_LEFT | wxST_NO_AUTORESIZE);
360     MakeBold(m_instructions);
361     m_vSizer->Add(m_instructions, wxSizerFlags(0).Border(wxALL, 8));
362 
363     // Grids have either 3 or 4 columns, so compute width of largest label as scaling term for column widths
364     double minLeftCol = StringWidth(this,
365         _(" -999.99 px/min (-999.99 arc-sec/min )")) + 6;
366     double minRightCol = 1.25 * (StringWidth(this,
367         _(" 9.99 px ( 9.99 arc-sec)")) + 6);
368     // Start of status group
369     wxStaticBoxSizer *status_group = new wxStaticBoxSizer(wxVERTICAL, this, _("Measurement Status"));
370     m_statusgrid = new wxGrid(this, wxID_ANY);
371     m_statusgrid->CreateGrid(3, 4);
372     m_statusgrid->GetGridWindow()->Bind(wxEVT_MOTION, &GuidingAsstWin::OnMouseMove, this, wxID_ANY, wxID_ANY, new GridTooltipInfo(m_statusgrid, 1));
373     m_statusgrid->SetRowLabelSize(1);
374     m_statusgrid->SetColLabelSize(1);
375     m_statusgrid->EnableEditing(false);
376     m_statusgrid->SetDefaultColSize((round(2.0 * minLeftCol / 4.0) + 0.5));
377 
378     int col = 0;
379     int row = 0;
380     m_statusgrid->SetCellValue(row, col++, _("Start time"));
381     m_timestamp_loc.Set(row, col++);
382     m_statusgrid->SetCellValue(row, col++, _("Exposure time"));
383     m_exposuretime_loc.Set(row, col++);
384 
385     StartRow(row, col);
386     m_statusgrid->SetCellValue(row, col++, _("SNR"));
387     m_snr_loc.Set(row, col++);
388     m_statusgrid->SetCellValue(row, col++, _("Star mass"));
389     m_starmass_loc.Set(row, col++);
390 
391     StartRow(row, col);
392     m_statusgrid->SetCellValue(row, col++, _("Elapsed time"));
393     m_elapsedtime_loc.Set(row, col++);
394     m_statusgrid->SetCellValue(row, col++, _("Sample count"));
395     m_samplecount_loc.Set(row, col++);
396 
397     //StartRow(row, col);
398     //m_statusgrid->SetCellValue(_("Frequency cut-off:"), row, col++);   // Leave out for now, probably not useful to users
399     //m_hfcutoff_loc.Set(row, col++);
400 
401     status_group->Add(m_statusgrid);
402     hTopSizer->Add(status_group, wxSizerFlags(0).Border(wxALL, 8));
403     // End of status group
404 
405     // Start of star displacement group
406     wxStaticBoxSizer *displacement_group = new wxStaticBoxSizer(wxVERTICAL, this, _("High-frequency Star Motion"));
407     m_displacementgrid = new wxGrid(this, wxID_ANY);
408     m_displacementgrid->CreateGrid(3, 2);
409     m_displacementgrid->GetGridWindow()->Bind(wxEVT_MOTION, &GuidingAsstWin::OnMouseMove, this, wxID_ANY, wxID_ANY, new GridTooltipInfo(m_displacementgrid, 2));
410     m_displacementgrid->SetRowLabelSize(1);
411     m_displacementgrid->SetColLabelSize(1);
412     m_displacementgrid->EnableEditing(false);
413     m_displacementgrid->SetDefaultColSize(minRightCol);
414 
415     row = 0;
416     col = 0;
417     m_displacementgrid->SetCellValue(row, col++, _("Right ascension, RMS"));
418     m_ra_rms_loc.Set(row, col++);
419 
420     StartRow(row, col);
421     m_displacementgrid->SetCellValue(row, col++, _("Declination, RMS"));
422     m_dec_rms_loc.Set(row, col++);
423 
424     StartRow(row, col);
425     m_displacementgrid->SetCellValue(row, col++, _("Total, RMS"));
426     m_total_rms_loc.Set(row, col++);
427 
428     displacement_group->Add(m_displacementgrid);
429     hTopSizer->Add(displacement_group, wxSizerFlags(0).Border(wxALL, 8));
430     vResultsSizer->Add(hTopSizer);
431     // End of displacement group
432 
433     // Start of "Other" (peak and drift) group
434     wxStaticBoxSizer *other_group = new wxStaticBoxSizer(wxVERTICAL, this, _("Other Star Motion"));
435     m_othergrid = new wxGrid(this, wxID_ANY);
436     m_othergrid->CreateGrid(9, 2);
437     m_othergrid->GetGridWindow()->Bind(wxEVT_MOTION, &GuidingAsstWin::OnMouseMove, this, wxID_ANY, wxID_ANY, new GridTooltipInfo(m_othergrid, 3));
438     m_othergrid->SetRowLabelSize(1);
439     m_othergrid->SetColLabelSize(1);
440     m_othergrid->EnableEditing(false);
441     m_othergrid->SetDefaultColSize(minLeftCol);
442 
443     TextWrapper w(this, minLeftCol);
444 
445     row = 0;
446     col = 0;
447     m_othergrid->SetCellValue(row, col++, w.Wrap(_("Right ascension, Peak")));
448     m_ra_peak_loc.Set(row, col++);
449 
450     StartRow(row, col);
451     m_othergrid->SetCellValue(row, col++, w.Wrap(_("Declination, Peak")));
452     m_dec_peak_loc.Set(row, col++);
453 
454     StartRow(row, col);
455     m_othergrid->SetCellValue(row, col++, w.Wrap(_("Right ascension, Peak-Peak")));
456     m_ra_peakpeak_loc.Set(row, col++);
457 
458     StartRow(row, col);
459     m_othergrid->SetCellValue(row, col++, w.Wrap(_("Right ascension Drift Rate")));
460     m_ra_drift_loc.Set(row, col++);
461 
462     StartRow(row, col);
463     m_othergrid->SetCellValue(row, col++, w.Wrap(_("Right ascension Max Drift Rate")));
464     m_ra_peak_drift_loc.Set(row, col++);
465 
466     StartRow(row, col);
467     m_othergrid->SetCellValue(row, col++, w.Wrap(_("Drift-limiting exposure")));
468     m_ra_drift_exp_loc.Set(row, col++);
469 
470     StartRow(row, col);
471     m_othergrid->SetCellValue(row, col++, w.Wrap(_("Declination Drift Rate")));
472     m_dec_drift_loc.Set(row, col++);
473 
474     StartRow(row, col);
475     m_othergrid->SetCellValue(row, col++, w.Wrap(_("Declination Backlash")));
476     m_backlash_loc.Set(row, col++);
477 
478     StartRow(row, col);
479     m_othergrid->SetCellValue(row, col++, w.Wrap(_("Polar Alignment Error")));
480     m_pae_loc.Set(row, col++);
481 
482     m_othergrid->AutoSizeColumn(0);
483     m_othergrid->AutoSizeRows();
484 
485     other_group->Add(m_othergrid);
486     hBottomSizer->Add(other_group, wxSizerFlags(0).Border(wxALL, 8));
487     // End of peak and drift group
488 
489     // Start of Recommendations group - just a place-holder for layout, populated in MakeRecommendations
490     m_recommend_group = new wxStaticBoxSizer(wxVERTICAL, this, _("Recommendations"));
491     m_recommendgrid = new wxFlexGridSizer(2, 0, 0);
492     m_recommendgrid->AddGrowableCol(0);
493     m_ra_msg = nullptr;
494     m_dec_msg = nullptr;
495     m_snr_msg = nullptr;
496     m_backlash_msg = nullptr;
497     m_pae_msg = nullptr;
498     m_hfd_msg = nullptr;
499     m_exposure_msg = nullptr;
500     m_calibration_msg = nullptr;
501     m_binning_msg = nullptr;
502 
503     m_recommend_group->Add(m_recommendgrid, wxSizerFlags(1).Expand());
504     // Add buttons for viewing the Dec backlash graph or getting help
505     wxBoxSizer* hBtnSizer = new wxBoxSizer(wxHORIZONTAL);
506     m_graphBtn = new wxButton(this, wxID_ANY, _("Show Backlash Graph"));
507     m_graphBtn->SetToolTip(_("Show graph of backlash measurement points"));
508     m_graphBtn->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(GuidingAsstWin::OnGraph), nullptr, this);
509     m_graphBtn->Enable(false);
510     hBtnSizer->Add(m_graphBtn, wxSizerFlags(0).Border(wxALL, 5));
511     wxButton* helpBtn = new wxButton(this, wxID_ANY, _("Help"));
512     helpBtn->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(GuidingAsstWin::OnHelp), nullptr, this);
513     hBtnSizer->Add(50, 0);
514     hBtnSizer->Add(helpBtn, wxSizerFlags(0).Border(wxALL, 5));
515     m_recommend_group->Add(hBtnSizer, wxSizerFlags(0).Border(wxALL, 5));
516     // Recommendations will be hidden/shown depending on state
517     hBottomSizer->Add(m_recommend_group, wxSizerFlags(0).Border(wxALL, 8));
518     vResultsSizer->Add(hBottomSizer);
519 
520     m_vSizer->Add(vResultsSizer);
521     m_recommend_group->Show(false);
522     // End of recommendations
523 
524     m_backlashCB = new wxCheckBox(this, wxID_ANY, _("Measure Declination Backlash"));
525     m_backlashCB->SetToolTip(_("PHD2 will move the guide star a considerable distance north, then south to measure backlash. Be sure the selected star has "
526         "plenty of room to move in the north direction.  If the guide star is lost, increase the size of the search region to at least 20 px"));
527     if (TheScope())
528     {
529         m_backlashCB->SetValue(true);
530         m_backlashCB->Enable(true);
531     }
532     else
533     {
534         m_backlashCB->SetValue(false);
535         m_backlashCB->Enable(false);
536     }
537     // Text area for showing backlash measuring steps
538     m_gaStatus = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxSize(500, 40), wxALIGN_CENTER);
539     MakeBold(m_gaStatus);
540     m_vSizer->Add(m_gaStatus, wxSizerFlags(0).Border(wxALL, 8).Center());
541 
542     wxBoxSizer *btnSizer = new wxBoxSizer(wxHORIZONTAL);
543     btnSizer->Add(10, 0);       // a little spacing left of Start button
544     btnSizer->Add(m_backlashCB, wxSizerFlags(0).Border(wxALL, 8));
545     btnSizer->Add(40, 0);       // Put a spacer between the button and checkbox
546 
547     m_start = new wxButton(this, wxID_ANY, _("Start"), wxDefaultPosition, wxDefaultSize, 0);
548     m_start->SetToolTip(_("Start measuring (disables guiding)"));
549     btnSizer->Add(m_start, 0, wxALL, 5);
550     m_start->Enable(false);
551 
552     btnReviewPrev = new OptionsButton(this, GA_REVIEW_BUTTON, _("Review previous"), wxDefaultPosition, wxDefaultSize, 0);
553     btnReviewPrev->SetToolTip(_("Review previous Guiding Assistant results"));
554     btnReviewPrev->Enable(GetGAHistoryCount() > 0);
555     btnSizer->Add(btnReviewPrev, 0, wxALL, 5);
556 
557     m_stop = new wxButton(this, wxID_ANY, _("Stop"), wxDefaultPosition, wxDefaultSize, 0);
558     m_stop->SetToolTip(_("Stop measuring and re-enable guiding"));
559     m_stop->Enable(false);
560 
561     btnSizer->Add(m_stop, 0, wxALL, 5);
562     m_vSizer->Add(btnSizer, 0, wxEXPAND, 5);
563 
564     SetAutoLayout(true);
565     SetSizerAndFit(m_vSizer);
566 
567     Connect(wxEVT_CLOSE_WINDOW, wxCloseEventHandler(GuidingAsstWin::OnClose));
568     Connect(APPSTATE_NOTIFY_EVENT, wxCommandEventHandler(GuidingAsstWin::OnAppStateNotify));
569     m_start->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(GuidingAsstWin::OnStart), nullptr, this);
570     m_stop->Connect(wxEVT_COMMAND_BUTTON_CLICKED, wxCommandEventHandler(GuidingAsstWin::OnStop), nullptr, this);
571     Bind(wxEVT_BUTTON, &GuidingAsstWin::OnReviewPrevious, this, GA_REVIEW_BUTTON, GA_REVIEW_BUTTON);
572     Bind(wxEVT_MENU, &GuidingAsstWin::OnGAReviewSelection, this, GA_REVIEW_ITEMS_BASE, GA_REVIEW_ITEMS_LIMIT);
573 
574     if (m_backlashCB->IsEnabled())
575         m_backlashTool = new BacklashTool();
576 
577     m_measuringBacklash = false;
578     origMultistarMode = pFrame->pGuider->GetMultiStarMode();
579 
580     int xpos = pConfig->Global.GetInt("/GuidingAssistant/pos.x", -1);
581     int ypos = pConfig->Global.GetInt("/GuidingAssistant/pos.y", -1);
582     MyFrame::PlaceWindowOnScreen(this, xpos, ypos);
583 
584     wxCommandEvent dummy;
585     OnAppStateNotify(dummy); // init state-dependent controls
586 
587     reviewMode = false;
588     if (pFrame->pGuider->IsGuiding())
589     {
590         OnStart(dummy);             // Auto-start if we're already guiding
591     }
592 }
593 
~GuidingAsstWin(void)594 GuidingAsstWin::~GuidingAsstWin(void)
595 {
596     pFrame->pGuidingAssistant = 0;
597     delete m_backlashTool;
598 }
599 
StatsReset()600 void GuidingAsstWin::StatsReset()
601 {
602     m_hpfRAStats.ClearAll();
603     m_lpfRAStats.ClearAll();
604     m_hpfDecStats.ClearAll();
605     m_decAxisStats.ClearAll();
606     m_raAxisStats.ClearAll();
607 }
608 
GetGridToolTip(int gridNum,const wxGridCellCoords & coords,wxString * s)609 static bool GetGridToolTip(int gridNum, const wxGridCellCoords& coords, wxString *s)
610 {
611     int col = coords.GetCol();
612 
613     if (gridNum > 1 && col != 0)
614         return false;
615     else
616     if (col != 0 && col != 2)
617         return false;
618 
619     switch (gridNum * 100 + coords.GetRow())
620     {
621         // status grid
622         case 101:
623         {
624             if (col == 0)
625                 *s = _("Signal-to-noise ratio; a measure of how well PHD2 can isolate the star from the sky/noise background");
626             else
627                 *s = _("Measure of overall star brightness. Consider using 'Auto-select Star' (Alt-S) to choose the star.");
628             break;
629         }
630 
631         // displacement grid
632         case 200: *s = _("Measure of typical high-frequency right ascension star movements; guiding usually cannot correct for fluctuations this small."); break;
633         case 201: *s = _("Measure of typical high-frequency declination star movements; guiding usually cannot correct for fluctuations this small."); break;
634 
635         // other grid
636         case 300: *s = _("Maximum sample-sample deflection seen in right ascension."); break;
637         case 301: *s = _("Maximum sample-sample deflection seen in declination."); break;
638         case 302: *s = _("Maximum peak-peak deflection seen in right ascension during sampling period."); break;
639         case 303: *s = _("Estimated overall drift rate in right ascension."); break;
640         case 304: *s = _("Maximum drift rate in right ascension during sampling period."); break;
641         case 305: *s = _("Exposure time to keep maximum RA drift below the recommended min-move level."); break;
642         case 306: *s = _("Estimated overall drift rate in declination."); break;
643         case 307: *s = _("Estimated declination backlash if test was completed. Results are time to clear backlash (ms) and corresponding gear angle (arc-sec). Uncertainty estimate is one unit of standard deviation"); break;
644         case 308: *s = _("Estimate of polar alignment error. If the scope declination is unknown, the value displayed is a lower bound and the actual error may be larger."); break;
645 
646         default: return false;
647     }
648 
649     return true;
650 }
651 
OnMouseMove(wxMouseEvent & ev)652 void GuidingAsstWin::OnMouseMove(wxMouseEvent& ev)
653 {
654     GridTooltipInfo *info = static_cast<GridTooltipInfo *>(ev.GetEventUserData());
655     wxGridCellCoords coords(info->grid->XYToCell(info->grid->CalcUnscrolledPosition(ev.GetPosition())));
656     if (coords != info->prevCoords)
657     {
658         info->prevCoords = coords;
659         wxString s;
660         if (GetGridToolTip(info->gridNum, coords, &s))
661             info->grid->GetGridWindow()->SetToolTip(s);
662         else
663             info->grid->GetGridWindow()->UnsetToolTip();
664     }
665     ev.Skip();
666 }
667 
FillInstructions(DialogState eState)668 void GuidingAsstWin::FillInstructions(DialogState eState)
669 {
670     wxString instr;
671 
672     switch (eState)
673     {
674     case STATE_NO_STAR:
675         instr = _("Choose a non-saturated star with a good SNR (>= 8) and begin guiding");
676         break;
677     case STATE_START_READY:
678         if (!m_measurementsTaken)
679             instr = _("Click Start to begin measurements.  Guiding will be disabled during this time so the star will move around.");
680         else
681             instr = m_instructions->GetLabel();
682         break;
683     case STATE_MEASURING:
684         instr = _("Guiding output is disabled and star movement is being measured.  Click Stop after 2 minutes (longer if you're measuring RA tracking accuracy of the mount).");
685         break;
686     case STATE_STOPPED:
687         instr = _("Guiding has been resumed. Look at the recommendations and make any desired changes.  Click Start to repeat the measurements, or close the window to continue guiding.");
688         break;
689     }
690     m_instructions->SetLabel(instr);
691     m_instructions->Wrap(700);
692     m_instructions->Layout();
693 }
694 
BacklashStep(const PHD_Point & camLoc)695 void GuidingAsstWin::BacklashStep(const PHD_Point& camLoc)
696 {
697     BacklashTool::MeasurementResults qual;
698     m_backlashTool->DecMeasurementStep(camLoc);
699     wxString bl_msg = _("Measuring backlash: ") + m_backlashTool->GetLastStatus();
700     m_gaStatus->SetLabel(bl_msg);
701     if (m_backlashTool->GetBltState() == BacklashTool::BLT_STATE_COMPLETED)
702     {
703         wxString bl_msg = _("Measuring backlash: ") + m_backlashTool->GetLastStatus();
704         m_gaStatus->SetLabel(bl_msg);
705         if (m_backlashTool->GetBltState() == BacklashTool::BLT_STATE_COMPLETED)
706         {
707             try
708             {
709                 if (inBLTWrapUp)
710                 {
711                     Debug.Write("GA-BLT: Re-entrancy in Backlash step!\n");
712                     return;
713                 }
714                 else
715                     inBLTWrapUp = true;
716                 Debug.Write("GA-BLT: state = completed\n");
717                 qual = m_backlashTool->GetMeasurementQuality();
718                 if (qual == BacklashTool::MEASUREMENT_VALID || qual == BacklashTool::MEASUREMENT_TOO_FEW_NORTH)
719                 {
720                     Debug.Write("GA-BLT: Wrap-up after normal completion\n");
721                     // populate result variables
722                     m_backlashPx = m_backlashTool->GetBacklashResultPx();
723                     m_backlashMs = m_backlashTool->GetBacklashResultMs();
724                     double bltSigmaPx;
725                     m_backlashTool->GetBacklashSigma(&bltSigmaPx, &m_backlashSigmaMs);
726                     double bltGearAngle = (m_backlashPx * pFrame->GetCameraPixelScale());
727                     double bltGearAngleSigma = (bltSigmaPx * pFrame->GetCameraPixelScale());
728                     wxString preamble = ((m_backlashMs >= 5000 || qual == BacklashTool::MEASUREMENT_TOO_FEW_NORTH) ? ">=" : "");
729                     wxString outStr, outStrTr;  // untranslated and translated
730                     if (qual == BacklashTool::MEASUREMENT_VALID)
731                     {
732                         outStr = wxString::Format("%s %d  +/-  %0.0f ms (%0.1f  +/-  %0.1f arc-sec)",
733                                                   preamble, wxMax(0, m_backlashMs), m_backlashSigmaMs,
734                                                   wxMax(0, bltGearAngle), bltGearAngleSigma);
735                         outStrTr = wxString::Format("%s %d  +/-  %0.0f %s (%0.1f  +/-  %0.1f %s)",
736                                                     preamble, wxMax(0, m_backlashMs), m_backlashSigmaMs, _("ms"),
737                                                     wxMax(0, bltGearAngle), bltGearAngleSigma, _("arc-sec"));
738                     }
739                     else
740                     {
741                         outStr = wxString::Format("%s %d  +/-  ms (test impaired)",
742                                                   preamble, wxMax(0, m_backlashMs));
743                         outStrTr = wxString::Format("%s %d  +/-  %s",
744                                                     preamble, wxMax(0, m_backlashMs), _("ms (test impaired)"));
745                     }
746                     m_othergrid->SetCellValue(m_backlash_loc, outStrTr);
747                     HighlightCell(m_othergrid, m_backlash_loc);
748                     outStr += "\n";
749                     GuideLog.NotifyGAResult("Backlash=" + outStr);
750                     Debug.Write("BLT: Reported result = " + outStr);
751                     m_graphBtn->Enable(true);
752                 }
753                 else
754                 {
755                     m_othergrid->SetCellValue(m_backlash_loc, "");
756                 }
757                 EndBacklashTest(qual == BacklashTool::MEASUREMENT_VALID || qual == BacklashTool::MEASUREMENT_TOO_FEW_NORTH);
758             }
759             catch (const wxString& msg)
760             {
761                 Debug.Write(wxString::Format("GA-BLT: fault in completion-processing at %d, %s\n", __LINE__, msg));
762                 EndBacklashTest(false);
763             }
764         }
765     }
766     else if (m_backlashTool->GetBltState() == BacklashTool::BLT_STATE_ABORTED)
767         EndBacklashTest(false);
768 
769     inBLTWrapUp = false;
770 }
771 
BacklashError()772 void GuidingAsstWin::BacklashError()
773 {
774     EndBacklashTest(false);
775 }
776 
777 // Event handlers for applying recommendations
OnRAMinMove(wxCommandEvent & event)778 void GuidingAsstWin::OnRAMinMove(wxCommandEvent& event)
779 {
780     GuideAlgorithm *raAlgo = pMount->GetXGuideAlgorithm();
781 
782     if (!raAlgo)
783         return;
784 
785     if (raAlgo->GetMinMove() >= 0.0)
786     {
787         if (!raAlgo->SetMinMove(m_ra_minmove_rec))
788         {
789             Debug.Write(wxString::Format("GuideAssistant changed RA_MinMove to %0.2f\n", m_ra_minmove_rec));
790             pFrame->pGraphLog->UpdateControls();
791             pFrame->NotifyGuidingParam("RA " + raAlgo->GetGuideAlgorithmClassName() + " MinMove ", m_ra_minmove_rec);
792             m_raMinMoveButton->Enable(false);
793             m_flushConfig = true;
794         }
795         else
796             Debug.Write("GuideAssistant could not change RA_MinMove\n");
797     }
798     else
799         Debug.Write("GuideAssistant logic flaw, RA algorithm has no MinMove property\n");
800 }
801 
OnDecMinMove(wxCommandEvent & event)802 void GuidingAsstWin::OnDecMinMove(wxCommandEvent& event)
803 {
804     GuideAlgorithm *decAlgo = pMount->GetYGuideAlgorithm();
805 
806     if (!decAlgo)
807         return;
808 
809     if (decAlgo->GetMinMove() >= 0.0)
810     {
811         if (!decAlgo->SetMinMove(m_dec_minmove_rec))
812         {
813             Debug.Write(wxString::Format("GuideAssistant changed Dec_MinMove to %0.2f\n", m_dec_minmove_rec));
814             pFrame->pGraphLog->UpdateControls();
815             pFrame->NotifyGuidingParam("Declination " + decAlgo->GetGuideAlgorithmClassName() + " MinMove ", m_dec_minmove_rec);
816             m_decMinMoveButton->Enable(false);
817             m_flushConfig = true;
818         }
819         else
820             Debug.Write("GuideAssistant could not change Dec_MinMove\n");
821     }
822     else
823         Debug.Write("GuideAssistant logic flaw, Dec algorithm has no MinMove property\n");
824 }
825 
OnDecAlgoChange(wxCommandEvent & event)826 void GuidingAsstWin::OnDecAlgoChange(wxCommandEvent& event)
827 {
828     if (pMount->IsStepGuider())
829         return;                         // should never happen
830     pMount->SetGuidingEnabled(false);
831     // Need to make algo change through AD UI controls to keep everything in-synch
832     Mount::MountConfigDialogPane* currMountPane = pFrame->pAdvancedDialog->GetCurrentMountPane();
833     currMountPane->ChangeYAlgorithm("Lowpass2");
834     Debug.Write("GuideAssistant changed Dec algo to Lowpass2\n");
835     GuideAlgorithm *decAlgo = pMount->GetYGuideAlgorithm();
836     if (!decAlgo || decAlgo->GetGuideAlgorithmClassName() != "Lowpass2")
837     {
838         Debug.Write("GuideAssistant could not set Dec algo to Lowpass2\n");
839         return;
840     }
841 
842     double newAggr = 80.0;
843     decAlgo->SetParam("aggressiveness", newAggr);
844     decAlgo->SetParam("minMove", m_dec_minmove_rec);
845     Debug.Write(wxString::Format("GuideAssistant set Lowpass2 aggressiveness = %0.2f, min-move = %0.2f\n", newAggr, m_dec_minmove_rec));
846     pFrame->pGraphLog->UpdateControls();
847     pMount->SetGuidingEnabled(true);
848     pFrame->NotifyGuidingParam("Declination algorithm", wxString("Lowpass2"));
849     pFrame->NotifyGuidingParam("Declination Lowpass2 aggressivness", newAggr);
850     pFrame->NotifyGuidingParam("Declination Lowpass2 MinMove", m_dec_minmove_rec);
851 
852     m_decAlgoButton->Enable(false);
853 
854     m_flushConfig = true;
855 }
856 
OnDecBacklash(wxCommandEvent & event)857 void GuidingAsstWin::OnDecBacklash(wxCommandEvent& event)
858 {
859     BacklashComp *pComp = TheScope()->GetBacklashComp();
860 
861     pComp->SetBacklashPulseWidth(m_backlashRecommendedMs, 0. /* floor */, 0. /* ceiling */);
862     pComp->EnableBacklashComp(!pMount->IsStepGuider());
863     m_decBacklashButton->Enable(false);
864     m_flushConfig = true;
865 }
866 
OnGraph(wxCommandEvent & event)867 void GuidingAsstWin::OnGraph(wxCommandEvent& event)
868 {
869     if (reviewMode)
870         m_backlashTool->ShowGraph(this, gaDetails.BLTNorthMoves, gaDetails.BLTSouthMoves, gaDetails.BLTMsmtPulse);
871     else
872         m_backlashTool->ShowGraph(this, m_backlashTool->GetNorthSteps(), m_backlashTool->GetSouthSteps(), m_backlashTool->GetBLTMsmtPulseSize());
873 }
874 
OnHelp(wxCommandEvent & event)875 void GuidingAsstWin::OnHelp(wxCommandEvent& event)
876 {
877     pFrame->help->Display("Tools.htm#Guiding_Assistant");   // named anchors in help file are not subject to translation
878 }
879 
SizedMsg(const wxString & msg)880 static wxString SizedMsg(const wxString& msg)
881 {
882     if (msg.length() < 70)
883         return msg + wxString(' ', 70 - msg.length());
884     else
885         return msg;
886 }
887 
888 // Adds a recommendation string and a button bound to the passed event handler
AddRecommendationBtn(const wxString & msg,void (GuidingAsstWin::* handler)(wxCommandEvent &),wxButton ** ppButton)889 wxStaticText *GuidingAsstWin::AddRecommendationBtn(const wxString& msg,
890                                                    void (GuidingAsstWin::* handler)(wxCommandEvent&),
891                                                    wxButton **ppButton)
892 {
893     wxStaticText *rec_label;
894 
895     rec_label = new wxStaticText(this, wxID_ANY, SizedMsg(msg));
896     rec_label->Wrap(250);
897     m_recommendgrid->Add(rec_label, 1, wxALIGN_LEFT | wxALL, 5);
898     if (handler)
899     {
900         int min_h;
901         int min_w;
902         this->GetTextExtent(_("Apply"), &min_w, &min_h);
903         *ppButton = new wxButton(this, wxID_ANY, _("Apply"), wxDefaultPosition, wxSize(min_w + 8, min_h + 8), 0);
904         m_recommendgrid->Add(*ppButton, 0, wxALIGN_RIGHT | wxALL, 5);
905         (*ppButton)->Connect(wxEVT_COMMAND_BUTTON_CLICKED, reinterpret_cast<wxObjectEventFunction>(handler), nullptr, this);
906     }
907     else
908     {
909         wxStaticText *rec_tmp = new wxStaticText(this, wxID_ANY, wxEmptyString, wxDefaultPosition, wxDefaultSize);
910         m_recommendgrid->Add(rec_tmp, 0, wxALL, 5);
911     }
912     return rec_label;
913 }
914 
915 // Jacket for simple addition of a text-only recommendation
AddRecommendationMsg(const wxString & msg)916 wxStaticText *GuidingAsstWin::AddRecommendationMsg(const wxString& msg)
917 {
918     return AddRecommendationBtn(msg, nullptr, nullptr);
919 }
920 
LogResults()921 void GuidingAsstWin::LogResults()
922 {
923     wxString str;
924     Debug.Write("Guiding Assistant results follow:\n");
925     str = wxString::Format("SNR=%s, Samples=%s, Elapsed Time=%s, RA HPF-RMS=%s, Dec HPF-RMS=%s, Total HPF-RMS=%s\n",
926         m_statusgrid->GetCellValue(m_snr_loc), m_statusgrid->GetCellValue(m_samplecount_loc), m_statusgrid->GetCellValue(m_elapsedtime_loc),
927         m_displacementgrid->GetCellValue(m_ra_rms_loc),
928         m_displacementgrid->GetCellValue(m_dec_rms_loc), m_displacementgrid->GetCellValue(m_total_rms_loc));
929 
930     GuideLog.NotifyGAResult(str);
931     Debug.Write(str);
932     str = wxString::Format("RA Peak=%s, RA Peak-Peak %s, RA Drift Rate=%s, Max RA Drift Rate=%s, Drift-Limiting Exp=%s\n",
933         m_othergrid->GetCellValue(m_ra_peak_loc),
934         m_othergrid->GetCellValue(m_ra_peakpeak_loc), m_othergrid->GetCellValue(m_ra_drift_loc),
935         m_othergrid->GetCellValue(m_ra_peak_drift_loc),
936         m_othergrid->GetCellValue(m_ra_drift_exp_loc)
937         );
938     GuideLog.NotifyGAResult(str);
939     Debug.Write(str);
940     str = wxString::Format("Dec Drift Rate=%s, Dec Peak=%s, PA Error=%s\n",
941         m_othergrid->GetCellValue(m_dec_drift_loc), m_othergrid->GetCellValue(m_dec_peak_loc),
942         m_othergrid->GetCellValue(m_pae_loc));
943     GuideLog.NotifyGAResult(str);
944     Debug.Write(str);
945 }
946 
947 // Get info regarding any saved GA sessions that include a BLT
GetBLTHistory(const std::vector<wxString> & Timestamps,int * oldestBLTInx,int * BLTCount)948 static void GetBLTHistory(const std::vector<wxString>&Timestamps, int* oldestBLTInx, int* BLTCount)
949 {
950     int oldestInx = -1;
951     int bltCount = 0;
952     for (int inx = 0; inx < Timestamps.size(); inx++)
953     {
954         wxString northBLT = "/GA/" + Timestamps[inx] + "/BLT_north";
955         if (pConfig->Profile.GetString(northBLT, wxEmptyString) != wxEmptyString)
956         {
957             bltCount++;
958             if (oldestInx < 0)
959                 oldestInx = inx;
960         }
961     }
962     *oldestBLTInx = oldestInx;
963     *BLTCount = bltCount;
964 }
965 
GetGAHistoryCount()966 int GuidingAsstWin::GetGAHistoryCount()
967 {
968     std::vector<wxString> timeStamps = pConfig->Profile.GetGroupNames("/GA");
969     return timeStamps.size();
970 }
971 
972 // Insure that no more than 3 GA sessions are kept in the profile while also keeping at least one BLT measurement if one exists
TrimGAHistory(bool FreshBLT,int HistoryDepth)973 static void TrimGAHistory(bool FreshBLT, int HistoryDepth)
974 {
975     int bltCount;
976     int oldestBLTInx;
977     int totalGAs;
978     wxString targetEntry;
979 
980     std::vector<wxString> timeStamps;
981     timeStamps = pConfig->Profile.GetGroupNames("/GA");
982     totalGAs = timeStamps.size();
983     GetBLTHistory(timeStamps, &oldestBLTInx, &bltCount);
984     if (totalGAs > HistoryDepth)
985     {
986         if (FreshBLT || bltCount == 0 || oldestBLTInx > 0 || bltCount > 1 || bltCount == totalGAs)
987             targetEntry = timeStamps[0];
988         else
989             targetEntry = timeStamps[1];
990         pConfig->Profile.DeleteGroup("/GA/" + targetEntry);
991         Debug.Write(wxString::Format("GA-History: removed entry for %s\n", targetEntry));
992     }
993 }
994 
995 // Save the results from the most recent GA run in the profile
SaveGAResults(const wxString * AllRecommendations)996 void GuidingAsstWin::SaveGAResults(const wxString* AllRecommendations)
997 {
998     wxString prefix = "/GA/" + startStr;
999 
1000     pConfig->Profile.SetString(prefix + "/timestamp", m_statusgrid->GetCellValue(m_timestamp_loc));
1001     pConfig->Profile.SetString(prefix + "/snr", m_statusgrid->GetCellValue(m_snr_loc));
1002     pConfig->Profile.SetString(prefix + "/star_mass", m_statusgrid->GetCellValue(m_starmass_loc));
1003     pConfig->Profile.SetString(prefix + "/sample_count", m_statusgrid->GetCellValue(m_samplecount_loc));
1004     pConfig->Profile.SetString(prefix + "/elapsed_time", m_statusgrid->GetCellValue(m_elapsedtime_loc));
1005     pConfig->Profile.SetString(prefix + "/exposure_time", m_statusgrid->GetCellValue(m_exposuretime_loc));
1006     pConfig->Profile.SetString(prefix + "/ra_hpf_rms", m_displacementgrid->GetCellValue(m_ra_rms_loc));
1007     pConfig->Profile.SetString(prefix + "/dec_hpf_rms", m_displacementgrid->GetCellValue(m_dec_rms_loc));
1008     pConfig->Profile.SetString(prefix + "/total_hpf_rms", m_displacementgrid->GetCellValue(m_total_rms_loc));
1009     pConfig->Profile.SetString(prefix + "/ra_peak", m_othergrid->GetCellValue(m_ra_peak_loc));
1010     pConfig->Profile.SetString(prefix + "/ra_peak_peak", m_othergrid->GetCellValue(m_ra_peakpeak_loc));
1011     pConfig->Profile.SetString(prefix + "/ra_drift_rate", m_othergrid->GetCellValue(m_ra_drift_loc));
1012     pConfig->Profile.SetString(prefix + "/ra_peak_drift_rate", m_othergrid->GetCellValue(m_ra_peak_drift_loc));
1013     pConfig->Profile.SetString(prefix + "/ra_drift_exposure", m_othergrid->GetCellValue(m_ra_drift_exp_loc));
1014     pConfig->Profile.SetString(prefix + "/dec_drift_rate", m_othergrid->GetCellValue(m_dec_drift_loc));
1015     pConfig->Profile.SetString(prefix + "/dec_peak", m_othergrid->GetCellValue(m_dec_peak_loc));
1016     pConfig->Profile.SetString(prefix + "/pa_error", m_othergrid->GetCellValue(m_pae_loc));
1017     pConfig->Profile.SetString(prefix + "/dec_corrected_rms", std::to_string(decCorrectedRMS));
1018     pConfig->Profile.SetString(prefix + "/backlash_info", m_othergrid->GetCellValue(m_backlash_loc));
1019     pConfig->Profile.SetString(prefix + "/dec_lf_drift_rate", std::to_string(decDriftPerMin));
1020     pConfig->Profile.SetString(prefix + "/rec_ra_minmove", std::to_string(m_ra_minmove_rec));
1021     pConfig->Profile.SetString(prefix + "/rec_dec_minmove", std::to_string(m_dec_minmove_rec));
1022     if (m_backlashRecommendedMs > 0)
1023         pConfig->Profile.SetString(prefix + "/BLT_pulse", std::to_string(m_backlashMs));
1024     pConfig->Profile.SetString(prefix + "/recommendations", *AllRecommendations);
1025     bool freshBLT = m_backlashTool && m_backlashTool->IsGraphable();        // Just did a BLT that is viewable
1026     if (freshBLT)
1027     {
1028         pConfig->Profile.SetInt(prefix + "/BLT_MsmtPulse", m_backlashTool->GetBLTMsmtPulseSize());
1029         std::vector<double> northSteps = m_backlashTool->GetNorthSteps();
1030         std::vector<double> southSteps = m_backlashTool->GetSouthSteps();
1031         wxString stepStr = "";
1032 
1033         for (std::vector<double>::const_iterator it = northSteps.begin(); it != northSteps.end(); ++it)
1034         {
1035             stepStr += wxString::Format("%0.1f,", *it);
1036         }
1037         stepStr = stepStr.Left(stepStr.length() - 2);
1038         pConfig->Profile.SetString(prefix + "/BLT_north", stepStr);
1039 
1040         stepStr = "";
1041 
1042         for (std::vector<double>::const_iterator it = southSteps.begin(); it != southSteps.end(); ++it)
1043         {
1044             stepStr += wxString::Format("%0.1f,", *it);
1045         }
1046         stepStr = stepStr.Left(stepStr.length() - 2);
1047         pConfig->Profile.SetString(prefix + "/BLT_South", stepStr);
1048     }
1049     TrimGAHistory(freshBLT, MAX_GA_HISTORY);
1050 }
1051 
1052 // Reload GA results for the passed timestamp
LoadGAResults(const wxString & TimeStamp,GADetails * Details)1053 void GuidingAsstWin::LoadGAResults(const wxString& TimeStamp, GADetails* Details)
1054 {
1055     wxString prefix = "/GA/" + TimeStamp;
1056     *Details = {};              // Reset all vars
1057     Details->TimeStamp = pConfig->Profile.GetString(prefix + "/timestamp", wxEmptyString);
1058     Details->SNR = pConfig->Profile.GetString(prefix + "/snr", wxEmptyString);
1059     Details->StarMass = pConfig->Profile.GetString(prefix + "/star_mass", wxEmptyString);
1060     Details->SampleCount = pConfig->Profile.GetString(prefix + "/sample_count", wxEmptyString);
1061     Details->ExposureTime = pConfig->Profile.GetString(prefix + "/exposure_time", wxEmptyString);
1062     Details->ElapsedTime = pConfig->Profile.GetString(prefix + "/elapsed_time", wxEmptyString);
1063     Details->RA_HPF_RMS = pConfig->Profile.GetString(prefix + "/ra_hpf_rms", wxEmptyString);
1064     Details->Dec_HPF_RMS = pConfig->Profile.GetString(prefix + "/dec_hpf_rms", wxEmptyString);
1065     Details->Total_HPF_RMS = pConfig->Profile.GetString(prefix + "/total_hpf_rms", wxEmptyString);
1066     Details->RAPeak = pConfig->Profile.GetString(prefix + "/ra_peak", wxEmptyString);
1067     Details->RAPeak_Peak = pConfig->Profile.GetString(prefix + "/ra_peak_peak", wxEmptyString);
1068     Details->RADriftRate = pConfig->Profile.GetString(prefix + "/ra_drift_rate", wxEmptyString);
1069     Details->RAMaxDriftRate = pConfig->Profile.GetString(prefix + "/ra_peak_drift_rate", wxEmptyString);
1070     Details->DriftLimitingExposure = pConfig->Profile.GetString(prefix + "/ra_drift_exposure", wxEmptyString);
1071     Details->DecDriftRate = pConfig->Profile.GetString(prefix + "/dec_drift_rate", wxEmptyString);
1072     Details->DecPeak = pConfig->Profile.GetString(prefix + "/dec_peak", wxEmptyString);
1073     Details->PAError = pConfig->Profile.GetString(prefix + "/pa_error", wxEmptyString);
1074     Details->DecCorrectedRMS = pConfig->Profile.GetString(prefix + "/dec_corrected_rms", wxEmptyString);
1075     Details->BackLashInfo = pConfig->Profile.GetString(prefix + "/backlash_info", wxEmptyString);
1076     Details->Dec_LF_DriftRate = pConfig->Profile.GetString(prefix + "/dec_lf_drift_rate", wxEmptyString);
1077     Details->RecRAMinMove = pConfig->Profile.GetString(prefix + "/rec_ra_minmove", wxEmptyString);
1078     Details->RecDecMinMove = pConfig->Profile.GetString(prefix + "/rec_dec_minmove", wxEmptyString);
1079     Details->BLTAmount = pConfig->Profile.GetString(prefix + "/BLT_pulse", wxEmptyString);
1080     Details->Recommendations = pConfig->Profile.GetString(prefix + "/recommendations", wxEmptyString);
1081     wxString northBLT = pConfig->Profile.GetString(prefix + "/BLT_North", wxEmptyString);
1082     wxString southBLT = pConfig->Profile.GetString(prefix + "/BLT_South", wxEmptyString);
1083     Details->BLTMsmtPulse = pConfig->Profile.GetInt(prefix + "/BLT_MsmtPulse", -1);
1084     if (northBLT.Length() > 0 && southBLT.Length() > 0)
1085     {
1086         wxStringTokenizer tok;
1087         wxString strVal;
1088         double ptVal;
1089         tok.SetString(northBLT, ",");
1090         while (tok.HasMoreTokens())
1091         {
1092             strVal = tok.GetNextToken();
1093             strVal.ToDouble(&ptVal);
1094             Details->BLTNorthMoves.push_back(ptVal);
1095         }
1096 
1097         tok.SetString(southBLT, ",");
1098         while (tok.HasMoreTokens())
1099         {
1100             strVal = tok.GetNextToken();
1101             strVal.ToDouble(&ptVal);
1102             Details->BLTSouthMoves.push_back(ptVal);
1103         }
1104     }
1105 }
1106 
1107 // Compute a drift-corrected value for Dec RMS and use that as a seeing estimate.  For long GA runs, compute values for overlapping
1108 // 2-minute intervals and use the smallest result
1109 // Perform suitable sanity checks, revert to default "smart" recommendations if things look wonky
GetMinMoveRecs(double & RecRA,double & RecDec)1110 void GuidingAsstWin::GetMinMoveRecs(double& RecRA, double&RecDec)
1111 {
1112     AxisStats decVals;
1113     double bestEstimate = 1000;
1114     double slope = 0;
1115     double intcpt = 0;
1116     double rSquared = 0;
1117     double selRSquared = 0;
1118     double selSlope = 0;
1119     double correctedRMS;
1120     const int MEASUREMENT_WINDOW_SIZE = 120;     // seconds
1121     const int WINDOW_ADJUSTMENT = MEASUREMENT_WINDOW_SIZE / 2;
1122 
1123     int lastInx = m_decAxisStats.GetCount() - 1;
1124     double pxscale = pFrame->GetCameraPixelScale();
1125     StarDisplacement val = m_decAxisStats.GetEntry(0);
1126     double tStart = val.DeltaTime;
1127     double multiplier_ra = 0.65;                                    // 65% of Dec recommendation, empirical value
1128     double multiplier_dec = (pxscale < 1.5) ? 1.28 : 1.65;          // 20% or 10% activity target based on normal distribution
1129 
1130     try
1131     {
1132         if (m_decAxisStats.GetLastEntry().DeltaTime - tStart > 1.2 * MEASUREMENT_WINDOW_SIZE)           //Long GA run, more than 2.4 minutes
1133         {
1134             bool done = false;
1135             int inx = 0;
1136             while (!done)
1137             {
1138                 val = m_decAxisStats.GetEntry(inx);
1139                 decVals.AddGuideInfo(val.DeltaTime, val.StarPos, 0);
1140                 // Compute the minimum sigma for sliding, overlapping 2-min elapsed time intervals. Include the final interval if it's >= 1.6 minutes
1141                 if (val.DeltaTime - tStart >= MEASUREMENT_WINDOW_SIZE || (inx == lastInx && val.DeltaTime - tStart >= 0.8 * MEASUREMENT_WINDOW_SIZE))
1142                 {
1143                     if (decVals.GetCount() > 1)
1144                     {
1145                         double simpleSigma = decVals.GetSigma();
1146                         rSquared = decVals.GetLinearFitResults(&slope, &intcpt, &correctedRMS);
1147                         // If there is little drift relative to the random movements, the drift-correction is irrelevant and can actually degrade the result.  So don't use the drift-corrected
1148                         // RMS unless it's smaller than the simple sigma
1149                         if (correctedRMS < simpleSigma)
1150                         {
1151                             if (correctedRMS < bestEstimate)            // Keep track of the smallest value seen
1152                             {
1153                                 bestEstimate = correctedRMS;
1154                                 selRSquared = rSquared;
1155                                 selSlope = slope;
1156                             }
1157                         }
1158                         else
1159                             bestEstimate = wxMin(bestEstimate, simpleSigma);
1160                         Debug.Write(wxString::Format("GA long series, window start=%0.0f, window end=%0.0f, Uncorrected RMS=%0.3f, Drift=%0.3f, Corrected RMS=%0.3f, R-sq=%0.3f\n",
1161                             tStart, val.DeltaTime, simpleSigma, slope * 60, correctedRMS, rSquared));
1162                     }
1163                     // Move the start of the next window earlier by 1 minute
1164                     double targetTime = val.DeltaTime - WINDOW_ADJUSTMENT;
1165                     while (m_decAxisStats.GetEntry(inx).DeltaTime > targetTime)
1166                         inx--;
1167                     tStart = m_decAxisStats.GetEntry(inx).DeltaTime;
1168 
1169                     decVals.ClearAll();
1170                 }
1171                 else
1172                 {
1173                     inx++;
1174                 }
1175                 done = (inx > lastInx);
1176             }
1177             Debug.Write(wxString::Format("Full uncorrected RMS=%0.3fpx, Selected Dec drift=%0.3f px/min, Best seeing estimate=%0.3fpx, R-sq=%0.3f\n",
1178                 m_decAxisStats.GetSigma(), selSlope * 60, bestEstimate, selRSquared));
1179         }
1180         else         // Normal GA run of <= 2.4 minutes, just use the entire interval for stats
1181         {
1182             if (m_decAxisStats.GetCount() > 1)
1183             {
1184                 double simpleSigma = m_decAxisStats.GetSigma();
1185                 rSquared = m_decAxisStats.GetLinearFitResults(&slope, &intcpt, &correctedRMS);
1186                 // If there is little drift relative to the random movements, the drift-correction is irrelevant and can actually degrade the result.  So don't use the drift-corrected
1187                 // RMS unless it's smaller than the simple sigma
1188                 if (correctedRMS < simpleSigma)
1189                     bestEstimate = correctedRMS;
1190                 else
1191                     bestEstimate = simpleSigma;
1192                 Debug.Write(wxString::Format("Uncorrected Dec RMS=%0.3fpx, Dec drift=%0.3f px/min, Best seeing estimate=%0.3fpx, R-sq=%0.3f\n",
1193                     simpleSigma, slope * 60, bestEstimate, rSquared));
1194             }
1195         }
1196         if (origMultistarMode)
1197             bestEstimate *= 0.9;
1198         // round up to next multiple of .05, but do not go below 0.05 pixel
1199         double const unit = 0.05;
1200         double roundUpEst = std::max(round(bestEstimate * multiplier_dec / unit + 0.5) * unit, 0.05);
1201         // Now apply a sanity check - there are still numerous things that could have gone wrong during the GA
1202         if (pxscale * roundUpEst <= 1.25)           // Min-move below 1.25 arc-sec is credible
1203         {
1204             RecDec = roundUpEst;
1205             RecRA = wxMax(0.1, RecDec * multiplier_ra);
1206             Debug.Write(wxString::Format("GA Min-Move recommendations are seeing-based: Dec=%0.3f, RA=%0.3f\n", RecDec, RecRA));
1207         }
1208         else
1209         {
1210             // Just reiterate the estimates made in the new-profile-wiz
1211             RecDec = GuideAlgorithm::SmartDefaultMinMove(pFrame->GetFocalLength(), pCamera->GetCameraPixelSize(), pCamera->Binning);
1212             RecRA = wxMax(0.1, RecDec * multiplier_ra);
1213             Debug.Write(wxString::Format("GA Min-Move calcs failed sanity-check, DecEst=%0.3f, Dec-HPF-Sigma=%0.3f\n", roundUpEst, m_hpfDecStats.GetSigma()));
1214             Debug.Write(wxString::Format("GA Min-Move recs reverting to smart defaults, RA=%0.3f, Dec=%0.3f\n", RecRA, RecDec));
1215         }
1216     }
1217     catch (const wxString& msg)
1218     {
1219         Debug.Write("Exception thrown in GA min-move calcs: " + msg + "\n");
1220         // Punt by reiterating estimates made by new-profile-wiz
1221         RecDec = GuideAlgorithm::SmartDefaultMinMove(pFrame->GetFocalLength(), pCamera->GetCameraPixelSize(), pCamera->Binning);
1222         RecRA = RecDec * multiplier_ra / multiplier_dec;
1223         Debug.Write(wxString::Format("GA Min-Move recs reverting to smart defaults, RA=%0.3f, Dec=%0.3f\n", RecRA, RecDec));
1224     }
1225 }
1226 
1227 // See if the mount probably has large Dec backlash, using either blt results or from inference. If so, we should relax the recommendations regarding
1228 // polar alignment error
LikelyBacklash(const CalibrationDetails & calDetails)1229 bool GuidingAsstWin::LikelyBacklash(const CalibrationDetails& calDetails)
1230 {
1231     bool likely = false;
1232     BacklashComp* blc = TheScope()->GetBacklashComp();              // Always valid
1233 
1234     try
1235     {
1236         if (m_backlashTool->GetBltState() == BacklashTool::BLT_STATE_COMPLETED && m_backlashMs > MAX_BACKLASH_COMP)
1237         {
1238             // Just ran the BLT and the result is too big for BLC
1239             likely = true;
1240         }
1241         if (!likely)
1242         {
1243             // May have tried BLC in the past with a too-large pulse size
1244             int pulseSize;
1245             int floor;
1246             int ceiling;
1247             blc->GetBacklashCompSettings(&pulseSize, &floor, &ceiling);
1248             likely = (pulseSize > MAX_BACKLASH_COMP);
1249         }
1250         if (!likely)
1251         {
1252             // If guide mode isn't 'Auto' or 'None', user can benefit from larger polar alignment error
1253             DEC_GUIDE_MODE decMode = TheScope()->GetDecGuideMode();
1254             likely = (decMode != DEC_AUTO && decMode != DEC_NONE);
1255         }
1256         if (!likely && calDetails.decStepCount > 0)
1257         {
1258             // See if the last calibration showed little or no Dec movement to the south
1259             wxRealPoint northStart = calDetails.decSteps[0];
1260             wxRealPoint northEnd = calDetails.decSteps[calDetails.decStepCount - 1];
1261             double northDist = sqrt(pow(northStart.x - northEnd.x, 2) + pow(northStart.y - northEnd.y, 2));
1262             wxRealPoint southEnd = calDetails.decSteps[calDetails.decSteps.size() - 1];
1263             double southDist = sqrt(pow(northEnd.x - southEnd.x, 2) + pow(northEnd.y - southEnd.y, 2));
1264             likely = (southDist <= 0.1 * northDist);
1265         }
1266     }
1267     catch (const wxString& msg)
1268     {
1269         Debug.Write(wxString::Format("GA-LikelyBacklash: exception at %d, %s\n", __LINE__, msg));
1270     }
1271 
1272     return likely;
1273 }
1274 
1275 // Produce recommendations for "live" GA run
MakeRecommendations()1276 void GuidingAsstWin::MakeRecommendations()
1277 {
1278     CalibrationDetails calDetails;
1279     TheScope()->LoadCalibrationDetails(&calDetails);
1280     m_suspectCalibration = calDetails.lastIssue != CI_None || m_backlashTool->GetBacklashExempted();
1281 
1282     GetMinMoveRecs(m_ra_minmove_rec, m_dec_minmove_rec);
1283 
1284     // Refine the drift-limiting exposure value based on the ra_min_move recommendation
1285     m_othergrid->SetCellValue(m_ra_drift_exp_loc, maxRateRA <= 0.0 ? _(" ") :
1286         wxString::Format("%6.1f %s ", m_ra_minmove_rec / maxRateRA, (_("s"))));
1287 
1288     LogResults();               // Dump the raw statistics
1289 
1290     // REMINDER: Any new recommendations must also be done in 'DisplayStaticRecommendations'
1291     // Clump the no-button messages at the top
1292     // ideal exposure ranges in general
1293     double rarms = m_hpfRAStats.GetSigma();
1294     double multiplier_ra  = 1.0;   // 66% prediction interval
1295     double ideal_min_exposure = 2.0;
1296     double ideal_max_exposure = 4.0;
1297     // adjust the min-exposure downward if drift limiting exposure is lower; then adjust range accordingly
1298     double drift_exp;
1299     if (maxRateRA > 0)
1300         drift_exp = ceil((multiplier_ra * rarms / maxRateRA) / 0.5) * 0.5;                       // Rounded up to nearest 0.5 sec
1301     else
1302         drift_exp = ideal_min_exposure;
1303 
1304     double min_rec_range = 2.0;
1305     double pxscale = pFrame->GetCameraPixelScale();
1306     m_min_exp_rec = std::max(1.0, std::min(drift_exp, ideal_min_exposure));                         // smaller of drift and ideal, never less than 1.0
1307 
1308     if (drift_exp > m_min_exp_rec)
1309     {
1310         if (drift_exp < ideal_max_exposure)
1311             m_max_exp_rec = std::max(drift_exp, m_min_exp_rec + min_rec_range);
1312         else
1313             m_max_exp_rec = ideal_max_exposure;
1314     }
1315     else
1316         m_max_exp_rec = m_min_exp_rec + min_rec_range;
1317 
1318     m_recommendgrid->Clear(true);
1319 
1320     wxString logStr;
1321     wxString allRecommendations;
1322 
1323     // Always make a recommendation on exposure times
1324     wxString msg = wxString::Format(_("Try to keep your exposure times in the range of %.1fs to %.1fs"), m_min_exp_rec, m_max_exp_rec);
1325     allRecommendations += "Exp:" + msg + "\n";
1326     m_exposure_msg = AddRecommendationMsg(msg);
1327     Debug.Write(wxString::Format("Recommendation: %s\n", msg));
1328     // Binning opportunity if image scale is < 0.5
1329     if (pxscale <= 0.5 && pCamera->Binning == 1 && pCamera->MaxBinning > 1)
1330     {
1331         wxString msg = _("Try binning your guide camera");
1332         allRecommendations += "Bin:" + msg + "\n";
1333         m_binning_msg = AddRecommendationMsg(msg);
1334         Debug.Write(wxString::Format("Recommendation: %s\n", msg));
1335     }
1336     // Previous calibration alert
1337     if (m_suspectCalibration)
1338     {
1339         wxString msg = _("Consider re-doing your calibration ");
1340         if (calDetails.lastIssue != CI_None)
1341             msg += _("(Prior alert)");
1342         else
1343             msg += _("(Backlash clearing)");
1344         allRecommendations += "Cal:" + msg + "\n";
1345         m_calibration_msg = AddRecommendationMsg(msg);
1346         logStr = wxString::Format("Recommendation: %s\n", msg);
1347         Debug.Write(logStr);
1348         GuideLog.NotifyGAResult(logStr);
1349     }
1350     // SNR
1351     if ((sumSNR / (double)m_lpfRAStats.GetCount()) < 5.0)
1352     {
1353         wxString msg(_("Consider using a brighter star for the test or increasing the exposure time"));
1354         allRecommendations += "Star:" + msg + "\n";
1355         m_snr_msg = AddRecommendationMsg(msg);
1356         logStr = wxString::Format("Recommendation: %s\n", msg);
1357         Debug.Write(logStr);
1358         GuideLog.NotifyGAResult(logStr);
1359     }
1360 
1361     // Alignment error
1362     if (alignmentError > 5.0)
1363     {
1364         wxString msg = "";
1365         // If the mount looks like it has large Dec backlash, ignore alignment error below 10 arc-min
1366         if (LikelyBacklash(calDetails))
1367         {
1368             if (alignmentError > 10.0)
1369                 msg = _("Polar alignment error > 10 arc-min; try using the Drift Align tool to improve alignment.");
1370         }
1371         else
1372         {
1373             msg = alignmentError < 10.0 ?
1374                 _("Polar alignment error > 5 arc-min; that could probably be improved.") :
1375                 _("Polar alignment error > 10 arc-min; try using the Drift Align tool to improve alignment.");
1376         }
1377         if (msg != "")
1378         {
1379             allRecommendations += "PA:" + msg + "\n";
1380             m_pae_msg = AddRecommendationMsg(msg);
1381             logStr = wxString::Format("Recommendation: %s\n", msg);
1382             Debug.Write(logStr);
1383             GuideLog.NotifyGAResult(logStr);
1384         }
1385     }
1386 
1387     // Star HFD
1388     const Star& star = pFrame->pGuider->PrimaryStar();
1389     if (pxscale > 1.0 && star.HFD > 4.5)
1390     {
1391         wxString msg(_("Consider trying to improve focus on the guide camera"));
1392         allRecommendations += "StarHFD:" + msg + "\n";
1393         m_hfd_msg = AddRecommendationMsg(msg);
1394         logStr = wxString::Format("Recommendation: %s\n", msg);
1395         Debug.Write(logStr);
1396         GuideLog.NotifyGAResult(logStr);
1397     }
1398 
1399     // RA min-move
1400     if (pMount->GetXGuideAlgorithm() && pMount->GetXGuideAlgorithm()->GetMinMove() >= 0.0)
1401     {
1402         wxString msgText = wxString::Format(_("Try setting RA min-move to %0.2f"), m_ra_minmove_rec);
1403         allRecommendations += "RAMinMove:" + msgText + "\n";
1404         m_ra_msg = AddRecommendationBtn(msgText, &GuidingAsstWin::OnRAMinMove, &m_raMinMoveButton);
1405         logStr = wxString::Format("Recommendation: %s\n", msgText);
1406         Debug.Write(logStr);
1407         GuideLog.NotifyGAResult(logStr);
1408     }
1409 
1410     // Dec min-move
1411     if (pMount->GetYGuideAlgorithm() && pMount->GetYGuideAlgorithm()->GetMinMove() >= 0.0)
1412     {
1413         wxString msgText = wxString::Format(_("Try setting Dec min-move to %0.2f"), m_dec_minmove_rec);
1414         allRecommendations += "DecMinMove:" + msgText + "\n";
1415         m_dec_msg = AddRecommendationBtn(msgText, &GuidingAsstWin::OnDecMinMove, &m_decMinMoveButton);
1416         logStr = wxString::Format("Recommendation: %s\n", msgText);
1417         Debug.Write(logStr);
1418         GuideLog.NotifyGAResult(logStr);
1419     }
1420 
1421     // Backlash comp
1422     bool smallBacklash = false;
1423     if (m_backlashTool->GetBltState() == BacklashTool::BLT_STATE_COMPLETED)
1424     {
1425         wxString msg;
1426 
1427         if (m_backlashMs > 0)
1428         {
1429             m_backlashRecommendedMs = (int)(floor(m_backlashMs / 10) * 10);        // round down to nearest 10ms
1430             m_backlashRecommendedMs = wxMax(m_backlashRecommendedMs, 10);
1431         }
1432         else
1433             m_backlashRecommendedMs = 0;
1434         bool largeBL = m_backlashMs > MAX_BACKLASH_COMP;
1435         if (m_backlashMs < 100)
1436         {
1437             msg = _("Backlash is small, no compensation needed");              // assume it was a small measurement error
1438             smallBacklash = true;
1439         }
1440         else if (m_backlashMs <= MAX_BACKLASH_COMP)
1441             msg = wxString::Format(_("Try starting with a Dec backlash compensation of %d ms"), m_backlashRecommendedMs);
1442         else
1443         {
1444             msg = wxString::Format(_("Backlash is >= %d ms; you may need to guide in only one Dec direction (currently %s)"), m_backlashMs,
1445                 decDriftPerMin >= 0 ? _("South") : _("North"));
1446         }
1447         allRecommendations += "BLT:" + msg + "\n";
1448         m_backlash_msg = AddRecommendationBtn(msg, &GuidingAsstWin::OnDecBacklash, &m_decBacklashButton);
1449         m_decBacklashButton->Enable(!largeBL && m_backlashRecommendedMs > 100);
1450         logStr = wxString::Format("Recommendation: %s\n", msg);
1451         Debug.Write(logStr);
1452         GuideLog.NotifyGAResult(logStr);
1453     }
1454 
1455     bool hasEncoders = pMount->HasHPDecEncoder();
1456     if (hasEncoders || smallBacklash)               // Uses encoders or has zero backlash
1457     {
1458         GuideAlgorithm *decAlgo = pMount->GetYGuideAlgorithm();
1459         wxString algoChoice = decAlgo->GetGuideAlgorithmClassName();
1460         if (algoChoice == "ResistSwitch")           // automatically rules out AO's
1461         {
1462             wxString msgText = _("Try using Lowpass2 for Dec guiding");
1463             allRecommendations += "DecAlgo:" + msgText + "\n";
1464             m_decAlgo_msg = AddRecommendationBtn(msgText, &GuidingAsstWin::OnDecAlgoChange, &m_decAlgoButton);
1465             logStr = wxString::Format("Recommendation: %s\n", msgText);
1466             Debug.Write(logStr);
1467             GuideLog.NotifyGAResult(logStr);
1468         }
1469     }
1470 
1471     GuideLog.NotifyGACompleted();
1472     SaveGAResults(&allRecommendations);
1473     m_recommend_group->Show(true);
1474 
1475     m_statusgrid->Layout();
1476     Layout();
1477     GetSizer()->Fit(this);
1478     Debug.Write("End of Guiding Assistant output....\n");
1479 }
1480 
1481 // Show recommendations from a previous GA that is being reviewed
DisplayStaticRecommendations(const GADetails & details)1482 void GuidingAsstWin::DisplayStaticRecommendations(const GADetails& details)
1483 {
1484     std::vector<wxString> recList;
1485     wxString allRecs = details.Recommendations;
1486     bool done = false;
1487     size_t end;
1488 
1489     m_recommendgrid->Clear(true);               // Always start fresh, delete any child buttons
1490     while (!done)
1491     {
1492         end = allRecs.find_first_of("\n");
1493         if (end > 0)
1494         {
1495             wxString rec = allRecs.Left(end);
1496             size_t colPos = rec.find_first_of(":");
1497             wxString which = rec.SubString(0, colPos - 1);
1498             wxString what = rec.SubString(colPos + 1, end);
1499             if (which == "Exp")
1500             {
1501                 m_exposure_msg = AddRecommendationMsg(what);
1502             }
1503             else if (which == "Bin")
1504             {
1505                 m_binning_msg = AddRecommendationMsg(what);
1506             }
1507             else if (which == "Cal")
1508             {
1509                 m_calibration_msg = AddRecommendationMsg(what);
1510             }
1511             else if (which == "Star")
1512             {
1513                 m_snr_msg = AddRecommendationMsg(what);
1514             }
1515             else if (which == "PA")
1516             {
1517                 m_pae_msg = AddRecommendationMsg(what);
1518             }
1519             else if (which == "StarHFD")
1520             {
1521                 m_hfd_msg = AddRecommendationMsg(what);
1522             }
1523             else if (which == "RAMinMove")
1524             {
1525                 details.RecRAMinMove.ToDouble(&m_ra_minmove_rec);
1526                 m_ra_msg = AddRecommendationBtn(what, &GuidingAsstWin::OnRAMinMove, &m_raMinMoveButton);
1527             }
1528             else if (which == "DecMinMove")
1529             {
1530                 details.RecDecMinMove.ToDouble(&m_dec_minmove_rec);
1531                 m_dec_msg = AddRecommendationBtn(what, &GuidingAsstWin::OnDecMinMove, &m_decMinMoveButton);
1532             }
1533             else if (which == "DecAlgo")
1534             {
1535                 m_decAlgo_msg = AddRecommendationBtn(what, &GuidingAsstWin::OnDecAlgoChange, &m_decAlgoButton);
1536             }
1537             else if (which == "BLT")
1538             {
1539                 m_backlashMs = wxAtoi(details.BLTAmount);
1540                 bool largeBL = m_backlashMs > MAX_BACKLASH_COMP;
1541                 m_backlash_msg = AddRecommendationBtn(what, &GuidingAsstWin::OnDecBacklash, &m_decBacklashButton);
1542                 m_decBacklashButton->Enable(!largeBL && m_backlashRecommendedMs > 100);
1543             }
1544             allRecs = allRecs.Mid(end + 1);
1545             done = allRecs.size() == 0;
1546         }
1547     }
1548     m_recommend_group->Show(true);
1549 
1550     m_statusgrid->Layout();
1551     Layout();
1552     GetSizer()->Fit(this);
1553 }
1554 
OnStart(wxCommandEvent & event)1555 void GuidingAsstWin::OnStart(wxCommandEvent& event)
1556 {
1557     if (!pFrame->pGuider->IsGuiding())
1558         return;
1559 
1560     double exposure = (double) pFrame->RequestedExposureDuration() / 1000.0;
1561     double lp_cutoff = wxMax(6.0, 3.0 * exposure);
1562     double hp_cutoff = 1.0;
1563 
1564     pFrame->pGuider->SetMultiStarMode(false);
1565     StatsReset();
1566     m_raHPF = HighPassFilter(hp_cutoff, exposure);
1567     m_raLPF = LowPassFilter(lp_cutoff, exposure);
1568     m_decHPF = HighPassFilter(hp_cutoff, exposure);
1569 
1570     sumSNR = sumMass = 0.0;
1571 
1572     m_start->Enable(false);
1573     m_stop->Enable(true);
1574     btnReviewPrev->Enable(false);
1575     reviewMode = false;
1576     m_dlgState = STATE_MEASURING;
1577     FillInstructions(m_dlgState);
1578     m_gaStatus->SetLabel(_("Measuring..."));
1579     m_recommend_group->Show(false);
1580     HighlightCell(m_displacementgrid, m_ra_rms_loc);
1581     HighlightCell(m_displacementgrid, m_dec_rms_loc);
1582     HighlightCell(m_displacementgrid, m_total_rms_loc);
1583 
1584     Debug.AddLine("GuidingAssistant: Disabling guide output");
1585 
1586     if (pMount)
1587     {
1588         m_savePrimaryMountEnabled = pMount->GetGuidingEnabled();
1589         pMount->SetGuidingEnabled(false);
1590     }
1591     if (pSecondaryMount)
1592     {
1593         m_saveSecondaryMountEnabled = pSecondaryMount->GetGuidingEnabled();
1594         pSecondaryMount->SetGuidingEnabled(false);
1595     }
1596 
1597     m_guideOutputDisabled = true;
1598 
1599     startStr = wxDateTime::Now().FormatISOCombined(' ');
1600     m_measuring = true;
1601     m_startTime = ::wxGetUTCTimeMillis().GetValue();
1602     SetSizerAndFit(m_vSizer);
1603 }
1604 
1605 // For a mouse-click on the 'Review Previous' options button, show a pop-up menu for whatever saved GAs are available
OnReviewPrevious(wxCommandEvent & event)1606 void GuidingAsstWin::OnReviewPrevious(wxCommandEvent& event)
1607 {
1608     std::vector<wxString> entryNames;
1609     entryNames = pConfig->Profile.GetGroupNames("/GA");
1610 
1611     wxMenu* reviewList = new wxMenu();
1612     for (int inx = 0; inx < entryNames.size(); inx++)
1613     {
1614         reviewList->Append(GA_REVIEW_ITEMS_BASE + inx, entryNames[inx]);
1615     }
1616     PopupMenu(reviewList, btnReviewPrev->GetPosition().x,
1617         btnReviewPrev->GetPosition().y + btnReviewPrev->GetSize().GetHeight());
1618 
1619     wxDELETE(reviewList);
1620 }
1621 
1622 // Handle the user's choice of a GA entry for review (see above)
OnGAReviewSelection(wxCommandEvent & evt)1623 void GuidingAsstWin::OnGAReviewSelection(wxCommandEvent& evt)
1624 {
1625     int id = evt.GetId();
1626     wxMenu *menu = static_cast<wxMenu *>(evt.GetEventObject());
1627     wxString timeStamp = menu->GetLabelText(id);
1628 
1629     reviewMode = true;
1630     LoadGAResults(timeStamp, &gaDetails);
1631     m_graphBtn->Enable(gaDetails.BLTNorthMoves.size() > 0);
1632     DisplayStaticResults(gaDetails);
1633 }
1634 
DoStop(const wxString & status)1635 void GuidingAsstWin::DoStop(const wxString& status)
1636 {
1637     m_measuring = false;
1638     m_recommendgrid->Show(true);
1639     m_dlgState = STATE_STOPPED;
1640     m_measurementsTaken = true;
1641 
1642     FillInstructions(m_dlgState);
1643 
1644     if (m_guideOutputDisabled)
1645     {
1646         Debug.Write(wxString::Format("GuidingAssistant: Re-enabling guide output (%d, %d)\n", m_savePrimaryMountEnabled, m_saveSecondaryMountEnabled));
1647 
1648         if (pMount)
1649             pMount->SetGuidingEnabled(m_savePrimaryMountEnabled);
1650         if (pSecondaryMount)
1651             pSecondaryMount->SetGuidingEnabled(m_saveSecondaryMountEnabled);
1652 
1653         m_guideOutputDisabled = false;
1654         pFrame->pGuider->SetMultiStarMode(origMultistarMode);           // may force an auto-find to refresh secondary star data
1655     }
1656 
1657     m_start->Enable(pFrame->pGuider->IsGuiding());
1658     btnReviewPrev->Enable(GetGAHistoryCount() > 0);
1659     m_stop->Enable(false);
1660 
1661     if (m_origSubFrames != -1)
1662     {
1663         pCamera->UseSubframes = m_origSubFrames ? true : false;
1664         m_origSubFrames = -1;
1665     }
1666 }
1667 
EndBacklashTest(bool completed)1668 void GuidingAsstWin::EndBacklashTest(bool completed)
1669 {
1670     if (!completed)
1671     {
1672         m_backlashTool->StopMeasurement();
1673         m_othergrid->SetCellValue(m_backlash_loc, _("Backlash test aborted, see graph..."));
1674         m_graphBtn->Enable(m_backlashTool->IsGraphable());
1675     }
1676 
1677     m_measuringBacklash = false;
1678     m_backlashCB->Enable(true);
1679     Layout();
1680     GetSizer()->Fit(this);
1681 
1682     m_start->Enable(pFrame->pGuider->IsGuiding());
1683     m_stop->Enable(false);
1684     MakeRecommendations();
1685     if (!completed)
1686     {
1687         wxCommandEvent dummy;
1688         OnAppStateNotify(dummy);            // Make sure UI is in synch
1689     }
1690     DoStop();
1691 }
1692 
OnStop(wxCommandEvent & event)1693 void GuidingAsstWin::OnStop(wxCommandEvent& event)
1694 {
1695     bool performBLT = m_backlashCB->IsChecked();
1696     bool longEnough;
1697     if (m_elapsedSecs < GA_MIN_SAMPLING_PERIOD && !m_measuringBacklash)
1698     {
1699         SampleWait waitDlg(GA_MIN_SAMPLING_PERIOD - m_elapsedSecs, performBLT);
1700         longEnough = (waitDlg.ShowModal() == wxOK);
1701     }
1702     else
1703         longEnough = true;
1704 
1705     m_gaStatus->SetLabel(wxEmptyString);
1706     if (longEnough && performBLT)
1707     {
1708         if (!m_measuringBacklash)                               // Run the backlash test after the sampling was completed
1709         {
1710             m_measuringBacklash = true;
1711             if (m_origSubFrames == -1)
1712                 m_origSubFrames = pCamera->UseSubframes ? 1 : 0;
1713             pCamera->UseSubframes = false;
1714 
1715             m_gaStatus->SetLabelText(_("Measuring backlash... ") + m_backlashTool->GetLastStatus());
1716             Layout();
1717             GetSizer()->Fit(this);
1718             m_backlashCB->Enable(false);                        // Don't let user turn it off once we've started
1719             m_measuring = false;
1720             m_backlashTool->StartMeasurement(decDriftPerMin);
1721             m_instructions->SetLabel(_("Measuring backlash... "));
1722         }
1723         else
1724         {
1725             // User hit stop during bl test
1726             m_gaStatus->SetLabelText(wxEmptyString);
1727             EndBacklashTest(false);
1728         }
1729     }
1730     else
1731     {
1732         if (longEnough)
1733             MakeRecommendations();
1734         DoStop();
1735     }
1736 }
1737 
OnAppStateNotify(wxCommandEvent & WXUNUSED (event))1738 void GuidingAsstWin::OnAppStateNotify(wxCommandEvent& WXUNUSED(event))
1739 {
1740     if (m_measuring || m_measuringBacklash)
1741     {
1742         if (!pFrame->pGuider->IsGuiding())
1743         {
1744             // if guiding stopped, stop measuring
1745             DoStop(_("Guiding stopped"));
1746         }
1747     }
1748     else
1749     {
1750         bool can_start = pFrame->pGuider->IsGuiding();
1751         m_start->Enable(can_start);
1752         if (can_start)
1753             m_dlgState = STATE_START_READY;
1754         else
1755             m_dlgState = STATE_NO_STAR;
1756         FillInstructions(m_dlgState);
1757     }
1758 }
1759 
OnClose(wxCloseEvent & evt)1760 void GuidingAsstWin::OnClose(wxCloseEvent& evt)
1761 {
1762     DoStop();
1763 
1764     // save the window position
1765     int x, y;
1766     GetPosition(&x, &y);
1767     pConfig->Global.SetInt("/GuidingAssistant/pos.x", x);
1768     pConfig->Global.SetInt("/GuidingAssistant/pos.y", y);
1769 
1770     if (m_flushConfig)
1771     {
1772         pConfig->Flush();
1773         m_flushConfig = false;
1774     }
1775 
1776     Destroy();
1777 }
1778 
FillResultCell(wxGrid * pGrid,const wxGridCellCoords & loc,double pxVal,double asVal,const wxString & units1,const wxString & units2,const wxString & extraInfo)1779 void GuidingAsstWin::FillResultCell(wxGrid *pGrid, const wxGridCellCoords& loc, double pxVal, double asVal, const wxString& units1, const wxString& units2,
1780     const wxString& extraInfo)
1781 {
1782     pGrid->SetCellValue(loc, wxString::Format("%6.2f %s (%6.2f %s %s)", pxVal, units1, asVal, units2, extraInfo));
1783 }
1784 
DisplayStaticResults(const GADetails & details)1785 void GuidingAsstWin::DisplayStaticResults(const GADetails& details)
1786 {
1787     wxString SEC(_("s"));
1788     wxString MSEC(_("ms"));
1789     wxString PX(_("px"));
1790     wxString ARCSEC(_("arc-sec"));
1791     wxString ARCMIN(_("arc-min"));
1792     wxString PXPERMIN(_("px/min"));
1793     wxString PXPERSEC(_("px/sec"));
1794     wxString ARCSECPERMIN(_("arc-sec/min"));
1795     wxString ARCSECPERSEC(_("arc-sec/sec"));
1796 
1797     // Display high-freq stats
1798     m_statusgrid->SetCellValue(m_timestamp_loc, details.TimeStamp);
1799     m_statusgrid->SetCellValue(m_exposuretime_loc, details.ExposureTime);
1800     m_statusgrid->SetCellValue(m_snr_loc, details.SNR);
1801     m_statusgrid->SetCellValue(m_starmass_loc, details.StarMass);
1802     m_statusgrid->SetCellValue(m_elapsedtime_loc, details.ElapsedTime);
1803     m_statusgrid->SetCellValue(m_samplecount_loc, details.SampleCount);
1804 
1805     // Fill other grids
1806     m_displacementgrid->SetCellValue(m_ra_rms_loc, details.RA_HPF_RMS);
1807     m_displacementgrid->SetCellValue(m_dec_rms_loc, details.Dec_HPF_RMS);
1808     m_displacementgrid->SetCellValue(m_total_rms_loc, details.Total_HPF_RMS);
1809     m_othergrid->SetCellValue(m_ra_peak_loc, details.RAPeak);
1810     m_othergrid->SetCellValue(m_dec_peak_loc, details.DecPeak);
1811     m_othergrid->SetCellValue(m_ra_peakpeak_loc, details.RAPeak_Peak);
1812     m_othergrid->SetCellValue(m_ra_drift_loc, details.RADriftRate);
1813     m_othergrid->SetCellValue(m_ra_peak_drift_loc, details.RAMaxDriftRate);
1814     m_othergrid->SetCellValue(m_ra_drift_exp_loc, details.DriftLimitingExposure);
1815     m_othergrid->SetCellValue(m_dec_drift_loc, details.DecDriftRate);
1816     m_othergrid->SetCellValue(m_backlash_loc, details.BackLashInfo);
1817     m_othergrid->SetCellValue(m_pae_loc, details.PAError);
1818 
1819     if (details.Recommendations.size() > 0)
1820         DisplayStaticRecommendations(details);
1821 }
1822 
UpdateInfo(const GuideStepInfo & info)1823 void GuidingAsstWin::UpdateInfo(const GuideStepInfo& info)
1824 {
1825     double ra = info.mountOffset.X;
1826     double dec = info.mountOffset.Y;
1827     if (pMount->IsStepGuider())
1828     {
1829         PHD_Point mountLoc;
1830         TheScope()->TransformCameraCoordinatesToMountCoordinates(info.cameraOffset, mountLoc);
1831         ra = mountLoc.X;
1832         dec = mountLoc.Y;
1833     }
1834     // Update the time measures
1835     wxLongLong_t elapsedms = ::wxGetUTCTimeMillis().GetValue() - m_startTime;
1836     m_elapsedSecs = (double)elapsedms / 1000.0;
1837     // add offset info to various stats accumulations
1838     m_hpfRAStats.AddValue(m_raHPF.AddValue(ra));
1839     double prevRAlpf = m_raLPF.GetCurrentLPF();
1840     double newRAlpf = m_raLPF.AddValue(ra);
1841     if (m_lpfRAStats.GetCount() == 0)
1842         prevRAlpf = newRAlpf;
1843     m_lpfRAStats.AddValue(newRAlpf);
1844     m_hpfDecStats.AddValue(m_decHPF.AddValue(dec));
1845     if (m_decAxisStats.GetCount() == 0)
1846         m_axisTimebase = wxGetCurrentTime();
1847     m_decAxisStats.AddGuideInfo(wxGetCurrentTime() - m_axisTimebase, dec, 0);
1848     m_raAxisStats.AddGuideInfo(wxGetCurrentTime() - m_axisTimebase, ra, 0);
1849 
1850     // Compute the maximum interval RA movement rate using low-passed-filtered data
1851     if (m_lpfRAStats.GetCount() == 1)
1852     {
1853         m_startPos = info.mountOffset;
1854         maxRateRA = 0.0;
1855     }
1856     else
1857     {
1858         double dt = info.time - m_lastTime;
1859         if (dt > 0.0001)
1860         {
1861             double raRate = fabs(newRAlpf - prevRAlpf) / dt;
1862             if (raRate > maxRateRA)
1863                 maxRateRA = raRate;
1864         }
1865     }
1866 
1867     m_lastTime = info.time;
1868     sumSNR += info.starSNR;
1869     sumMass += info.starMass;
1870     double n = (double)m_lpfRAStats.GetCount();
1871 
1872     wxString SEC(_("s"));
1873     wxString MSEC(_("ms"));
1874     wxString PX(_("px"));
1875     wxString ARCSEC(_("arc-sec"));
1876     wxString ARCMIN(_("arc-min"));
1877     wxString PXPERMIN(_("px/min"));
1878     wxString PXPERSEC(_("px/sec"));
1879     wxString ARCSECPERMIN(_("arc-sec/min"));
1880     wxString ARCSECPERSEC(_("arc-sec/sec"));
1881 
1882     m_statusgrid->SetCellValue(m_timestamp_loc, startStr);
1883     m_statusgrid->SetCellValue(m_exposuretime_loc, wxString::Format("%g%s", (double)pFrame->RequestedExposureDuration() / 1000.0, SEC));
1884     m_statusgrid->SetCellValue(m_snr_loc, wxString::Format("%.1f", sumSNR / n));
1885     m_statusgrid->SetCellValue(m_starmass_loc, wxString::Format("%.1f", sumMass / n));
1886     m_statusgrid->SetCellValue(m_elapsedtime_loc, wxString::Format("%u%s", (unsigned int)(elapsedms / 1000), SEC));
1887     m_statusgrid->SetCellValue(m_samplecount_loc, wxString::Format("%.0f", n));
1888 
1889     if (n > 1)
1890     {
1891         // Update the realtime high-frequency stats
1892         double rarms = m_hpfRAStats.GetSigma();
1893         double decrms = m_hpfDecStats.GetSigma();
1894         double combined = hypot(rarms, decrms);
1895 
1896         // Update the running estimate of polar alignment error using linear-fit dec drift rate
1897         double pxscale = pFrame->GetCameraPixelScale();
1898         double declination = pPointingSource->GetDeclination();
1899         double cosdec;
1900         if (declination == UNKNOWN_DECLINATION)
1901             cosdec = 1.0; // assume declination 0
1902         else
1903             cosdec = cos(declination);
1904         // polar alignment error from Barrett:
1905         // http://celestialwonders.com/articles/polaralignment/PolarAlignmentAccuracy.pdf
1906         double intcpt;
1907         m_decAxisStats.GetLinearFitResults(&decDriftPerMin, &intcpt);
1908         decDriftPerMin = 60.0 * decDriftPerMin;
1909         alignmentError = 3.8197 * fabs(decDriftPerMin) * pxscale / cosdec;
1910 
1911         // update grid display w/ running stats
1912         FillResultCell(m_displacementgrid, m_ra_rms_loc, rarms, rarms * pxscale, PX, ARCSEC);
1913         FillResultCell(m_displacementgrid, m_dec_rms_loc, decrms, decrms * pxscale, PX, ARCSEC);
1914         FillResultCell(m_displacementgrid, m_total_rms_loc, combined, combined * pxscale, PX, ARCSEC);
1915         FillResultCell(m_othergrid, m_ra_peak_loc,
1916             m_raAxisStats.GetMaxDelta(), m_raAxisStats.GetMaxDelta() * pxscale, PX, ARCSEC);
1917         FillResultCell(m_othergrid, m_dec_peak_loc,
1918             m_decAxisStats.GetMaxDelta(), m_decAxisStats.GetMaxDelta() * pxscale, PX, ARCSEC);
1919         double raPkPk = m_lpfRAStats.GetMaximum() - m_lpfRAStats.GetMinimum();
1920         FillResultCell(m_othergrid, m_ra_peakpeak_loc, raPkPk, raPkPk * pxscale, PX, ARCSEC);
1921         double raDriftRate = (ra - m_startPos.X) / m_elapsedSecs * 60.0;            // Raw max-min, can't smooth this one reliably
1922         FillResultCell(m_othergrid, m_ra_drift_loc, raDriftRate, raDriftRate * pxscale, PXPERMIN, ARCSECPERMIN);
1923         FillResultCell(m_othergrid, m_ra_peak_drift_loc, maxRateRA, maxRateRA * pxscale, PXPERSEC, ARCSECPERSEC);
1924         m_othergrid->SetCellValue(m_ra_drift_exp_loc, maxRateRA <= 0.0 ? _(" ") :
1925             wxString::Format("%6.1f %s ", 1.3 * rarms / maxRateRA, SEC));              // Will get revised when min-move is computed
1926         FillResultCell(m_othergrid, m_dec_drift_loc, decDriftPerMin, decDriftPerMin * pxscale, PXPERMIN, ARCSECPERMIN);
1927         m_othergrid->SetCellValue(m_pae_loc, wxString::Format("%s %.1f %s", declination == UNKNOWN_DECLINATION ? "> " : "", alignmentError, ARCMIN));
1928     }
1929 
1930 }
1931 
CreateDialogBox()1932 wxWindow *GuidingAssistant::CreateDialogBox()
1933 {
1934     return new GuidingAsstWin();
1935 }
1936 
NotifyGuideStep(const GuideStepInfo & info)1937 void GuidingAssistant::NotifyGuideStep(const GuideStepInfo& info)
1938 {
1939     if (pFrame && pFrame->pGuidingAssistant)
1940     {
1941         GuidingAsstWin *win = static_cast<GuidingAsstWin *>(pFrame->pGuidingAssistant);
1942         if (win->m_measuring)
1943             win->UpdateInfo(info);
1944     }
1945 }
1946 
NotifyFrameDropped(const FrameDroppedInfo & info)1947 void GuidingAssistant::NotifyFrameDropped(const FrameDroppedInfo& info)
1948 {
1949     if (pFrame && pFrame->pGuidingAssistant)
1950     {
1951         // anything needed?
1952     }
1953 }
1954 
NotifyBacklashStep(const PHD_Point & camLoc)1955 void GuidingAssistant::NotifyBacklashStep(const PHD_Point& camLoc)
1956 {
1957     if (pFrame && pFrame->pGuidingAssistant)
1958     {
1959         GuidingAsstWin *win = static_cast<GuidingAsstWin *>(pFrame->pGuidingAssistant);
1960         if (win->m_measuringBacklash)
1961             win->BacklashStep(camLoc);
1962     }
1963 }
1964 
NotifyBacklashError()1965 void GuidingAssistant::NotifyBacklashError()
1966 {
1967     if (pFrame && pFrame->pGuidingAssistant)
1968     {
1969         GuidingAsstWin *win = static_cast<GuidingAsstWin *>(pFrame->pGuidingAssistant);
1970         if (win->m_measuringBacklash)
1971             win->BacklashError();
1972     }
1973 }
1974 
UpdateUIControls()1975 void GuidingAssistant::UpdateUIControls()
1976 {
1977     // notify GuidingAssistant window to update its controls
1978     if (pFrame && pFrame->pGuidingAssistant)
1979     {
1980         wxCommandEvent event(APPSTATE_NOTIFY_EVENT, pFrame->GetId());
1981         event.SetEventObject(pFrame);
1982         wxPostEvent(pFrame->pGuidingAssistant, event);
1983     }
1984 }
1985