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