File indexing completed on 2024-04-14 14:10:37

0001 /*
0002     SPDX-FileCopyrightText: 2004 Jasem Mutlaq
0003     SPDX-FileCopyrightText: 2020 Eric Dejouhanet <eric.dejouhanet@gmail.com>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 
0007     Some code fragments were adapted from Peter Kirchgessner's FITS plugin
0008     SPDX-FileCopyrightText: Peter Kirchgessner <http://members.aol.com/pkirchg>
0009 */
0010 
0011 #include "config-kstars.h"
0012 
0013 #include "fits_debug.h"
0014 #include "fitssepdetector.h"
0015 #include "fitsdata.h"
0016 #include "Options.h"
0017 #include "kspaths.h"
0018 
0019 #include <memory>
0020 #include <math.h>
0021 #include <QPointer>
0022 #include <QtConcurrent>
0023 
0024 #ifdef HAVE_STELLARSOLVER
0025 #include "ekos/auxiliary/stellarsolverprofileeditor.h"
0026 #include <stellarsolver.h>
0027 #else
0028 #include <cstring>
0029 #include "sep/sep.h"
0030 #endif
0031 
0032 //void FITSSEPDetector::configure(const QString &param, const QVariant &value)
0033 //{
0034 //    if (param == "numStars")
0035 //        numStars = value.toInt();
0036 //    else if (param == "fractionRemoved")
0037 //        fractionRemoved = value.toDouble();
0038 //    else if (param == "deblendNThresh")
0039 //        deblendNThresh = value.toInt();
0040 //    else if (param == "deblendMincont")
0041 //        deblendMincont = value.toDouble();
0042 //    else if (param == "radiusIsBoundary")
0043 //        radiusIsBoundary = value.toBool();
0044 //    else
0045 //        qCDebug(KSTARS_FITS) << "Bad SEP Parameter!!!!! " << param;
0046 //}
0047 
0048 // TODO: (hy 4/11/2020)
0049 // The api into these star detection methods should be generalized so that various parameters
0050 // could be controlled by the caller. For instance, saturation removal, removal of N% of the largest stars
0051 // number of stars desired, some parameters to the SEP algorithms, as all of these may have different
0052 // optimal values for different uses. Also, there may be other desired outputs
0053 // (e.g. unfiltered number of stars detected,background sky level). Waiting on rlancaste's
0054 // investigations into SEP before doing this.
0055 
0056 QFuture<bool> FITSSEPDetector::findSources(QRect const &boundary)
0057 {
0058     return QtConcurrent::run(this, &FITSSEPDetector::findSourcesAndBackground, boundary);
0059 }
0060 
0061 bool FITSSEPDetector::findSourcesAndBackground(QRect const &boundary)
0062 {
0063 #ifndef HAVE_STELLARSOLVER
0064     Q_UNUSED(boundary)
0065     return false;
0066 #else
0067     QList<Edge*> starCenters;
0068     SkyBackground skyBG;
0069     int maxStarsCount = getValue("maxStarsCount", 100000).toInt();
0070 
0071     int optionsProfileIndex = getValue("optionsProfileIndex", -1).toInt();
0072     Ekos::ProfileGroup group = static_cast<Ekos::ProfileGroup>(getValue("optionsProfileGroup", 1).toInt());
0073     QScopedPointer<StellarSolver, QScopedPointerDeleteLater> solver(new StellarSolver(m_ImageData->getStatistics(),
0074             m_ImageData->getImageBuffer()));
0075     QString filename = "";
0076     QPointer<FITSData> image(m_ImageData);
0077     switch(group)
0078     {
0079         case Ekos::AlignProfiles:
0080             //So it should not be here if it is Align.
0081             break;
0082         case Ekos::GuideProfiles:
0083             filename = "SavedGuideProfiles.ini";
0084             break;
0085         case Ekos::FocusProfiles:
0086             filename = "SavedFocusProfiles.ini";
0087             break;
0088         case Ekos::HFRProfiles:
0089             filename = "SavedHFRProfiles.ini";
0090             break;
0091     }
0092 
0093     QString savedOptionsProfiles = QDir(KSPaths::writableLocation(QStandardPaths::AppLocalDataLocation)).filePath(filename);
0094     QList<SSolver::Parameters> optionsList;
0095     if(QFile(savedOptionsProfiles).exists())
0096         optionsList = StellarSolver::loadSavedOptionsProfiles(savedOptionsProfiles);
0097     else
0098     {
0099         switch(group)
0100         {
0101             case Ekos::AlignProfiles:
0102                 optionsList = Ekos::getDefaultAlignOptionsProfiles();
0103                 break;
0104             case Ekos::GuideProfiles:
0105                 optionsList = Ekos::getDefaultGuideOptionsProfiles();
0106                 break;
0107             case Ekos::FocusProfiles:
0108                 optionsList = Ekos::getDefaultFocusOptionsProfiles();
0109                 break;
0110             case Ekos::HFRProfiles:
0111                 optionsList = Ekos::getDefaultHFROptionsProfiles();
0112                 break;
0113         }
0114     }
0115     if (optionsProfileIndex >= 0 && optionsList.count() > optionsProfileIndex)
0116     {
0117         auto params = optionsList[optionsProfileIndex];
0118         params.partition = Options::stellarSolverPartition();
0119         solver->setParameters(params);
0120         qCDebug(KSTARS_FITS) << "Sextract with: " << optionsList[optionsProfileIndex].listName;
0121     }
0122     else
0123     {
0124         auto params = SSolver::Parameters();  // This is default
0125         params.partition = Options::stellarSolverPartition();
0126         solver->setParameters(params);
0127     }
0128 
0129     QList<FITSImage::Star> stars;
0130     const bool runHFR = group != Ekos::AlignProfiles;
0131 
0132     solver->setLogLevel(SSolver::LOG_NONE);
0133     solver->setSSLogLevel(SSolver::LOG_OFF);
0134 
0135     if (boundary.isValid())
0136         solver->extract(runHFR, boundary);
0137     else
0138         solver->extract(runHFR);
0139 
0140     stars = solver->getStarList();
0141 
0142     // If m_ImageData goes out of scope, also return.
0143     if (stars.empty() || image.isNull())
0144         return false;
0145 
0146     auto bg = solver->getBackground();
0147 
0148     skyBG.mean = bg.global;
0149     skyBG.sigma = bg.globalrms;
0150     skyBG.numPixelsInSkyEstimate = bg.bw * bg.bh;
0151     skyBG.setStarsDetected(bg.num_stars_detected);
0152     m_ImageData->setSkyBackground(skyBG);
0153 
0154     //There is more information that can be obtained by the Stellarsolver->
0155     //Background info, Star positions(if a plate solve was done before), etc
0156     //The information is available as long as the StellarSolver exists.
0157 
0158     // Let's sort edges, starting with widest
0159     if (runHFR)
0160         std::sort(stars.begin(), stars.end(), [](const FITSImage::Star & star1, const FITSImage::Star & star2) -> bool { return star1.HFR > star2.HFR;});
0161     else
0162         std::sort(stars.begin(), stars.end(), [](const FITSImage::Star & star1, const FITSImage::Star & star2) -> bool { return star1.flux > star2.flux;});
0163 
0164 
0165     // Take only the first maxNumCenters stars
0166     int starCount = qMin(maxStarsCount, stars.count());
0167     starCenters.reserve(starCount);
0168     for (int i = 0; i < starCount; i++)
0169     {
0170         Edge *oneEdge = new Edge();
0171         oneEdge->x = stars[i].x;
0172         oneEdge->y = stars[i].y;
0173         oneEdge->val = stars[i].peak;
0174         oneEdge->sum = stars[i].flux;
0175         oneEdge->HFR = stars[i].HFR;
0176         oneEdge->width = stars[i].a;
0177         oneEdge->numPixels = stars[i].numPixels;
0178         if (stars[i].a > 0)
0179             // See page 63 to find the ellipticity equation for SEP.
0180             // http://astroa.physics.metu.edu.tr/MANUALS/sextractor/Guide2source_extractor.pdf
0181             oneEdge->ellipticity = 1 - stars[i].b / stars[i].a;
0182         else
0183             oneEdge->ellipticity = 0;
0184 
0185         starCenters.append(oneEdge);
0186     }
0187     m_ImageData->setStarCenters(starCenters);
0188     return true;
0189 #endif
0190 }
0191 
0192 template <typename T>
0193 void FITSSEPDetector::getFloatBuffer(float * buffer, int x, int y, int w, int h, FITSData const *data) const
0194 {
0195     auto * rawBuffer = reinterpret_cast<T const *>(data->getImageBuffer());
0196 
0197     if (buffer == nullptr)
0198         return;
0199 
0200     float * floatPtr = buffer;
0201 
0202     int x2 = x + w;
0203     int y2 = y + h;
0204 
0205     FITSImage::Statistic const &stats = data->getStatistics();
0206 
0207     for (int y1 = y; y1 < y2; y1++)
0208     {
0209         int offset = y1 * stats.width;
0210         for (int x1 = x; x1 < x2; x1++)
0211         {
0212             *floatPtr++ = rawBuffer[offset + x1];
0213         }
0214     }
0215 }
0216 
0217 SkyBackground::SkyBackground(double mean_, double sigma_, double numPixels_)
0218 {
0219     initialize(mean_, sigma_, numPixels_);
0220 }
0221 
0222 void SkyBackground::initialize(double mean_, double sigma_,
0223                                double numPixelsInSkyEstimate_, int numStars_)
0224 {
0225     mean = mean_;
0226     sigma = sigma_;
0227     numPixelsInSkyEstimate = numPixelsInSkyEstimate_;
0228     varSky = sigma_ * sigma_;
0229     starsDetected = numStars_;
0230 }
0231 
0232 // Taken from: http://www1.phys.vt.edu/~jhs/phys3154/snr20040108.pdf
0233 double SkyBackground::SNR(double flux, double numPixels, double gain) const
0234 {
0235     if (numPixelsInSkyEstimate <= 0 || gain <= 0)
0236         return 0;
0237     // const double numer = flux - numPixels * mean;
0238     const double numer = flux;  // It seems SEP flux subtracts out the background.
0239     const double denom = sqrt( numer / gain + numPixels * varSky  * (1 + 1 / numPixelsInSkyEstimate));
0240     if (denom <= 0)
0241         return 0;
0242     return numer / denom;
0243 
0244 }