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