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(¶ms, 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(¶ms, 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