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