File indexing completed on 2024-05-12 15:23:42

0001 /*  Algorithms used in the internal guider.
0002     Copyright (C) 2012 Andrew Stepanenko
0003 
0004     Modified by Jasem Mutlaq for KStars.
0005     SPDX-FileCopyrightText: Jasem Mutlaq <mutlaqja@ikarustech.com>
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008  */
0009 
0010 #include "guidealgorithms.h"
0011 
0012 #include <set>
0013 #include <QObject>
0014 
0015 #include "ekos_guide_debug.h"
0016 #include "ekos/auxiliary/stellarsolverprofileeditor.h"
0017 #include "Options.h"
0018 #include "fitsviewer/fitsdata.h"
0019 
0020 #define SMART_THRESHOLD    0
0021 #define SEP_THRESHOLD      1
0022 #define CENTROID_THRESHOLD 2
0023 #define AUTO_THRESHOLD     3
0024 #define NO_THRESHOLD       4
0025 #define SEP_MULTISTAR      5
0026 
0027 // smart threshold algorithm param
0028 // width of outer frame for background calculation
0029 #define SMART_FRAME_WIDTH 4
0030 // cut-factor above average threshold
0031 #define SMART_CUT_FACTOR 0.1
0032 
0033 struct Peak
0034 {
0035     int x;
0036     int y;
0037     float val;
0038 
0039     Peak() = default;
0040     Peak(int x_, int y_, float val_) : x(x_), y(y_), val(val_) { }
0041     bool operator<(const Peak &rhs) const
0042     {
0043         return val < rhs.val;
0044     }
0045 };
0046 
0047 // JM: Why not use QPoint?
0048 typedef struct
0049 {
0050     int x, y;
0051 } point_t;
0052 
0053 namespace
0054 {
0055 
0056 void psf_conv(float *dst, const float *src, int width, int height)
0057 {
0058     //dst.Init(src.Size);
0059 
0060     //                       A      B1     B2    C1     C2    C3     D1     D2     D3
0061     const double PSF[] = { 0.906, 0.584, 0.365, .117, .049, -0.05, -.064, -.074, -.094 };
0062 
0063     //memset(dst.px, 0, src.NPixels * sizeof(float));
0064 
0065     /* PSF Grid is:
0066     D3 D3 D3 D3 D3 D3 D3 D3 D3
0067     D3 D3 D3 D2 D1 D2 D3 D3 D3
0068     D3 D3 C3 C2 C1 C2 C3 D3 D3
0069     D3 D2 C2 B2 B1 B2 C2 D2 D3
0070     D3 D1 C1 B1 A  B1 C1 D1 D3
0071     D3 D2 C2 B2 B1 B2 C2 D2 D3
0072     D3 D3 C3 C2 C1 C2 C3 D3 D3
0073     D3 D3 D3 D2 D1 D2 D3 D3 D3
0074     D3 D3 D3 D3 D3 D3 D3 D3 D3
0075 
0076     1@A
0077     4@B1, B2, C1, C3, D1
0078     8@C2, D2
0079     44 * D3
0080     */
0081 
0082     int psf_size = 4;
0083 
0084     for (int y = psf_size; y < height - psf_size; y++)
0085     {
0086         for (int x = psf_size; x < width - psf_size; x++)
0087         {
0088             float A, B1, B2, C1, C2, C3, D1, D2, D3;
0089 
0090 #define PX(dx, dy) *(src + width * (y + (dy)) + x + (dx))
0091             A =  PX(+0, +0);
0092             B1 = PX(+0, -1) + PX(+0, +1) + PX(+1, +0) + PX(-1, +0);
0093             B2 = PX(-1, -1) + PX(+1, -1) + PX(-1, +1) + PX(+1, +1);
0094             C1 = PX(+0, -2) + PX(-2, +0) + PX(+2, +0) + PX(+0, +2);
0095             C2 = PX(-1, -2) + PX(+1, -2) + PX(-2, -1) + PX(+2, -1) + PX(-2, +1) + PX(+2, +1) + PX(-1, +2) + PX(+1, +2);
0096             C3 = PX(-2, -2) + PX(+2, -2) + PX(-2, +2) + PX(+2, +2);
0097             D1 = PX(+0, -3) + PX(-3, +0) + PX(+3, +0) + PX(+0, +3);
0098             D2 = PX(-1, -3) + PX(+1, -3) + PX(-3, -1) + PX(+3, -1) + PX(-3, +1) + PX(+3, +1) + PX(-1, +3) + PX(+1, +3);
0099             D3 = PX(-4, -2) + PX(-3, -2) + PX(+3, -2) + PX(+4, -2) + PX(-4, -1) + PX(+4, -1) + PX(-4, +0) + PX(+4, +0) + PX(-4,
0100                     +1) + PX(+4, +1) + PX(-4, +2) + PX(-3, +2) + PX(+3, +2) + PX(+4, +2);
0101 #undef PX
0102             int i;
0103             const float *uptr;
0104 
0105             uptr = src + width * (y - 4) + (x - 4);
0106             for (i = 0; i < 9; i++)
0107                 D3 += *uptr++;
0108 
0109             uptr = src + width * (y - 3) + (x - 4);
0110             for (i = 0; i < 3; i++)
0111                 D3 += *uptr++;
0112             uptr += 3;
0113             for (i = 0; i < 3; i++)
0114                 D3 += *uptr++;
0115 
0116             uptr = src + width * (y + 3) + (x - 4);
0117             for (i = 0; i < 3; i++)
0118                 D3 += *uptr++;
0119             uptr += 3;
0120             for (i = 0; i < 3; i++)
0121                 D3 += *uptr++;
0122 
0123             uptr = src + width * (y + 4) + (x - 4);
0124             for (i = 0; i < 9; i++)
0125                 D3 += *uptr++;
0126 
0127             double mean = (A + B1 + B2 + C1 + C2 + C3 + D1 + D2 + D3) / 81.0;
0128             double PSF_fit = PSF[0] * (A - mean) + PSF[1] * (B1 - 4.0 * mean) + PSF[2] * (B2 - 4.0 * mean) +
0129                              PSF[3] * (C1 - 4.0 * mean) + PSF[4] * (C2 - 8.0 * mean) + PSF[5] * (C3 - 4.0 * mean) +
0130                              PSF[6] * (D1 - 4.0 * mean) + PSF[7] * (D2 - 8.0 * mean) + PSF[8] * (D3 - 44.0 * mean);
0131 
0132             dst[width * y + x] = (float) PSF_fit;
0133         }
0134     }
0135 }
0136 
0137 void GetStats(double *mean, double *stdev, int width, const float *img, const QRect &win)
0138 {
0139     // Determine the mean and standard deviation
0140     double sum = 0.0;
0141     double a = 0.0;
0142     double q = 0.0;
0143     double k = 1.0;
0144     double km1 = 0.0;
0145 
0146     const float *p0 = img + win.top() * width + win.left();
0147     for (int y = 0; y < win.height(); y++)
0148     {
0149         const float *end = p0 + win.height();
0150         for (const float *p = p0; p < end; p++)
0151         {
0152             double const x = (double) * p;
0153             sum += x;
0154             double const a0 = a;
0155             a += (x - a) / k;
0156             q += (x - a0) * (x - a);
0157             km1 = k;
0158             k += 1.0;
0159         }
0160         p0 += width;
0161     }
0162 
0163     *mean = sum / km1;
0164     *stdev = sqrt(q / km1);
0165 }
0166 
0167 void RemoveItems(std::set<Peak> &stars, const std::set<int> &to_erase)
0168 {
0169     int n = 0;
0170     for (std::set<Peak>::iterator it = stars.begin(); it != stars.end(); n++)
0171     {
0172         if (to_erase.find(n) != to_erase.end())
0173         {
0174             std::set<Peak>::iterator next = it;
0175             ++next;
0176             stars.erase(it);
0177             it = next;
0178         }
0179         else
0180             ++it;
0181     }
0182 }
0183 
0184 float *createFloatImage(const QSharedPointer<FITSData> &target)
0185 {
0186     QSharedPointer<FITSData> imageData;
0187 
0188     /*************
0189     if (target.isNull())
0190         imageData = m_ImageData;
0191     else
0192     *********/ // shouldn't be null
0193     imageData = target;
0194 
0195     // #1 Convert to float array
0196     // We only process 1st plane if it is a color image
0197     uint32_t imgSize = imageData->width() * imageData->height();
0198     float *imgFloat  = new float[imgSize];
0199 
0200     if (imgFloat == nullptr)
0201     {
0202         qCritical() << "Not enough memory for float image array!";
0203         return nullptr;
0204     }
0205 
0206     switch (imageData->getStatistics().dataType)
0207     {
0208         case TBYTE:
0209         {
0210             uint8_t const *buffer = imageData->getImageBuffer();
0211             for (uint32_t i = 0; i < imgSize; i++)
0212                 imgFloat[i] = buffer[i];
0213         }
0214         break;
0215 
0216         case TSHORT:
0217         {
0218             int16_t const *buffer = reinterpret_cast<int16_t const *>(imageData->getImageBuffer());
0219             for (uint32_t i = 0; i < imgSize; i++)
0220                 imgFloat[i] = buffer[i];
0221         }
0222         break;
0223 
0224         case TUSHORT:
0225         {
0226             uint16_t const *buffer = reinterpret_cast<uint16_t const*>(imageData->getImageBuffer());
0227             for (uint32_t i = 0; i < imgSize; i++)
0228                 imgFloat[i] = buffer[i];
0229         }
0230         break;
0231 
0232         case TLONG:
0233         {
0234             int32_t const *buffer = reinterpret_cast<int32_t const*>(imageData->getImageBuffer());
0235             for (uint32_t i = 0; i < imgSize; i++)
0236                 imgFloat[i] = buffer[i];
0237         }
0238         break;
0239 
0240         case TULONG:
0241         {
0242             uint32_t const *buffer = reinterpret_cast<uint32_t const*>(imageData->getImageBuffer());
0243             for (uint32_t i = 0; i < imgSize; i++)
0244                 imgFloat[i] = buffer[i];
0245         }
0246         break;
0247 
0248         case TFLOAT:
0249         {
0250             float const *buffer = reinterpret_cast<float const*>(imageData->getImageBuffer());
0251             for (uint32_t i = 0; i < imgSize; i++)
0252                 imgFloat[i] = buffer[i];
0253         }
0254         break;
0255 
0256         case TLONGLONG:
0257         {
0258             int64_t const *buffer = reinterpret_cast<int64_t const*>(imageData->getImageBuffer());
0259             for (uint32_t i = 0; i < imgSize; i++)
0260                 imgFloat[i] = buffer[i];
0261         }
0262         break;
0263 
0264         case TDOUBLE:
0265         {
0266             double const *buffer = reinterpret_cast<double const*>(imageData->getImageBuffer());
0267             for (uint32_t i = 0; i < imgSize; i++)
0268                 imgFloat[i] = buffer[i];
0269         }
0270         break;
0271 
0272         default:
0273             delete[] imgFloat;
0274             return nullptr;
0275     }
0276 
0277     return imgFloat;
0278 }
0279 
0280 }  // namespace
0281 
0282 // Based on PHD2 algorithm
0283 QList<Edge*> GuideAlgorithms::detectStars(const QSharedPointer<FITSData> &imageData, const QRect &trackingBox)
0284 {
0285     //Debug.Write(wxString::Format("Star::AutoFind called with edgeAllowance = %d searchRegion = %d\n", extraEdgeAllowance, searchRegion));
0286 
0287     // run a 3x3 median first to eliminate hot pixels
0288     //usImage smoothed;
0289     //smoothed.CopyFrom(image);
0290     //Median3(smoothed);
0291     constexpr int extraEdgeAllowance = 0;
0292     const QSharedPointer<FITSData> smoothed(new FITSData(imageData));
0293     smoothed->applyFilter(FITS_MEDIAN);
0294 
0295     int searchRegion = trackingBox.width();
0296 
0297     int subW = smoothed->width();
0298     int subH = smoothed->height();
0299     int size = subW * subH;
0300 
0301     // convert to floating point
0302     float *conv = createFloatImage(smoothed);
0303 
0304     // run the PSF convolution
0305     {
0306         float *tmp = new float[size] {};
0307         psf_conv(tmp, conv, subW, subH);
0308         delete [] conv;
0309         // Swap
0310         conv = tmp;
0311     }
0312 
0313     enum { CONV_RADIUS = 4 };
0314     int dw = subW;      // width of the downsampled image
0315     int dh = subH;     // height of the downsampled image
0316     QRect convRect(CONV_RADIUS, CONV_RADIUS, dw - 2 * CONV_RADIUS, dh - 2 * CONV_RADIUS);  // region containing valid data
0317 
0318     enum { TOP_N = 100 };  // keep track of the brightest stars
0319     std::set<Peak> stars;  // sorted by ascending intensity
0320 
0321     double global_mean, global_stdev;
0322     GetStats(&global_mean, &global_stdev, subW, conv, convRect);
0323 
0324     //Debug.Write(wxString::Format("AutoFind: global mean = %.1f, stdev %.1f\n", global_mean, global_stdev));
0325 
0326     const double threshold = 0.1;
0327     //Debug.Write(wxString::Format("AutoFind: using threshold = %.1f\n", threshold));
0328 
0329     // find each local maximum
0330     int srch = 4;
0331     for (int y = convRect.top() + srch; y <= convRect.bottom() - srch; y++)
0332     {
0333         for (int x = convRect.left() + srch; x <= convRect.right() - srch; x++)
0334         {
0335             float val = conv[dw * y + x];
0336             bool ismax = false;
0337             if (val > 0.0)
0338             {
0339                 ismax = true;
0340                 for (int j = -srch; j <= srch; j++)
0341                 {
0342                     for (int i = -srch; i <= srch; i++)
0343                     {
0344                         if (i == 0 && j == 0)
0345                             continue;
0346                         if (conv[dw * (y + j) + (x + i)] > val)
0347                         {
0348                             ismax = false;
0349                             break;
0350                         }
0351                     }
0352                 }
0353             }
0354             if (!ismax)
0355                 continue;
0356 
0357             // compare local maximum to mean value of surrounding pixels
0358             const int local = 7;
0359             double local_mean, local_stdev;
0360             QRect localRect(x - local, y - local, 2 * local + 1, 2 * local + 1);
0361             localRect = localRect.intersected(convRect);
0362             GetStats(&local_mean, &local_stdev, subW, conv, localRect);
0363 
0364             // this is our measure of star intensity
0365             double h = (val - local_mean) / global_stdev;
0366 
0367             if (h < threshold)
0368             {
0369                 //  Debug.Write(wxString::Format("AG: local max REJECT [%d, %d] PSF %.1f SNR %.1f\n", imgx, imgy, val, SNR));
0370                 continue;
0371             }
0372 
0373             // coordinates on the original image
0374             int downsample = 1;
0375             int imgx = x * downsample + downsample / 2;
0376             int imgy = y * downsample + downsample / 2;
0377 
0378             stars.insert(Peak(imgx, imgy, h));
0379             if (stars.size() > TOP_N)
0380                 stars.erase(stars.begin());
0381         }
0382     }
0383 
0384     //for (std::set<Peak>::const_reverse_iterator it = stars.rbegin(); it != stars.rend(); ++it)
0385     //qCDebug(KSTARS_EKOS_GUIDE) << "AutoFind: local max [" << it->x << "," << it->y << "]" << it->val;
0386 
0387     // merge stars that are very close into a single star
0388     {
0389         const int minlimitsq = 5 * 5;
0390 repeat:
0391         for (std::set<Peak>::const_iterator a = stars.begin(); a != stars.end(); ++a)
0392         {
0393             std::set<Peak>::const_iterator b = a;
0394             ++b;
0395             for (; b != stars.end(); ++b)
0396             {
0397                 int dx = a->x - b->x;
0398                 int dy = a->y - b->y;
0399                 int d2 = dx * dx + dy * dy;
0400                 if (d2 < minlimitsq)
0401                 {
0402                     // very close, treat as single star
0403                     //Debug.Write(wxString::Format("AutoFind: merge [%d, %d] %.1f - [%d, %d] %.1f\n", a->x, a->y, a->val, b->x, b->y, b->val));
0404                     // erase the dimmer one
0405                     stars.erase(a);
0406                     goto repeat;
0407                 }
0408             }
0409         }
0410     }
0411 
0412     // exclude stars that would fit within a single searchRegion box
0413     {
0414         // build a list of stars to be excluded
0415         std::set<int> to_erase;
0416         const int extra = 5; // extra safety margin
0417         const int fullw = searchRegion + extra;
0418         for (std::set<Peak>::const_iterator a = stars.begin(); a != stars.end(); ++a)
0419         {
0420             std::set<Peak>::const_iterator b = a;
0421             ++b;
0422             for (; b != stars.end(); ++b)
0423             {
0424                 int dx = abs(a->x - b->x);
0425                 int dy = abs(a->y - b->y);
0426                 if (dx <= fullw && dy <= fullw)
0427                 {
0428                     // stars closer than search region, exclude them both
0429                     // but do not let a very dim star eliminate a very bright star
0430                     if (b->val / a->val >= 5.0)
0431                     {
0432                         //Debug.Write(wxString::Format("AutoFind: close dim-bright [%d, %d] %.1f - [%d, %d] %.1f\n", a->x, a->y, a->val, b->x, b->y, b->val));
0433                     }
0434                     else
0435                     {
0436                         //Debug.Write(wxString::Format("AutoFind: too close [%d, %d] %.1f - [%d, %d] %.1f\n", a->x, a->y, a->val, b->x, b->y, b->val));
0437                         to_erase.insert(std::distance(stars.begin(), a));
0438                         to_erase.insert(std::distance(stars.begin(), b));
0439                     }
0440                 }
0441             }
0442         }
0443         RemoveItems(stars, to_erase);
0444     }
0445 
0446     // exclude stars too close to the edge
0447     {
0448         enum { MIN_EDGE_DIST = 40 };
0449         int edgeDist = MIN_EDGE_DIST;//pConfig->Profile.GetInt("/StarAutoFind/MinEdgeDist", MIN_EDGE_DIST);
0450         if (edgeDist < searchRegion)
0451             edgeDist = searchRegion;
0452         edgeDist += extraEdgeAllowance;
0453 
0454         std::set<Peak>::iterator it = stars.begin();
0455         while (it != stars.end())
0456         {
0457             std::set<Peak>::iterator next = it;
0458             ++next;
0459             if (it->x <= edgeDist || it->x >= subW - edgeDist ||
0460                     it->y <= edgeDist || it->y >= subH - edgeDist)
0461             {
0462                 //Debug.Write(wxString::Format("AutoFind: too close to edge [%d, %d] %.1f\n", it->x, it->y, it->val));
0463                 stars.erase(it);
0464             }
0465             it = next;
0466         }
0467     }
0468 
0469     QList<Edge*> centers;
0470     for (std::set<Peak>::reverse_iterator it = stars.rbegin(); it != stars.rend(); ++it)
0471     {
0472         Edge *center = new Edge;
0473         center->x = it->x;
0474         center->y = it->y;
0475         center->val = it->val;
0476         centers.append(center);
0477     }
0478 
0479     delete [] conv;
0480 
0481     return centers;
0482 }
0483 
0484 
0485 template <typename T>
0486 GuiderUtils::Vector GuideAlgorithms::findLocalStarPosition(QSharedPointer<FITSData> &imageData,
0487         const int algorithmIndex,
0488         const int videoWidth,
0489         const int videoHeight,
0490         const QRect &trackingBox)
0491 {
0492     static const double P0 = 0.906, P1 = 0.584, P2 = 0.365, P3 = 0.117, P4 = 0.049, P5 = -0.05, P6 = -0.064, P7 = -0.074,
0493                         P8 = -0.094;
0494 
0495     GuiderUtils::Vector ret(-1, -1, -1);
0496     int i, j;
0497     double resx, resy, mass, threshold, pval;
0498     T const *psrc    = nullptr;
0499     T const *porigin = nullptr;
0500     T const *pptr;
0501 
0502     if (trackingBox.isValid() == false)
0503         return GuiderUtils::Vector(-1, -1, -1);
0504 
0505     if (imageData.isNull())
0506     {
0507         qCWarning(KSTARS_EKOS_GUIDE) << "Cannot process a nullptr image.";
0508         return GuiderUtils::Vector(-1, -1, -1);
0509     }
0510 
0511     if (algorithmIndex == SEP_THRESHOLD)
0512     {
0513         QVariantMap settings;
0514         settings["optionsProfileIndex"] = Options::guideOptionsProfile();
0515         settings["optionsProfileGroup"] = static_cast<int>(Ekos::GuideProfiles);
0516         imageData->setSourceExtractorSettings(settings);
0517         QFuture<bool> result = imageData->findStars(ALGORITHM_SEP, trackingBox);
0518         result.waitForFinished();
0519         if (result.result())
0520         {
0521             imageData->getHFR(HFR_MEDIAN);
0522             const Edge star = imageData->getSelectedHFRStar();
0523             ret = GuiderUtils::Vector(star.x, star.y, 0);
0524         }
0525         else
0526             ret = GuiderUtils::Vector(-1, -1, -1);
0527 
0528         return ret;
0529     }
0530 
0531     T const *pdata = reinterpret_cast<T const*>(imageData->getImageBuffer());
0532 
0533     qCDebug(KSTARS_EKOS_GUIDE) << "Tracking Square " << trackingBox;
0534 
0535     double square_square = trackingBox.width() * trackingBox.width();
0536 
0537     psrc = porigin = pdata + trackingBox.y() * videoWidth + trackingBox.x();
0538 
0539     resx = resy = 0;
0540     threshold = mass = 0;
0541 
0542     // several threshold adaptive smart algorithms
0543     switch (algorithmIndex)
0544     {
0545         case CENTROID_THRESHOLD:
0546         {
0547             int width  = trackingBox.width();
0548             int height = trackingBox.width();
0549             float i0, i1, i2, i3, i4, i5, i6, i7, i8;
0550             int ix = 0, iy = 0;
0551             int xM4;
0552             T const *p;
0553             double average, fit, bestFit = 0;
0554             int minx = 0;
0555             int maxx = width;
0556             int miny = 0;
0557             int maxy = height;
0558             for (int x = minx; x < maxx; x++)
0559                 for (int y = miny; y < maxy; y++)
0560                 {
0561                     i0 = i1 = i2 = i3 = i4 = i5 = i6 = i7 = i8 = 0;
0562                     xM4                                        = x - 4;
0563                     p                                          = psrc + (y - 4) * videoWidth + xM4;
0564                     i8 += *p++;
0565                     i8 += *p++;
0566                     i8 += *p++;
0567                     i8 += *p++;
0568                     i8 += *p++;
0569                     i8 += *p++;
0570                     i8 += *p++;
0571                     i8 += *p++;
0572                     i8 += *p++;
0573                     p = psrc + (y - 3) * videoWidth + xM4;
0574                     i8 += *p++;
0575                     i8 += *p++;
0576                     i8 += *p++;
0577                     i7 += *p++;
0578                     i6 += *p++;
0579                     i7 += *p++;
0580                     i8 += *p++;
0581                     i8 += *p++;
0582                     i8 += *p++;
0583                     p = psrc + (y - 2) * videoWidth + xM4;
0584                     i8 += *p++;
0585                     i8 += *p++;
0586                     i5 += *p++;
0587                     i4 += *p++;
0588                     i3 += *p++;
0589                     i4 += *p++;
0590                     i5 += *p++;
0591                     i8 += *p++;
0592                     i8 += *p++;
0593                     p = psrc + (y - 1) * videoWidth + xM4;
0594                     i8 += *p++;
0595                     i7 += *p++;
0596                     i4 += *p++;
0597                     i2 += *p++;
0598                     i1 += *p++;
0599                     i2 += *p++;
0600                     i4 += *p++;
0601                     i8 += *p++;
0602                     i8 += *p++;
0603                     p = psrc + (y + 0) * videoWidth + xM4;
0604                     i8 += *p++;
0605                     i6 += *p++;
0606                     i3 += *p++;
0607                     i1 += *p++;
0608                     i0 += *p++;
0609                     i1 += *p++;
0610                     i3 += *p++;
0611                     i6 += *p++;
0612                     i8 += *p++;
0613                     p = psrc + (y + 1) * videoWidth + xM4;
0614                     i8 += *p++;
0615                     i7 += *p++;
0616                     i4 += *p++;
0617                     i2 += *p++;
0618                     i1 += *p++;
0619                     i2 += *p++;
0620                     i4 += *p++;
0621                     i8 += *p++;
0622                     i8 += *p++;
0623                     p = psrc + (y + 2) * videoWidth + xM4;
0624                     i8 += *p++;
0625                     i8 += *p++;
0626                     i5 += *p++;
0627                     i4 += *p++;
0628                     i3 += *p++;
0629                     i4 += *p++;
0630                     i5 += *p++;
0631                     i8 += *p++;
0632                     i8 += *p++;
0633                     p = psrc + (y + 3) * videoWidth + xM4;
0634                     i8 += *p++;
0635                     i8 += *p++;
0636                     i8 += *p++;
0637                     i7 += *p++;
0638                     i6 += *p++;
0639                     i7 += *p++;
0640                     i8 += *p++;
0641                     i8 += *p++;
0642                     i8 += *p++;
0643                     p = psrc + (y + 4) * videoWidth + xM4;
0644                     i8 += *p++;
0645                     i8 += *p++;
0646                     i8 += *p++;
0647                     i8 += *p++;
0648                     i8 += *p++;
0649                     i8 += *p++;
0650                     i8 += *p++;
0651                     i8 += *p++;
0652                     i8 += *p++;
0653                     average = (i0 + i1 + i2 + i3 + i4 + i5 + i6 + i7 + i8) / 85.0;
0654                     fit     = P0 * (i0 - average) + P1 * (i1 - 4 * average) + P2 * (i2 - 4 * average) +
0655                               P3 * (i3 - 4 * average) + P4 * (i4 - 8 * average) + P5 * (i5 - 4 * average) +
0656                               P6 * (i6 - 4 * average) + P7 * (i7 - 8 * average) + P8 * (i8 - 48 * average);
0657                     if (bestFit < fit)
0658                     {
0659                         bestFit = fit;
0660                         ix      = x;
0661                         iy      = y;
0662                     }
0663                 }
0664 
0665             if (bestFit > 50)
0666             {
0667                 double sumX  = 0;
0668                 double sumY  = 0;
0669                 double total = 0;
0670                 for (int y = iy - 4; y <= iy + 4; y++)
0671                 {
0672                     p = psrc + y * width + ix - 4;
0673                     for (int x = ix - 4; x <= ix + 4; x++)
0674                     {
0675                         double w = *p++;
0676                         sumX += x * w;
0677                         sumY += y * w;
0678                         total += w;
0679                     }
0680                 }
0681                 if (total > 0)
0682                 {
0683                     ret = (GuiderUtils::Vector(trackingBox.x(), trackingBox.y(), 0) + GuiderUtils::Vector(sumX / total, sumY / total, 0));
0684                     return ret;
0685                 }
0686             }
0687 
0688             return GuiderUtils::Vector(-1, -1, -1);
0689         }
0690         break;
0691         // Alexander's Stepanenko smart threshold algorithm
0692         case SMART_THRESHOLD:
0693         {
0694             point_t bbox_lt = { trackingBox.x() - SMART_FRAME_WIDTH, trackingBox.y() - SMART_FRAME_WIDTH };
0695             point_t bbox_rb = { trackingBox.x() + trackingBox.width() + SMART_FRAME_WIDTH,
0696                                 trackingBox.y() + trackingBox.width() + SMART_FRAME_WIDTH
0697                               };
0698             int offset      = 0;
0699 
0700             // clip frame
0701             if (bbox_lt.x < 0)
0702                 bbox_lt.x = 0;
0703             if (bbox_lt.y < 0)
0704                 bbox_lt.y = 0;
0705             if (bbox_rb.x > videoWidth)
0706                 bbox_rb.x = videoWidth;
0707             if (bbox_rb.y > videoHeight)
0708                 bbox_rb.y = videoHeight;
0709 
0710             // calc top bar
0711             int box_wd  = bbox_rb.x - bbox_lt.x;
0712             int box_ht  = trackingBox.y() - bbox_lt.y;
0713             int pix_cnt = 0;
0714             if (box_wd > 0 && box_ht > 0)
0715             {
0716                 pix_cnt += box_wd * box_ht;
0717                 for (j = bbox_lt.y; j < trackingBox.y(); ++j)
0718                 {
0719                     offset = j * videoWidth;
0720                     for (i = bbox_lt.x; i < bbox_rb.x; ++i)
0721                     {
0722                         pptr = pdata + offset + i;
0723                         threshold += *pptr;
0724                     }
0725                 }
0726             }
0727             // calc left bar
0728             box_wd = trackingBox.x() - bbox_lt.x;
0729             box_ht = trackingBox.width();
0730             if (box_wd > 0 && box_ht > 0)
0731             {
0732                 pix_cnt += box_wd * box_ht;
0733                 for (j = trackingBox.y(); j < trackingBox.y() + box_ht; ++j)
0734                 {
0735                     offset = j * videoWidth;
0736                     for (i = bbox_lt.x; i < trackingBox.x(); ++i)
0737                     {
0738                         pptr = pdata + offset + i;
0739                         threshold += *pptr;
0740                     }
0741                 }
0742             }
0743             // calc right bar
0744             box_wd = bbox_rb.x - trackingBox.x() - trackingBox.width();
0745             box_ht = trackingBox.width();
0746             if (box_wd > 0 && box_ht > 0)
0747             {
0748                 pix_cnt += box_wd * box_ht;
0749                 for (j = trackingBox.y(); j < trackingBox.y() + box_ht; ++j)
0750                 {
0751                     offset = j * videoWidth;
0752                     for (i = trackingBox.x() + trackingBox.width(); i < bbox_rb.x; ++i)
0753                     {
0754                         pptr = pdata + offset + i;
0755                         threshold += *pptr;
0756                     }
0757                 }
0758             }
0759             // calc bottom bar
0760             box_wd = bbox_rb.x - bbox_lt.x;
0761             box_ht = bbox_rb.y - trackingBox.y() - trackingBox.width();
0762             if (box_wd > 0 && box_ht > 0)
0763             {
0764                 pix_cnt += box_wd * box_ht;
0765                 for (j = trackingBox.y() + trackingBox.width(); j < bbox_rb.y; ++j)
0766                 {
0767                     offset = j * videoWidth;
0768                     for (i = bbox_lt.x; i < bbox_rb.x; ++i)
0769                     {
0770                         pptr = pdata + offset + i;
0771                         threshold += *pptr;
0772                     }
0773                 }
0774             }
0775             // find maximum
0776             double max_val = 0;
0777             for (j = 0; j < trackingBox.width(); ++j)
0778             {
0779                 for (i = 0; i < trackingBox.width(); ++i)
0780                 {
0781                     pptr = psrc + i;
0782                     if (*pptr > max_val)
0783                         max_val = *pptr;
0784                 }
0785                 psrc += videoWidth;
0786             }
0787             if (pix_cnt != 0)
0788                 threshold /= (double)pix_cnt;
0789 
0790             // cut by 10% higher then average threshold
0791             if (max_val > threshold)
0792                 threshold += (max_val - threshold) * SMART_CUT_FACTOR;
0793 
0794             //log_i("smart thr. = %f cnt = %d", threshold, pix_cnt);
0795             break;
0796         }
0797         // simple adaptive threshold
0798         case AUTO_THRESHOLD:
0799         {
0800             for (j = 0; j < trackingBox.width(); ++j)
0801             {
0802                 for (i = 0; i < trackingBox.width(); ++i)
0803                 {
0804                     pptr = psrc + i;
0805                     threshold += *pptr;
0806                 }
0807                 psrc += videoWidth;
0808             }
0809             threshold /= square_square;
0810             break;
0811         }
0812         // no threshold subtracion
0813         default:
0814         {
0815         }
0816     }
0817 
0818     psrc = porigin;
0819     for (j = 0; j < trackingBox.width(); ++j)
0820     {
0821         for (i = 0; i < trackingBox.width(); ++i)
0822         {
0823             pptr = psrc + i;
0824             pval = *pptr - threshold;
0825             pval = pval < 0 ? 0 : pval;
0826 
0827             resx += (double)i * pval;
0828             resy += (double)j * pval;
0829 
0830             mass += pval;
0831         }
0832         psrc += videoWidth;
0833     }
0834 
0835     if (mass == 0)
0836         mass = 1;
0837 
0838     resx /= mass;
0839     resy /= mass;
0840 
0841     ret = GuiderUtils::Vector(trackingBox.x(), trackingBox.y(), 0) + GuiderUtils::Vector(resx, resy, 0);
0842 
0843     return ret;
0844 }
0845 
0846 GuiderUtils::Vector GuideAlgorithms::findLocalStarPosition(QSharedPointer<FITSData> &imageData,
0847         const int algorithmIndex,
0848         const int videoWidth,
0849         const int videoHeight,
0850         const QRect &trackingBox)
0851 {
0852     switch (imageData->dataType())
0853     {
0854         case TBYTE:
0855             return findLocalStarPosition<uint8_t>(imageData, algorithmIndex, videoWidth, videoHeight, trackingBox);
0856 
0857         case TSHORT:
0858             return findLocalStarPosition<int16_t>(imageData, algorithmIndex, videoWidth, videoHeight, trackingBox);
0859 
0860         case TUSHORT:
0861             return findLocalStarPosition<uint16_t>(imageData, algorithmIndex, videoWidth, videoHeight, trackingBox);
0862 
0863         case TLONG:
0864             return findLocalStarPosition<int32_t>(imageData, algorithmIndex, videoWidth, videoHeight, trackingBox);
0865 
0866         case TULONG:
0867             return findLocalStarPosition<uint32_t>(imageData, algorithmIndex, videoWidth, videoHeight, trackingBox);
0868 
0869         case TFLOAT:
0870             return findLocalStarPosition<float>(imageData, algorithmIndex, videoWidth, videoHeight, trackingBox);
0871 
0872         case TLONGLONG:
0873             return findLocalStarPosition<int64_t>(imageData, algorithmIndex, videoWidth, videoHeight, trackingBox);
0874 
0875         case TDOUBLE:
0876             return findLocalStarPosition<double>(imageData, algorithmIndex, videoWidth, videoHeight, trackingBox);
0877 
0878         default:
0879             break;
0880     }
0881 
0882     return GuiderUtils::Vector(-1, -1, -1);
0883 }