1 /*
2 * statswindow.cpp
3 * PHD Guiding
4 *
5 * Created by Andy Galasso
6 * Copyright (c) 2014 Andy Galasso
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 "statswindow.h"
37
38 enum {
39 TIMER_ID_COOLER = 101,
40 };
41
wxBEGIN_EVENT_TABLE(StatsWindow,wxWindow)42 wxBEGIN_EVENT_TABLE(StatsWindow, wxWindow)
43 EVT_BUTTON(BUTTON_GRAPH_LENGTH, StatsWindow::OnButtonLength)
44 EVT_MENU_RANGE(MENU_LENGTH_BEGIN, MENU_LENGTH_END, StatsWindow::OnMenuLength)
45 EVT_BUTTON(BUTTON_GRAPH_CLEAR, StatsWindow::OnButtonClear)
46 EVT_TIMER(TIMER_ID_COOLER, StatsWindow::OnTimerCooler)
47 wxEND_EVENT_TABLE()
48
49 StatsWindow::StatsWindow(wxWindow *parent)
50 : wxWindow(parent, wxID_ANY),
51 m_visible(false),
52 m_coolerTimer(this, TIMER_ID_COOLER),
53 m_lastFrameSize(wxDefaultSize)
54 {
55 SetBackgroundColour(*wxBLACK);
56
57 m_grid1 = new wxGrid(this, wxID_ANY);
58
59 m_grid1->CreateGrid(4, 3);
60 m_grid1->SetRowLabelSize(1);
61 m_grid1->SetColLabelSize(1);
62 m_grid1->EnableEditing(false);
63 m_grid1->SetDefaultCellBackgroundColour(*wxBLACK);
64 m_grid1->SetDefaultCellTextColour(*wxLIGHT_GREY);
65 m_grid1->SetGridLineColour(wxColour(40, 40, 40));
66
67 int col = 0;
68 int row = 0;
69 m_grid1->SetCellValue(row, col++, "");
70 m_grid1->SetCellValue(row, col++, _("RMS [px]"));
71 m_grid1->SetCellValue(row, col++, _("Peak [px]"));
72 ++row, col = 0;
73 m_grid1->SetCellValue(row, col++, _("RA"));
74 m_grid1->SetCellValue(row, col++, _T(" 99.99 (99.99'')"));
75 m_grid1->SetCellValue(row, col++, _(" 99.99 (99.99'')"));
76 ++row, col = 0;
77 m_grid1->SetCellValue(row, col++, _("Dec"));
78 ++row, col = 0;
79 m_grid1->SetCellValue(row, col++, _("Total"));
80
81 m_grid1->AutoSize();
82 m_grid1->SetCellValue(1, 1, "");
83 m_grid1->SetCellValue(1, 2, "");
84 m_grid1->ClearSelection();
85
86 m_grid2 = new wxGrid(this, wxID_ANY);
87 m_grid2->CreateGrid(12, 2);
88 m_grid2->SetRowLabelSize(1);
89 m_grid2->SetColLabelSize(1);
90 m_grid2->EnableEditing(false);
91 m_grid2->SetDefaultCellBackgroundColour(*wxBLACK);
92 m_grid2->SetDefaultCellTextColour(*wxLIGHT_GREY);
93 m_grid2->SetGridLineColour(wxColour(40, 40, 40));
94
95 row = 0, col = 0;
96 m_grid2->SetCellValue(row, col++, _("RA Osc"));
97 ++row, col = 0;
98 m_grid2->SetCellValue(row, col++, _("RA Limited"));
99 ++row, col = 0;
100 m_grid2->SetCellValue(row, col++, _("Dec Limited"));
101 ++row, col = 0;
102 m_grid2->SetCellValue(row, col++, _("Star lost"));
103 ++row, col = 0;
104 m_grid2->SetCellValue(row, col++, _("Declination"));
105 ++row, col = 0;
106 m_grid2->SetCellValue(row, col++, _("Pier Side"));
107 m_grid2->SetCellValue(3, 1, _T("MMMMMM"));
108 ++row, col = 0;
109 m_grid2->SetCellValue(row, col++, _("Rotator Pos"));
110 ++row, col = 0;
111 m_grid2->SetCellValue(row, col++, _("Camera binning"));
112 ++row, col = 0;
113 m_grid2->SetCellValue(row, col++, _("Image size"));
114 m_frameSizeRow = row;
115 ++row, col = 0;
116 m_grid2->SetCellValue(row, col++, _("Pixel scale"));
117 m_pixelScaleRow = row;
118 ++row, col = 0;
119 m_grid2->SetCellValue(row, col++, _("Field of View"));
120 // Make sure the column is big enough
121 m_grid2->SetCellValue(row, col, _("nnn.n x nnn.n arc-min"));
122 ++row, col = 0;
123 m_grid2->SetCellValue(row, col++, _("Camera cooler"));
124 m_grid2->SetCellValue(row, col, "-99" DEGREES_SYMBOL " / -99" DEGREES_SYMBOL ", 999%");
125 m_coolerRow = row;
126
127 m_grid2->AutoSize();
128 m_grid2->SetCellValue(3, 1, wxEmptyString);
129 m_grid2->SetCellValue(m_pixelScaleRow + 1, 1, wxEmptyString); // field of view row
130 m_grid2->ClearSelection();
131
132 wxSizer *sizer1 = new wxBoxSizer(wxHORIZONTAL);
133
134 wxButton *clearButton = new wxButton(this, BUTTON_GRAPH_CLEAR, _("Clear"));
135 clearButton->SetToolTip(_("Clear graph data and stats"));
136 clearButton->SetBackgroundStyle(wxBG_STYLE_TRANSPARENT);
137 sizer1->Add(clearButton, 0, wxALL, 10);
138
139 m_pLengthButton = new OptionsButton(this, BUTTON_GRAPH_LENGTH, _T("XXXXXXX:888888"), wxDefaultPosition, wxSize(220, -1));
140 m_pLengthButton->SetToolTip(_("Select the number of frames of history for stats and the graph"));
141 m_length = dynamic_cast<MyFrame *>(parent)->pGraphLog->GetLength();
142 m_pLengthButton->SetLabel(wxString::Format(_T("x:%3d"), m_length));
143 sizer1->Add(m_pLengthButton, 0, wxALL, 10);
144
145 wxSizer *sizer2 = new wxBoxSizer(wxVERTICAL);
146
147 sizer2->Add(sizer1, 0, wxEXPAND, 10);
148 sizer2->Add(m_grid1, wxSizerFlags(0).Border(wxALL, 10));
149 sizer2->Add(m_grid2, wxSizerFlags(0).Border(wxALL, 10));
150
151 SetSizerAndFit(sizer2);
152 }
153
~StatsWindow(void)154 StatsWindow::~StatsWindow(void)
155 {
156 }
157
SetState(bool is_active)158 void StatsWindow::SetState(bool is_active)
159 {
160 m_visible = is_active;
161 if (m_visible)
162 {
163 UpdateStats();
164 UpdateCooler();
165 }
166 }
167
arcsecs(double px,double sampling)168 static wxString arcsecs(double px, double sampling)
169 {
170 if (sampling != 1.0)
171 return wxString::Format("% 4.2f (%.2f'')", px, px * sampling);
172 else
173 return wxString::Format("% 4.2f", px);
174 }
175
UpdateStats(void)176 void StatsWindow::UpdateStats(void)
177 {
178 if (!m_visible)
179 return;
180
181 if (!pFrame || !pFrame->pGraphLog)
182 return;
183
184 int length = pFrame->pGraphLog->GetLength();
185 if (m_length != length)
186 {
187 m_pLengthButton->SetLabel(wxString::Format(_T("x:%3d"), length));
188 m_length = length;
189 }
190
191 const SummaryStats& stats = pFrame->pGraphLog->Stats();
192 const double sampling = pFrame->GetCameraPixelScale();
193
194 m_grid1->BeginBatch();
195 m_grid2->BeginBatch();
196
197 int row = 1, col = 1;
198 m_grid1->SetCellValue(row++, col, arcsecs(stats.rms_ra, sampling));
199 m_grid1->SetCellValue(row++, col, arcsecs(stats.rms_dec, sampling));
200 m_grid1->SetCellValue(row++, col, arcsecs(stats.rms_tot, sampling));
201
202 row = 1, col = 2;
203 m_grid1->SetCellValue(row++, col, arcsecs(stats.ra_peak, sampling));
204 m_grid1->SetCellValue(row++, col, arcsecs(stats.dec_peak, sampling));
205
206 row = 0, col = 1;
207 if (stats.osc_alert)
208 m_grid2->SetCellTextColour(row, col, wxColour(185, 20, 0));
209 else
210 m_grid2->SetCellTextColour(row, col, *wxLIGHT_GREY);
211 m_grid2->SetCellValue(row++, col, wxString::Format("% .02f", stats.osc_index));
212
213 unsigned int historyItems = wxMax(pFrame->pGraphLog->GetHistoryItemCount(), 1); // avoid divide-by-zero
214 if (stats.ra_limit_cnt > 0)
215 m_grid2->SetCellTextColour(row, col, wxColour(185, 20, 0));
216 else
217 m_grid2->SetCellTextColour(row, col, *wxLIGHT_GREY);
218 m_grid2->SetCellValue(row++, col, wxString::Format(" %u (%.f%%)", stats.ra_limit_cnt, stats.ra_limit_cnt * 100. / historyItems));
219
220 if (stats.dec_limit_cnt > 0)
221 m_grid2->SetCellTextColour(row, col, wxColour(185, 20, 0));
222 else
223 m_grid2->SetCellTextColour(row, col, *wxLIGHT_GREY);
224 m_grid2->SetCellValue(row++, col, wxString::Format(" %u (%.f%%)", stats.dec_limit_cnt, stats.dec_limit_cnt * 100. / historyItems));
225
226 m_grid2->SetCellValue(row++, col, wxString::Format(" %u", stats.star_lost_cnt));
227
228 m_grid1->EndBatch();
229 m_grid2->EndBatch();
230 }
231
CamCoolerStatus()232 static wxString CamCoolerStatus()
233 {
234 bool on;
235 double setpt, power, temp;
236 bool err = pCamera->GetCoolerStatus(&on, &setpt, &power, &temp);
237 if (err)
238 return _("Camera error");
239 else if (on)
240 return wxString::Format(_("%.f%s / %.f%s, %.f%%"), temp, DEGREES_SYMBOL, setpt, DEGREES_SYMBOL, power);
241 else
242 return wxString::Format(_("%.f%s, Off"), temp, DEGREES_SYMBOL);
243 }
244
UpdateCooler()245 void StatsWindow::UpdateCooler()
246 {
247 m_coolerTimer.Stop();
248
249 if (!m_visible)
250 return;
251
252 wxString s;
253 if (pCamera && pCamera->Connected)
254 {
255 if (pCamera->HasCooler)
256 {
257 s = CamCoolerStatus();
258 enum { COOLER_POLL_INTERVAL_MS = 10000 };
259 m_coolerTimer.StartOnce(COOLER_POLL_INTERVAL_MS);
260 }
261 else
262 s = _("None");
263 }
264 m_grid2->SetCellValue(m_coolerRow, 1, s);
265 }
266
fov(const wxSize & sensorFormat,double sampling)267 static wxString fov(const wxSize& sensorFormat, double sampling)
268 {
269 if (sampling != 1.0)
270 return wxString::Format("% 4.1f x % 4.1f %s", sensorFormat.x * sampling / 60.0, sensorFormat.y * sampling / 60.0, _("arc-min"));
271 else
272 return " ";
273 }
274
UpdateImageSize(const wxSize & frameSize)275 void StatsWindow::UpdateImageSize(const wxSize& frameSize)
276 {
277 if (m_visible && frameSize != m_lastFrameSize)
278 {
279 wxString sensorStr = wxString::Format("%d x %d %s", frameSize.x, frameSize.y, _("px"));
280 m_grid2->SetCellValue(m_frameSizeRow, 1, sensorStr);
281 const double sampling = pFrame ? pFrame->GetCameraPixelScale() : 1.0;
282 m_grid2->SetCellValue(m_pixelScaleRow, 1, wxString::Format(_("%.1f\"/%s"), sampling, _("px")));
283 m_grid2->SetCellValue(m_pixelScaleRow + 1, 1, fov(frameSize, sampling));
284 m_lastFrameSize = frameSize;
285 }
286 }
287
ResetImageSize()288 void StatsWindow::ResetImageSize()
289 {
290 m_lastFrameSize = wxDefaultSize;
291 m_grid2->SetCellValue(m_frameSizeRow, 1, wxEmptyString);
292 m_grid2->SetCellValue(m_pixelScaleRow, 1, wxEmptyString);
293 m_grid2->SetCellValue(m_pixelScaleRow + 1, 1, wxEmptyString);
294 }
295
OnTimerCooler(wxTimerEvent &)296 void StatsWindow::OnTimerCooler(wxTimerEvent&)
297 {
298 UpdateCooler();
299 }
300
RotatorPosStr()301 static wxString RotatorPosStr()
302 {
303 if (!pRotator)
304 return _("N/A");
305 double pos = Rotator::RotatorPosition();
306 if (pos == Rotator::POSITION_UNKNOWN)
307 return _("Unknown");
308 else
309 return wxString::Format("%5.1f", norm(pos, 0.0, 360.0));
310 }
311
UpdateScopePointing()312 void StatsWindow::UpdateScopePointing()
313 {
314 if (pPointingSource)
315 {
316 double declination = pPointingSource->GetDeclination();
317 PierSide pierSide = pPointingSource->SideOfPier();
318
319 m_grid2->BeginBatch();
320 int row = 4, col = 1;
321 m_grid2->SetCellValue(row++, col, Mount::DeclinationStrTr(declination, "% .1f" DEGREES_SYMBOL));
322 m_grid2->SetCellValue(row++, col, Mount::PierSideStrTr(pierSide));
323 m_grid2->SetCellValue(row++, col, RotatorPosStr());
324 m_grid2->SetCellValue(row++, col, pCamera ? wxString::Format("%hu", pCamera->Binning) : _("N/A"));
325 m_grid2->EndBatch();
326 }
327 }
328
OnButtonLength(wxCommandEvent & WXUNUSED (evt))329 void StatsWindow::OnButtonLength(wxCommandEvent& WXUNUSED(evt))
330 {
331 wxMenu *menu = pFrame->pGraphLog->GetLengthMenu();
332
333 PopupMenu(menu, m_pLengthButton->GetPosition().x,
334 m_pLengthButton->GetPosition().y + m_pLengthButton->GetSize().GetHeight());
335
336 delete menu;
337 }
338
OnMenuLength(wxCommandEvent & evt)339 void StatsWindow::OnMenuLength(wxCommandEvent& evt)
340 {
341 pFrame->pGraphLog->OnMenuLength(evt);
342 }
343
OnButtonClear(wxCommandEvent & evt)344 void StatsWindow::OnButtonClear(wxCommandEvent& evt)
345 {
346 pFrame->pGraphLog->OnButtonClear(evt);
347 }
348