1 /*  MainWindow for StellarSolver Tester Application, developed by Robert Lancaster, 2020
2 
3     This application is free software; you can redistribute it and/or
4     modify it under the terms of the GNU General Public
5     License as published by the Free Software Foundation; either
6     version 2 of the License, or (at your option) any later version.
7 */
8 
9 #include "mainwindow.h"
10 #include "ui_mainwindow.h"
11 
12 #include <QUuid>
13 #include <QDebug>
14 #include <QImageReader>
15 #include <QTableWidgetItem>
16 #include <QPainter>
17 #include <QDesktopServices>
18 
19 #include <stdio.h>
20 #include <stdlib.h>
21 #include <string.h>
22 #include <ctype.h>
23 #include <sys/types.h>
24 
25 #ifndef _MSC_VER
26 #include <sys/time.h>
27 #include <libgen.h>
28 #include <getopt.h>
29 #include <dirent.h>
30 #endif
31 
32 #include <time.h>
33 
34 #include <assert.h>
35 
36 #include <QShortcut>
37 #include <QThread>
38 #include <QInputDialog>
39 #include <QtConcurrent>
40 #include <QToolTip>
41 #include <QtGlobal>
42 #include "version.h"
43 
MainWindow()44 MainWindow::MainWindow() :
45     QMainWindow(),
46     ui(new Ui::MainWindow)
47 {
48     ui->setupUi(this);
49 
50 
51     this->show();
52 
53     //The Options at the top of the Window
54     connect(ui->ImageLoad, &QAbstractButton::clicked, this, &MainWindow::imageLoad );
55     ui->ImageLoad->setToolTip("Loads an Image into the Viewer");
56     connect(ui->zoomIn, &QAbstractButton::clicked, this, &MainWindow::zoomIn );
57     ui->zoomIn->setToolTip("Zooms In on the Image");
58     connect(ui->zoomOut, &QAbstractButton::clicked, this, &MainWindow::zoomOut );
59     ui->zoomOut->setToolTip("Zooms Out on the Image");
60     connect(ui->AutoScale, &QAbstractButton::clicked, this, &MainWindow::autoScale );
61     ui->AutoScale->setToolTip("Rescales the image based on the available space");
62 
63     // Keyboard shortcuts -- if you add more, remember to add to the helpPopup() method below.
64     QShortcut *s = new QShortcut(QKeySequence(QKeySequence::ZoomIn), ui->Image);
65     connect(s, &QShortcut::activated, this, &MainWindow::zoomIn);
66     s = new QShortcut(QKeySequence(QKeySequence::ZoomOut), ui->Image);
67     connect(s, &QShortcut::activated, this, &MainWindow::zoomOut);
68     s = new QShortcut(QKeySequence(tr("Ctrl+0")), ui->Image);
69     connect(s, &QShortcut::activated, this, &MainWindow::autoScale);
70     s = new QShortcut(QKeySequence(QKeySequence::Open), ui->Image);
71     connect(s, &QShortcut::activated, this, &MainWindow::imageLoad );
72     s = new QShortcut(QKeySequence(QKeySequence::MoveToNextChar), ui->Image);
73     connect(s, &QShortcut::activated, this, &MainWindow::panRight);
74     s = new QShortcut(QKeySequence(QKeySequence::MoveToPreviousChar), ui->Image);
75     connect(s, &QShortcut::activated, this, &MainWindow::panLeft);
76     s = new QShortcut(QKeySequence(QKeySequence::MoveToNextLine), ui->Image);
77     connect(s, &QShortcut::activated, this, &MainWindow::panDown);
78     s = new QShortcut(QKeySequence(QKeySequence::MoveToPreviousLine), ui->Image);
79     connect(s, &QShortcut::activated, this, &MainWindow::panUp);
80     s = new QShortcut(QKeySequence(QKeySequence::Quit), ui->Image);
81     connect(s, &QShortcut::activated, this, &QCoreApplication::quit);
82     s = new QShortcut(QKeySequence(tr("Ctrl+l")), ui->Image);
83     connect(s, &QShortcut::activated, this, &MainWindow::toggleLogDisplay);
84     s = new QShortcut(QKeySequence(QKeySequence::FullScreen), ui->Image);
85     connect(s, &QShortcut::activated, this, &MainWindow::toggleFullScreen);
86     s = new QShortcut(QKeySequence(QKeySequence::HelpContents), ui->Image);
87     connect(s, &QShortcut::activated, this, &MainWindow::helpPopup);
88     s = new QShortcut(QKeySequence("?"), ui->Image);
89     connect(s, &QShortcut::activated, this, &MainWindow::helpPopup);
90     s = new QShortcut(QKeySequence("h"), ui->Image);
91     connect(s, &QShortcut::activated, this, &MainWindow::helpPopup);
92 
93     //The Options at the bottom of the Window
94     ui->trials->setToolTip("The number of times to Sextract or Solve to get an average time that it takes.");
95     connect(ui->startSextraction, &QAbstractButton::clicked, this, &MainWindow::sextractButtonClicked );
96     connect(ui->startSolving, &QAbstractButton::clicked, this, &MainWindow::solveButtonClicked );
97     //ui->SextractStars->setToolTip("Sextract the stars in the image using the chosen method and load them into the star table");
98     //ui->m_ExtractorType->setToolTip("Lets you choose the Internal StellarSolver SEP or external Sextractor program");
99     //ui->optionsProfileSextract->setToolTip("The Options Profile to use for Sextracting.");
100     //ui->editSextractorProfile->setToolTip("Loads the currently selected sextrctor profile into the profile editor");
101     connect(ui->editSextractorProfile, &QAbstractButton::clicked, this, [this]()
102     {
103         ui->optionsProfile->setCurrentIndex(ui->sextractionProfile->currentIndex());
104         ui->optionsTab->setCurrentIndex(1);
105         ui->sextractionProfile->setCurrentIndex(0);
106     });
107     // ui->SolveImage->setToolTip("Solves the image using the method chosen in the dropdown box");
108     //ui->solverType->setToolTip("Lets you choose how to solve the image");
109     //ui->optionsProfileSolve->setToolTip("The Options Profile to use for Solving.");
110     //ui->editSolverProfile->setToolTip("Loads the currently selected solver profile into the profile editor");
111     connect(ui->editSolverProfile, &QAbstractButton::clicked, this, [this]()
112     {
113         ui->optionsProfile->setCurrentIndex(ui->solverProfile->currentIndex());
114         ui->optionsTab->setCurrentIndex(1);
115         ui->solverProfile->setCurrentIndex(0);
116     });
117     connect(ui->Abort, &QAbstractButton::clicked, this, &MainWindow::abort );
118     ui->Abort->setToolTip("Aborts the current process if one is running.");
119     connect(ui->ClearStars, &QAbstractButton::clicked, this, &MainWindow::clearStars );
120     ui->ClearStars->setToolTip("Clears the star table and the stars from the image");
121 
122     connect(ui->ClearResults, &QAbstractButton::clicked, this, &MainWindow::clearResults );
123     ui->ClearResults->setToolTip("Clears the Results Table");
124 
125     //The Options for the StellarSolver Options
126     ui->optionsProfile->setToolTip("The Options Profile currently in the options box. Selecting a profile will reset all the Sextractor and Solver settings to that profile.");
127     connect(ui->optionsProfile, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &MainWindow::loadOptionsProfile);
128     ui->addOptionProfile->setToolTip("Adds the current options in the left option pane to a new profile");
129     connect(ui->addOptionProfile, &QAbstractButton::clicked, this, [this]()
130     {
131         bool ok;
132         QString name = QInputDialog::getText(this, tr("New Options Profile"),
133                                              tr("What would you like your profile to be called?"), QLineEdit::Normal,
134                                              "", &ok);
135         if (ok && !name.isEmpty())
136         {
137             optionsAreSaved = true;
138             SSolver::Parameters params = getSettingsFromUI();
139             params.listName = name;
140             ui->optionsProfile->setCurrentIndex(0); //So we don't trigger any loading of any other profiles
141             optionsList.append(params);
142             ui->optionsProfile->addItem(name);
143             ui->sextractionProfile->addItem(name);
144             ui->solverProfile->addItem(name);
145             ui->optionsProfile->setCurrentText(name);
146         }
147     });
148     ui->removeOptionProfile->setToolTip("Removes the selected profile from the list of profiles");
149     connect(ui->removeOptionProfile, &QAbstractButton::clicked, this, [this]()
150     {
151         int item = ui->optionsProfile->currentIndex();
152         if(item < 1)
153         {
154             QMessageBox::critical(nullptr, "Message", "You can't delete this profile");
155             return;
156         }
157         ui->optionsProfile->setCurrentIndex(0); //So we don't trigger any loading of any other profiles
158         ui->optionsProfile->removeItem(item);
159         ui->sextractionProfile->removeItem(item);
160         ui->solverProfile->removeItem(item);
161         optionsList.removeAt(item - 1);
162 
163     });
164     ui->saveSettings->setToolTip("Saves a file with Options Profiles to a desired location");
165     connect(ui->saveSettings, &QPushButton::clicked, this, &MainWindow::saveOptionsProfiles);
166     ui->loadSettings->setToolTip("Loads a file with Options Profiles from a saved location");
167     connect(ui->loadSettings, &QPushButton::clicked, this, &MainWindow::loadOptionsProfiles);
168 
169     QWidget *sextractorOptions = ui->optionsBox->widget(0);
170     QWidget *starFilterOptions = ui->optionsBox->widget(1);
171     QWidget *astrometryOptions = ui->optionsBox->widget(2);
172 
173     QList<QLineEdit *> lines;
174     lines = sextractorOptions->findChildren<QLineEdit *>();
175     lines.append(starFilterOptions->findChildren<QLineEdit *>());
176     lines.append(astrometryOptions->findChildren<QLineEdit *>());
177     foreach(QLineEdit *line, lines)
178         connect(line, &QLineEdit::textEdited, this, &MainWindow::settingJustChanged);
179 
180     QList<QCheckBox *> checks;
181     checks = sextractorOptions->findChildren<QCheckBox *>();
182     checks.append(starFilterOptions->findChildren<QCheckBox *>());
183     checks.append(astrometryOptions->findChildren<QCheckBox *>());
184     foreach(QCheckBox *check, checks)
185         connect(check, &QCheckBox::stateChanged, this, &MainWindow::settingJustChanged);
186 
187     QList<QComboBox *> combos;
188     combos = sextractorOptions->findChildren<QComboBox *>();
189     combos.append(starFilterOptions->findChildren<QComboBox *>());
190     combos.append(astrometryOptions->findChildren<QComboBox *>());
191     foreach(QComboBox *combo, combos)
192         connect(combo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &MainWindow::settingJustChanged);
193 
194     QList<QSpinBox *> spins;
195     spins = sextractorOptions->findChildren<QSpinBox *>();
196     spins.append(starFilterOptions->findChildren<QSpinBox *>());
197     spins.append(astrometryOptions->findChildren<QSpinBox *>());
198     foreach(QSpinBox *spin, spins)
199         connect(spin, QOverload<int>::of(&QSpinBox::valueChanged), this, &MainWindow::settingJustChanged);
200 
201 
202     //Hides the panels into the sides and bottom
203     ui->vertSplitter->setSizes(QList<int>() << ui->vertSplitter->height() << 0 );
204     ui->horSplitter->setSizes(QList<int>() << 100 << ui->horSplitter->width() / 2  << 0 );
205 
206     //Settings for the External Sextractor and Solver
207     ui->configFilePath->setToolTip("The path to the Astrometry.cfg file used by astrometry.net for configuration.");
208     ui->sextractorPath->setToolTip("The path to the external Sextractor executable");
209     ui->solverPath->setToolTip("The path to the external Astrometry.net solve-field executable");
210     ui->astapPath->setToolTip("The path to the external ASTAP executable");
211     ui->basePath->setToolTip("The base path where sextractor and astrometry temporary files are saved on your computer");
212     ui->openTemp->setToolTip("Opens the directory (above) to where the external solvers save their files");
213     ui->wcsPath->setToolTip("The path to wcsinfo for the external Astrometry.net");
214     ui->cleanupTemp->setToolTip("This option allows the program to clean up temporary files created when running various processes");
215     ui->generateAstrometryConfig->setToolTip("Determines whether to generate an astrometry.cfg file based on the options in the options panel or to use the external config file above.");
216     ui->onlySendFITSFiles->setToolTip("This option only applies to the local external solvers using their own builtin star extractor. If it is off, the file will be sent in its native form.  If it is on, it will be converted to FITS.  This is good for avoiding Python usage.");
217     ui->onlineServer->setToolTip("This is the server that StellarSolver will use for the Online solves.  This will typically be nova.astrometry.net, but it could also be an ANSVR server or a custom one.");
218     ui->apiKey->setToolTip("This is the api key used for astrometry.net online.  You can enter your own and then have access to your solves later.");
219     connect(ui->openTemp, &QAbstractButton::clicked, this, [this]()
220     {
221         QDesktopServices::openUrl(QUrl::fromLocalFile(ui->basePath->text()));
222     });
223     //StellarSolver Tester Options
224     connect(ui->showStars, &QAbstractButton::clicked, this, &MainWindow::updateImage );
225     ui->setSubFrame->setToolTip("Sets or clears the Subframe for Sextraction if desired");
226     connect(ui->setSubFrame, &QAbstractButton::clicked, this, &MainWindow::setSubframe );
227 
228     connect(ui->setPathsAutomatically, QOverload<int>::of(&QComboBox::currentIndexChanged), this, [this](int num)
229     {
230 
231         ExternalProgramPaths paths;
232         switch(num)
233         {
234             case 0:
235                 paths = ExternalSextractorSolver::getLinuxDefaultPaths();
236                 break;
237             case 1:
238                 paths = ExternalSextractorSolver::getLinuxInternalPaths();
239                 break;
240             case 2:
241                 paths = ExternalSextractorSolver::getMacHomebrewPaths();
242                 break;
243             case 3:
244                 paths = ExternalSextractorSolver::getMacInternalPaths();
245                 break;
246             case 4:
247                 paths = ExternalSextractorSolver::getWinANSVRPaths();
248                 break;
249             case 5:
250                 paths = ExternalSextractorSolver::getWinCygwinPaths();
251                 break;
252             default:
253                 paths = ExternalSextractorSolver::getLinuxDefaultPaths();
254                 break;
255         }
256 
257         ui->sextractorPath->setText(paths.sextractorBinaryPath);
258         ui->configFilePath->setText(paths.confPath);
259         ui->solverPath->setText(paths.solverPath);
260         ui->astapPath->setText(paths.astapBinaryPath);
261         ui->wcsPath->setText(paths.wcsPath);
262     });
263     ui->setPathsAutomatically->setToolTip("This allows you to select the default values of typical configurations of paths to external files/programs on different systems from a dropdown");
264 
265     //Sextractor Settings
266 
267     ui->apertureShape->setToolTip("This selects whether to instruct the Sextractor to use Ellipses or Circles for flux calculations");
268     ui->kron_fact->setToolTip("This sets the Kron Factor for use with the kron radius for flux calculations.");
269     ui->subpix->setToolTip("The subpix setting.  The instructions say to make it 5");
270     ui->detect_thresh->setToolTip("Currently disabled parameter for detection/analysis threshold.  We are using a different method.");
271     ui->r_min->setToolTip("The minimum radius for stars for flux calculations.");
272     //no inflags???;
273     ui->magzero->setToolTip("This is the 'zero' magnitude used for settting the magnitude scale for the stars in the image during sextraction.");
274     ui->minarea->setToolTip("This is the minimum area in pixels for a star detection, smaller stars are ignored.");
275     ui->deblend_thresh->setToolTip("The number of thresholds the intensity range is divided up into");
276     ui->deblend_contrast->setToolTip("The percentage of flux a separate peak must # have to be considered a separate object");
277 
278     ui->cleanCheckBox->setToolTip("Attempts to 'clean' the image to remove artifacts caused by bright objects");
279     ui->clean_param->setToolTip("The cleaning parameter, not sure what it does.");
280 
281     //This generates an array that can be used as a convFilter based on the desired FWHM
282     ui->fwhm->setToolTip("A function that I made that creates a convolution filter based upon the desired FWHM for better star detection of that star size and shape");
283 
284     ui->showConv->setToolTip("Loads the convolution filter into a window for viewing");
285 
286     ui->partition->setToolTip("Whether or not to partition the image during SEP operations for Internal SEP.  This can greatly speed up star extraction, but at the cost of possibly missing some objects.  For solving, Focusing, and guiding operations, this doesn't matter, but for doing science, you might want to turn it off.");
287 
288     connect(ui->showConv,&QPushButton::clicked,this,[this](){
289         if(!convInspector)
290         {
291             convInspector = new QDialog(this);
292             convInspector->setWindowTitle("Convolution Filter Inspector");
293             convInspector->setWindowFlags(Qt::Tool | Qt::WindowStaysOnTopHint);
294             convTable = new QTableWidget(this);
295             QGridLayout *layout = new QGridLayout(this);
296             convInspector->setLayout(layout);
297             layout->addWidget(convTable);
298         }
299         convInspector->show();
300         reloadConvTable();
301     });
302 
303     connect(ui->fwhm,QOverload<int>::of(&QSpinBox::valueChanged),this, &MainWindow::reloadConvTable);
304 
305     //Star Filter Settings
306     connect(ui->resortQT, &QCheckBox::stateChanged, this, [this]()
307     {
308         ui->resort->setChecked(ui->resortQT->isChecked());
309     });
310     ui->resortQT->setToolTip("This resorts the stars based on magnitude.  It MUST be checked for the next couple of filters to be enabled.");
311     ui->maxSize->setToolTip("This is the maximum diameter of stars to include in pixels");
312     ui->minSize->setToolTip("This is the minimum diameter of stars to include in pixels");
313     ui->maxEllipse->setToolTip("Stars are typically round, this filter divides stars' semi major and minor axes and rejects stars with distorted shapes greater than this number (1 is perfectly round)");
314     ui->initialKeep->setToolTip("Keep just this number of stars in the list based upon star size.  They will be the biggest in the list.  If there are less than this number, they will all be kept.  This filter is primarily for HFR operations, so they take less time.");
315     ui->keepNum->setToolTip("Keep just this number of star in the list based on magnitude.  They will be the brightest in the list.  If there are less than this number, they will all be kept.  This filter is mainly for the solver, so that it takes less time.");
316     ui->brightestPercent->setToolTip("Removes the brightest % of stars from the image");
317     ui->dimmestPercent->setToolTip("Removes the dimmest % of stars from the image");
318     ui->saturationLimit->setToolTip("Removes stars above a certain % of the saturation limit of an image of this data type");
319 
320     //Astrometry Settings
321     ui->inParallel->setToolTip("Loads the Astrometry index files in parallel.  This can speed it up, but uses more resources");
322     ui->multiAlgo->setToolTip("Allows solving in multiple threads or multiple cores with several algorithms");
323     ui->solverTimeLimit->setToolTip("This is the maximum time the Astrometry.net solver should spend on the image before giving up");
324     ui->minWidth->setToolTip("Sets a the minimum degree limit in the scales for Astrometry to search if the scale parameter isn't set");
325     ui->maxWidth->setToolTip("Sets a the maximum degree limit in the scales for Astrometry to search if the scale parameter isn't set");
326 
327     connect(ui->resort, &QCheckBox::stateChanged, this, [this]()
328     {
329         ui->resortQT->setChecked(ui->resort->isChecked());
330     });
331     ui->autoDown->setToolTip("This determines whether to automatically downsample or use the parameter below.");
332     ui->downsample->setToolTip("This downsamples or bins the image to hopefully make it solve faster.");
333     ui->resort->setToolTip("This resorts the stars based on magnitude. It usually makes it solve faster.");
334     ui->use_scale->setToolTip("Whether or not to use the estimated image scale below to try to speed up the solve");
335     ui->scale_low->setToolTip("The minimum size for the estimated image scale");
336     ui->scale_high->setToolTip("The maximum size for the estimated image scale");
337     ui->units->setToolTip("The units for the estimated image scale");
338 
339     ui->use_position->setToolTip("Whether or not to use the estimated position below to try to speed up the solve");
340     ui->ra->setToolTip("The estimated RA of the object in decimal form in hours not degrees");
341     ui->dec->setToolTip("The estimated DEC of the object in decimal form in degrees");
342     ui->radius->setToolTip("The search radius (degrees) of a circle centered on this position for astrometry.net to search for solutions");
343 
344     ui->oddsToKeep->setToolTip("The Astrometry oddsToKeep Parameter.  This may need to be changed or removed");
345     ui->oddsToSolve->setToolTip("The Astrometry oddsToSolve Parameter.  This may need to be changed or removed");
346     ui->oddsToTune->setToolTip("The Astrometry oddsToTune Parameter.  This may need to be changed or removed");
347 
348     ui->logToFile->setToolTip("Whether the stellarsolver should just output to the log window or whether it should log to a file.");
349     ui->logFileName->setToolTip("The name and path of the file to log to, if this is blank, it will automatically log to a file in the temp Directory with an automatically generated name.");
350     ui->logLevel->setToolTip("The verbosity level of the log to be displayed in the log window or saved to a file.");
351 
352     connect(ui->indexFolderPaths, &QComboBox::currentTextChanged, this, [this]()
353     {
354         loadIndexFilesList();
355     });
356     ui->indexFolderPaths->setToolTip("The paths on your compute to search for index files.  To add another, just start typing in the box.  To select one to look at, use the drop down.");
357     connect(ui->removeIndexPath, &QPushButton::clicked, this, [this]()
358     {
359         ui->indexFolderPaths->removeItem( ui->indexFolderPaths->currentIndex());
360     });
361     ui->removeIndexPath->setToolTip("Removes the selected path in the index folder paths dropdown so that it won't get passed to the solver");
362     connect(ui->addIndexPath, &QPushButton::clicked, this, [this]()
363     {
364         QString dir = QFileDialog::getExistingDirectory(this, "Load Index File Directory",
365                       QDir::homePath(),
366                       QFileDialog::ShowDirsOnly
367                       | QFileDialog::DontResolveSymlinks);
368         if (dir.isEmpty())
369             return;
370         ui->indexFolderPaths->addItem( dir );
371         ui->indexFolderPaths->setCurrentIndex(ui->indexFolderPaths->count() - 1);
372     });
373     ui->addIndexPath->setToolTip("Adds a path the user selects to the list of index folder paths");
374 
375     //Behaviors and Settings for the StarTable
376     connect(this, &MainWindow::readyForStarTable, this, &MainWindow::displayTable);
377     ui->starTable->setSelectionBehavior(QAbstractItemView::SelectRows);
378     connect(ui->starTable, &QTableWidget::itemSelectionChanged, this, &MainWindow::starClickedInTable);
379     ui->starTable->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
380     connect(ui->exportStarTable, &QAbstractButton::clicked, this, &MainWindow::saveStarTable);
381     ui->showStars->setToolTip("This toggles the stars circles on and off in the image");
382     connect(ui->starOptions, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &MainWindow::updateImage);
383     ui->starOptions->setToolTip("This allows you to select different types of star circles to put on the stars.  Warning, some require HFR to have been calculated first.");
384     connect(ui->showFluxInfo, &QCheckBox::stateChanged, this, [this]()
385     {
386         showFluxInfo = ui->showFluxInfo->isChecked();
387         updateHiddenStarTableColumns();
388     });
389     ui->showFluxInfo->setToolTip("This toggles whether to show or hide the HFR, peak, Flux columns in the star table after Sextraction.");
390     connect(ui->showStarShapeInfo, &QCheckBox::stateChanged, this, [this]()
391     {
392         showStarShapeInfo = ui->showStarShapeInfo->isChecked();
393         updateHiddenStarTableColumns();
394     });
395     ui->showStarShapeInfo->setToolTip("This toggles whether to show or hide the information about each star's semi-major axis, semi-minor axis, and orientation in the star table after sextraction.");
396 
397     //Behaviors for the Mouse over the Image to interact with the StartList and the UI
398     connect(ui->Image, &ImageLabel::mouseMoved, this, &MainWindow::mouseMovedOverImage);
399     connect(ui->Image, &ImageLabel::mouseClicked, this, &MainWindow::mouseClickedInImage);
400     connect(ui->Image, &ImageLabel::mouseDown, this, &MainWindow::mousePressedInImage);
401 
402     //Behavior and settings for the Results Table
403     setupResultsTable();
404     ui->resultsTable->setSelectionBehavior(QAbstractItemView::SelectRows);
405     ui->resultsTable->horizontalHeader()->setSectionResizeMode(QHeaderView::ResizeToContents);
406     connect(ui->exportResultsTable, &QAbstractButton::clicked, this, &MainWindow::saveResultsTable);
407     ui->exportResultsTable->setToolTip("Exports the log of processes executed during this session to a CSV file for further analysis");
408     connect(ui->showSextractorParams, &QCheckBox::stateChanged, this, [this]()
409     {
410         showSextractorParams = ui->showSextractorParams->isChecked();
411         updateHiddenResultsTableColumns();
412     });
413     ui->showSextractorParams->setToolTip("This toggles whether to show or hide the Sextractor Settings in the Results table at the bottom");
414     connect(ui->showAstrometryParams, &QCheckBox::stateChanged, this, [this]()
415     {
416         showAstrometryParams = ui->showAstrometryParams->isChecked();
417         updateHiddenResultsTableColumns();
418     });
419     ui->showAstrometryParams->setToolTip("This toggles whether to show or hide the Astrometry Settings in the Results table at the bottom");
420     connect(ui->showSolutionDetails, &QCheckBox::stateChanged, this, [this]()
421     {
422         showSolutionDetails = ui->showSolutionDetails->isChecked();
423         updateHiddenResultsTableColumns();
424     });
425     ui->showSolutionDetails->setToolTip("This toggles whether to show or hide the Solution Details in the Results table at the bottom");
426 
427     debayerParams.method  = DC1394_BAYER_METHOD_NEAREST;
428     debayerParams.filter  = DC1394_COLOR_FILTER_RGGB;
429     debayerParams.offsetX = debayerParams.offsetY = 0;
430 
431     setWindowTitle(StellarSolver::getVersion());
432 
433     ui->progressBar->setTextVisible(false);
434     timerMonitor.setInterval(1000); //1 sec intervals
435     connect(&timerMonitor, &QTimer::timeout, this, [this]()
436     {
437         ui->status->setText(QString("Processing Trial %1: %2 s").arg(currentTrial).arg((int)processTimer.elapsed() / 1000) + 1);
438     });
439 
440     setWindowIcon(QIcon(":/StellarSolverIcon.png"));
441 
442     //This Load the saved settings for the StellarSolver
443     QSettings programSettings("Astrometry Freeware", "StellarSolver");
444 
445     //These will set the index
446     int index = 0;
447 #if defined(Q_OS_OSX)
448     if(QFile("/usr/local/bin/solve-field").exists())
449         index = 2;
450     else
451         index = 3;
452 #elif defined(Q_OS_LINUX)
453     index = 0;
454 #else //Windows
455     index = 4;
456 #endif
457 
458     index = programSettings.value("setPathsIndex", index).toInt();
459     ui->setPathsAutomatically->setCurrentIndex(index);
460 
461     //This gets a temporary ExternalSextractorSolver to get the defaults
462     //It tries to load from the saved settings if possible as well.
463     ExternalSextractorSolver extTemp(processType, m_ExtractorType, solverType, stats, m_ImageBuffer, this);
464     ui->sextractorPath->setText(programSettings.value("sextractorBinaryPath", extTemp.sextractorBinaryPath).toString());
465     ui->configFilePath->setText(programSettings.value("confPath", extTemp.confPath).toString());
466     ui->solverPath->setText(programSettings.value("solverPath", extTemp.solverPath).toString());
467     ui->astapPath->setText(programSettings.value("astapBinaryPath", extTemp.astapBinaryPath).toString());
468     ui->wcsPath->setText(programSettings.value("wcsPath", extTemp.wcsPath).toString());
469     ui->cleanupTemp->setChecked(programSettings.value("cleanupTemporaryFiles", extTemp.cleanupTemporaryFiles).toBool());
470     ui->generateAstrometryConfig->setChecked(programSettings.value("autoGenerateAstroConfig",
471             extTemp.autoGenerateAstroConfig).toBool());
472     ui->onlySendFITSFiles->setChecked(programSettings.value("onlySendFITSFiles", false).toBool());
473     ui->onlineServer->setText(programSettings.value("onlineServer", "http://nova.astrometry.net").toString());
474     ui->apiKey->setText(programSettings.value("apiKey", "iczikaqstszeptgs").toString());
475 
476     //These load the default settings from the StellarSolver usting a temporary object
477     StellarSolver temp(processType, stats, m_ImageBuffer, this);
478     ui->basePath->setText(QDir::tempPath());
479     sendSettingsToUI(temp.getCurrentParameters());
480     optionsList = temp.getBuiltInProfiles();
481     foreach(SSolver::Parameters param, optionsList)
482     {
483         ui->optionsProfile->addItem(param.listName);
484         ui->sextractionProfile->addItem(param.listName);
485         ui->solverProfile->addItem(param.listName);
486     }
487     optionsAreSaved = true;  //This way the next command won't trigger the unsaved warning.
488     ui->optionsProfile->setCurrentIndex(0);
489     ui->sextractionProfile->setCurrentIndex(programSettings.value("sextractionProfile", 6).toInt());
490     ui->solverProfile->setCurrentIndex(programSettings.value("solverProfile", 7).toInt());
491 
492     QString storedPaths = programSettings.value("indexFolderPaths", "").toString();
493     QStringList indexFilePaths;
494     if(storedPaths == "")
495         indexFilePaths = temp.getDefaultIndexFolderPaths();
496     else
497         indexFilePaths = storedPaths.split(",");
498     foreach(QString pathName, indexFilePaths)
499         ui->indexFolderPaths->addItem(pathName);
500     loadIndexFilesList();
501 
502     ui->imageScrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
503     ui->imageScrollArea->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOn);
504     ui->imageScrollArea->verticalScrollBar()->setMinimum(0);
505     ui->imageScrollArea->verticalScrollBar()->setMaximum(5000);
506     ui->imageScrollArea->horizontalScrollBar()->setMinimum(0);
507     ui->imageScrollArea->horizontalScrollBar()->setMaximum(5000);
508     ui->imageScrollArea->verticalScrollBar()->setValue(2500);
509     ui->imageScrollArea->horizontalScrollBar()->setValue(2500);
510 }
511 
settingJustChanged()512 void MainWindow::settingJustChanged()
513 {
514     if(ui->optionsProfile->currentIndex() != 0 )
515         ui->optionsProfile->setCurrentIndex(0);
516     optionsAreSaved = false;
517 }
518 
reloadConvTable()519 void MainWindow::reloadConvTable()
520 {
521     if(convInspector && convInspector->isVisible())
522     {
523         convTable->clear();
524         Parameters params;
525         StellarSolver::createConvFilterFromFWHM(&params, ui->fwhm->value());
526         int size = sqrt(params.convFilter.size());
527         convTable->setRowCount(size);
528         convTable->setColumnCount(size);
529         int i = 0;
530         for(int r = 0; r < size; r++)
531         {
532             convTable->setRowHeight(r, 50);
533             for(int c = 0; c < size; c++)
534             {
535                 convTable->setColumnWidth(c, 50);
536                 QTableWidgetItem *newItem = new QTableWidgetItem(QString::number(params.convFilter.at(i), 'g', 4));
537                 double col = params.convFilter.at(i) * 255;
538                 QFont font = newItem->font();
539                 font.setPixelSize(10);
540                 newItem->setFont(font);
541                 newItem->setFlags(newItem->flags() ^ Qt::ItemIsEditable);
542                 newItem->setBackground(QBrush(QColor(col,col,col)));
543                 newItem->setForeground(QBrush(Qt::yellow));
544                 convTable->setItem(r,c,newItem);
545                 i++;
546             }
547         }
548         convInspector->resize(size* 50 + 60, size * 50 + 60);
549     }
550 }
551 
loadOptionsProfile()552 void MainWindow::loadOptionsProfile()
553 {
554     if(ui->optionsProfile->currentIndex() == 0)
555         return;
556 
557     SSolver::Parameters oldOptions = getSettingsFromUI();
558 
559     if( !optionsAreSaved )
560     {
561         if(QMessageBox::question(this, "Abort?",
562                                  "You made unsaved changes in the settings, do you really wish to overwrite them?") == QMessageBox::No)
563         {
564             ui->optionsProfile->setCurrentIndex(0);
565             return;
566         }
567         optionsAreSaved = true; //They just got overwritten
568     }
569 
570     SSolver::Parameters newOptions = optionsList.at(ui->optionsProfile->currentIndex() - 1);
571     QList<QWidget *> controls = ui->optionsBox->findChildren<QWidget *>();
572     foreach(QWidget *control, controls)
573         control->blockSignals(true);
574     sendSettingsToUI(newOptions);
575     foreach(QWidget *control, controls)
576         control->blockSignals(false);
577     reloadConvTable();
578 }
579 
~MainWindow()580 MainWindow::~MainWindow()
581 {
582     delete ui;
583 }
584 
585 //This method clears the stars and star displays
clearStars()586 void MainWindow::clearStars()
587 {
588     ui->starTable->clearContents();
589     ui->starTable->setRowCount(0);
590     ui->starTable->setColumnCount(0);
591     selectedStar = 0;
592     stars.clear();
593     updateImage();
594 }
595 
596 //This method clears the tables and displays when the user requests it.
clearResults()597 void MainWindow::clearResults()
598 {
599     ui->logDisplay->clear();
600     ui->resultsTable->clearContents();
601     ui->resultsTable->setRowCount(0);
602 }
603 
604 //These methods are for the logging of information to the textfield at the bottom of the window.
logOutput(QString text)605 void MainWindow::logOutput(QString text)
606 {
607     ui->logDisplay->append(text);
608     ui->logDisplay->show();
609 }
610 
toggleFullScreen()611 void MainWindow::toggleFullScreen()
612 {
613   if (isFullScreen())
614     showNormal();
615   else
616     showFullScreen();
617 }
618 
toggleLogDisplay()619 void MainWindow::toggleLogDisplay()
620 {
621   if (ui->tabWidget->isVisible())
622     ui->tabWidget->hide();
623   else
624     ui->tabWidget->show();
625 }
626 
helpPopup()627 void MainWindow::helpPopup()
628 {
629   QString helpMessage =
630     QString("<table>"
631             "<tr><td>Zoom In: </td><td>%1</td></tr>"
632             "<tr><td>Zoom Out: </td><td>%2</td></tr>\n"
633             "<tr><td>Pan Up: </td><td>%3</td></tr>\n"
634             "<tr><td>Pan Down: </td><td>%4</td></tr>\n"
635             "<tr><td>Pan Left: </td><td>%5</td></tr>\n"
636             "<tr><td>Pan Right: </td><td>%6</td></tr>\n"
637             "<tr><td>AutoScale: </td><td>%7</td></tr>\n"
638             "<tr><td>LoadImage: </td><td>%8</td></tr>\n"
639             "<tr><td>Quit: </td><td>%9</td></tr>\n"
640             "<tr><td>Toggle Log Display: </td><td>%10</td></tr>\n"
641             "<tr><td>Toggle Full Screen: </td><td>%11</td></tr>\n"
642             "<tr>Help: </td><td>%12</td></tr>\n"
643             "</table>"
644             )
645     .arg(QKeySequence(QKeySequence::ZoomIn).toString(QKeySequence::NativeText))
646     .arg(QKeySequence(QKeySequence::ZoomOut).toString(QKeySequence::NativeText))
647     .arg(QKeySequence(QKeySequence::MoveToPreviousLine).toString(QKeySequence::NativeText))
648     .arg(QKeySequence(QKeySequence::MoveToNextLine).toString(QKeySequence::NativeText))
649     .arg(QKeySequence(QKeySequence::MoveToPreviousChar).toString(QKeySequence::NativeText))
650     .arg(QKeySequence(QKeySequence::MoveToNextChar).toString(QKeySequence::NativeText))
651     .arg("Ctrl+0")
652     .arg(QKeySequence(QKeySequence::Open).toString(QKeySequence::NativeText))
653     .arg(QKeySequence(QKeySequence::Quit).toString(QKeySequence::NativeText))
654     .arg("Ctrl+l")
655     .arg(QKeySequence(QKeySequence::FullScreen).toString(QKeySequence::NativeText))
656     .arg(QKeySequence(QKeySequence::HelpContents).toString(QKeySequence::NativeText));
657 
658   QMessageBox *msgBox = new QMessageBox(this);
659   msgBox->setIcon( QMessageBox::Information );
660   msgBox->setText(helpMessage);
661   msgBox->setAttribute(Qt::WA_DeleteOnClose);
662   msgBox->setModal(false);
663   msgBox->show();
664 }
665 
setSubframe()666 void MainWindow::setSubframe()
667 {
668     if(useSubframe)
669     {
670         useSubframe = false;
671         ui->setSubFrame->setChecked(false);
672     }
673     else
674     {
675         settingSubframe = true;
676         ui->mouseInfo->setText("Please select your subframe now.");
677     }
678     updateImage();
679 }
680 
startProcessMonitor()681 void MainWindow::startProcessMonitor()
682 {
683     ui->status->setText(QString("Processing Trial %1").arg(currentTrial));
684     ui->progressBar->setRange(0, 0);
685     timerMonitor.start();
686     processTimer.start();
687 }
688 
stopProcessMonitor()689 void MainWindow::stopProcessMonitor()
690 {
691     timerMonitor.stop();
692     ui->progressBar->setRange(0, 10);
693     ui->status->setText("No Process Running");
694 }
695 
696 //I wrote this method because we really want to do this before all 4 processes
697 //It was getting repetitive.
prepareForProcesses()698 bool MainWindow::prepareForProcesses()
699 {
700     if(ui->vertSplitter->sizes().last() < 10)
701         ui->vertSplitter->setSizes(QList<int>() << ui->vertSplitter->height() / 2 << 100 );
702     ui->logDisplay->verticalScrollBar()->setValue(ui->logDisplay->verticalScrollBar()->maximum());
703 
704     if(!imageLoaded)
705     {
706         logOutput("Please Load an Image First");
707         return false;
708     }
709     if(stellarSolver != nullptr)
710     {
711         if(stellarSolver->isRunning())
712         {
713             const SSolver::ProcessType type = static_cast<SSolver::ProcessType>(stellarSolver->property("ProcessType").toInt());
714             if(type == SOLVE && !stellarSolver->solvingDone())
715             {
716                 if(QMessageBox::question(this, "Abort?", "StellarSolver is solving now. Abort it?") == QMessageBox::No)
717                     return false;
718             }
719             else if((type == EXTRACT || type == EXTRACT_WITH_HFR) && !sextractorComplete())
720             {
721                 if(QMessageBox::question(this, "Abort?", "StellarSolver is extracting sources now. Abort it?") == QMessageBox::No)
722                     return false;
723             }
724             stellarSolver->abort();
725         }
726     }
727 
728     numberOfTrials = ui->trials->value();
729     totalTime = 0;
730     currentTrial = 0;
731     lastSolution = FITSImage::Solution();
732     hasHFRData = false;
733 
734     return true;
735 }
736 
737 //I wrote this method to display the table after sextraction has occured.
displayTable()738 void MainWindow::displayTable()
739 {
740     sortStars();
741     updateStarTableFromList();
742 
743     if(ui->horSplitter->sizes().last() < 10)
744         ui->horSplitter->setSizes(QList<int>() << ui->optionsBox->width() << ui->horSplitter->width() / 2 << 200 );
745     updateImage();
746 }
747 
748 //This method is intended to load a list of the index files to display as feedback to the user.
loadIndexFilesList()749 void MainWindow::loadIndexFilesList()
750 {
751     QString currentPath = ui->indexFolderPaths->currentText();
752     QDir dir(currentPath);
753     ui->indexFiles->clear();
754     if(dir.exists())
755     {
756         dir.setNameFilters(QStringList() << "*.fits" << "*.fit");
757         if(dir.entryList().count() == 0)
758             ui->indexFiles->addItem("No index files in Folder");
759         ui->indexFiles->addItems(dir.entryList());
760     }
761     else
762         ui->indexFiles->addItem("Invalid Folder");
763 }
764 
765 
766 //The following methods are meant for starting the sextractor and image solving.
767 //The methods run when the buttons are clicked.  They call the methods inside StellarSolver and ExternalSextractorSovler
768 
769 //This method responds when the user clicks the Sextract Button
sextractButtonClicked()770 void MainWindow::sextractButtonClicked()
771 {
772     if(!prepareForProcesses())
773         return;
774 
775     int type = ui->sextractorTypeForSextraction->currentIndex();
776     switch(type)
777     {
778         case 0:
779             processType = EXTRACT;
780             m_ExtractorType = EXTRACTOR_INTERNAL;
781             break;
782         case 1:
783             processType = EXTRACT_WITH_HFR;
784             m_ExtractorType = EXTRACTOR_INTERNAL;
785             break;
786         case 2:
787             processType = EXTRACT;
788             m_ExtractorType = EXTRACTOR_EXTERNAL;
789             break;
790         case 3:
791             processType = EXTRACT_WITH_HFR;
792             m_ExtractorType = EXTRACTOR_EXTERNAL;
793             break;
794 
795     }
796     profileSelection = ui->sextractionProfile->currentIndex();
797     sextractImage();
798 }
799 
800 //This method responds when the user clicks the Sextract Button
solveButtonClicked()801 void MainWindow::solveButtonClicked()
802 {
803     if(!prepareForProcesses())
804         return;
805 
806     processType = SOLVE;
807     profileSelection = ui->solverProfile->currentIndex();
808 
809     solveImage();
810 }
811 
resetStellarSolver()812 void MainWindow::resetStellarSolver()
813 {
814     if(stellarSolver != nullptr)
815     {
816         auto *solver = stellarSolver.release();
817         solver->disconnect(this);
818         if(solver->isRunning())
819         {
820             solver->setLoadWCS(false);
821             connect(solver, &StellarSolver::finished, solver, &StellarSolver::deleteLater);
822             solver->abort();
823         }
824         else
825             solver->deleteLater();
826     }
827 
828     stellarSolver.reset(new StellarSolver(stats, m_ImageBuffer, this));
829     connect(stellarSolver.get(), &StellarSolver::logOutput, this, &MainWindow::logOutput);
830 }
831 
832 
sextractImage()833 void MainWindow::sextractImage()
834 {
835     //This makes sure the solver is done before starting another time
836     //That way the timer is accurate.
837     while(stellarSolver->isRunning())
838         qApp->processEvents();
839 
840     currentTrial++;
841     clearStars();
842 
843     //Since this tester uses it many times, it doesn't *need* to replace the stellarsolver every time
844     //resetStellarSolver();
845 
846     stellarSolver->setProperty("ExtractorType", m_ExtractorType);
847     stellarSolver->setProperty("ProcessType", processType);
848 
849     //These set the StellarSolver Parameters
850     if(profileSelection == 0)
851         stellarSolver->setParameters(getSettingsFromUI());
852     else
853         stellarSolver->setParameters(optionsList.at(profileSelection - 1));
854 
855 
856     setupExternalSextractorSolverIfNeeded();
857     setupStellarSolverParameters();
858 
859     if(useSubframe)
860         stellarSolver->setUseSubframe(subframe);
861     else
862         stellarSolver->clearSubFrame();
863 
864     connect(stellarSolver.get(), &StellarSolver::ready, this, &MainWindow::sextractorComplete);
865 
866     startProcessMonitor();
867     stellarSolver->start();
868 }
869 
870 //This method runs when the user clicks the Sextract and Solve buttton
solveImage()871 void MainWindow::solveImage()
872 {
873     //This makes sure the solver is done before starting another time
874     //That way the timer is accurate.
875     while(stellarSolver->isRunning())
876         qApp->processEvents();
877 
878     currentTrial++;
879 
880     if(hasWCSData)
881     {
882         hasWCSData = false;
883         delete [] wcs_coord;
884     }
885 
886     //Since this tester uses it many times, it doesn't *need* to replace the stellarsolver every time
887     //resetStellarSolver();
888 
889     m_ExtractorType = (SSolver::ExtractorType) ui->sextractorTypeForSolving->currentIndex();
890     solverType = (SSolver::SolverType) ui->solverType->currentIndex();
891 
892     stellarSolver->setProperty("ProcessType", processType);
893     stellarSolver->setProperty("ExtractorType", m_ExtractorType);
894     stellarSolver->setProperty("SolverType", solverType);
895 
896     //These set the StellarSolver Parameters
897     if(profileSelection == 0)
898         stellarSolver->setParameters(getSettingsFromUI());
899     else
900         stellarSolver->setParameters(optionsList.at(profileSelection - 1));
901 
902     setupStellarSolverParameters();
903     setupExternalSextractorSolverIfNeeded();
904 
905     stellarSolver->clearSubFrame();
906 
907     //Setting the initial search scale settings
908     if(ui->use_scale->isChecked())
909         stellarSolver->setSearchScale(ui->scale_low->text().toDouble(), ui->scale_high->text().toDouble(),
910                                       (SSolver::ScaleUnits)ui->units->currentIndex());
911     else
912         stellarSolver->setProperty("UseScale", false);
913     //Setting the initial search location settings
914     if(ui->use_position->isChecked())
915         stellarSolver->setSearchPositionRaDec(ui->ra->text().toDouble(), ui->dec->text().toDouble());
916     else
917         stellarSolver->setProperty("UsePosition", false);
918 
919     connect(stellarSolver.get(), &StellarSolver::ready, this, &MainWindow::solverComplete);
920     if(currentTrial >= numberOfTrials)
921     {
922         stellarSolver->setLoadWCS(true);
923         connect(stellarSolver.get(), &StellarSolver::wcsReady, this, &MainWindow::loadWCSComplete);
924     }
925     else
926         stellarSolver->setLoadWCS(false);
927 
928     startProcessMonitor();
929     stellarSolver->start();
930 }
931 
932 //This sets up the External Sextractor and Solver and sets settings specific to them
setupExternalSextractorSolverIfNeeded()933 void MainWindow::setupExternalSextractorSolverIfNeeded()
934 {
935     //External options
936     stellarSolver->setProperty("FileToProcess", fileToProcess);
937     stellarSolver->setProperty("BasePath", ui->basePath->text());
938     stellarSolver->setProperty("SextractorBinaryPath", ui->sextractorPath->text());
939     stellarSolver->setProperty("ConfPath", ui->configFilePath->text());
940     stellarSolver->setProperty("SolverPath", ui->solverPath->text());
941     stellarSolver->setProperty("ASTAPBinaryPath", ui->astapPath->text());
942     stellarSolver->setProperty("WCSPath", ui->wcsPath->text());
943     stellarSolver->setProperty("CleanupTemporaryFiles", ui->cleanupTemp->isChecked());
944     stellarSolver->setProperty("AutoGenerateAstroConfig", ui->generateAstrometryConfig->isChecked());
945     stellarSolver->setProperty("OnlySendFITSFiles", ui->onlySendFITSFiles->isChecked());
946 
947     //Online Options
948     stellarSolver->setProperty("BasePath",  ui->basePath->text());
949     stellarSolver->setProperty("AstrometryAPIKey", ui->apiKey->text());
950     stellarSolver->setProperty("AstrometryAPIURL", ui->onlineServer->text());
951 }
952 
setupStellarSolverParameters()953 void MainWindow::setupStellarSolverParameters()
954 {
955     //Index Folder Paths
956     QStringList indexFolderPaths;
957     for(int i = 0; i < ui->indexFolderPaths->count(); i++)
958     {
959         indexFolderPaths << ui->indexFolderPaths->itemText(i);
960     }
961     stellarSolver->setIndexFolderPaths(indexFolderPaths);
962 
963     //These setup Logging if desired
964     stellarSolver->setProperty("LogToFile", ui->logToFile->isChecked());
965     QString filename = ui->logFileName->text();
966     if(filename != "" && QFileInfo(filename).dir().exists() && !QFileInfo(filename).isDir())
967         stellarSolver->m_LogFileName=filename;
968     stellarSolver->setLogLevel((SSolver::logging_level)ui->logLevel->currentIndex());
969     stellarSolver->setSSLogLevel((SSolver::SSolverLogLevel)ui->stellarSolverLogLevel->currentIndex());
970 }
971 
972 //This sets all the settings for either the internal or external sextractor
973 //based on the requested settings in the mainwindow interface.
974 //If you are implementing the StellarSolver Library in your progra, you may choose to change some or all of these settings or use the defaults.
getSettingsFromUI()975 SSolver::Parameters MainWindow::getSettingsFromUI()
976 {
977     SSolver::Parameters params;
978     params.listName = "Custom";
979     params.description = ui->description->toPlainText();
980     //These are to pass the parameters to the internal sextractor
981     params.apertureShape = (SSolver::Shape) ui->apertureShape->currentIndex();
982     params.kron_fact = ui->kron_fact->text().toDouble();
983     params.subpix = ui->subpix->text().toInt() ;
984     params.r_min = ui->r_min->text().toFloat();
985     //params.inflags
986     params.magzero = ui->magzero->text().toFloat();
987     params.minarea = ui->minarea->text().toFloat();
988     params.deblend_thresh = ui->deblend_thresh->text().toInt();
989     params.deblend_contrast = ui->deblend_contrast->text().toFloat();
990     params.clean = (ui->cleanCheckBox->isChecked()) ? 1 : 0;
991     params.clean_param = ui->clean_param->text().toDouble();
992     StellarSolver::createConvFilterFromFWHM(&params, ui->fwhm->value());
993     params.partition = ui->partition->isChecked();
994 
995     //Star Filter Settings
996     params.resort = ui->resort->isChecked();
997     params.maxSize = ui->maxSize->text().toDouble();
998     params.minSize = ui->minSize->text().toDouble();
999     params.maxEllipse = ui->maxEllipse->text().toDouble();
1000     params.initialKeep = ui->initialKeep->text().toInt();
1001     params.keepNum = ui->keepNum->text().toInt();
1002     params.removeBrightest = ui->brightestPercent->text().toDouble();
1003     params.removeDimmest = ui->dimmestPercent->text().toDouble();
1004     params.saturationLimit = ui->saturationLimit->text().toDouble();
1005 
1006     //Settings that usually get set by the config file
1007 
1008     params.maxwidth = ui->maxWidth->text().toDouble();
1009     params.minwidth = ui->minWidth->text().toDouble();
1010     params.inParallel = ui->inParallel->isChecked();
1011     params.multiAlgorithm = (SSolver::MultiAlgo)ui->multiAlgo->currentIndex();
1012     params.solverTimeLimit = ui->solverTimeLimit->text().toInt();
1013 
1014     params.resort = ui->resort->isChecked();
1015     params.autoDownsample = ui->autoDown->isChecked();
1016     params.downsample = ui->downsample->value();
1017     params.search_radius = ui->radius->text().toDouble();
1018 
1019     //Setting the settings to know when to stop or keep searching for solutions
1020     params.logratio_tokeep = ui->oddsToKeep->text().toDouble();
1021     params.logratio_totune = ui->oddsToTune->text().toDouble();
1022     params.logratio_tosolve = ui->oddsToSolve->text().toDouble();
1023 
1024     return params;
1025 }
1026 
sendSettingsToUI(SSolver::Parameters a)1027 void MainWindow::sendSettingsToUI(SSolver::Parameters a)
1028 {
1029     ui->description->setText(a.description);
1030     //Sextractor Settings
1031 
1032     ui->apertureShape->setCurrentIndex(a.apertureShape);
1033     connect(ui->apertureShape, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &MainWindow::settingJustChanged);
1034     ui->kron_fact->setText(QString::number(a.kron_fact));
1035     ui->subpix->setText(QString::number(a.subpix));
1036     ui->r_min->setText(QString::number(a.r_min));
1037 
1038     ui->magzero->setText(QString::number(a.magzero));
1039     ui->minarea->setText(QString::number(a.minarea));
1040     ui->deblend_thresh->setText(QString::number(a.deblend_thresh));
1041     ui->deblend_contrast->setText(QString::number(a.deblend_contrast));
1042     ui->cleanCheckBox->setChecked(a.clean == 1);
1043     ui->clean_param->setText(QString::number(a.clean_param));
1044     ui->fwhm->setValue(a.fwhm);
1045     ui->partition->setChecked(a.partition);
1046 
1047     //Star Filter Settings
1048 
1049     ui->maxSize->setText(QString::number(a.maxSize));
1050     ui->minSize->setText(QString::number(a.minSize));
1051     ui->maxEllipse->setText(QString::number(a.maxEllipse));
1052     ui->initialKeep->setText(QString::number(a.initialKeep));
1053     ui->keepNum->setText(QString::number(a.keepNum));
1054     ui->brightestPercent->setText(QString::number(a.removeBrightest));
1055     ui->dimmestPercent->setText(QString::number(a.removeDimmest));
1056     ui->saturationLimit->setText(QString::number(a.saturationLimit));
1057 
1058     //Astrometry Settings
1059 
1060     ui->autoDown->setChecked(a.autoDownsample);
1061     ui->downsample->setValue(a.downsample);
1062     ui->inParallel->setChecked(a.inParallel);
1063     ui->multiAlgo->setCurrentIndex(a.multiAlgorithm);
1064     ui->solverTimeLimit->setText(QString::number(a.solverTimeLimit));
1065     ui->minWidth->setText(QString::number(a.minwidth));
1066     ui->maxWidth->setText(QString::number(a.maxwidth));
1067     ui->radius->setText(QString::number(a.search_radius));
1068     ui->resort->setChecked(a.resort);
1069 
1070     //Astrometry Log Ratio Settings
1071 
1072     ui->oddsToKeep->setText(QString::number(a.logratio_tokeep));
1073     ui->oddsToSolve->setText(QString::number(a.logratio_tosolve));
1074     ui->oddsToTune->setText(QString::number(a.logratio_totune));
1075 }
1076 
1077 
1078 //This runs when the sextractor is complete.
1079 //It reports the time taken, prints a message, loads the sextraction stars to the startable, and adds the sextraction stats to the results table.
sextractorComplete()1080 bool MainWindow::sextractorComplete()
1081 {
1082     elapsed = processTimer.elapsed() / 1000.0;
1083 
1084 
1085     disconnect(stellarSolver.get(), &StellarSolver::ready, this, &MainWindow::sextractorComplete);
1086 
1087     if(!stellarSolver->failed() && stellarSolver->sextractionDone())
1088     {
1089         totalTime += elapsed; //Only add to total time if it was successful
1090         stars = stellarSolver->getStarList();
1091         logOutput("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
1092         if(stellarSolver->isCalculatingHFR())
1093             logOutput(QString(stellarSolver->getCommandString() + " with HFR success! Got %1 stars").arg(stars.size()));
1094         else
1095             logOutput(QString(stellarSolver->getCommandString() + " success! Got %1 stars").arg(stars.size()));
1096         logOutput(QString("Sextraction took a total of: %1 second(s).").arg( elapsed));
1097         logOutput("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
1098         if(currentTrial < numberOfTrials)
1099         {
1100             sextractImage();
1101             return true;
1102         }
1103         stopProcessMonitor();
1104         hasHFRData = stellarSolver->isCalculatingHFR();
1105     }
1106     else
1107     {
1108         logOutput("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
1109         logOutput(QString(stellarSolver->getCommandString() + "failed after %1 second(s).").arg( elapsed));
1110         logOutput("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
1111         stopProcessMonitor();
1112         if(currentTrial == 1)
1113             return false;
1114         currentTrial--; //This solve was NOT successful so it should not be counted in the average.
1115     }
1116 
1117     emit readyForStarTable();
1118     ui->resultsTable->insertRow(ui->resultsTable->rowCount());
1119     addSextractionToTable();
1120     QTimer::singleShot(100, [this]()
1121     {
1122         ui->resultsTable->verticalScrollBar()->setValue(ui->resultsTable->verticalScrollBar()->maximum());
1123     });
1124     return true;
1125 }
1126 
1127 //This runs when the solver is complete.  It reports the time taken, prints a message, and adds the solution to the results table.
solverComplete()1128 bool MainWindow::solverComplete()
1129 {
1130     elapsed = processTimer.elapsed() / 1000.0;
1131 
1132     disconnect(stellarSolver.get(), &StellarSolver::ready, this, &MainWindow::solverComplete);
1133 
1134     if(!stellarSolver->failed() && stellarSolver->solvingDone())
1135     {
1136         totalTime += elapsed; //Only add to the total time if it was successful
1137         ui->progressBar->setRange(0, 10);
1138         logOutput("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
1139         logOutput(QString(stellarSolver->getCommandString() + " took a total of: %1 second(s).").arg( elapsed));
1140         logOutput("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
1141         lastSolution = stellarSolver->getSolution();
1142         if(currentTrial < numberOfTrials)
1143         {
1144             solveImage();
1145             return true;
1146         }
1147         stopProcessMonitor();
1148     }
1149     else
1150     {
1151         logOutput("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
1152         logOutput(QString(stellarSolver->getCommandString() + "failed after %1 second(s).").arg( elapsed));
1153         logOutput("+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++");
1154         stopProcessMonitor();
1155         if(currentTrial == 1)
1156             return false;
1157         currentTrial--; //This solve was NOT successful so it should not be counted in the average.
1158     }
1159 
1160     ui->resultsTable->insertRow(ui->resultsTable->rowCount());
1161     addSextractionToTable();
1162     if(stellarSolver->solvingDone())
1163         addSolutionToTable(stellarSolver->getSolution());
1164     else
1165         addSolutionToTable(lastSolution);
1166     QTimer::singleShot(100, [this]()
1167     {
1168         ui->resultsTable->verticalScrollBar()->setValue(ui->resultsTable->verticalScrollBar()->maximum());
1169     });
1170     return true;
1171 }
1172 
loadWCSComplete()1173 bool MainWindow::loadWCSComplete()
1174 {
1175     disconnect(stellarSolver.get(), &StellarSolver::wcsReady, this, &MainWindow::loadWCSComplete);
1176     FITSImage::wcs_point * coord = stellarSolver->getWCSCoord();
1177     if(coord)
1178     {
1179         hasWCSData = true;
1180         wcs_coord = coord;
1181         stars = stellarSolver->getStarList();
1182         hasHFRData = stellarSolver->isCalculatingHFR();
1183         if(stars.count() > 0)
1184             emit readyForStarTable();
1185         return true;
1186     }
1187     return false;
1188 }
1189 
1190 //This method will attempt to abort the sextractor, sovler, and any other processes currently being run, no matter which type
abort()1191 void MainWindow::abort()
1192 {
1193     numberOfTrials = currentTrial;
1194     if(stellarSolver != nullptr && stellarSolver->isRunning())
1195     {
1196         stellarSolver->abort();
1197         logOutput("Aborting Process. . .");
1198     }
1199     else
1200         logOutput("No Processes Running.");
1201 
1202 }
1203 
1204 //This method is meant to clear out the Astrometry settings that should change with each image
1205 //They might be loaded from a fits file, but if the file doesn't contain them, they should be cleared.
clearAstrometrySettings()1206 void MainWindow::clearAstrometrySettings()
1207 {
1208     //Note that due to connections, it automatically sets the variable as well.
1209     ui->use_scale->setChecked(false);
1210     ui->scale_low->setText("");
1211     ui->scale_high->setText("");
1212     ui->use_position->setChecked(false);
1213     ui->ra->setText("");
1214     ui->dec->setText("");
1215 }
1216 
1217 
1218 
1219 
1220 
1221 //The following methods deal with the loading and displaying of the image
1222 
1223 //I wrote this method to select the file name for the image and call the load methods below to load it
imageLoad()1224 bool MainWindow::imageLoad()
1225 {
1226     if(stellarSolver != nullptr && stellarSolver->isRunning())
1227     {
1228         QMessageBox::critical(nullptr, "Message", "A Process is currently running on the image, please wait until it is completed");
1229         return false;
1230     }
1231     QString fileURL = QFileDialog::getOpenFileName(nullptr, "Load Image", dirPath,
1232                       "Images (*.fits *.fit *.bmp *.gif *.jpg *.jpeg *.tif *.tiff)");
1233     if (fileURL.isEmpty())
1234         return false;
1235     QFileInfo fileInfo(fileURL);
1236     if(!fileInfo.exists())
1237         return false;
1238 
1239     QFileInfo newFileInfo(fileURL);
1240     dirPath = fileInfo.absolutePath();
1241     fileToProcess = fileURL;
1242 
1243     clearAstrometrySettings();
1244     if(stellarSolver != nullptr)
1245         disconnect(stellarSolver.get(), &StellarSolver::logOutput, this, &MainWindow::logOutput);
1246     if(hasWCSData)
1247     {
1248         delete [] wcs_coord;
1249         hasWCSData = false;
1250     }
1251 
1252     bool loadSuccess;
1253     if(newFileInfo.suffix() == "fits" || newFileInfo.suffix() == "fit")
1254         loadSuccess = loadFits();
1255     else
1256         loadSuccess = loadOtherFormat();
1257 
1258     if(loadSuccess)
1259     {
1260         imageLoaded = true;
1261         clearStars();
1262         ui->horSplitter->setSizes(QList<int>() << ui->optionsBox->width() << ui->horSplitter->width() << 0 );
1263         ui->fileNameDisplay->setText("Image: " + fileURL);
1264         initDisplayImage();
1265 
1266         resetStellarSolver();
1267         return true;
1268     }
1269     return false;
1270 }
1271 
1272 //This method was copied and pasted and modified from the method privateLoad in fitsdata in KStars
1273 //It loads a FITS file, reads the FITS Headers, and loads the data from the image
loadFits()1274 bool MainWindow::loadFits()
1275 {
1276 
1277     int status = 0, anynullptr = 0;
1278     long naxes[3];
1279     QString errMessage;
1280 
1281     // Use open diskfile as it does not use extended file names which has problems opening
1282     // files with [ ] or ( ) in their names.
1283     if (fits_open_diskfile(&fptr, fileToProcess.toLatin1(), READONLY, &status))
1284     {
1285         logOutput(QString("Error opening fits file %1").arg(fileToProcess));
1286         return false;
1287     }
1288     else
1289         stats.size = QFile(fileToProcess).size();
1290 
1291     if (fits_movabs_hdu(fptr, 1, IMAGE_HDU, &status))
1292     {
1293         logOutput(QString("Could not locate image HDU."));
1294         fits_close_file(fptr, &status);
1295         return false;
1296     }
1297 
1298     int fitsBitPix = 0;
1299     if (fits_get_img_param(fptr, 3, &fitsBitPix, &(stats.ndim), naxes, &status))
1300     {
1301         logOutput(QString("FITS file open error (fits_get_img_param)."));
1302         fits_close_file(fptr, &status);
1303         return false;
1304     }
1305 
1306     if (stats.ndim < 2)
1307     {
1308         errMessage = "1D FITS images are not supported.";
1309         QMessageBox::critical(nullptr, "Message", errMessage);
1310         logOutput(errMessage);
1311         fits_close_file(fptr, &status);
1312         return false;
1313     }
1314 
1315     switch (fitsBitPix)
1316     {
1317         case BYTE_IMG:
1318             stats.dataType      = SEP_TBYTE;
1319             stats.bytesPerPixel = sizeof(uint8_t);
1320             break;
1321         case SHORT_IMG:
1322             // Read SHORT image as USHORT
1323             stats.dataType      = TUSHORT;
1324             stats.bytesPerPixel = sizeof(int16_t);
1325             break;
1326         case USHORT_IMG:
1327             stats.dataType      = TUSHORT;
1328             stats.bytesPerPixel = sizeof(uint16_t);
1329             break;
1330         case LONG_IMG:
1331             // Read LONG image as ULONG
1332             stats.dataType      = TULONG;
1333             stats.bytesPerPixel = sizeof(int32_t);
1334             break;
1335         case ULONG_IMG:
1336             stats.dataType      = TULONG;
1337             stats.bytesPerPixel = sizeof(uint32_t);
1338             break;
1339         case FLOAT_IMG:
1340             stats.dataType      = TFLOAT;
1341             stats.bytesPerPixel = sizeof(float);
1342             break;
1343         case LONGLONG_IMG:
1344             stats.dataType      = TLONGLONG;
1345             stats.bytesPerPixel = sizeof(int64_t);
1346             break;
1347         case DOUBLE_IMG:
1348             stats.dataType      = TDOUBLE;
1349             stats.bytesPerPixel = sizeof(double);
1350             break;
1351         default:
1352             errMessage = QString("Bit depth %1 is not supported.").arg(fitsBitPix);
1353             QMessageBox::critical(nullptr, "Message", errMessage);
1354             logOutput(errMessage);
1355             fits_close_file(fptr, &status);
1356             return false;
1357     }
1358 
1359     if (stats.ndim < 3)
1360         naxes[2] = 1;
1361 
1362     if (naxes[0] == 0 || naxes[1] == 0)
1363     {
1364         errMessage = QString("Image has invalid dimensions %1x%2").arg(naxes[0]).arg(naxes[1]);
1365         QMessageBox::critical(nullptr, "Message", errMessage);
1366         logOutput(errMessage);
1367     }
1368 
1369     stats.width               = static_cast<uint16_t>(naxes[0]);
1370     stats.height              = static_cast<uint16_t>(naxes[1]);
1371     stats.channels            = static_cast<uint8_t>(naxes[2]);
1372     stats.samples_per_channel = stats.width * stats.height;
1373 
1374     clearImageBuffers();
1375 
1376 
1377 
1378     m_ImageBufferSize = stats.samples_per_channel * stats.channels * static_cast<uint16_t>(stats.bytesPerPixel);
1379     m_ImageBuffer = new uint8_t[m_ImageBufferSize];
1380     if (m_ImageBuffer == nullptr)
1381     {
1382         logOutput(QString("FITSData: Not enough memory for image_buffer channel. Requested: %1 bytes ").arg(m_ImageBufferSize));
1383         clearImageBuffers();
1384         fits_close_file(fptr, &status);
1385         return false;
1386     }
1387 
1388     long nelements = stats.samples_per_channel * stats.channels;
1389 
1390     if (fits_read_img(fptr, static_cast<uint16_t>(stats.dataType), 1, nelements, nullptr, m_ImageBuffer, &anynullptr, &status))
1391     {
1392         errMessage = "Error reading image.";
1393         QMessageBox::critical(nullptr, "Message", errMessage);
1394         logOutput(errMessage);
1395         fits_close_file(fptr, &status);
1396         return false;
1397     }
1398 
1399     if(checkDebayer())
1400         debayer();
1401 
1402     getSolverOptionsFromFITS();
1403 
1404     fits_close_file(fptr, &status);
1405 
1406     return true;
1407 }
1408 
1409 //This method I wrote combining code from the fits loading method above, the fits debayering method below, and QT
1410 //I also consulted the ImageToFITS method in fitsdata in KStars
1411 //The goal of this method is to load the data from a file that is not FITS format
loadOtherFormat()1412 bool MainWindow::loadOtherFormat()
1413 {
1414     QImageReader fileReader(fileToProcess.toLatin1());
1415 
1416     if (QImageReader::supportedImageFormats().contains(fileReader.format()) == false)
1417     {
1418         logOutput("Failed to convert" + fileToProcess + "to FITS since format, " + fileReader.format() +
1419                   ", is not supported in Qt");
1420         return false;
1421     }
1422 
1423     QString errMessage;
1424     QImage imageFromFile;
1425     if(!imageFromFile.load(fileToProcess.toLatin1()))
1426     {
1427         logOutput("Failed to open image.");
1428         return false;
1429     }
1430 
1431     imageFromFile = imageFromFile.convertToFormat(QImage::Format_RGB32);
1432 
1433     int fitsBitPix =
1434         8; //Note: This will need to be changed.  I think QT only loads 8 bpp images.  Also the depth method gives the total bits per pixel in the image not just the bits per pixel in each channel.
1435     switch (fitsBitPix)
1436     {
1437         case BYTE_IMG:
1438             stats.dataType      = SEP_TBYTE;
1439             stats.bytesPerPixel = sizeof(uint8_t);
1440             break;
1441         case SHORT_IMG:
1442             // Read SHORT image as USHORT
1443             stats.dataType      = TUSHORT;
1444             stats.bytesPerPixel = sizeof(int16_t);
1445             break;
1446         case USHORT_IMG:
1447             stats.dataType      = TUSHORT;
1448             stats.bytesPerPixel = sizeof(uint16_t);
1449             break;
1450         case LONG_IMG:
1451             // Read LONG image as ULONG
1452             stats.dataType      = TULONG;
1453             stats.bytesPerPixel = sizeof(int32_t);
1454             break;
1455         case ULONG_IMG:
1456             stats.dataType      = TULONG;
1457             stats.bytesPerPixel = sizeof(uint32_t);
1458             break;
1459         case FLOAT_IMG:
1460             stats.dataType      = TFLOAT;
1461             stats.bytesPerPixel = sizeof(float);
1462             break;
1463         case LONGLONG_IMG:
1464             stats.dataType      = TLONGLONG;
1465             stats.bytesPerPixel = sizeof(int64_t);
1466             break;
1467         case DOUBLE_IMG:
1468             stats.dataType      = TDOUBLE;
1469             stats.bytesPerPixel = sizeof(double);
1470             break;
1471         default:
1472             errMessage = QString("Bit depth %1 is not supported.").arg(fitsBitPix);
1473             QMessageBox::critical(nullptr, "Message", errMessage);
1474             logOutput(errMessage);
1475             return false;
1476     }
1477 
1478     stats.width = static_cast<uint16_t>(imageFromFile.width());
1479     stats.height = static_cast<uint16_t>(imageFromFile.height());
1480     stats.channels = 3;
1481     stats.samples_per_channel = stats.width * stats.height;
1482     clearImageBuffers();
1483     m_ImageBufferSize = stats.samples_per_channel * stats.channels * static_cast<uint16_t>(stats.bytesPerPixel);
1484     m_ImageBuffer = new uint8_t[m_ImageBufferSize];
1485     if (m_ImageBuffer == nullptr)
1486     {
1487         logOutput(QString("FITSData: Not enough memory for image_buffer channel. Requested: %1 bytes ").arg(m_ImageBufferSize));
1488         clearImageBuffers();
1489         return false;
1490     }
1491 
1492     auto debayered_buffer = reinterpret_cast<uint8_t *>(m_ImageBuffer);
1493     auto * original_bayered_buffer = reinterpret_cast<uint8_t *>(imageFromFile.bits());
1494 
1495     // Data in RGB32, with bytes in the order of B,G,R,A, we need to copy them into 3 layers for FITS
1496 
1497     uint8_t * rBuff = debayered_buffer;
1498     uint8_t * gBuff = debayered_buffer + (stats.width * stats.height);
1499     uint8_t * bBuff = debayered_buffer + (stats.width * stats.height * 2);
1500 
1501     int imax = stats.samples_per_channel * 4 - 4;
1502     for (int i = 0; i <= imax; i += 4)
1503     {
1504         *rBuff++ = original_bayered_buffer[i + 2];
1505         *gBuff++ = original_bayered_buffer[i + 1];
1506         *bBuff++ = original_bayered_buffer[i + 0];
1507     }
1508 
1509     return true;
1510 }
1511 
1512 //This method was copied and pasted from Fitsdata in KStars
1513 //It gets the bayer pattern information from the FITS header
checkDebayer()1514 bool MainWindow::checkDebayer()
1515 {
1516     int status = 0;
1517     char bayerPattern[64];
1518 
1519     // Let's search for BAYERPAT keyword, if it's not found we return as there is no bayer pattern in this image
1520     if (fits_read_keyword(fptr, "BAYERPAT", bayerPattern, nullptr, &status))
1521         return false;
1522 
1523     if (stats.dataType != TUSHORT && stats.dataType != SEP_TBYTE)
1524     {
1525         logOutput("Only 8 and 16 bits bayered images supported.");
1526         return false;
1527     }
1528     QString pattern(bayerPattern);
1529     pattern = pattern.remove('\'').trimmed();
1530 
1531     if (pattern == "RGGB")
1532         debayerParams.filter = DC1394_COLOR_FILTER_RGGB;
1533     else if (pattern == "GBRG")
1534         debayerParams.filter = DC1394_COLOR_FILTER_GBRG;
1535     else if (pattern == "GRBG")
1536         debayerParams.filter = DC1394_COLOR_FILTER_GRBG;
1537     else if (pattern == "BGGR")
1538         debayerParams.filter = DC1394_COLOR_FILTER_BGGR;
1539     // We return unless we find a valid pattern
1540     else
1541     {
1542         logOutput(QString("Unsupported bayer pattern %1.").arg(pattern));
1543         return false;
1544     }
1545 
1546     fits_read_key(fptr, TINT, "XBAYROFF", &debayerParams.offsetX, nullptr, &status);
1547     fits_read_key(fptr, TINT, "YBAYROFF", &debayerParams.offsetY, nullptr, &status);
1548 
1549     //HasDebayer = true;
1550 
1551     return true;
1552 }
1553 
1554 //This method was copied and pasted from Fitsdata in KStars
1555 //It debayers the image using the methods below
debayer()1556 bool MainWindow::debayer()
1557 {
1558     switch (stats.dataType)
1559     {
1560         case SEP_TBYTE:
1561             return debayer_8bit();
1562 
1563         case TUSHORT:
1564             return debayer_16bit();
1565 
1566         default:
1567             return false;
1568     }
1569 }
1570 
1571 //This method was copied and pasted from Fitsdata in KStars
1572 //This method debayers 8 bit images
debayer_8bit()1573 bool MainWindow::debayer_8bit()
1574 {
1575     dc1394error_t error_code;
1576 
1577     uint32_t rgb_size = stats.samples_per_channel * 3 * stats.bytesPerPixel;
1578     auto * destinationBuffer = new uint8_t[rgb_size];
1579 
1580     auto * bayer_source_buffer      = reinterpret_cast<uint8_t *>(m_ImageBuffer);
1581     auto * bayer_destination_buffer = reinterpret_cast<uint8_t *>(destinationBuffer);
1582 
1583     if (bayer_destination_buffer == nullptr)
1584     {
1585         logOutput("Unable to allocate memory for temporary bayer buffer.");
1586         return false;
1587     }
1588 
1589     int ds1394_height = stats.height;
1590     auto dc1394_source = bayer_source_buffer;
1591 
1592     if (debayerParams.offsetY == 1)
1593     {
1594         dc1394_source += stats.width;
1595         ds1394_height--;
1596     }
1597 
1598     if (debayerParams.offsetX == 1)
1599     {
1600         dc1394_source++;
1601     }
1602 
1603     error_code = dc1394_bayer_decoding_8bit(dc1394_source, bayer_destination_buffer, stats.width, ds1394_height,
1604                                             debayerParams.filter,
1605                                             debayerParams.method);
1606 
1607     if (error_code != DC1394_SUCCESS)
1608     {
1609         logOutput(QString("Debayer failed (%1)").arg(error_code));
1610         stats.channels = 1;
1611         delete[] destinationBuffer;
1612         return false;
1613     }
1614 
1615     if (m_ImageBufferSize != rgb_size)
1616     {
1617         delete[] m_ImageBuffer;
1618         m_ImageBuffer = new uint8_t[rgb_size];
1619 
1620         if (m_ImageBuffer == nullptr)
1621         {
1622             delete[] destinationBuffer;
1623             logOutput("Unable to allocate memory for temporary bayer buffer.");
1624             return false;
1625         }
1626 
1627         m_ImageBufferSize = rgb_size;
1628     }
1629 
1630     auto bayered_buffer = reinterpret_cast<uint8_t *>(m_ImageBuffer);
1631 
1632     // Data in R1G1B1, we need to copy them into 3 layers for FITS
1633 
1634     uint8_t * rBuff = bayered_buffer;
1635     uint8_t * gBuff = bayered_buffer + (stats.width * stats.height);
1636     uint8_t * bBuff = bayered_buffer + (stats.width * stats.height * 2);
1637 
1638     int imax = stats.samples_per_channel * 3 - 3;
1639     for (int i = 0; i <= imax; i += 3)
1640     {
1641         *rBuff++ = bayer_destination_buffer[i];
1642         *gBuff++ = bayer_destination_buffer[i + 1];
1643         *bBuff++ = bayer_destination_buffer[i + 2];
1644     }
1645 
1646     delete[] destinationBuffer;
1647     return true;
1648 }
1649 
1650 //This method was copied and pasted from Fitsdata in KStars
1651 //This method debayers 16 bit images
debayer_16bit()1652 bool MainWindow::debayer_16bit()
1653 {
1654     dc1394error_t error_code;
1655 
1656     uint32_t rgb_size = stats.samples_per_channel * 3 * stats.bytesPerPixel;
1657     auto * destinationBuffer = new uint8_t[rgb_size];
1658 
1659     auto * bayer_source_buffer      = reinterpret_cast<uint16_t *>(m_ImageBuffer);
1660     auto * bayer_destination_buffer = reinterpret_cast<uint16_t *>(destinationBuffer);
1661 
1662     if (bayer_destination_buffer == nullptr)
1663     {
1664         logOutput("Unable to allocate memory for temporary bayer buffer.");
1665         return false;
1666     }
1667 
1668     int ds1394_height = stats.height;
1669     auto dc1394_source = bayer_source_buffer;
1670 
1671     if (debayerParams.offsetY == 1)
1672     {
1673         dc1394_source += stats.width;
1674         ds1394_height--;
1675     }
1676 
1677     if (debayerParams.offsetX == 1)
1678     {
1679         dc1394_source++;
1680     }
1681 
1682     error_code = dc1394_bayer_decoding_16bit(dc1394_source, bayer_destination_buffer, stats.width, ds1394_height,
1683                  debayerParams.filter,
1684                  debayerParams.method, 16);
1685 
1686     if (error_code != DC1394_SUCCESS)
1687     {
1688         logOutput(QString("Debayer failed (%1)").arg(error_code));
1689         stats.channels = 1;
1690         delete[] destinationBuffer;
1691         return false;
1692     }
1693 
1694     if (m_ImageBufferSize != rgb_size)
1695     {
1696         delete[] m_ImageBuffer;
1697         m_ImageBuffer = new uint8_t[rgb_size];
1698 
1699         if (m_ImageBuffer == nullptr)
1700         {
1701             delete[] destinationBuffer;
1702             logOutput("Unable to allocate memory for temporary bayer buffer.");
1703             return false;
1704         }
1705 
1706         m_ImageBufferSize = rgb_size;
1707     }
1708 
1709     auto bayered_buffer = reinterpret_cast<uint16_t *>(m_ImageBuffer);
1710 
1711     // Data in R1G1B1, we need to copy them into 3 layers for FITS
1712 
1713     uint16_t * rBuff = bayered_buffer;
1714     uint16_t * gBuff = bayered_buffer + (stats.width * stats.height);
1715     uint16_t * bBuff = bayered_buffer + (stats.width * stats.height * 2);
1716 
1717     int imax = stats.samples_per_channel * 3 - 3;
1718     for (int i = 0; i <= imax; i += 3)
1719     {
1720         *rBuff++ = bayer_destination_buffer[i];
1721         *gBuff++ = bayer_destination_buffer[i + 1];
1722         *bBuff++ = bayer_destination_buffer[i + 2];
1723     }
1724 
1725     delete[] destinationBuffer;
1726     return true;
1727 }
1728 
1729 //This method was copied and pasted from Fitsview in KStars
1730 //It sets up the image that will be displayed on the screen
initDisplayImage()1731 void MainWindow::initDisplayImage()
1732 {
1733     // Account for leftover when sampling. Thus a 5-wide image sampled by 2
1734     // would result in a width of 3 (samples 0, 2 and 4).
1735 
1736     int w = (stats.width + sampling - 1) / sampling;
1737     int h = (stats.height + sampling - 1) / sampling;
1738 
1739     if (stats.channels == 1)
1740     {
1741         rawImage = QImage(w, h, QImage::Format_Indexed8);
1742 
1743         rawImage.setColorCount(256);
1744         for (int i = 0; i < 256; i++)
1745             rawImage.setColor(i, qRgb(i, i, i));
1746     }
1747     else
1748     {
1749         rawImage = QImage(w, h, QImage::Format_RGB32);
1750     }
1751     doStretch(&rawImage);
1752     autoScale();
1753 
1754 }
1755 
adjustScrollBar(QScrollBar * scrollBar,double factor)1756 void adjustScrollBar(QScrollBar *scrollBar, double factor)
1757 {
1758     scrollBar->setValue(int(factor * scrollBar->value()
1759                             + ((factor - 1) * scrollBar->pageStep()/2)));
1760 }
1761 
slideScrollBar(QScrollBar * scrollBar,double factor)1762 void slideScrollBar(QScrollBar *scrollBar, double factor)
1763 {
1764   constexpr double panFactor = 10.0;
1765   const int newValue = std::min(scrollBar->maximum(),
1766                                 std::max(0, (int) (scrollBar->value() + factor * scrollBar->pageStep()/panFactor)));
1767   scrollBar->setValue(newValue);
1768 }
1769 
panLeft()1770 void MainWindow::panLeft()
1771 {
1772   if(!imageLoaded)
1773     return;
1774   slideScrollBar(ui->imageScrollArea->horizontalScrollBar(), -1);
1775 }
1776 
panRight()1777 void MainWindow::panRight()
1778 {
1779   if(!imageLoaded)
1780     return;
1781   slideScrollBar(ui->imageScrollArea->horizontalScrollBar(), 1);
1782 }
1783 
panUp()1784 void MainWindow::panUp()
1785 {
1786   if(!imageLoaded)
1787     return;
1788   slideScrollBar(ui->imageScrollArea->verticalScrollBar(), 1);
1789 }
1790 
panDown()1791 void MainWindow::panDown()
1792 {
1793   if(!imageLoaded)
1794     return;
1795   slideScrollBar(ui->imageScrollArea->verticalScrollBar(), -1);
1796 }
1797 
1798 //This method reacts when the user clicks the zoom in button
zoomIn()1799 void MainWindow::zoomIn()
1800 {
1801     if(!imageLoaded)
1802         return;
1803     constexpr double zoomFactor = 1.5;
1804     currentZoom *= zoomFactor;
1805     adjustScrollBar(ui->imageScrollArea->verticalScrollBar(), zoomFactor);
1806     adjustScrollBar(ui->imageScrollArea->horizontalScrollBar(), zoomFactor);
1807     updateImage();
1808 }
1809 
1810 //This method reacts when the user clicks the zoom out button
zoomOut()1811 void MainWindow::zoomOut()
1812 {
1813     if(!imageLoaded)
1814         return;
1815 
1816     constexpr double zoomFactor = 1.5;
1817     currentZoom /= zoomFactor;
1818     adjustScrollBar(ui->imageScrollArea->verticalScrollBar(), 1.0/zoomFactor);
1819     adjustScrollBar(ui->imageScrollArea->horizontalScrollBar(), 1.0/zoomFactor);
1820     updateImage();
1821 }
1822 
1823 //This code was copied and pasted and modified from rescale in Fitsview in KStars
1824 //This method reacts when the user clicks the autoscale button and is called when the image is first loaded.
autoScale()1825 void MainWindow::autoScale()
1826 {
1827     if(!imageLoaded)
1828         return;
1829 
1830     int w = (stats.width + sampling - 1) / sampling;
1831     int h = (stats.height + sampling - 1) / sampling;
1832 
1833     double width = ui->imageScrollArea->rect().width();
1834     double height = ui->imageScrollArea->rect().height() - 30; //The minus 30 is due to the image filepath label
1835 
1836     // Find the zoom level which will enclose the current FITS in the current window size
1837     double zoomX                  = ( width / w);
1838     double zoomY                  = ( height / h);
1839     (zoomX < zoomY) ? currentZoom = zoomX : currentZoom = zoomY;
1840 
1841     updateImage();
1842 }
1843 
1844 
1845 //This method is intended to get the position and size of the star for rendering purposes
1846 //It is used to draw circles/ellipses for the stars and to detect when the mouse is over a star
getStarSizeInImage(FITSImage::Star star,bool & accurate)1847 QRect MainWindow::getStarSizeInImage(FITSImage::Star star, bool &accurate)
1848 {
1849     accurate = true;
1850     double width = 0;
1851     double height = 0;
1852     double a = star.a;
1853     double b = star.b;
1854     double HFR = star.HFR;
1855     int starOption = ui->starOptions->currentIndex();
1856 
1857     //This is so that the star will appear on the screen even though it has invalid size info.
1858     //The External Astrometry.net solver does not report star sizes, nor does the online solver.
1859     if((a <= 0 || b <= 0) && (starOption == 0 || starOption == 1))
1860     {
1861         a = 5;
1862         b = 5;
1863         accurate = false;
1864     }
1865     if((HFR <= 0) && (starOption == 2 || starOption == 3))
1866     {
1867         HFR = 5;
1868         accurate = false;
1869     }
1870 
1871     switch(starOption)
1872     {
1873         case 0: //Ellipse from Sextraction
1874             width = 2 * a ;
1875             height = 2 * b;
1876             break;
1877 
1878         case 1: //Circle from Sextraction
1879         {
1880             double size = 2 * sqrt( pow(a, 2) + pow(b, 2) );
1881             width = size;
1882             height = size;
1883         }
1884         break;
1885 
1886         case 2: //HFD Size, based on HFR, 2 x radius is the diameter
1887             width = 2 * HFR;
1888             height = 2 * HFR;
1889             break;
1890 
1891         case 3: //2 x HFD size, based on HFR, 4 x radius is 2 x the diameter
1892             width = 4 * HFR;
1893             height = 4 * HFR;
1894             break;
1895     }
1896 
1897     double starx = star.x * currentWidth / stats.width ;
1898     double stary = star.y * currentHeight / stats.height;
1899     double starw = width * currentWidth / stats.width;
1900     double starh = height * currentHeight / stats.height;
1901     return QRect(starx - starw, stary - starh, starw * 2, starh * 2);
1902 }
1903 
1904 //This method is very loosely based on updateFrame in Fitsview in Kstars
1905 //It will redraw the image when the user loads an image, zooms in, zooms out, or autoscales
1906 //It will also redraw the image when a change needs to be made in how the circles for the stars are displayed such as highlighting one star
updateImage()1907 void MainWindow::updateImage()
1908 {
1909     if(!imageLoaded || rawImage.isNull())
1910         return;
1911 
1912     int w = (stats.width + sampling - 1) / sampling;
1913     int h = (stats.height + sampling - 1) / sampling;
1914     currentWidth  = static_cast<int> (w * (currentZoom));
1915     currentHeight = static_cast<int> (h * (currentZoom));
1916 
1917     scaledImage = rawImage.scaled(currentWidth, currentHeight, Qt::KeepAspectRatio, Qt::SmoothTransformation);
1918     QPixmap renderedImage = QPixmap::fromImage(scaledImage);
1919     if(ui->showStars->isChecked())
1920     {
1921         QPainter p(&renderedImage);
1922         for(int starnum = 0 ; starnum < stars.size() ; starnum++)
1923         {
1924             FITSImage::Star star = stars.at(starnum);
1925             bool accurate;
1926             QRect starInImage = getStarSizeInImage(star, accurate);
1927             p.save();
1928             p.translate(starInImage.center());
1929             p.rotate(star.theta);
1930             p.translate(-starInImage.center());
1931 
1932             if(starnum == selectedStar)
1933             {
1934                 QPen highlighter(QColor("yellow"));
1935                 highlighter.setWidth(4);
1936                 p.setPen(highlighter);
1937                 p.setOpacity(1);
1938             }
1939             else
1940             {
1941                 if(accurate)
1942                 {
1943                     QPen highlighter(QColor("green"));
1944                     highlighter.setWidth(2);
1945                     p.setPen(highlighter);
1946                     p.setOpacity(1);
1947                 }
1948                 else
1949                 {
1950                     p.setPen(QColor("red"));
1951                     p.setOpacity(0.6);
1952                 }
1953             }
1954             p.drawEllipse(starInImage);
1955             p.restore();
1956         }
1957         if(useSubframe)
1958         {
1959             QPen highlighter(QColor("green"));
1960             highlighter.setWidth(2);
1961             p.setPen(highlighter);
1962             p.setOpacity(1);
1963             double x = subframe.x() * currentWidth / stats.width ;
1964             double y = subframe.y() * currentHeight / stats.height;
1965             double w = subframe.width() * currentWidth / stats.width;
1966             double h = subframe.height() * currentHeight / stats.height;
1967             p.drawRect(QRect(x, y, w, h));
1968         }
1969         p.end();
1970     }
1971 
1972     ui->Image->setPixmap(renderedImage);
1973     ui->Image->setFixedSize(renderedImage.size());
1974 }
1975 
1976 //This code is copied and pasted from FITSView in KStars
1977 //It handles the stretch of the image
doStretch(QImage * outputImage)1978 void MainWindow::doStretch(QImage *outputImage)
1979 {
1980     if (outputImage->isNull())
1981         return;
1982     Stretch stretch(static_cast<int>(stats.width),
1983                     static_cast<int>(stats.height),
1984                     stats.channels, static_cast<uint16_t>(stats.dataType));
1985 
1986     // Compute new auto-stretch params.
1987     stretchParams = stretch.computeParams(m_ImageBuffer);
1988 
1989     stretch.setParams(stretchParams);
1990     stretch.run(m_ImageBuffer, outputImage, sampling);
1991 }
1992 
1993 //This method was copied and pasted from Fitsdata in KStars
1994 //It clears the image buffer out.
clearImageBuffers()1995 void MainWindow::clearImageBuffers()
1996 {
1997     delete[] m_ImageBuffer;
1998     m_ImageBuffer = nullptr;
1999     //m_BayerBuffer = nullptr;
2000 }
2001 
2002 //I wrote this method to respond when the user's mouse is over a star
2003 //It displays details about that particular star in a tooltip and highlights it in the image
2004 //It also displays the x and y position of the mouse in the image and the pixel value at that spot now.
mouseMovedOverImage(QPoint location)2005 void MainWindow::mouseMovedOverImage(QPoint location)
2006 {
2007     if(imageLoaded)
2008     {
2009         double x = location.x() * stats.width / currentWidth;
2010         double y = location.y() * stats.height / currentHeight;
2011 
2012         if(x < 0)
2013             x = 0;
2014         if(y < 0)
2015             y = 0;
2016         if(x > stats.width)
2017             x = stats.width;
2018         if(y > stats.height)
2019             y = stats.height;
2020 
2021         if(settingSubframe)
2022         {
2023             int subX = subframe.x();
2024             int subY = subframe.y();
2025             int w = x - subX;
2026             int h = y - subY;
2027             subframe = QRect(subX, subY, w, h);
2028             updateImage();
2029         }
2030 
2031         QString mouseText = "";
2032         if(hasWCSData)
2033         {
2034             int index = x + y * stats.width;
2035             mouseText = QString("RA: %1, DEC: %2, Value: %3").arg(StellarSolver::raString(wcs_coord[index].ra)).arg(
2036                             StellarSolver::decString(wcs_coord[index].dec)).arg(getValue(x, y));
2037         }
2038         else
2039             mouseText = QString("X: %1, Y: %2, Value: %3").arg(x).arg(y).arg(getValue(x, y));
2040         if(useSubframe)
2041             mouseText = mouseText + QString(", Subframe X: %1, Y: %2, W: %3, H: %4").arg(subframe.x()).arg(subframe.y()).arg(
2042                             subframe.width()).arg(subframe.height());
2043         ui->mouseInfo->setText(mouseText);
2044 
2045         bool starFound = false;
2046         for(int i = 0 ; i < stars.size() ; i ++)
2047         {
2048             FITSImage::Star star = stars.at(i);
2049             bool accurate;
2050             QRect starInImage = getStarSizeInImage(star, accurate);
2051             if(starInImage.contains(location))
2052             {
2053                 QString text = QString("Star: %1, x: %2, y: %3\nmag: %4, flux: %5, peak:%6").arg(i + 1).arg(star.x).arg(star.y).arg(
2054                                    star.mag).arg(star.flux).arg(star.peak);
2055                 if(hasHFRData)
2056                     text += ", " + QString("HFR: %1").arg(star.HFR);
2057                 if(hasWCSData)
2058                     text += "\n" + QString("RA: %1, DEC: %2").arg(StellarSolver::raString(star.ra)).arg(StellarSolver::decString(star.dec));
2059                 QToolTip::showText(QCursor::pos(), text, ui->Image);
2060                 selectedStar = i;
2061                 starFound = true;
2062                 updateImage();
2063             }
2064         }
2065         if(!starFound)
2066             QToolTip::hideText();
2067     }
2068 }
2069 
2070 
2071 
2072 //This function is based upon code in the method mouseMoveEvent in FITSLabel in KStars
2073 //It is meant to get the value from the image buffer at a particular pixel location for the display
2074 //when the mouse is over a certain pixel
getValue(int x,int y)2075 QString MainWindow::getValue(int x, int y)
2076 {
2077     if (m_ImageBuffer == nullptr)
2078         return "";
2079 
2080     int index = y * stats.width + x;
2081     QString stringValue;
2082 
2083     switch (stats.dataType)
2084     {
2085         case TBYTE:
2086             stringValue = QLocale().toString(m_ImageBuffer[index]);
2087             break;
2088 
2089         case TSHORT:
2090             stringValue = QLocale().toString((reinterpret_cast<int16_t *>(m_ImageBuffer))[index]);
2091             break;
2092 
2093         case TUSHORT:
2094             stringValue = QLocale().toString((reinterpret_cast<uint16_t *>(m_ImageBuffer))[index]);
2095             break;
2096 
2097         case TLONG:
2098             stringValue = QLocale().toString((reinterpret_cast<int32_t *>(m_ImageBuffer))[index]);
2099             break;
2100 
2101         case TULONG:
2102             stringValue = QLocale().toString((reinterpret_cast<uint32_t *>(m_ImageBuffer))[index]);
2103             break;
2104 
2105         case TFLOAT:
2106             stringValue = QLocale().toString((reinterpret_cast<float *>(m_ImageBuffer))[index], 'f', 5);
2107             break;
2108 
2109         case TLONGLONG:
2110             stringValue = QLocale().toString(static_cast<int>((reinterpret_cast<int64_t *>(m_ImageBuffer))[index]));
2111             break;
2112 
2113         case TDOUBLE:
2114             stringValue = QLocale().toString((reinterpret_cast<float *>(m_ImageBuffer))[index], 'f', 5);
2115 
2116             break;
2117 
2118         default:
2119             break;
2120     }
2121     return stringValue;
2122 }
2123 
2124 //I wrote the is method to respond when the user clicks on a star
2125 //It highlights the row in the star table that corresponds to that star
mouseClickedInImage(QPoint location)2126 void MainWindow::mouseClickedInImage(QPoint location)
2127 {
2128     if(settingSubframe)
2129         settingSubframe = false;
2130 
2131     for(int i = 0 ; i < stars.size() ; i ++)
2132     {
2133         bool accurate;
2134         QRect starInImage = getStarSizeInImage(stars.at(i), accurate);
2135         if(starInImage.contains(location))
2136             ui->starTable->selectRow(i);
2137     }
2138 }
2139 
mousePressedInImage(QPoint location)2140 void MainWindow::mousePressedInImage(QPoint location)
2141 {
2142     if(settingSubframe)
2143     {
2144         if(!useSubframe)
2145         {
2146             useSubframe = true;
2147             ui->setSubFrame->setChecked(true);
2148             double x = location.x() * stats.width / currentWidth;
2149             double y = location.y() * stats.height / currentHeight;
2150             subframe = QRect(x, y, 0, 0);
2151         }
2152     }
2153 }
2154 
2155 
2156 //THis method responds to row selections in the table and higlights the star you select in the image
starClickedInTable()2157 void MainWindow::starClickedInTable()
2158 {
2159     if(ui->starTable->selectedItems().count() > 0)
2160     {
2161         QTableWidgetItem *currentItem = ui->starTable->selectedItems().first();
2162         selectedStar = ui->starTable->row(currentItem);
2163         FITSImage::Star star = stars.at(selectedStar);
2164         double starx = star.x * currentWidth / stats.width ;
2165         double stary = star.y * currentHeight / stats.height;
2166         updateImage();
2167         ui->imageScrollArea->ensureVisible(starx, stary);
2168     }
2169 }
2170 
2171 //This sorts the stars by magnitude for display purposes
sortStars()2172 void MainWindow::sortStars()
2173 {
2174     if(stars.size() > 1)
2175     {
2176         //Note that a star is dimmer when the mag is greater!
2177         //We want to sort in decreasing order though!
2178         std::sort(stars.begin(), stars.end(), [](const FITSImage::Star & s1, const FITSImage::Star & s2)
2179         {
2180             return s1.mag < s2.mag;
2181         });
2182     }
2183 }
2184 
2185 //This is a helper function that I wrote for the methods below
2186 //It add a column with a particular name to the specified table
addColumnToTable(QTableWidget * table,QString heading)2187 void addColumnToTable(QTableWidget *table, QString heading)
2188 {
2189     int colNum = table->columnCount();
2190     table->insertColumn(colNum);
2191     table->setHorizontalHeaderItem(colNum, new QTableWidgetItem(heading));
2192 }
2193 
2194 //This is a method I wrote to hide the desired columns in a table based on their name
setColumnHidden(QTableWidget * table,QString colName,bool hidden)2195 void setColumnHidden(QTableWidget *table, QString colName, bool hidden)
2196 {
2197     for(int c = 0; c < table->columnCount() ; c ++)
2198     {
2199         if(table->horizontalHeaderItem(c)->text() == colName)
2200             table->setColumnHidden(c, hidden);
2201     }
2202 }
2203 
2204 //This is a helper function that I wrote for the methods below
2205 //It sets the value of a cell in the column of the specified name in the last row in the table
setItemInColumn(QTableWidget * table,QString colName,QString value)2206 bool setItemInColumn(QTableWidget *table, QString colName, QString value)
2207 {
2208     int row = table->rowCount() - 1;
2209     for(int c = 0; c < table->columnCount() ; c ++)
2210     {
2211         if(table->horizontalHeaderItem(c)->text() == colName)
2212         {
2213             table->setItem(row, c, new QTableWidgetItem(value));
2214             return true;
2215         }
2216     }
2217     return false;
2218 }
2219 
2220 //This copies the stars into the table
updateStarTableFromList()2221 void MainWindow::updateStarTableFromList()
2222 {
2223     QTableWidget *table = ui->starTable;
2224     table->clearContents();
2225     table->setRowCount(0);
2226     table->setColumnCount(0);
2227     selectedStar = 0;
2228     addColumnToTable(table, "MAG_AUTO");
2229     addColumnToTable(table, "RA (J2000)");
2230     addColumnToTable(table, "DEC (J2000)");
2231     addColumnToTable(table, "X_IMAGE");
2232     addColumnToTable(table, "Y_IMAGE");
2233 
2234 
2235     addColumnToTable(table, "FLUX_AUTO");
2236     addColumnToTable(table, "PEAK");
2237     if(hasHFRData)
2238         addColumnToTable(table, "HFR");
2239 
2240     addColumnToTable(table, "a");
2241     addColumnToTable(table, "b");
2242     addColumnToTable(table, "theta");
2243 
2244     for(int i = 0; i < stars.size(); i ++)
2245     {
2246         table->insertRow(table->rowCount());
2247         FITSImage::Star star = stars.at(i);
2248 
2249         setItemInColumn(table, "MAG_AUTO", QString::number(star.mag));
2250         setItemInColumn(table, "X_IMAGE", QString::number(star.x));
2251         setItemInColumn(table, "Y_IMAGE", QString::number(star.y));
2252         if(hasWCSData)
2253         {
2254             setItemInColumn(table, "RA (J2000)", StellarSolver::raString(star.ra));
2255             setItemInColumn(table, "DEC (J2000)", StellarSolver::decString(star.dec));
2256         }
2257 
2258         setItemInColumn(table, "FLUX_AUTO", QString::number(star.flux));
2259         setItemInColumn(table, "PEAK", QString::number(star.peak));
2260         if(hasHFRData)
2261             setItemInColumn(table, "HFR", QString::number(star.HFR));
2262 
2263         setItemInColumn(table, "a", QString::number(star.a));
2264         setItemInColumn(table, "b", QString::number(star.b));
2265         setItemInColumn(table, "theta", QString::number(star.theta));
2266     }
2267     updateHiddenStarTableColumns();
2268 }
2269 
updateHiddenStarTableColumns()2270 void MainWindow::updateHiddenStarTableColumns()
2271 {
2272     QTableWidget *table = ui->starTable;
2273 
2274     setColumnHidden(table, "FLUX_AUTO", !showFluxInfo);
2275     setColumnHidden(table, "PEAK", !showFluxInfo);
2276     setColumnHidden(table, "RA (J2000)", !hasWCSData);
2277     setColumnHidden(table, "DEC (J2000)", !hasWCSData);
2278     setColumnHidden(table, "a", !showStarShapeInfo);
2279     setColumnHidden(table, "b", !showStarShapeInfo);
2280     setColumnHidden(table, "theta", !showStarShapeInfo);
2281 }
2282 
2283 //This method is copied and pasted and modified from getSolverOptionsFromFITS in Align in KStars
2284 //Then it was split in two parts, the other part was sent to the ExternalSextractorSolver class since the internal solver doesn't need it
2285 //This part extracts the options from the FITS file and prepares them for use by the internal or external solver
getSolverOptionsFromFITS()2286 bool MainWindow::getSolverOptionsFromFITS()
2287 {
2288     clearAstrometrySettings();
2289 
2290     double ra, dec;
2291 
2292     int status = 0, fits_ccd_width, fits_ccd_height, fits_binx = 1, fits_biny = 1;
2293     char comment[128], error_status[512];
2294     fitsfile *fptr = nullptr;
2295 
2296     double fits_fov_x, fits_fov_y, fov_lower, fov_upper, fits_ccd_hor_pixel = -1,
2297                                                          fits_ccd_ver_pixel = -1, fits_focal_length = -1;
2298 
2299     status = 0;
2300 
2301     // Use open diskfile as it does not use extended file names which has problems opening
2302     // files with [ ] or ( ) in their names.
2303     if (fits_open_diskfile(&fptr, fileToProcess.toLatin1(), READONLY, &status))
2304     {
2305         fits_report_error(stderr, status);
2306         fits_get_errstatus(status, error_status);
2307         logOutput(QString::fromUtf8(error_status));
2308         return false;
2309     }
2310 
2311     status = 0;
2312     if (fits_movabs_hdu(fptr, 1, IMAGE_HDU, &status))
2313     {
2314         fits_report_error(stderr, status);
2315         fits_get_errstatus(status, error_status);
2316         logOutput(QString::fromUtf8(error_status));
2317         fits_close_file(fptr, &status);
2318         return false;
2319     }
2320 
2321     status = 0;
2322     if (fits_read_key(fptr, TINT, "NAXIS1", &fits_ccd_width, comment, &status))
2323     {
2324         fits_report_error(stderr, status);
2325         fits_get_errstatus(status, error_status);
2326         logOutput("FITS header: cannot find NAXIS1.");
2327         fits_close_file(fptr, &status);
2328         return false;
2329     }
2330 
2331     status = 0;
2332     if (fits_read_key(fptr, TINT, "NAXIS2", &fits_ccd_height, comment, &status))
2333     {
2334         fits_report_error(stderr, status);
2335         fits_get_errstatus(status, error_status);
2336         logOutput("FITS header: cannot find NAXIS2.");
2337         fits_close_file(fptr, &status);
2338         return false;
2339     }
2340 
2341     bool coord_ok = true;
2342 
2343     status = 0;
2344     char objectra_str[32];
2345     if (fits_read_key(fptr, TSTRING, "OBJCTRA", objectra_str, comment, &status))
2346     {
2347         if (fits_read_key(fptr, TDOUBLE, "RA", &ra, comment, &status))
2348         {
2349             fits_report_error(stderr, status);
2350             fits_get_errstatus(status, error_status);
2351             coord_ok = false;
2352             logOutput(QString("FITS header: cannot find OBJCTRA (%1).").arg(QString(error_status)));
2353         }
2354         else
2355             // Degrees to hours
2356             ra /= 15;
2357     }
2358     else
2359     {
2360         dms raDMS = dms::fromString(objectra_str, false);
2361         ra        = raDMS.Hours();
2362     }
2363 
2364     status = 0;
2365     char objectde_str[32];
2366     if (coord_ok && fits_read_key(fptr, TSTRING, "OBJCTDEC", objectde_str, comment, &status))
2367     {
2368         if (fits_read_key(fptr, TDOUBLE, "DEC", &dec, comment, &status))
2369         {
2370             fits_report_error(stderr, status);
2371             fits_get_errstatus(status, error_status);
2372             coord_ok = false;
2373             logOutput(QString("FITS header: cannot find OBJCTDEC (%1).").arg(QString(error_status)));
2374         }
2375     }
2376     else
2377     {
2378         dms deDMS = dms::fromString(objectde_str, true);
2379         dec       = deDMS.Degrees();
2380     }
2381 
2382     if(coord_ok)
2383     {
2384         ui->use_position->setChecked(true);
2385         ui->ra->setText(QString::number(ra));
2386         ui->dec->setText(QString::number(dec));
2387 
2388     }
2389 
2390     status = 0;
2391     double pixelScale = 0;
2392     // If we have pixel scale in arcsecs per pixel then lets use that directly
2393     // instead of calculating it from FOCAL length and other information
2394     if (fits_read_key(fptr, TDOUBLE, "SCALE", &pixelScale, comment, &status) == 0)
2395     {
2396         double fov_low  = 0.9 * pixelScale;
2397         double fov_high = 1.1 * pixelScale;
2398 
2399         ui->scale_low->setText(QString::number(fov_low));
2400         ui->scale_high->setText(QString::number(fov_high));
2401         ui->units->setCurrentIndex(SSolver::ARCSEC_PER_PIX);
2402         ui->use_scale->setChecked(true);
2403         fits_close_file(fptr, &status);
2404         return true;
2405     }
2406 
2407     if (fits_read_key(fptr, TDOUBLE, "FOCALLEN", &fits_focal_length, comment, &status))
2408     {
2409         int integer_focal_length = -1;
2410         if (fits_read_key(fptr, TINT, "FOCALLEN", &integer_focal_length, comment, &status))
2411         {
2412             fits_report_error(stderr, status);
2413             fits_get_errstatus(status, error_status);
2414             logOutput(QString("FITS header: cannot find FOCALLEN: (%1).").arg(QString(error_status)));
2415             fits_close_file(fptr, &status);
2416             return false;
2417         }
2418         else
2419             fits_focal_length = integer_focal_length;
2420     }
2421 
2422     status = 0;
2423     if (fits_read_key(fptr, TDOUBLE, "PIXSIZE1", &fits_ccd_hor_pixel, comment, &status))
2424     {
2425         fits_report_error(stderr, status);
2426         fits_get_errstatus(status, error_status);
2427         logOutput(QString("FITS header: cannot find PIXSIZE1 (%1).").arg(QString(error_status)));
2428         fits_close_file(fptr, &status);
2429         return false;
2430     }
2431 
2432     status = 0;
2433     if (fits_read_key(fptr, TDOUBLE, "PIXSIZE2", &fits_ccd_ver_pixel, comment, &status))
2434     {
2435         fits_report_error(stderr, status);
2436         fits_get_errstatus(status, error_status);
2437         logOutput(QString("FITS header: cannot find PIXSIZE2 (%1).").arg(QString(error_status)));
2438         fits_close_file(fptr, &status);
2439         return false;
2440     }
2441 
2442     status = 0;
2443     fits_read_key(fptr, TINT, "XBINNING", &fits_binx, comment, &status);
2444     status = 0;
2445     fits_read_key(fptr, TINT, "YBINNING", &fits_biny, comment, &status);
2446 
2447     // Calculate FOV
2448     fits_fov_x = 206264.8062470963552 * fits_ccd_width * fits_ccd_hor_pixel / 1000.0 / fits_focal_length * fits_binx;
2449     fits_fov_y = 206264.8062470963552 * fits_ccd_height * fits_ccd_ver_pixel / 1000.0 / fits_focal_length * fits_biny;
2450 
2451     fits_fov_x /= 60.0;
2452     fits_fov_y /= 60.0;
2453 
2454     // let's stretch the boundaries by 10%
2455     fov_lower = qMin(fits_fov_x, fits_fov_y);
2456     fov_upper = qMax(fits_fov_x, fits_fov_y);
2457 
2458     fov_lower *= 0.90;
2459     fov_upper *= 1.10;
2460 
2461     //Final Options that get stored.
2462 
2463     ui->scale_low->setText(QString::number(fov_lower));
2464     ui->scale_high->setText(QString::number(fov_upper));
2465     ui->units->setCurrentIndex(SSolver::ARCMIN_WIDTH);
2466     ui->use_scale->setChecked(true);
2467 
2468     fits_close_file(fptr, &status);
2469 
2470     return true;
2471 }
2472 
2473 
2474 //Note: The next 3 functions are designed to work in an easily editable way.
2475 //To add new columns to this table, just add them to the first function
2476 //To have it fill the column when a Sextraction or Solve is complete, add it to one or both of the next two functions
2477 //So that the column gets setup and then gets filled in.
2478 
2479 //This method sets up the results table to start with.
setupResultsTable()2480 void MainWindow::setupResultsTable()
2481 {
2482 
2483     QTableWidget *table = ui->resultsTable;
2484 
2485     //These are in the order that they will appear in the table.
2486 
2487     addColumnToTable(table, "Avg Time");
2488     addColumnToTable(table, "# Trials");
2489     addColumnToTable(table, "Command");
2490     addColumnToTable(table, "Profile");
2491     addColumnToTable(table, "Loglvl");
2492     addColumnToTable(table, "Stars");
2493     //Sextractor Parameters
2494     addColumnToTable(table, "Shape");
2495     addColumnToTable(table, "Kron");
2496     addColumnToTable(table, "Subpix");
2497     addColumnToTable(table, "r_min");
2498     addColumnToTable(table, "minarea");
2499     addColumnToTable(table, "d_thresh");
2500     addColumnToTable(table, "d_cont");
2501     addColumnToTable(table, "clean");
2502     addColumnToTable(table, "clean param");
2503     addColumnToTable(table, "fwhm");
2504     addColumnToTable(table, "part");
2505     //Star Filtering Parameters
2506     addColumnToTable(table, "Max Size");
2507     addColumnToTable(table, "Min Size");
2508     addColumnToTable(table, "Max Ell");
2509     addColumnToTable(table, "Ini Keep");
2510     addColumnToTable(table, "Keep #");
2511     addColumnToTable(table, "Cut Bri");
2512     addColumnToTable(table, "Cut Dim");
2513     addColumnToTable(table, "Sat Lim");
2514     //Astrometry Parameters
2515     addColumnToTable(table, "Pos?");
2516     addColumnToTable(table, "Scale?");
2517     addColumnToTable(table, "Resort?");
2518     addColumnToTable(table, "AutoDown");
2519     addColumnToTable(table, "Down");
2520     addColumnToTable(table, "in ||");
2521     addColumnToTable(table, "Multi");
2522     addColumnToTable(table, "# Thread");
2523     //Results
2524     addColumnToTable(table, "RA (J2000)");
2525     addColumnToTable(table, "DEC (J2000)");
2526     addColumnToTable(table, "RA ERR \"");
2527     addColumnToTable(table, "DEC ERR \"");
2528     addColumnToTable(table, "Orientation˚");
2529     addColumnToTable(table, "Field Width \'");
2530     addColumnToTable(table, "Field Height \'");
2531     addColumnToTable(table, "PixScale \"");
2532     addColumnToTable(table, "Parity");
2533     addColumnToTable(table, "Field");
2534 
2535     updateHiddenResultsTableColumns();
2536 }
2537 
2538 //This adds a Sextraction to the Results Table
2539 //To add, remove, or change the way certain columns are filled when a sextraction is finished, edit them here.
addSextractionToTable()2540 void MainWindow::addSextractionToTable()
2541 {
2542     QTableWidget *table = ui->resultsTable;
2543     SSolver::Parameters params = stellarSolver->getCurrentParameters();
2544 
2545     setItemInColumn(table, "Avg Time", QString::number(totalTime / currentTrial));
2546     setItemInColumn(table, "# Trials", QString::number(currentTrial));
2547     if(stellarSolver->isCalculatingHFR())
2548         setItemInColumn(table, "Command", stellarSolver->getCommandString() + " w/HFR");
2549     else
2550         setItemInColumn(table, "Command", stellarSolver->getCommandString());
2551     setItemInColumn(table, "Profile", params.listName);
2552     setItemInColumn(table, "Loglvl", stellarSolver->getLogLevelString());
2553     setItemInColumn(table, "Stars", QString::number(stellarSolver->getNumStarsFound()));
2554     //Sextractor Parameters
2555     setItemInColumn(table, "Shape", stellarSolver->getShapeString());
2556     setItemInColumn(table, "Kron", QString::number(params.kron_fact));
2557     setItemInColumn(table, "Subpix", QString::number(params.subpix));
2558     setItemInColumn(table, "r_min", QString::number(params.r_min));
2559     setItemInColumn(table, "minarea", QString::number(params.minarea));
2560     setItemInColumn(table, "d_thresh", QString::number(params.deblend_thresh));
2561     setItemInColumn(table, "d_cont", QString::number(params.deblend_contrast));
2562     setItemInColumn(table, "clean", QString::number(params.clean));
2563     setItemInColumn(table, "clean param", QString::number(params.clean_param));
2564     setItemInColumn(table, "fwhm", QString::number(params.fwhm));
2565     setItemInColumn(table, "part", QString::number(params.partition));
2566     setItemInColumn(table, "Field", ui->fileNameDisplay->text());
2567 
2568     //StarFilter Parameters
2569     setItemInColumn(table, "Max Size", QString::number(params.maxSize));
2570     setItemInColumn(table, "Min Size", QString::number(params.minSize));
2571     setItemInColumn(table, "Max Ell", QString::number(params.maxEllipse));
2572     setItemInColumn(table, "Ini Keep", QString::number(params.initialKeep));
2573     setItemInColumn(table, "Keep #", QString::number(params.keepNum));
2574     setItemInColumn(table, "Cut Bri", QString::number(params.removeBrightest));
2575     setItemInColumn(table, "Cut Dim", QString::number(params.removeDimmest));
2576     setItemInColumn(table, "Sat Lim", QString::number(params.saturationLimit));
2577 
2578 }
2579 
2580 //This adds a solution to the Results Table
2581 //To add, remove, or change the way certain columns are filled when a solve is finished, edit them here.
addSolutionToTable(FITSImage::Solution solution)2582 void MainWindow::addSolutionToTable(FITSImage::Solution solution)
2583 {
2584     QTableWidget *table = ui->resultsTable;
2585     SSolver::Parameters params = stellarSolver->getCurrentParameters();
2586 
2587     setItemInColumn(table, "Avg Time", QString::number(totalTime / currentTrial));
2588     setItemInColumn(table, "# Trials", QString::number(currentTrial));
2589     setItemInColumn(table, "Command", stellarSolver->getCommandString());
2590     setItemInColumn(table, "Profile", params.listName);
2591 
2592     //Astrometry Parameters
2593     setItemInColumn(table, "Pos?", stellarSolver->property("UsePosition").toString());
2594     setItemInColumn(table, "Scale?", stellarSolver->property("UseScale").toString());
2595     setItemInColumn(table, "Resort?", QVariant(params.resort).toString());
2596     setItemInColumn(table, "AutoDown", QVariant(params.autoDownsample).toString());
2597     setItemInColumn(table, "Down", QVariant(params.downsample).toString());
2598     setItemInColumn(table, "in ||", QVariant(params.inParallel).toString());
2599     setItemInColumn(table, "Multi", stellarSolver->getMultiAlgoString());
2600     setItemInColumn(table, "# Thread", QVariant(stellarSolver->getNumThreads()).toString());
2601 
2602 
2603     //Results
2604     setItemInColumn(table, "RA (J2000)", StellarSolver::raString(solution.ra));
2605     setItemInColumn(table, "DEC (J2000)", StellarSolver::decString(solution.dec));
2606     if(solution.raError == 0)
2607         setItemInColumn(table, "RA ERR \"", "--");
2608     else
2609         setItemInColumn(table, "RA ERR \"", QString::number(solution.raError, 'f', 2));
2610     if(solution.decError == 0)
2611         setItemInColumn(table, "DEC ERR \"", "--");
2612     else
2613         setItemInColumn(table, "DEC ERR \"", QString::number(solution.decError, 'f', 2));
2614     setItemInColumn(table, "Orientation˚", QString::number(solution.orientation));
2615     setItemInColumn(table, "Field Width \'", QString::number(solution.fieldWidth));
2616     setItemInColumn(table, "Field Height \'", QString::number(solution.fieldHeight));
2617     setItemInColumn(table, "PixScale \"", QString::number(solution.pixscale));
2618     setItemInColumn(table, "Parity", solution.parity);
2619     setItemInColumn(table, "Field", ui->fileNameDisplay->text());
2620 }
2621 
2622 //I wrote this method to hide certain columns in the Results Table if the user wants to reduce clutter in the table.
updateHiddenResultsTableColumns()2623 void MainWindow::updateHiddenResultsTableColumns()
2624 {
2625     QTableWidget *table = ui->resultsTable;
2626     //Sextractor Params
2627     setColumnHidden(table, "Shape", !showSextractorParams);
2628     setColumnHidden(table, "Kron", !showSextractorParams);
2629     setColumnHidden(table, "Subpix", !showSextractorParams);
2630     setColumnHidden(table, "r_min", !showSextractorParams);
2631     setColumnHidden(table, "minarea", !showSextractorParams);
2632     setColumnHidden(table, "d_thresh", !showSextractorParams);
2633     setColumnHidden(table, "d_cont", !showSextractorParams);
2634     setColumnHidden(table, "clean", !showSextractorParams);
2635     setColumnHidden(table, "clean param", !showSextractorParams);
2636     setColumnHidden(table, "fwhm", !showSextractorParams);
2637     setColumnHidden(table, "part", !showSextractorParams);
2638     //Star Filtering Parameters
2639     setColumnHidden(table, "Max Size", !showSextractorParams);
2640     setColumnHidden(table, "Min Size", !showSextractorParams);
2641     setColumnHidden(table, "Max Ell", !showSextractorParams);
2642     setColumnHidden(table, "Ini Keep", !showSextractorParams);
2643     setColumnHidden(table, "Keep #", !showSextractorParams);
2644     setColumnHidden(table, "Cut Bri", !showSextractorParams);
2645     setColumnHidden(table, "Cut Dim", !showSextractorParams);
2646     setColumnHidden(table, "Sat Lim", !showSextractorParams);
2647     //Astrometry Parameters
2648     setColumnHidden(table, "Pos?", !showAstrometryParams);
2649     setColumnHidden(table, "Scale?", !showAstrometryParams);
2650     setColumnHidden(table, "Resort?", !showAstrometryParams);
2651     setColumnHidden(table, "AutoDown", !showAstrometryParams);
2652     setColumnHidden(table, "Down", !showAstrometryParams);
2653     setColumnHidden(table, "in ||", !showAstrometryParams);
2654     setColumnHidden(table, "Multi", !showAstrometryParams);
2655     setColumnHidden(table, "# Thread", !showAstrometryParams);
2656     //Results
2657     setColumnHidden(table, "RA (J2000)", !showSolutionDetails);
2658     setColumnHidden(table, "DEC (J2000)", !showSolutionDetails);
2659     setColumnHidden(table, "RA ERR \"", !showSolutionDetails);
2660     setColumnHidden(table, "DEC ERR \"", !showSolutionDetails);
2661     setColumnHidden(table, "Orientation˚", !showSolutionDetails);
2662     setColumnHidden(table, "Field Width \'", !showSolutionDetails);
2663     setColumnHidden(table, "Field Height \'", !showSolutionDetails);
2664     setColumnHidden(table, "PixScale \"", !showSolutionDetails);
2665     setColumnHidden(table, "Parity", !showSolutionDetails);
2666 }
2667 
2668 //This will write the Results table to a csv file if the user desires
2669 //Then the user can analyze the solution information in more detail to try to perfect sextractor and solver parameters
saveResultsTable()2670 void MainWindow::saveResultsTable()
2671 {
2672     if (ui->resultsTable->rowCount() == 0)
2673         return;
2674 
2675     QUrl exportFile = QFileDialog::getSaveFileUrl(this, "Export Results Table", dirPath,
2676                       "CSV File (*.csv)");
2677     if (exportFile.isEmpty()) // if user presses cancel
2678         return;
2679     if (exportFile.toLocalFile().endsWith(QLatin1String(".csv")) == false)
2680         exportFile.setPath(exportFile.toLocalFile() + ".csv");
2681 
2682     QString path = exportFile.toLocalFile();
2683 
2684     if (QFile::exists(path))
2685     {
2686         int r = QMessageBox::question(this, "Overwrite it?",
2687                                       QString("A file named \"%1\" already exists. Do you want to overwrite it?").arg(exportFile.fileName()));
2688         if (r == QMessageBox::No)
2689             return;
2690     }
2691 
2692     if (!exportFile.isValid())
2693     {
2694         QString message = QString("Invalid URL: %1").arg(exportFile.url());
2695         QMessageBox::warning(this, "Invalid URL", message);
2696         return;
2697     }
2698 
2699     QFile file;
2700     file.setFileName(path);
2701     if (!file.open(QIODevice::WriteOnly))
2702     {
2703         QString message = QString("Unable to write to file %1").arg(path);
2704         QMessageBox::warning(this, "Could Not Open File", message);
2705         return;
2706     }
2707 
2708     QTextStream outstream(&file);
2709 
2710     for (int c = 0; c < ui->resultsTable->columnCount(); c++)
2711     {
2712         outstream << ui->resultsTable->horizontalHeaderItem(c)->text() << ',';
2713     }
2714     outstream << "\n";
2715 
2716     for (int r = 0; r < ui->resultsTable->rowCount(); r++)
2717     {
2718         for (int c = 0; c < ui->resultsTable->columnCount(); c++)
2719         {
2720             QTableWidgetItem *cell = ui->resultsTable->item(r, c);
2721 
2722             if (cell)
2723                 outstream << cell->text() << ',';
2724             else
2725                 outstream << " " << ',';
2726         }
2727         outstream << endl;
2728     }
2729     QMessageBox::information(this, "Message", QString("Results Table Saved as: %1").arg(path));
2730     file.close();
2731 }
2732 
2733 //This will write the Star table to a csv file if the user desires
2734 //Then the user can analyze the solution information in more detail to try to analyze the stars found or try to perfect sextractor parameters
saveStarTable()2735 void MainWindow::saveStarTable()
2736 {
2737     if (ui->starTable->rowCount() == 0)
2738         return;
2739 
2740     QUrl exportFile = QFileDialog::getSaveFileUrl(this, "Export Star Table", dirPath,
2741                       "CSV File (*.csv)");
2742     if (exportFile.isEmpty()) // if user presses cancel
2743         return;
2744     if (exportFile.toLocalFile().endsWith(QLatin1String(".csv")) == false)
2745         exportFile.setPath(exportFile.toLocalFile() + ".csv");
2746 
2747     QString path = exportFile.toLocalFile();
2748 
2749     if (QFile::exists(path))
2750     {
2751         int r = QMessageBox::question(this, "Overwrite it?",
2752                                       QString("A file named \"%1\" already exists. Do you want to overwrite it?").arg(exportFile.fileName()));
2753         if (r == QMessageBox::No)
2754             return;
2755     }
2756 
2757     if (!exportFile.isValid())
2758     {
2759         QString message = QString("Invalid URL: %1").arg(exportFile.url());
2760         QMessageBox::warning(this, "Invalid URL", message);
2761         return;
2762     }
2763 
2764     QFile file;
2765     file.setFileName(path);
2766     if (!file.open(QIODevice::WriteOnly))
2767     {
2768         QString message = QString("Unable to write to file %1").arg(path);
2769         QMessageBox::warning(this, "Could Not Open File", message);
2770         return;
2771     }
2772 
2773     QTextStream outstream(&file);
2774 
2775     for (int c = 0; c < ui->starTable->columnCount(); c++)
2776     {
2777         outstream << ui->starTable->horizontalHeaderItem(c)->text() << ',';
2778     }
2779     outstream << "\n";
2780 
2781     for (int r = 0; r < ui->starTable->rowCount(); r++)
2782     {
2783         for (int c = 0; c < ui->starTable->columnCount(); c++)
2784         {
2785             QTableWidgetItem *cell = ui->starTable->item(r, c);
2786 
2787             if (cell)
2788                 outstream << cell->text() << ',';
2789             else
2790                 outstream << " " << ',';
2791         }
2792         outstream << endl;
2793     }
2794     QMessageBox::information(this, "Message", QString("Star Table Saved as: %1").arg(path));
2795     file.close();
2796 }
2797 
saveOptionsProfiles()2798 void MainWindow::saveOptionsProfiles()
2799 {
2800     QString fileURL = QFileDialog::getSaveFileName(nullptr, "Save Options Profiles", dirPath,
2801                       "INI files(*.ini)");
2802     if (fileURL.isEmpty())
2803         return;
2804     QSettings settings(fileURL, QSettings::IniFormat);
2805     for(int i = 0 ; i < optionsList.count(); i++)
2806     {
2807         SSolver::Parameters params = optionsList.at(i);
2808         settings.beginGroup(params.listName);
2809         QMap<QString, QVariant> map = SSolver::Parameters::convertToMap(params);
2810         QMapIterator<QString, QVariant> it(map);
2811         while(it.hasNext())
2812         {
2813             it.next();
2814             settings.setValue(it.key(), it.value());
2815         }
2816         settings.endGroup();
2817     }
2818 
2819 }
2820 
loadOptionsProfiles()2821 void MainWindow::loadOptionsProfiles()
2822 {
2823     QString fileURL = QFileDialog::getOpenFileName(nullptr, "Load Options Profiles File", dirPath,
2824                       "INI files(*.ini)");
2825     if (fileURL.isEmpty())
2826         return;
2827     if(!QFileInfo(fileURL).exists())
2828     {
2829         QMessageBox::warning(this, "Message", "The file doesn't exist");
2830         return;
2831     }
2832 
2833     ui->optionsProfile->blockSignals(true);
2834     ui->optionsProfile->clear();
2835     ui->optionsProfile->addItem("Unsaved Options");
2836     optionsList = StellarSolver::loadSavedOptionsProfiles(fileURL);
2837     foreach(SSolver::Parameters params, optionsList)
2838         ui->optionsProfile->addItem(params.listName);
2839     ui->optionsProfile->blockSignals(false);
2840 }
2841 
closeEvent(QCloseEvent * event)2842 void MainWindow::closeEvent(QCloseEvent *event)
2843 {
2844     QSettings programSettings("Astrometry Freeware", "StellarSolver");
2845 
2846     programSettings.setValue("setPathsIndex", ui->setPathsAutomatically->currentIndex());
2847 
2848     programSettings.setValue("sextractorBinaryPath", ui->sextractorPath->text());
2849     programSettings.setValue("sextractionProfile", ui->sextractionProfile->currentIndex());
2850     programSettings.setValue("confPath", ui->configFilePath->text());
2851     programSettings.setValue("solverPath", ui->solverPath->text());
2852     programSettings.setValue("solverProfile", ui->solverProfile->currentIndex());
2853     programSettings.setValue("astapBinaryPath", ui->astapPath->text());
2854     programSettings.setValue("wcsPath", ui->wcsPath->text());
2855     programSettings.setValue("cleanupTemporaryFiles",  ui->cleanupTemp->isChecked());
2856     programSettings.setValue("autoGenerateAstroConfig", ui->generateAstrometryConfig->isChecked());
2857     programSettings.setValue("onlySendFITSFiles", ui->onlySendFITSFiles->isChecked());
2858     programSettings.setValue("onlineServer", ui->onlineServer->text());
2859     programSettings.setValue("apiKey", ui->apiKey->text());
2860 
2861     QStringList items;
2862     for(int i = 0; i < ui->indexFolderPaths->count(); i++)
2863     {
2864         items << ui->indexFolderPaths->itemText(i);
2865     }
2866     programSettings.setValue("indexFolderPaths", items.join(","));
2867 
2868     programSettings.sync();
2869     event->accept();
2870 }
2871 
2872 
2873 
2874 
2875 
2876 
2877 
2878 
2879