1 /*
2     SPDX-FileCopyrightText: 2020 Hy Murveit <hy@murveit.com>
3 
4     SPDX-License-Identifier: GPL-2.0-or-later
5 */
6 
7 #ifndef ANALYZE_H
8 #define ANALYZE_H
9 
10 #include <QtDBus>
11 #include <memory>
12 
13 #include "ekos/ekos.h"
14 #include "ekos/mount/mount.h"
15 #include "indi/inditelescope.h"
16 #include "ui_analyze.h"
17 
18 class FITSViewer;
19 class OffsetDateTimeTicker;
20 
21 namespace Ekos
22 {
23 
24 class RmsFilter;
25 
26 /**
27  *@class Analyze
28  *@short Analysis tab for Ekos sessions.
29  *@author Hy Murveit
30  *@version 1.0
31  */
32 class Analyze : public QWidget, public Ui::Analyze
33 {
34         Q_OBJECT
35 
36     public:
37         Analyze();
38         ~Analyze();
39 
40         // Baseclass used to represent a segment of Timeline data.
41         class Session
42         {
43             public:
44                 // Start and end time in seconds since start of the log.
45                 double start, end;
46                 // y-offset for the timeline plot. Each line uses a different integer.
47                 int offset;
48                 // Variables used in temporary sessions. A temporary session
49                 // represents a process that has started but not yet finished.
50                 // Those are plotted "for now", but will be replaced by the
51                 // finished line when the process completes.
52                 // Rect is the temporary graphic on the Timeline, and
53                 // temporaryBrush defines its look.
54                 QCPItemRect *rect;
55                 QBrush temporaryBrush;
56 
Session(double s,double e,int o,QCPItemRect * r)57                 Session(double s, double e, int o, QCPItemRect *r)
58                     : start(s), end(e), offset(o), rect(r) {}
59 
60                 // These 2 are used to build tables for the details display.
61                 void setupTable(const QString &name, const QString &status,
62                                 const QDateTime &startClock, const QDateTime &endClock,
63                                 QTableWidget *table);
64                 void addRow(const QString &key, const QString &value);
65 
66                 // True if this session is temporary.
67                 bool isTemporary() const;
68 
69             private:
70                 QTableWidget *details;
71                 QString htmlString;
72         };
73         // Below are subclasses of Session used to represent all the different
74         // lines in the Timeline. Each of those hold different types of information
75         // about the process it represents.
76         class CaptureSession : public Session
77         {
78             public:
79                 bool aborted;
80                 QString filename;
81                 double duration;
82                 QString filter;
83                 double hfr;
CaptureSession(double start_,double end_,QCPItemRect * rect,bool aborted_,const QString & filename_,double duration_,const QString & filter_)84                 CaptureSession(double start_, double end_, QCPItemRect *rect,
85                                bool aborted_, const QString &filename_,
86                                double duration_, const QString &filter_)
87                     : Session(start_, end_, CAPTURE_Y, rect),
88                       aborted(aborted_), filename(filename_),
89                       duration(duration_), filter(filter_), hfr(0) {}
CaptureSession()90                 CaptureSession() : Session(0, 0, CAPTURE_Y, nullptr) {}
91         };
92         // Guide sessions collapse some of the possible guiding states.
93         // SimpleGuideState are those collapsed states.
94         typedef enum
95         {
96             G_IDLE, G_GUIDING, G_CALIBRATING, G_SUSPENDED, G_DITHERING, G_IGNORE
97         } SimpleGuideState;
98         class GuideSession : public Session
99         {
100             public:
101                 SimpleGuideState simpleState;
GuideSession(double start_,double end_,QCPItemRect * rect,SimpleGuideState state_)102                 GuideSession(double start_, double end_, QCPItemRect *rect, SimpleGuideState state_)
103                     : Session(start_, end_, GUIDE_Y, rect), simpleState(state_) {}
GuideSession()104                 GuideSession() : Session(0, 0, GUIDE_Y, nullptr) {}
105         };
106         class AlignSession : public Session
107         {
108             public:
109                 AlignState state;
AlignSession(double start_,double end_,QCPItemRect * rect,AlignState state_)110                 AlignSession(double start_, double end_, QCPItemRect *rect, AlignState state_)
111                     : Session(start_, end_, ALIGN_Y, rect), state(state_) {}
AlignSession()112                 AlignSession() : Session(0, 0, ALIGN_Y, nullptr) {}
113         };
114         class MountSession : public Session
115         {
116             public:
117                 ISD::Telescope::Status state;
MountSession(double start_,double end_,QCPItemRect * rect,ISD::Telescope::Status state_)118                 MountSession(double start_, double end_, QCPItemRect *rect, ISD::Telescope::Status state_)
119                     : Session(start_, end_, MOUNT_Y, rect), state(state_) {}
MountSession()120                 MountSession() : Session(0, 0, MOUNT_Y, nullptr) {}
121         };
122         class MountFlipSession : public Session
123         {
124             public:
125                 Mount::MeridianFlipStatus state;
MountFlipSession(double start_,double end_,QCPItemRect * rect,Mount::MeridianFlipStatus state_)126                 MountFlipSession(double start_, double end_, QCPItemRect *rect, Mount::MeridianFlipStatus state_)
127                     : Session(start_, end_, MERIDIAN_FLIP_Y, rect), state(state_) {}
MountFlipSession()128                 MountFlipSession() : Session(0, 0, MERIDIAN_FLIP_Y, nullptr) {}
129         };
130         class FocusSession : public Session
131         {
132             public:
133                 bool success;
134                 double temperature;
135                 QString filter;
136                 QString points;
137                 QVector<double> positions; // Double to be more friendly to QCustomPlot addData.
138                 QVector<double> hfrs;
FocusSession()139                 FocusSession() : Session(0, 0, FOCUS_Y, nullptr) {}
140                 FocusSession(double start_, double end_, QCPItemRect *rect, bool ok, double temperature_,
141                              const QString &filter_, const QString &points_);
142         };
143 
144     public slots:
145         // These slots are messages received from the different Ekos processes
146         // used to gather data about those processes.
147 
148         // From Capture
149         void captureComplete(const QString &filename, double exposureSeconds, const QString &filter,
150                              double hfr, int numStars, int median, double eccentricity);
151         void captureStarting(double exposureSeconds, const QString &filter);
152         void captureAborted(double exposureSeconds);
153 
154         // From Guide
155         void guideState(Ekos::GuideState status);
156         void guideStats(double raError, double decError, int raPulse, int decPulse,
157                         double snr, double skyBg, int numStars);
158 
159         // From Focus
160         void autofocusStarting(double temperature, const QString &filter);
161         void autofocusComplete(const QString &filter, const QString &points);
162         void autofocusAborted(const QString &filter, const QString &points);
163         void newTemperature(double temperatureDelta, double temperature);
164 
165         // From Align
166         void alignState(Ekos::AlignState state);
167 
168         // From Mount
169         void mountState(ISD::Telescope::Status status);
170         void mountCoords(const SkyPoint &position, ISD::Telescope::PierSide pierSide, const dms &haValue);
171         void mountFlipStatus(Ekos::Mount::MeridianFlipStatus status);
172 
173     private slots:
174 
175     signals:
176 
177     private:
178 
179         // The file-reading, processInputLine(), and signal-slot codepaths share the methods below
180         // to process their messages. Time is the offset in seconds from the start of the log.
181         // BatchMode is true in the file reading path. It means don't call replot() as there may be
182         // many more messages to come. The rest of the args are specific to the message type.
183         void processCaptureStarting(double time, double exposureSeconds, const QString &filter, bool batchMode = false);
184         void processCaptureComplete(double time, const QString &filename, double exposureSeconds, const QString &filter,
185                                     double hfr, int numStars, int median, double eccentricity, bool batchMode = false);
186         void processCaptureAborted(double time, double exposureSeconds, bool batchMode = false);
187         void processAutofocusStarting(double time, double temperature, const QString &filter, bool batchMode = false);
188         void processAutofocusComplete(double time, const QString &filter, const QString &points, bool batchMode = false);
189         void processAutofocusAborted(double time, const QString &filter, const QString &points, bool batchMode = false);
190         void processTemperature(double time, double temperature, bool batchMode = false);
191         void processGuideState(double time, const QString &state, bool batchMode = false);
192         void processGuideStats(double time, double raError, double decError, int raPulse,
193                                int decPulse, double snr, double skyBg, int numStars, bool batchMode = false);
194         void processMountCoords(double time, double ra, double dec, double az, double alt,
195                                 int pierSide, double ha, bool batchMode = false);
196 
197         void processMountState(double time, const QString &statusString, bool batchMode = false);
198         void processAlignState(double time, const QString &statusString, bool batchMode = false);
199         void processMountFlipState(double time, const QString &statusString, bool batchMode = false);
200 
201         // Plotting primatives.
202         void replot(bool adjustSlider = true);
203         void zoomIn();
204         void zoomOut();
205         void scroll(int value);
206         void scrollRight();
207         void scrollLeft();
208 
209         // maxXValue keeps the largest time offset we've received so far.
210         // It represents the extent of the plots (0 -> maxXValue).
211         // This is called each time a message is received in case that message's
212         // time is past the current value of maxXValue.
213         void updateMaxX(double time);
214 
215         // Callbacks for when the timeline is clicked. ProcessTimelineClick
216         // will determine which segment on which line was clicked and then
217         // call captureSessionClicked() or focusSessionClicked, etc.
218         void processTimelineClick(QMouseEvent *event, bool doubleClick);
219         void captureSessionClicked(CaptureSession &c, bool doubleClick);
220         void focusSessionClicked(FocusSession &c, bool doubleClick);
221         void guideSessionClicked(GuideSession &c, bool doubleClick);
222         void mountSessionClicked(MountSession &c, bool doubleClick);
223         void alignSessionClicked(AlignSession &c, bool doubleClick);
224         void mountFlipSessionClicked(MountFlipSession &c, bool doubleClick);
225 
226         // Low-level callbacks.
227         // These two call processTimelineClick().
228         void timelineMousePress(QMouseEvent *event);
229         void timelineMouseDoubleClick(QMouseEvent *event);
230         // Calls zoomIn or zoomOut.
231         void timelineMouseWheel(QWheelEvent *event);
232 
233         void processStatsClick(QMouseEvent *event, bool doubleClick);
234         void statsMousePress(QMouseEvent *event);
235         void statsMouseDoubleClick(QMouseEvent *event);
236         void statsMouseMove(QMouseEvent *event);
237         void setupKeyboardShortcuts(QCustomPlot *plot);
238 
239         // (Un)highlights a segment on the timeline after one is clicked.
240         // This indicates which segment's data is displayed in the
241         // graphicsPlot and details table.
242         void highlightTimelineItem(double y, double start, double end);
243         void unhighlightTimelineItem();
244 
245         // logTime() returns the number of seconds between "now" or "time" and
246         // the start of the log. They are useful for recording signal and storing
247         // them to file. They are not useful when reading data from files.
248         double logTime();
249         // Returns the number of seconds between time and the start of the log.
250         double logTime(const QDateTime &time);
251         // Goes back from logSeconds to human-readable clock time.
252         QDateTime clockTime(double logSeconds);
253 
254         // Add a new segment to the Timeline graph.
255         // Returns a rect item, which is only important temporary objects, who
256         // need to erase the item when the temporary session is removed.
257         // This memory is owned by QCustomPlot and shouldn't be freed.
258         // This pointer is stored in Session::rect.
259         QCPItemRect * addSession(double start, double end, double y,
260                                  const QBrush &brush, const QBrush *stripeBrush = nullptr);
261 
262         // Manage temporary sessions (only used for live data--file-reading doesn't
263         // need temporary sessions). For example, when an image capture has started
264         // but not yet completed, a temporary session is added to the timeline to
265         // represent the not-yet-completed capture.
266         void addTemporarySession(Session *session, double time, double duration,
267                                  int y_offset, const QBrush &brush);
268         void removeTemporarySession(Session *session);
269         void removeTemporarySessions();
270         void adjustTemporarySession(Session *session);
271         void adjustTemporarySessions();
272 
273         // Add new stats to the statsPlot.
274         void addGuideStats(double raDrift, double decDrift, int raPulse, int decPulse,
275                            double snr, int numStars, double skyBackground, double time);
276         void addGuideStatsInternal(double raDrift, double decDrift, double raPulse,
277                                    double decPulse, double snr, double numStars,
278                                    double skyBackground, double drift, double rms, double time);
279         void addMountCoords(double ra, double dec, double az, double alt, int pierSide,
280                             double ha, double time);
281         void addHFR(double hfr, int numCaptureStars, int median, double eccentricity,
282                     const double time, double startTime);
283         void addTemperature(double temperature, const double time);
284 
285         // Initialize the graphs (axes, linestyle, pen, name, checkbox callbacks).
286         // Returns the graph index.
287         int initGraph(QCustomPlot *plot, QCPAxis *yAxis, QCPGraph::LineStyle lineStyle,
288                       const QColor &color, const QString &name);
289         template <typename Func>
290         int initGraphAndCB(QCustomPlot *plot, QCPAxis *yAxis, QCPGraph::LineStyle lineStyle,
291                            const QColor &color, const QString &name, QCheckBox *cb, Func setCb);
292 
293         // Make graphs visible/invisible & add/delete them from the legend.
294         void toggleGraph(int graph_id, bool show);
295 
296         // Initializes the main QCustomPlot windows.
297         void initStatsPlot();
298         void initTimelinePlot();
299         void initGraphicsPlot();
300         void initInputSelection();
301 
302         // Displays the focus positions and HFRs on the graphics plot.
303         void displayFocusGraphics(const QVector<double> &positions, const QVector<double> &hfrs, bool success);
304         // Displays the guider ra and dec drift plot, and computes RMS errors.
305         void displayGuideGraphics(double start, double end, double *raRMS,
306                                   double *decRMS, double *totalRMS, int *numSamples);
307 
308         // Updates the stats value display boxes next to their checkboxes.
309         void updateStatsValues();
310         // Manages the statsPlot cursor.
311         void setStatsCursor(double time);
312         void removeStatsCursor();
313         void keepCurrent(int state);
314 
315         // Restore checkboxs from Options.
316         void initStatsCheckboxes();
317 
318         // Clears the data, resets the various plots & displays.
319         void reset();
320         void resetGraphicsPlot();
321 
322         // Resets the variables used to process the signals received.
323         void resetCaptureState();
324         void resetAutofocusState();
325         void resetGuideState();
326         void resetGuideStats();
327         void resetAlignState();
328         void resetMountState();
329         void resetMountCoords();
330         void resetMountFlipState();
331         void resetTemperature();
332 
333         // Read and display an input .analyze file.
334         double readDataFromFile(const QString &filename);
335         double processInputLine(const QString &line);
336 
337         // Opens a FITS file for viewing.
338         void displayFITS(const QString &filename);
339 
340         // Pop up a help-message window.
341         void helpMessage();
342 
343         // Write the analyze log file message.
344         void saveMessage(const QString &type, const QString &message);
345         // low level file writing.
346         void startLog();
347         void appendToLog(const QString &lines);
348 
349         // The .analyze log file being written.
350         QString logFilename { "" };
351         QFile logFile;
352         bool logInitialized { false };
353 
354         // These define the view for the timeline and stats plots.
355         // The plots start plotStart seconds from the start of the session, and
356         // are plotWidth seconds long. The end of the X-axis is maxXValue.
357         double plotStart { 0.0 };
358         double plotWidth { 10.0 };
359         double maxXValue { 10.0 };
360 
361         // Data are displayed in seconds since the session started.
362         // analyzeStartTime is when the session started, used to translate to clock time.
363         QDateTime analyzeStartTime;
364         QString analyzeTimeZone { "" };
365         bool startTimeInitialized { false };
366 
367         // displayStartTime is similar to analyzeStartTime, but references the
368         // start of the log being displayed (e.g. if reading from a file).
369         // When displaying the current session it should equal analyzeStartTime.
370         QDateTime displayStartTime;
371 
372         // AddGuideStats uses RmsFilter to compute RMS values of the squared
373         // RA and DEC errors, thus calculating the RMS error.
374         std::unique_ptr<RmsFilter> guiderRms;
375         std::unique_ptr<RmsFilter> captureRms;
376 
377         // Y-axes for the for several plots where we rescale based on data.
378         // QCustomPlot owns these pointers' memory, don't free it.
379         QCPAxis *snrAxis;
380         QCPAxis *numStarsAxis;
381         QCPAxis *skyBgAxis;
382         QCPAxis *medianAxis;
383         QCPAxis *numCaptureStarsAxis;
384         QCPAxis *temperatureAxis;
385         // Used to keep track of the y-axis position when moving it with the mouse.
386         double yAxisInitialPos = { 0 };
387 
388         // Used to display clock-time on the X-axis.
389         QSharedPointer<OffsetDateTimeTicker> dateTicker;
390 
391         // The rectangle over the current selection.
392         // Memory owned by QCustomPlot.
393         QCPItemRect *selectionHighlight { nullptr };
394 
395         // FITS Viewer to display FITS images.
396         QPointer<FITSViewer> fitsViewer;
397         // When trying to load a FITS file, if the original file path doesn't
398         // work, Analyze tries to find the file under the alternate folder.
399         QString alternateFolder;
400 
401         // The vertical line in the stats plot.
402         QCPItemLine *statsCursor { nullptr };
403         double statsCursorTime { -1 };
404 
405         // Keeps the directory from the last time the user loaded a .analyze file.
406         QUrl dirPath;
407 
408         // True if Analyze is displaying data as it comes in from the other modules.
409         // False if Analyze is displaying data read from a file.
410         bool runtimeDisplay { true };
411 
412         // When a module's session is ongoing, we represent it as a "temporary session"
413         // which will be replaced once the session is done.
414         CaptureSession temporaryCaptureSession;
415         FocusSession temporaryFocusSession;
416         GuideSession temporaryGuideSession;
417         AlignSession temporaryAlignSession;
418         MountSession temporaryMountSession;
419         MountFlipSession temporaryMountFlipSession;
420 
421         // Capture state-machine variables.
422         double captureStartedTime { -1 };
423         QString captureStartedFilter { "" };
424 
425         // Autofocus state-machine variables.
426         double autofocusStartedTime { -1 };
427         QString autofocusStartedFilter { "" };
428         double autofocusStartedTemperature { 0 };
429 
430         // GuideState state-machine variables.
431         SimpleGuideState lastGuideStateStarted { G_IDLE };
432         double guideStateStartedTime { -1 };
433 
434         // GuideStats state-machine variables.
435         double lastGuideStatsTime { -1 };
436         double lastCaptureRmsTime { -1 };
437         int numStarsMax { 0 };
438         double snrMax { 0 };
439         double skyBgMax { 0 };
440         int medianMax { 0 };
441         int numCaptureStarsMax { 0 };
442         double lastTemperature { -1000 };
443 
444         // AlignState state-machine variables.
445         AlignState lastAlignStateReceived { ALIGN_IDLE };
446         AlignState lastAlignStateStarted { ALIGN_IDLE };
447         double lastAlignStateStartedTime { -1 };
448 
449         // MountState state-machine variables.
450         double mountStateStartedTime { -1 };
451         ISD::Telescope::Status lastMountState { ISD::Telescope::Status::MOUNT_IDLE };
452 
453         // Mount coords state machine variables.
454         // Used to filter out mount Coords messages--we only process ones
455         // where the values have changed significantly.
456         double lastMountRa { -1 };
457         double lastMountDec { -1 };
458         double lastMountHa { -1 };
459         double lastMountAz { -1 };
460         double lastMountAlt { -1 };
461         int lastMountPierSide { -1 };
462 
463         // Flip state machine variables
464         Mount::MeridianFlipStatus lastMountFlipStateReceived { Mount::FLIP_NONE};
465         Mount::MeridianFlipStatus lastMountFlipStateStarted { Mount::FLIP_NONE };
466         double mountFlipStateStartedTime { -1 };
467 
468         // Y-offsets for the timeline plot for the various modules.
469         static constexpr int CAPTURE_Y = 1;
470         static constexpr int FOCUS_Y = 2;
471         static constexpr int ALIGN_Y = 3;
472         static constexpr int GUIDE_Y = 4;
473         static constexpr int MERIDIAN_FLIP_Y = 5;
474         static constexpr int MOUNT_Y = 6;
475         static constexpr int LAST_Y = 7;
476 };
477 }
478 
479 
480 #endif // Analyze
481