Kstars

fitsthresholddetector.cpp
1/*
2 SPDX-FileCopyrightText: 2004 Jasem Mutlaq
3 SPDX-FileCopyrightText: 2020 Eric Dejouhanet <eric.dejouhanet@gmail.com>
4
5 SPDX-License-Identifier: GPL-2.0-or-later
6
7 Some code fragments were adapted from Peter Kirchgessner's FITS plugin
8 SPDX-FileCopyrightText: Peter Kirchgessner <http://members.aol.com/pkirchg>
9*/
10
11#include <math.h>
12#include <cmath>
13#include <QtConcurrent>
14
15#include "fits_debug.h"
16#include "fitsthresholddetector.h"
17#include "fitsdata.h"
18
19//void FITSThresholdDetector::configure(const QString &setting, const QVariant &value)
20//{
21// if (!setting.compare("THRESHOLD_PERCENTAGE", Qt::CaseInsensitive))
22// if (value.canConvert<int>())
23// THRESHOLD_PERCENTAGE = value.value<int>();
24//}
25
26QFuture<bool> FITSThresholdDetector::findSources(QRect const &boundary)
27{
28 FITSImage::Statistic const &stats = m_ImageData->getStatistics();
29 switch (stats.dataType)
30 {
31#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
32 case TSHORT:
33 return QtConcurrent::run(&FITSThresholdDetector::findOneStar<int16_t>, this, boundary);
34
35 case TUSHORT:
36 return QtConcurrent::run(&FITSThresholdDetector::findOneStar<uint16_t>, this, boundary);
37
38 case TLONG:
39 return QtConcurrent::run(&FITSThresholdDetector::findOneStar<int32_t>, this, boundary);
40
41 case TULONG:
42 return QtConcurrent::run(&FITSThresholdDetector::findOneStar<uint32_t>, this, boundary);
43
44 case TFLOAT:
45 return QtConcurrent::run(&FITSThresholdDetector::findOneStar<float>, this, boundary);
46
47 case TLONGLONG:
48 return QtConcurrent::run(&FITSThresholdDetector::findOneStar<int64_t>, this, boundary);
49
50 case TDOUBLE:
51 return QtConcurrent::run(&FITSThresholdDetector::findOneStar<double>, this, boundary);
52
53 case TBYTE:
54 default:
55 return QtConcurrent::run(&FITSThresholdDetector::findOneStar<uint8_t>, this, boundary);
56#else
57 case TSHORT:
58 return QtConcurrent::run(this, &FITSThresholdDetector::findOneStar<int16_t>, boundary);
59
60 case TUSHORT:
61 return QtConcurrent::run(this, &FITSThresholdDetector::findOneStar<uint16_t>, boundary);
62
63 case TLONG:
64 return QtConcurrent::run(this, &FITSThresholdDetector::findOneStar<int32_t>, boundary);
65
66 case TULONG:
67 return QtConcurrent::run(this, &FITSThresholdDetector::findOneStar<uint32_t>, boundary);
68
69 case TFLOAT:
70 return QtConcurrent::run(this, &FITSThresholdDetector::findOneStar<float>, boundary);
71
72 case TLONGLONG:
73 return QtConcurrent::run(this, &FITSThresholdDetector::findOneStar<int64_t>, boundary);
74
75 case TDOUBLE:
76 return QtConcurrent::run(this, &FITSThresholdDetector::findOneStar<double>, boundary);
77
78 case TBYTE:
79 default:
80 return QtConcurrent::run(this, &FITSThresholdDetector::findOneStar<uint8_t>, boundary);
81#endif
82
83 }
84
85}
86
87template <typename T>
88bool FITSThresholdDetector::findOneStar(const QRect &boundary) const
89{
90 FITSImage::Statistic const &stats = m_ImageData->getStatistics();
91
92 int subX = boundary.x();
93 int subY = boundary.y();
94 int subW = subX + boundary.width();
95 int subH = subY + boundary.height();
96
97 float massX = 0, massY = 0, totalMass = 0;
98
99 auto * buffer = reinterpret_cast<T const *>(m_ImageData->getImageBuffer());
100
101 double THRESHOLD_PERCENTAGE = getValue("THRESHOLD_PERCENTAGE", 120.0).toDouble();
102 // TODO replace magic number with something more useful to understand
103 double threshold = stats.mean[0] * THRESHOLD_PERCENTAGE / 100.0;
104
105 for (int y = subY; y < subH; y++)
106 {
107 for (int x = subX; x < subW; x++)
108 {
109 T pixel = buffer[x + y * stats.width];
110 if (pixel > threshold)
111 {
112 totalMass += pixel;
113 massX += x * pixel;
114 massY += y * pixel;
115 }
116 }
117 }
118
119 qCDebug(KSTARS_FITS) << "FITS: Weighted Center is X: " << massX / totalMass << " Y: " << massY / totalMass;
120
121 auto * center = new Edge;
122 center->width = -1;
123 center->x = massX / totalMass + 0.5;
124 center->y = massY / totalMass + 0.5;
125 center->HFR = 1;
126
127 // Maximum Radius
128 int maxR = qMin(subW - 1, subH - 1) / 2;
129
130 // Critical threshold
131 double critical_threshold = threshold * 0.7;
132 double running_threshold = threshold;
133
134 while (running_threshold >= critical_threshold)
135 {
136 for (int r = maxR; r > 1; r--)
137 {
138 int pass = 0;
139
140 for (float theta = 0; theta < 2 * M_PI; theta += (2 * M_PI) / 10.0)
141 {
142 int testX = center->x + std::cos(theta) * r;
143 int testY = center->y + std::sin(theta) * r;
144
145 // if out of bound, break;
146 if (testX < subX || testX > subW || testY < subY || testY > subH)
147 break;
148
149 if (buffer[testX + testY * stats.width] > running_threshold)
150 pass++;
151 }
152
153 //qDebug() << Q_FUNC_INFO << "Testing for radius " << r << " passes # " << pass << " @ threshold " << running_threshold;
154 //if (pass >= 6)
155 if (pass >= 5)
156 {
157 center->width = r * 2;
158 break;
159 }
160 }
161
162 if (center->width > 0)
163 break;
164
165 // Increase threshold fuzziness by 10%
166 running_threshold -= running_threshold * 0.1;
167 }
168
169 // If no stars were detected
170 if (center->width == -1)
171 {
172 delete center;
173 return false;
174 }
175
176 // 30% fuzzy
177 //center->width += center->width*0.3 * (running_threshold / threshold);
178
179 double FSum = 0, HF = 0, TF = 0, min = stats.min[0];
180 const double resolution = 1.0 / 20.0;
181
182 int cen_y = qRound(center->y);
183
184 double rightEdge = center->x + center->width / 2.0;
185 double leftEdge = center->x - center->width / 2.0;
186
187 QVector<double> subPixels;
188 subPixels.reserve(center->width / resolution);
189
190 for (double x = leftEdge; x <= rightEdge; x += resolution)
191 {
192 //subPixels[x] = resolution * (image_buffer[static_cast<int>(floor(x)) + cen_y * stats.width] - min);
193 double slice = resolution * (buffer[static_cast<int>(floor(x)) + cen_y * stats.width] - min);
194 FSum += slice;
195 subPixels.append(slice);
196 }
197
198 // Half flux
199 HF = FSum / 2.0;
200
201 //double subPixelCenter = center->x - fmod(center->x,resolution);
202 int subPixelCenter = (center->width / resolution) / 2;
203
204 // Start from center
205 TF = subPixels[subPixelCenter];
206 double lastTF = TF;
207 // Integrate flux along radius axis until we reach half flux
208 //for (double k=resolution; k < (center->width/(2*resolution)); k += resolution)
209 for (int k = 1; k < subPixelCenter; k++)
210 {
211 TF += subPixels[subPixelCenter + k];
212 TF += subPixels[subPixelCenter - k];
213
214 if (TF >= HF)
215 {
216 // 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.
217 // The second method is not truly HFR but is much more resistant to noise.
218
219 // #1 Approximate HFR, accurate and reliable but quite variable to small changes in star flux
220 center->HFR = (k - 1 + ((HF - lastTF) / (TF - lastTF)) * 2) * resolution;
221
222 // #2 Not exactly HFR, but much more stable
223 //center->HFR = (k*resolution) * (HF/TF);
224 break;
225 }
226
227 lastTF = TF;
228 }
229
230 QList<Edge*> starCenters;
231 starCenters.append(center);
232 m_ImageData->setStarCenters(starCenters);
233
234 return true;
235}
void append(QList< T > &&value)
void reserve(qsizetype size)
int height() const const
int width() const const
int x() const const
int y() const const
QTextStream & center(QTextStream &stream)
QFuture< T > run(Function function,...)
double toDouble(bool *ok) const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Oct 11 2024 12:15:12 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.