1 /*  StellarSolver, StellarSolver Internal Library 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 #if defined(__APPLE__)
9 #include <sys/sysctl.h>
10 #elif defined(__FreeBSD__) || defined(__DragonFly__)
11 #include <sys/types.h>
12 #include <sys/sysctl.h>
13 #elif defined(_WIN32)
14 #include "windows.h"
15 #else //Linux
16 #include <QProcess>
17 #endif
18 
19 #include "stellarsolver.h"
20 #include "sextractorsolver.h"
21 #include "externalsextractorsolver.h"
22 #include "onlinesolver.h"
23 #include <QApplication>
24 #include <QSettings>
25 
26 using namespace SSolver;
27 
StellarSolver(ProcessType type,const FITSImage::Statistic & imagestats,const uint8_t * imageBuffer,QObject * parent)28 StellarSolver::StellarSolver(ProcessType type, const FITSImage::Statistic &imagestats, const uint8_t *imageBuffer,
29                              QObject *parent) : QObject(parent), m_Statistics(imagestats)
30 {
31     qRegisterMetaType<SolverType>("SolverType");
32     qRegisterMetaType<ProcessType>("ProcessType");
33     qRegisterMetaType<ExtractorType>("ExtractorType");
34     m_ProcessType = type;
35     m_ImageBuffer = imageBuffer;
36     m_Subframe = QRect(0, 0, m_Statistics.width, m_Statistics.height);
37 }
38 
StellarSolver(const FITSImage::Statistic & imagestats,uint8_t const * imageBuffer,QObject * parent)39 StellarSolver::StellarSolver(const FITSImage::Statistic &imagestats, uint8_t const *imageBuffer,
40                              QObject *parent) : QObject(parent), m_Statistics(imagestats)
41 {
42     qRegisterMetaType<SolverType>("SolverType");
43     qRegisterMetaType<ProcessType>("ProcessType");
44     qRegisterMetaType<ExtractorType>("ExtractorType");
45     m_ImageBuffer = imageBuffer;
46     m_Subframe = QRect(0, 0, m_Statistics.width, m_Statistics.height);
47 }
~StellarSolver()48 StellarSolver::~StellarSolver()
49 {
50 
51 }
52 
createSextractorSolver()53 SextractorSolver* StellarSolver::createSextractorSolver()
54 {
55     SextractorSolver *solver;
56 
57     if(m_ProcessType == SOLVE && m_SolverType == SOLVER_ONLINEASTROMETRY)
58     {
59         OnlineSolver *onlineSolver = new OnlineSolver(m_ProcessType, m_SextractorType, m_SolverType, m_Statistics, m_ImageBuffer,
60                 this);
61         onlineSolver->fileToProcess = m_FileToProcess;
62         onlineSolver->astrometryAPIKey = m_AstrometryAPIKey;
63         onlineSolver->astrometryAPIURL = m_AstrometryAPIURL;
64         onlineSolver->sextractorBinaryPath = m_SextractorBinaryPath;
65         solver = onlineSolver;
66     }
67     else if((m_ProcessType == SOLVE && m_SolverType == SOLVER_STELLARSOLVER) || (m_ProcessType != SOLVE
68             && m_SextractorType != EXTRACTOR_EXTERNAL))
69         solver = new InternalSextractorSolver(m_ProcessType, m_SextractorType, m_SolverType, m_Statistics, m_ImageBuffer, this);
70     else
71     {
72         ExternalSextractorSolver *extSolver = new ExternalSextractorSolver(m_ProcessType, m_SextractorType, m_SolverType,
73                 m_Statistics, m_ImageBuffer, this);
74         extSolver->fileToProcess = m_FileToProcess;
75         extSolver->sextractorBinaryPath = m_SextractorBinaryPath;
76         extSolver->confPath = m_ConfPath;
77         extSolver->solverPath = m_SolverPath;
78         extSolver->astapBinaryPath = m_ASTAPBinaryPath;
79         extSolver->wcsPath = m_WCSPath;
80         extSolver->cleanupTemporaryFiles = m_CleanupTemporaryFiles;
81         extSolver->autoGenerateAstroConfig = m_AutoGenerateAstroConfig;
82         extSolver->onlySendFITSFiles = m_OnlySendFITSFiles;
83         solver = extSolver;
84     }
85 
86     if(useSubframe)
87         solver->setUseSubframe(m_Subframe);
88     solver->m_LogToFile = m_LogToFile;
89     solver->m_LogFileName = m_LogFileName;
90     solver->m_AstrometryLogLevel = m_AstrometryLogLevel;
91     solver->m_SSLogLevel = m_SSLogLevel;
92     solver->m_BasePath = m_BasePath;
93     solver->m_ActiveParameters = params;
94     solver->indexFolderPaths = indexFolderPaths;
95     if(m_UseScale)
96         solver->setSearchScale(m_ScaleLow, m_ScaleHigh, m_ScaleUnit);
97     if(m_UsePosition)
98         solver->setSearchPositionInDegrees(m_SearchRA, m_SearchDE);
99     if(m_SSLogLevel != LOG_OFF)
100         connect(solver, &SextractorSolver::logOutput, this, &StellarSolver::logOutput);
101 
102     return solver;
103 }
104 
105 //Methods to get default file paths
getLinuxDefaultPaths()106 ExternalProgramPaths StellarSolver::getLinuxDefaultPaths()
107 {
108     return ExternalSextractorSolver::getLinuxDefaultPaths();
109 };
getLinuxInternalPaths()110 ExternalProgramPaths StellarSolver::getLinuxInternalPaths()
111 {
112     return ExternalSextractorSolver::getLinuxInternalPaths();
113 };
getMacHomebrewPaths()114 ExternalProgramPaths StellarSolver::getMacHomebrewPaths()
115 {
116     return ExternalSextractorSolver::getMacHomebrewPaths();
117 };
getMacInternalPaths()118 ExternalProgramPaths StellarSolver::getMacInternalPaths()
119 {
120     return ExternalSextractorSolver::getMacInternalPaths();
121 };
getWinANSVRPaths()122 ExternalProgramPaths StellarSolver::getWinANSVRPaths()
123 {
124     return ExternalSextractorSolver::getWinANSVRPaths();
125 };
getWinCygwinPaths()126 ExternalProgramPaths StellarSolver::getWinCygwinPaths()
127 {
128     return ExternalSextractorSolver::getLinuxDefaultPaths();
129 };
130 
extract(bool calculateHFR,QRect frame)131 void StellarSolver::extract(bool calculateHFR, QRect frame)
132 {
133     m_ProcessType = calculateHFR ? EXTRACT_WITH_HFR : EXTRACT;
134     useSubframe = !frame.isNull() && frame.isValid();
135     if (useSubframe)
136         m_Subframe = frame;
137     start();
138     m_SextractorSolver->wait();
139 }
140 
141 //This will allow the solver to gracefully disconnect, abort, finish, and get deleted
142 //Right now the internal solvers are all deleted when StellarSolver is deleted
143 //I might try experimenting with this.
releaseSextractorSolver(SextractorSolver * solver)144 void StellarSolver::releaseSextractorSolver(SextractorSolver *solver)
145 {
146     if(solver != nullptr)
147     {
148         if(solver->isRunning())
149         {
150             connect(solver, &SextractorSolver::finished, solver, &SextractorSolver::deleteLater);
151             solver->disconnect(this);
152             solver->abort();
153         }
154         else
155             solver->deleteLater();
156     }
157 }
158 
start()159 void StellarSolver::start()
160 {
161     if(checkParameters() == false)
162     {
163         emit logOutput("There is an issue with your parameters. Terminating the process.");
164         m_isRunning = false;
165         m_HasFailed = true;
166         emit ready();
167         emit finished();
168         return;
169     }
170 
171     m_SextractorSolver = createSextractorSolver();
172 
173     m_isRunning = true;
174     m_HasFailed = false;
175     if(m_ProcessType == EXTRACT || m_ProcessType == EXTRACT_WITH_HFR)
176     {
177         m_ExtractorStars.clear();
178         m_HasExtracted = false;
179     }
180     else
181     {
182         m_SolverStars.clear();
183         m_HasSolved = false;
184         hasWCS = false;
185         hasWCSCoord = false;
186         wcs_coord = nullptr;
187     }
188 
189     //    if(m_isBlocking)
190     //        return;
191 
192     //These are the solvers that support parallelization, ASTAP and the online ones do not
193     if(params.multiAlgorithm != NOT_MULTI && m_ProcessType == SOLVE && (m_SolverType == SOLVER_STELLARSOLVER
194             || m_SolverType == SOLVER_LOCALASTROMETRY))
195     {
196         //Note that it is good to do the Star Extraction before parallelization because it doesn't make sense to repeat this step in all the threads, especially since SEP is now also parallelized in StellarSolver.
197         if(m_SextractorType != EXTRACTOR_BUILTIN)
198         {
199             m_SextractorSolver->extract();
200             if(m_SextractorSolver->getNumStarsFound() == 0)
201             {
202                 emit logOutput("No stars were found, so the image cannot be solved");
203                 m_isRunning = false;
204                 m_HasFailed = true;
205                 emit ready();
206                 emit finished();
207                 return;
208             }
209         }
210         //Note that converting the image to a FITS file if desired, doesn't need to be repeated in all the threads, but also CFITSIO fails when accessed by multiple parallel threads.
211         if(m_SolverType == SOLVER_LOCALASTROMETRY && m_SextractorType == EXTRACTOR_BUILTIN && m_OnlySendFITSFiles)
212         {
213             QPointer<ExternalSextractorSolver> extSolver = (ExternalSextractorSolver*) (SextractorSolver*) m_SextractorSolver;
214             QFileInfo file(extSolver->fileToProcess);
215             if(file.suffix() != "fits" && file.suffix() != "fit")
216             {
217                 int ret = extSolver->saveAsFITS();
218                 if(ret != 0)
219                 {
220                     emit logOutput("Failed to save FITS File.");
221                     return;
222                 }
223             }
224         }
225         parallelSolve();
226     }
227     else if(m_SolverType == SOLVER_ONLINEASTROMETRY)
228     {
229         connect(m_SextractorSolver, &SextractorSolver::finished, this, &StellarSolver::processFinished);
230         m_SextractorSolver->execute();
231     }
232     else
233     {
234         connect(m_SextractorSolver, &SextractorSolver::finished, this, &StellarSolver::processFinished);
235         m_SextractorSolver->start();
236     }
237 
238 }
239 
checkParameters()240 bool StellarSolver::checkParameters()
241 {
242     if(m_SolverType == SOLVER_ASTAP && m_SextractorType != EXTRACTOR_BUILTIN)
243     {
244         if(m_SSLogLevel != LOG_OFF)
245             emit logOutput("ASTAP no longer supports alternative star extraction methods.  Changing to built-in star extraction.");
246         m_SextractorType = EXTRACTOR_BUILTIN;
247     }
248 
249     if(params.multiAlgorithm != NOT_MULTI && m_SolverType == SOLVER_ASTAP && m_ProcessType == SOLVE)
250     {
251         if(m_SSLogLevel != LOG_OFF)
252             emit logOutput("ASTAP does not support multi-threaded solves.  Disabling that option");
253         params.multiAlgorithm = NOT_MULTI;
254     }
255 
256     if(m_ProcessType == SOLVE  && params.autoDownsample)
257     {
258         //Take whichever one is bigger
259         int imageSize = m_Statistics.width > m_Statistics.height ? m_Statistics.width : m_Statistics.height;
260         params.downsample = imageSize / 2048 + 1;
261         if(m_SSLogLevel != LOG_OFF)
262             emit logOutput(QString("Automatically downsampling the image by %1").arg(params.downsample));
263     }
264 
265     if(m_ProcessType == SOLVE && m_SolverType != SOLVER_ASTAP)
266     {
267         if(m_SolverType == SOLVER_STELLARSOLVER && m_SextractorType != EXTRACTOR_INTERNAL)
268         {
269             if(m_SSLogLevel != LOG_OFF)
270                 emit logOutput("StellarSolver only uses the Internal SEP Sextractor since it doesn't save files to disk. Changing to Internal Star Extractor.");
271             m_SextractorType = EXTRACTOR_INTERNAL;
272         }
273 
274         if(params.multiAlgorithm == MULTI_AUTO)
275         {
276             if(m_UseScale && m_UsePosition)
277                 params.multiAlgorithm = NOT_MULTI;
278             else if(m_UsePosition)
279                 params.multiAlgorithm = MULTI_SCALES;
280             else if(m_UseScale)
281                 params.multiAlgorithm = MULTI_DEPTHS;
282             else
283                 params.multiAlgorithm = MULTI_SCALES;
284         }
285 
286         if(params.inParallel)
287         {
288             if(enoughRAMisAvailableFor(indexFolderPaths))
289             {
290                 if(m_SSLogLevel != LOG_OFF)
291                     emit logOutput("There should be enough RAM to load the indexes in parallel.");
292             }
293             else
294             {
295                 if(m_SSLogLevel != LOG_OFF)
296                 {
297                     emit logOutput("Not enough RAM is available on this system for loading the index files you have in parallel");
298                     emit logOutput("Disabling the inParallel option.");
299                 }
300                 params.inParallel = false;
301             }
302         }
303     }
304 
305     return true; //For now
306 }
307 
308 //This allows us to start multiple threads to search simulaneously in separate threads/cores
309 //to attempt to efficiently use modern multi core computers to speed up the solve
parallelSolve()310 void StellarSolver::parallelSolve()
311 {
312     if(params.multiAlgorithm == NOT_MULTI || !(m_SolverType == SOLVER_STELLARSOLVER || m_SolverType == SOLVER_LOCALASTROMETRY))
313         return;
314     parallelSolvers.clear();
315     m_ParallelSolversFinishedCount = 0;
316     int threads = QThread::idealThreadCount();
317 
318     if(params.multiAlgorithm == MULTI_SCALES)
319     {
320         //Attempt to search on multiple scales
321         //Note, originally I had each parallel solver getting equal ranges, but solves are faster on bigger scales
322         //So now I'm giving the bigger scale solvers more of a range to work with.
323         double minScale;
324         double maxScale;
325         ScaleUnits units;
326         if(m_UseScale)
327         {
328             minScale = m_ScaleLow;
329             maxScale = m_ScaleHigh;
330             units = m_ScaleUnit;
331         }
332         else
333         {
334             minScale = params.minwidth;
335             maxScale = params.maxwidth;
336             units = DEG_WIDTH;
337         }
338         double scaleConst = (maxScale - minScale) / pow(threads, 2);
339         if(m_SSLogLevel != LOG_OFF)
340             emit logOutput(QString("Starting %1 threads to solve on multiple scales").arg(threads));
341         for(double thread = 0; thread < threads; thread++)
342         {
343             double low = minScale + scaleConst * pow(thread, 2);
344             double high = minScale + scaleConst * pow(thread + 1, 2);
345             SextractorSolver *solver = m_SextractorSolver->spawnChildSolver(thread);
346             connect(solver, &SextractorSolver::finished, this, &StellarSolver::finishParallelSolve);
347             solver->setSearchScale(low, high, units);
348             parallelSolvers.append(solver);
349             if(m_SSLogLevel != LOG_OFF)
350                 emit logOutput(QString("Solver # %1, Low %2, High %3 %4").arg(parallelSolvers.count()).arg(low).arg(high).arg(
351                                    getScaleUnitString()));
352         }
353     }
354     //Note: it might be useful to do a parallel solve on multiple positions, but I am afraid
355     //that since it searches in a circle around the search position, it might be difficult to make it
356     //search a square grid without either missing sections of sky or overlapping search regions.
357     else if(params.multiAlgorithm == MULTI_DEPTHS)
358     {
359         //Attempt to search on multiple depths
360         int sourceNum = 200;
361         if(params.keepNum != 0)
362             sourceNum = params.keepNum;
363         int inc = sourceNum / threads;
364         //We don't need an unnecessary number of threads
365         if(inc < 10)
366             inc = 10;
367         if(m_SSLogLevel != LOG_OFF)
368             emit logOutput(QString("Starting %1 threads to solve on multiple depths").arg(sourceNum / inc));
369         for(int i = 1; i < sourceNum; i += inc)
370         {
371             SextractorSolver *solver = m_SextractorSolver->spawnChildSolver(i);
372             connect(solver, &SextractorSolver::finished, this, &StellarSolver::finishParallelSolve);
373             solver->depthlo = i;
374             solver->depthhi = i + inc;
375             parallelSolvers.append(solver);
376             if(m_SSLogLevel != LOG_OFF)
377                 emit logOutput(QString("Child Solver # %1, Depth Low %2, Depth High %3").arg(parallelSolvers.count()).arg(i).arg(i + inc));
378         }
379     }
380     for(auto solver : parallelSolvers)
381         solver->start();
382 }
383 
parallelSolversAreRunning() const384 bool StellarSolver::parallelSolversAreRunning() const
385 {
386     for(auto solver : parallelSolvers)
387         if(solver->isRunning())
388             return true;
389     return false;
390 }
processFinished(int code)391 void StellarSolver::processFinished(int code)
392 {
393     numStars  = m_SextractorSolver->getNumStarsFound();
394     if(code == 0)
395     {
396         if(m_ProcessType == SOLVE && m_SextractorSolver->solvingDone())
397         {
398             solution = m_SextractorSolver->getSolution();
399             m_SolverStars = m_SextractorSolver->getStarList();
400             if(m_SextractorSolver->hasWCSData())
401             {
402                 hasWCS = true;
403                 solverWithWCS = m_SextractorSolver;
404                 if(loadWCS)
405                 {
406                     solverWithWCS->computingWCS = true;
407                     disconnect(solverWithWCS, &SextractorSolver::finished, this, &StellarSolver::processFinished);
408                     connect(solverWithWCS, &SextractorSolver::finished, this, &StellarSolver::finishWCS);
409                     solverWithWCS->start();
410                 }
411             }
412             m_HasSolved = true;
413         }
414         else if((m_ProcessType == EXTRACT || m_ProcessType == EXTRACT_WITH_HFR) && m_SextractorSolver->sextractionDone())
415         {
416             m_ExtractorStars = m_SextractorSolver->getStarList();
417             background = m_SextractorSolver->getBackground();
418             m_CalculateHFR = m_SextractorSolver->isCalculatingHFR();
419             if(solverWithWCS)
420                 solverWithWCS->appendStarsRAandDEC(m_ExtractorStars);
421             m_HasExtracted = true;
422         }
423     }
424     else
425         m_HasFailed = true;
426 
427     if(m_ProcessType != SOLVE || !m_SextractorSolver->hasWCSData() || !loadWCS)
428         m_isRunning = false;
429 
430     emit ready();
431 
432     if(m_ProcessType != SOLVE || !m_SextractorSolver->hasWCSData() || !loadWCS)
433         emit finished();
434 }
435 
whichSolver(SextractorSolver * solver)436 int StellarSolver::whichSolver(SextractorSolver *solver)
437 {
438     for(int i = 0; i < parallelSolvers.count(); i++ )
439     {
440         if(parallelSolvers.at(i) == solver)
441             return i + 1;
442     }
443     return 0;
444 }
445 
446 //This slot listens for signals from the child solvers that they are in fact done with the solve
447 //If they
finishParallelSolve(int success)448 void StellarSolver::finishParallelSolve(int success)
449 {
450     m_ParallelSolversFinishedCount++;
451     SextractorSolver *reportingSolver = qobject_cast<SextractorSolver*>(sender());
452     if(!reportingSolver)
453         return;
454 
455     if(success == 0 && !m_HasSolved)
456     {
457         for(auto solver : parallelSolvers)
458         {
459             disconnect(solver, &SextractorSolver::logOutput, this, &StellarSolver::logOutput);
460             if(solver != reportingSolver && solver->isRunning())
461                 solver->abort();
462         }
463         if(m_SSLogLevel != LOG_OFF)
464         {
465             emit logOutput(QString("Successfully solved with child solver: %1").arg(whichSolver(reportingSolver)));
466             emit logOutput("Shutting down other child solvers");
467         }
468 
469         numStars = reportingSolver->getNumStarsFound();
470         solution = reportingSolver->getSolution();
471         m_SolverStars = reportingSolver->getStarList();
472 
473         if(reportingSolver->hasWCSData() && loadWCS)
474         {
475             solverWithWCS = reportingSolver;
476             hasWCS = true;
477             solverWithWCS->computingWCS = true;
478             disconnect(solverWithWCS, &SextractorSolver::finished, this, &StellarSolver::finishParallelSolve);
479             connect(solverWithWCS, &SextractorSolver::finished, this, &StellarSolver::finishWCS);
480             connect(solverWithWCS, &SextractorSolver::logOutput, this, &StellarSolver::logOutput);
481             solverWithWCS->start();
482         }
483         if(m_SextractorType !=
484                 EXTRACTOR_BUILTIN) //Note this is just cleaning up the files from the star extraction done prior to the parallel solve.  So for built in, it doesn't even do it, so no files to clean up
485             m_SextractorSolver->cleanupTempFiles();
486         m_HasSolved = true;
487         emit ready();
488     }
489     else
490     {
491         if(m_SSLogLevel != LOG_OFF && !m_HasSolved)
492             emit logOutput(QString("Child solver: %1 did not solve or was aborted").arg(whichSolver(reportingSolver)));
493     }
494 
495     if(m_ParallelSolversFinishedCount == parallelSolvers.count())
496     {
497 
498         if(m_HasSolved)
499         {
500             //Don't emit finished until WCS is done if we are doing WCS extraction.
501             if(!(loadWCS && hasWCS))
502             {
503                 m_isRunning = false;
504                 emit finished();
505             }
506         }
507         else
508         {
509             m_isRunning = false;
510             m_HasFailed = true;
511             emit ready();
512             emit finished();
513         }
514     }
515 }
516 
finishWCS()517 void StellarSolver::finishWCS()
518 {
519     if(solverWithWCS)
520     {
521         wcs_coord = solverWithWCS->getWCSCoord();
522         if(m_ExtractorStars.count() > 0)
523             solverWithWCS->appendStarsRAandDEC(m_ExtractorStars);
524         if(wcs_coord)
525         {
526             hasWCSCoord = true;
527             emit wcsReady();
528         }
529     }
530     m_isRunning = false;
531     emit finished();
532 }
533 
wcsToPixel(const FITSImage::wcs_point & skyPoint,QPointF & pixelPoint)534 bool StellarSolver::wcsToPixel(const FITSImage::wcs_point &skyPoint, QPointF &pixelPoint)
535 {
536     if(hasWCS && solverWithWCS)
537     {
538         solverWithWCS->wcsToPixel(skyPoint, pixelPoint);
539         return true;
540     }
541     return false;
542 }
543 
pixelToWCS(const QPointF & pixelPoint,FITSImage::wcs_point & skyPoint)544 bool StellarSolver::pixelToWCS(const QPointF &pixelPoint, FITSImage::wcs_point &skyPoint)
545 {
546     if(hasWCS && solverWithWCS)
547     {
548         solverWithWCS->pixelToWCS(pixelPoint, skyPoint);
549         return true;
550     }
551     return false;
552 }
553 
554 //This is the abort method.  The way that it works is that it creates a file.  Astrometry.net is monitoring for this file's creation in order to abort.
abort()555 void StellarSolver::abort()
556 {
557     for(auto solver : parallelSolvers)
558         solver->abort();
559     if(m_SextractorSolver)
560         m_SextractorSolver->abort();
561     wasAborted = true;
562 }
563 
564 //This method checks all the solvers and the internal running boolean to determine if anything is running.
isRunning() const565 bool StellarSolver::isRunning() const
566 {
567     if(parallelSolversAreRunning())
568         return true;
569     if(m_SextractorSolver && m_SextractorSolver->isRunning())
570         return true;
571     return m_isRunning;
572 }
573 
574 //This method uses a fwhm value to generate the conv filter the sextractor will use.
createConvFilterFromFWHM(Parameters * params,double fwhm)575 void StellarSolver::createConvFilterFromFWHM(Parameters *params, double fwhm)
576 {
577     params->fwhm = fwhm;
578     params->convFilter.clear();
579     double a = 1;
580     int size = abs(ceil(fwhm * 0.6));
581     for(int y = -size; y <= size; y++ )
582     {
583         for(int x = -size; x <= size; x++ )
584         {
585             double value = a * exp( ( -4.0 * log(2.0) * pow(sqrt( pow(x, 2) + pow(y, 2) ), 2) ) / pow(fwhm, 2));
586             params->convFilter.append(value);
587         }
588     }
589 }
590 
getBuiltInProfiles()591 QList<Parameters> StellarSolver::getBuiltInProfiles()
592 {
593     QList<Parameters> profileList;
594 
595     Parameters defaultProfile;
596     defaultProfile.listName = "1-Default";
597     defaultProfile.description = "Default profile. Generic and not optimized for any specific purpose.";
598     profileList.append(defaultProfile);
599 
600     Parameters fastSolving;
601     fastSolving.listName = "2-SingleThreadSolving";
602     fastSolving.description = "Profile intended for Plate Solving telescopic sized images in a single CPU Thread";
603     fastSolving.multiAlgorithm = NOT_MULTI;
604     fastSolving.minwidth = 0.1;
605     fastSolving.maxwidth = 10;
606     fastSolving.keepNum = 50;
607     fastSolving.initialKeep = 500;
608     fastSolving.maxEllipse = 1.5;
609     createConvFilterFromFWHM(&fastSolving, 4);
610     profileList.append(fastSolving);
611 
612     Parameters parLargeSolving;
613     parLargeSolving.listName = "3-LargeScaleSolving";
614     parLargeSolving.description = "Profile intended for Plate Solving camera lens sized images";
615     parLargeSolving.minwidth = 10;
616     parLargeSolving.maxwidth = 180;
617     parLargeSolving.keepNum = 50;
618     parLargeSolving.initialKeep = 500;
619     parLargeSolving.maxEllipse = 1.5;
620     createConvFilterFromFWHM(&parLargeSolving, 4);
621     profileList.append(parLargeSolving);
622 
623     Parameters fastSmallSolving;
624     fastSmallSolving.listName = "4-SmallScaleSolving";
625     fastSmallSolving.description = "Profile intended for Plate Solving telescopic sized images";
626     fastSmallSolving.minwidth = 0.1;
627     fastSmallSolving.maxwidth = 10;
628     fastSmallSolving.keepNum = 50;
629     fastSmallSolving.initialKeep = 500;
630     fastSmallSolving.maxEllipse = 1.5;
631     createConvFilterFromFWHM(&fastSmallSolving, 4);
632     profileList.append(fastSmallSolving);
633 
634     Parameters stars;
635     stars.listName = "5-AllStars";
636     stars.description = "Profile for the source extraction of all the stars in an image.";
637     stars.maxEllipse = 1.5;
638     createConvFilterFromFWHM(&stars, 1);
639     stars.r_min = 2;
640     profileList.append(stars);
641 
642     Parameters smallStars;
643     smallStars.listName = "6-SmallSizedStars";
644     smallStars.description = "Profile optimized for source extraction of smaller stars.";
645     smallStars.maxEllipse = 1.5;
646     createConvFilterFromFWHM(&smallStars, 1);
647     smallStars.r_min = 2;
648     smallStars.maxSize = 5;
649     smallStars.initialKeep = 500;
650     smallStars.saturationLimit = 80;
651     profileList.append(smallStars);
652 
653     Parameters mid;
654     mid.listName = "7-MidSizedStars";
655     mid.description = "Profile optimized for source extraction of medium sized stars.";
656     mid.maxEllipse = 1.5;
657     mid.minarea = 20;
658     createConvFilterFromFWHM(&mid, 4);
659     mid.r_min = 5;
660     mid.removeDimmest = 20;
661     mid.minSize = 2;
662     mid.maxSize = 10;
663     mid.initialKeep = 500;
664     mid.saturationLimit = 80;
665     profileList.append(mid);
666 
667     Parameters big;
668     big.listName = "8-BigSizedStars";
669     big.description = "Profile optimized for source extraction of larger stars.";
670     big.maxEllipse = 1.5;
671     big.minarea = 40;
672     createConvFilterFromFWHM(&big, 8);
673     big.r_min = 20;
674     big.minSize = 5;
675     big.initialKeep = 500;
676     big.removeDimmest = 50;
677     profileList.append(big);
678 
679     return profileList;
680 }
681 
loadSavedOptionsProfiles(QString savedOptionsProfiles)682 QList<SSolver::Parameters> StellarSolver::loadSavedOptionsProfiles(QString savedOptionsProfiles)
683 {
684     QList<SSolver::Parameters> optionsList;
685     if(!QFileInfo(savedOptionsProfiles).exists())
686     {
687         return StellarSolver::getBuiltInProfiles();
688     }
689     QSettings settings(savedOptionsProfiles, QSettings::IniFormat);
690     QStringList groups = settings.childGroups();
691     foreach(QString group, groups)
692     {
693         settings.beginGroup(group);
694         QStringList keys = settings.childKeys();
695         QMap<QString, QVariant> map;
696         foreach(QString key, keys)
697             map.insert(key, settings.value(key));
698         SSolver::Parameters newParams = SSolver::Parameters::convertFromMap(map);
699         settings.endGroup();
700         optionsList.append(newParams);
701     }
702     return optionsList;
703 }
704 
setParameterProfile(SSolver::Parameters::ParametersProfile profile)705 void StellarSolver::setParameterProfile(SSolver::Parameters::ParametersProfile profile)
706 {
707     QList<Parameters> profileList = getBuiltInProfiles();
708     setParameters(profileList.at(profile));
709 }
710 
setUseSubframe(QRect frame)711 void StellarSolver::setUseSubframe(QRect frame)
712 {
713     int x = frame.x();
714     int y = frame.y();
715     int w = frame.width();
716     int h = frame.height();
717     if(w < 0)
718     {
719         x = x + w; //It's negative
720         w = -w;
721     }
722     if(h < 0)
723     {
724         y = y + h; //It's negative
725         h = -h;
726     }
727     if(x < 0)
728         x = 0;
729     if(y < 0)
730         y = 0;
731     if(x > m_Statistics.width)
732         x = m_Statistics.width;
733     if(y > m_Statistics.height)
734         y = m_Statistics.height;
735 
736     useSubframe = true;
737     m_Subframe = QRect(x, y, w, h);
738 }
739 
740 //This is a convenience function used to set all the scale parameters based on the FOV high and low values wit their units.
setSearchScale(double fov_low,double fov_high,const QString & scaleUnits)741 void StellarSolver::setSearchScale(double fov_low, double fov_high, const QString &scaleUnits)
742 {
743     if(scaleUnits == "dw" || scaleUnits == "degw" || scaleUnits == "degwidth")
744         setSearchScale(fov_low, fov_high, DEG_WIDTH);
745     if(scaleUnits == "app" || scaleUnits == "arcsecperpix")
746         setSearchScale(fov_low, fov_high, ARCSEC_PER_PIX);
747     if(scaleUnits == "aw" || scaleUnits == "amw" || scaleUnits == "arcminwidth")
748         setSearchScale(fov_low, fov_high, ARCMIN_WIDTH);
749     if(scaleUnits == "focalmm")
750         setSearchScale(fov_low, fov_high, FOCAL_MM);
751 }
752 
753 //This is a convenience function used to set all the scale parameters based on the FOV high and low values wit their units.
setSearchScale(double fov_low,double fov_high,ScaleUnits units)754 void StellarSolver::setSearchScale(double fov_low, double fov_high, ScaleUnits units)
755 {
756     m_UseScale = true;
757     //L
758     m_ScaleLow = fov_low;
759     //H
760     m_ScaleHigh = fov_high;
761     //u
762     m_ScaleUnit = units;
763 }
764 
765 //This is a convenience function used to set all the search position parameters based on the ra, dec, and radius
766 //Warning!!  This method accepts the RA in decimal form and then will convert it to degrees for Astrometry.net
setSearchPositionRaDec(double ra,double dec)767 void StellarSolver::setSearchPositionRaDec(double ra, double dec)
768 {
769     setSearchPositionInDegrees(ra * 15.0, dec);
770 }
771 
772 //This is a convenience function used to set all the search position parameters based on the ra, dec, and radius
773 //Warning!!  This method accepts the RA in degrees just like the DEC
setSearchPositionInDegrees(double ra,double dec)774 void StellarSolver::setSearchPositionInDegrees(double ra, double dec)
775 {
776     m_UsePosition = true;
777     //3
778     m_SearchRA = ra;
779     //4
780     m_SearchDE = dec;
781 }
782 
addPathToListIfExists(QStringList * list,QString path)783 void addPathToListIfExists(QStringList *list, QString path)
784 {
785     if(list)
786     {
787         if(QFileInfo(path).exists())
788             list->append(path);
789     }
790 }
791 
getDefaultIndexFolderPaths()792 QStringList StellarSolver::getDefaultIndexFolderPaths()
793 {
794     QStringList indexFilePaths;
795 #if defined(Q_OS_OSX)
796     //Mac Default location
797     addPathToListIfExists(&indexFilePaths, QDir::homePath() + "/Library/Application Support/Astrometry");
798     //Homebrew location
799     addPathToListIfExists(&indexFilePaths, "/usr/local/share/astrometry");
800 #elif defined(Q_OS_LINUX)
801     //Linux Default Location
802     addPathToListIfExists(&indexFilePaths, "/usr/share/astrometry/");
803     //Linux Local KStars Location
804     addPathToListIfExists(&indexFilePaths, QDir::homePath() + "/.local/share/kstars/astrometry/");
805 #elif defined(_WIN32)
806     //Windows Locations
807     addPathToListIfExists(&indexFilePaths, QDir::homePath() + "/AppData/Local/cygwin_ansvr/usr/share/astrometry/data");
808     addPathToListIfExists(&indexFilePaths, "C:/cygwin/usr/share/astrometry/data");
809 #endif
810     return indexFilePaths;
811 }
812 
813 
814 
getWCSCoord()815 FITSImage::wcs_point * StellarSolver::getWCSCoord()
816 {
817     if(hasWCS && hasWCSCoord)
818         return wcs_coord;
819     else
820         return nullptr;
821 }
822 
appendStarsRAandDEC(QList<FITSImage::Star> & stars)823 bool StellarSolver::appendStarsRAandDEC(QList<FITSImage::Star> &stars)
824 {
825     if(solverWithWCS)
826         return solverWithWCS->appendStarsRAandDEC(stars);
827     return false;
828 }
829 
830 //This function should get the system RAM in bytes.  I may revise it later to get the currently available RAM
831 //But from what I read, getting the Available RAM is inconsistent and buggy on many systems.
getAvailableRAM(double & availableRAM,double & totalRAM)832 bool StellarSolver::getAvailableRAM(double &availableRAM, double &totalRAM)
833 {
834 #if defined(Q_OS_OSX) || defined(Q_OS_FREEBSD)
835 #if defined(Q_OS_FREEBSD)
836 #ifdef HW_REALMEM
837     int mib [] = { CTL_HW, HW_REALMEM };
838 #else
839     int mib [] = { CTL_HW, HW_PHYSMEM };
840 #endif
841 #else
842     int mib [] = { CTL_HW, HW_MEMSIZE };
843 #endif
844     size_t length;
845     length = sizeof(int64_t);
846     int64_t RAMcheck;
847     if(sysctl(mib, 2, &RAMcheck, &length, NULL, 0))
848         return false; // On Error
849     //Until I can figure out how to get free RAM on Mac
850     availableRAM = RAMcheck;
851     totalRAM = RAMcheck;
852 #elif defined(Q_OS_LINUX)
853     QProcess p;
854     p.start("awk", QStringList() << "/MemFree/ { print $2 }" << "/proc/meminfo");
855     p.waitForFinished();
856     QString memory = p.readAllStandardOutput();
857     availableRAM = memory.toLong() * 1024.0; //It is in kB on this system
858 
859     p.start("awk", QStringList() << "/MemTotal/ { print $2 }" << "/proc/meminfo");
860     p.waitForFinished();
861     memory = p.readAllStandardOutput();
862     totalRAM = memory.toLong() * 1024.0; //It is in kB on this system
863     p.close();
864 #else
865     MEMORYSTATUSEX memory_status;
866     ZeroMemory(&memory_status, sizeof(MEMORYSTATUSEX));
867     memory_status.dwLength = sizeof(MEMORYSTATUSEX);
868     if (GlobalMemoryStatusEx(&memory_status))
869     {
870         availableRAM = memory_status.ullAvailPhys;
871         totalRAM = memory_status.ullTotalPhys;
872     }
873     else
874     {
875         return false;
876     }
877 #endif
878     return true;
879 }
880 
881 //This should determine if enough RAM is available to load all the index files in parallel
enoughRAMisAvailableFor(QStringList indexFolders)882 bool StellarSolver::enoughRAMisAvailableFor(QStringList indexFolders)
883 {
884     double totalSize = 0;
885 
886     foreach(QString folder, indexFolders)
887     {
888         QDir dir(folder);
889         if(dir.exists())
890         {
891             dir.setNameFilters(QStringList() << "*.fits" << "*.fit");
892             QFileInfoList indexInfoList = dir.entryInfoList();
893             foreach(QFileInfo indexInfo, indexInfoList)
894                 totalSize += indexInfo.size();
895         }
896 
897     }
898     double availableRAM = 0;
899     double totalRAM = 0;
900     getAvailableRAM(availableRAM, totalRAM);
901     if(availableRAM == 0)
902     {
903         if(m_SSLogLevel != LOG_OFF)
904             emit logOutput("Unable to determine system RAM for inParallel Option");
905         return false;
906     }
907     double bytesInGB = 1024.0 * 1024.0 *
908                        1024.0; // B -> KB -> MB -> GB , float to make sure it reports the answer with any decimals
909     if(m_SSLogLevel != LOG_OFF)
910     {
911         emit logOutput(
912             QString("Evaluating Installed RAM for inParallel Option.  Total Size of Index files: %1 GB, Installed RAM: %2 GB, Free RAM: %3 GB").arg(
913                 totalSize / bytesInGB).arg(totalRAM / bytesInGB).arg(availableRAM / bytesInGB));
914 #if defined(Q_OS_OSX)
915         emit logOutput("Note: Free RAM for now is reported as Installed RAM on MacOS until I figure out how to get available RAM");
916 #endif
917     }
918     return availableRAM > totalSize;
919 }
920 
921 // Taken from: http://www1.phys.vt.edu/~jhs/phys3154/snr20040108.pdf
snr(const FITSImage::Background & background,const FITSImage::Star & star,double gain)922 double StellarSolver::snr(const FITSImage::Background &background,
923                           const FITSImage::Star &star,
924                           double gain)
925 {
926     const double numPixelsInSkyEstimate = background.bw * background.bh;
927     const double varSky = background.globalrms * background.globalrms;
928 
929     if (numPixelsInSkyEstimate <= 0 || gain <= 0)
930         return 0;
931 
932     // It seems SEP flux subtracts out the background, so no need
933     // for numer = flux - star.numPixels * mean;
934     const double numer = star.flux;
935     const double denom = sqrt( numer / gain + star.numPixels * varSky  * (1 + 1 / numPixelsInSkyEstimate));
936     if (denom <= 0)
937         return 0;
938     return numer / denom;
939 
940 }
941