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 ¶m, 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 }