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

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 <math.h>
0012 #include <cmath>
0013 #include <QtConcurrent>
0014 
0015 #include "fits_debug.h"
0016 #include "fitsthresholddetector.h"
0017 #include "fitsdata.h"
0018 
0019 //void FITSThresholdDetector::configure(const QString &setting, const QVariant &value)
0020 //{
0021 //    if (!setting.compare("THRESHOLD_PERCENTAGE", Qt::CaseInsensitive))
0022 //        if (value.canConvert<int>())
0023 //            THRESHOLD_PERCENTAGE = value.value<int>();
0024 //}
0025 
0026 QFuture<bool> FITSThresholdDetector::findSources(QRect const &boundary)
0027 {
0028     FITSImage::Statistic const &stats = m_ImageData->getStatistics();
0029     switch (stats.dataType)
0030     {
0031         case TSHORT:
0032             return QtConcurrent::run(this, &FITSThresholdDetector::findOneStar<int16_t>, boundary);
0033 
0034         case TUSHORT:
0035             return QtConcurrent::run(this, &FITSThresholdDetector::findOneStar<uint16_t>, boundary);
0036 
0037         case TLONG:
0038             return QtConcurrent::run(this, &FITSThresholdDetector::findOneStar<int32_t>, boundary);
0039 
0040         case TULONG:
0041             return QtConcurrent::run(this, &FITSThresholdDetector::findOneStar<uint32_t>, boundary);
0042 
0043         case TFLOAT:
0044             return QtConcurrent::run(this, &FITSThresholdDetector::findOneStar<float>, boundary);
0045 
0046         case TLONGLONG:
0047             return QtConcurrent::run(this, &FITSThresholdDetector::findOneStar<int64_t>, boundary);
0048 
0049         case TDOUBLE:
0050             return QtConcurrent::run(this, &FITSThresholdDetector::findOneStar<double>, boundary);
0051 
0052         case TBYTE:
0053         default:
0054             return QtConcurrent::run(this, &FITSThresholdDetector::findOneStar<uint8_t>, boundary);
0055     }
0056 
0057 }
0058 
0059 template <typename T>
0060 bool FITSThresholdDetector::findOneStar(const QRect &boundary) const
0061 {
0062     FITSImage::Statistic const &stats = m_ImageData->getStatistics();
0063 
0064     int subX = boundary.x();
0065     int subY = boundary.y();
0066     int subW = subX + boundary.width();
0067     int subH = subY + boundary.height();
0068 
0069     float massX = 0, massY = 0, totalMass = 0;
0070 
0071     auto * buffer = reinterpret_cast<T const *>(m_ImageData->getImageBuffer());
0072 
0073     double THRESHOLD_PERCENTAGE = getValue("THRESHOLD_PERCENTAGE", 120.0).toDouble();
0074     // TODO replace magic number with something more useful to understand
0075     double threshold = stats.mean[0] * THRESHOLD_PERCENTAGE / 100.0;
0076 
0077     for (int y = subY; y < subH; y++)
0078     {
0079         for (int x = subX; x < subW; x++)
0080         {
0081             T pixel = buffer[x + y * stats.width];
0082             if (pixel > threshold)
0083             {
0084                 totalMass += pixel;
0085                 massX += x * pixel;
0086                 massY += y * pixel;
0087             }
0088         }
0089     }
0090 
0091     qCDebug(KSTARS_FITS) << "FITS: Weighted Center is X: " << massX / totalMass << " Y: " << massY / totalMass;
0092 
0093     auto * center  = new Edge;
0094     center->width = -1;
0095     center->x     = massX / totalMass + 0.5;
0096     center->y     = massY / totalMass + 0.5;
0097     center->HFR   = 1;
0098 
0099     // Maximum Radius
0100     int maxR = qMin(subW - 1, subH - 1) / 2;
0101 
0102     // Critical threshold
0103     double critical_threshold = threshold * 0.7;
0104     double running_threshold  = threshold;
0105 
0106     while (running_threshold >= critical_threshold)
0107     {
0108         for (int r = maxR; r > 1; r--)
0109         {
0110             int pass = 0;
0111 
0112             for (float theta = 0; theta < 2 * M_PI; theta += (2 * M_PI) / 10.0)
0113             {
0114                 int testX = center->x + std::cos(theta) * r;
0115                 int testY = center->y + std::sin(theta) * r;
0116 
0117                 // if out of bound, break;
0118                 if (testX < subX || testX > subW || testY < subY || testY > subH)
0119                     break;
0120 
0121                 if (buffer[testX + testY * stats.width] > running_threshold)
0122                     pass++;
0123             }
0124 
0125             //qDebug() << Q_FUNC_INFO << "Testing for radius " << r << " passes # " << pass << " @ threshold " << running_threshold;
0126             //if (pass >= 6)
0127             if (pass >= 5)
0128             {
0129                 center->width = r * 2;
0130                 break;
0131             }
0132         }
0133 
0134         if (center->width > 0)
0135             break;
0136 
0137         // Increase threshold fuzziness by 10%
0138         running_threshold -= running_threshold * 0.1;
0139     }
0140 
0141     // If no stars were detected
0142     if (center->width == -1)
0143     {
0144         delete center;
0145         return false;
0146     }
0147 
0148     // 30% fuzzy
0149     //center->width += center->width*0.3 * (running_threshold / threshold);
0150 
0151     double FSum = 0, HF = 0, TF = 0, min = stats.min[0];
0152     const double resolution = 1.0 / 20.0;
0153 
0154     int cen_y = qRound(center->y);
0155 
0156     double rightEdge = center->x + center->width / 2.0;
0157     double leftEdge  = center->x - center->width / 2.0;
0158 
0159     QVector<double> subPixels;
0160     subPixels.reserve(center->width / resolution);
0161 
0162     for (double x = leftEdge; x <= rightEdge; x += resolution)
0163     {
0164         //subPixels[x] = resolution * (image_buffer[static_cast<int>(floor(x)) + cen_y * stats.width] - min);
0165         double slice = resolution * (buffer[static_cast<int>(floor(x)) + cen_y * stats.width] - min);
0166         FSum += slice;
0167         subPixels.append(slice);
0168     }
0169 
0170     // Half flux
0171     HF = FSum / 2.0;
0172 
0173     //double subPixelCenter = center->x - fmod(center->x,resolution);
0174     int subPixelCenter = (center->width / resolution) / 2;
0175 
0176     // Start from center
0177     TF            = subPixels[subPixelCenter];
0178     double lastTF = TF;
0179     // Integrate flux along radius axis until we reach half flux
0180     //for (double k=resolution; k < (center->width/(2*resolution)); k += resolution)
0181     for (int k = 1; k < subPixelCenter; k++)
0182     {
0183         TF += subPixels[subPixelCenter + k];
0184         TF += subPixels[subPixelCenter - k];
0185 
0186         if (TF >= HF)
0187         {
0188             // We have two ways to calculate HFR. The first is the correct method but it can get quite variable within 10% due to random fluctuations of the measured star.
0189             // The second method is not truly HFR but is much more resistant to noise.
0190 
0191             // #1 Approximate HFR, accurate and reliable but quite variable to small changes in star flux
0192             center->HFR = (k - 1 + ((HF - lastTF) / (TF - lastTF)) * 2) * resolution;
0193 
0194             // #2 Not exactly HFR, but much more stable
0195             //center->HFR = (k*resolution) * (HF/TF);
0196             break;
0197         }
0198 
0199         lastTF = TF;
0200     }
0201 
0202     QList<Edge*> starCenters;
0203     starCenters.append(center);
0204     m_ImageData->setStarCenters(starCenters);
0205 
0206     return true;
0207 }