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