bool Star::WasFound(void) { return WasFound(m_lastFindResult); }
bool Star::Find(const usImage *pImg, int searchRegion, int base_x, int base_y, FindMode mode) { FindResult Result = STAR_OK; double newX = base_x; double newY = base_y; try { Debug.Write(wxString::Format("Star::Find(%d, %d, %d, %d, (%d,%d,%d,%d))\n", searchRegion, base_x, base_y, mode, pImg->Subframe.x, pImg->Subframe.y, pImg->Subframe.width, pImg->Subframe.height)); if (base_x < 0 || base_y < 0) { throw ERROR_INFO("coordinates are invalid"); } int minx, miny, maxx, maxy; if (pImg->Subframe.IsEmpty()) { minx = miny = 0; maxx = pImg->Size.GetWidth() - 1; maxy = pImg->Size.GetHeight() - 1; } else { minx = pImg->Subframe.GetLeft(); maxx = pImg->Subframe.GetRight(); miny = pImg->Subframe.GetTop(); maxy = pImg->Subframe.GetBottom(); } // search region bounds int start_x = wxMax(base_x - searchRegion, minx); int end_x = wxMin(base_x + searchRegion, maxx); int start_y = wxMax(base_y - searchRegion, miny); int end_y = wxMin(base_y + searchRegion, maxy); const unsigned short *imgdata = pImg->ImageData; int rowsize = pImg->Size.GetWidth(); int peak_x = 0, peak_y = 0; unsigned int peak_val = 0; unsigned short max3[3] = { 0, 0, 0 }; if (mode == FIND_PEAK) { for (int y = start_y; y <= end_y; y++) { for (int x = start_x; x <= end_x; x++) { unsigned short val = imgdata[y * rowsize + x]; if (val > peak_val) { peak_val = val; peak_x = x; peak_y = y; } } } } else { // find the peak value within the search region using a smoothing function // also check for saturation for (int y = start_y + 1; y <= end_y - 1; y++) { for (int x = start_x + 1; x <= end_x - 1; x++) { unsigned short p = imgdata[y * rowsize + x]; unsigned int val = 2 * (unsigned int) p + imgdata[(y - 1) * rowsize + (x + 0)] + imgdata[(y + 0) * rowsize + (x - 1)] + imgdata[(y + 0) * rowsize + (x + 1)] + imgdata[(y + 1) * rowsize + (x + 0)]; if (val > peak_val) { peak_val = val; peak_x = x; peak_y = y; } if (p > max3[0]) std::swap(p, max3[0]); if (p > max3[1]) std::swap(p, max3[1]); if (p > max3[2]) std::swap(p, max3[2]); } } } // meaure noise in the annulus with inner radius A and outer radius B int const A = 7; // inner radius int const B = 12; // outer radius int const A2 = A * A; int const B2 = B * B; // find the mean and stdev of the background double sum = 0.0; double a = 0.0; double q = 0.0; int n = 0; const unsigned short *row = imgdata + rowsize * start_y; for (int y = start_y; y <= end_y; y++, row += rowsize) { int dy = y - peak_y; int dy2 = dy * dy; for (int x = start_x; x <= end_x; x++) { int dx = x - peak_x; int r2 = dx * dx + dy2; // exclude points not in annulus if (r2 <= A2 || r2 > B2) continue; double const val = (double) row[x]; sum += val; ++n; double const k = (double) n; double const a0 = a; a += (val - a) / k; q += (val - a0) * (val - a); } } double const mean_bg = sum / (double) n; double const sigma_bg = sqrt(q / (double) (n - 1)); double cx = 0.0; double cy = 0.0; double mass = 0.0; if (mode == FIND_PEAK) { mass = peak_val; n = 1; } else { unsigned short const thresh = (unsigned short)(mean_bg + 2.0 * sigma_bg); // find pixels over threshold within aperture; compute mass and centroid start_x = wxMax(peak_x - A, minx); end_x = wxMin(peak_x + A, maxx); start_y = wxMax(peak_y - A, miny); end_y = wxMin(peak_y + A, maxy); n = 0; row = imgdata + rowsize * start_y; for (int y = start_y; y <= end_y; y++, row += rowsize) { int dy = y - peak_y; int dy2 = dy * dy; if (dy2 > A2) continue; for (int x = start_x; x <= end_x; x++) { int dx = x - peak_x; // exclude points outside aperture if (dx * dx + dy2 > A2) continue; // exclude points below threshold unsigned short val = row[x]; if (val < thresh) continue; double const d = (double) val - mean_bg; cx += dx * d; cy += dy * d; mass += d; ++n; } } } Mass = mass; SNR = n > 0 ? mass / (sigma_bg * n) : 0.0; double const LOW_SNR = 3.0; if (mass < 10.0) Result = STAR_LOWMASS; else if (SNR < LOW_SNR) Result = STAR_LOWSNR; else { newX = peak_x + cx / mass; newY = peak_y + cy / mass; // even at saturation, the max values may vary a bit due to noise // Call it saturated if the the top three values are within 32 parts per 65535 of max if ((unsigned int)(max3[0] - max3[2]) * 65535U < 32U * (unsigned int) max3[0]) Result = STAR_SATURATED; } } catch (const wxString& Msg) { POSSIBLY_UNUSED(Msg); if (Result == STAR_OK) { Result = STAR_ERROR; } } // update state SetXY(newX, newY); m_lastFindResult = Result; bool bReturn = WasFound(Result); if (!bReturn) { Mass = 0.0; SNR = 0.0; } Debug.AddLine(wxString::Format("Star::Find returns %d (%d), X=%.2f, Y=%.2f, Mass=%.f, SNR=%.1f", bReturn, Result, newX, newY, Mass, SNR)); return bReturn; }
bool Star::Find(const usImage *pImg, int searchRegion, int base_x, int base_y, FindMode mode) { FindResult Result = STAR_OK; double newX = base_x; double newY = base_y; try { Debug.Write(wxString::Format("Star::Find(%d, %d, %d, %d, (%d,%d,%d,%d))\n", searchRegion, base_x, base_y, mode, pImg->Subframe.x, pImg->Subframe.y, pImg->Subframe.width, pImg->Subframe.height)); if (base_x < 0 || base_y < 0) { throw ERROR_INFO("coordinates are invalid"); } int minx, miny, maxx, maxy; if (pImg->Subframe.IsEmpty()) { minx = miny = 0; maxx = pImg->Size.GetWidth() - 1; maxy = pImg->Size.GetHeight() - 1; } else { minx = pImg->Subframe.GetLeft(); maxx = pImg->Subframe.GetRight(); miny = pImg->Subframe.GetTop(); maxy = pImg->Subframe.GetBottom(); } // search region bounds int start_x = wxMax(base_x - searchRegion, minx); int end_x = wxMin(base_x + searchRegion, maxx); int start_y = wxMax(base_y - searchRegion, miny); int end_y = wxMin(base_y + searchRegion, maxy); const unsigned short *imgdata = pImg->ImageData; int rowsize = pImg->Size.GetWidth(); int peak_x = 0, peak_y = 0; unsigned int peak_val = 0; unsigned short max3[3] = { 0, 0, 0 }; if (mode == FIND_PEAK) { for (int y = start_y; y <= end_y; y++) { for (int x = start_x; x <= end_x; x++) { unsigned short val = imgdata[y * rowsize + x]; if (val > peak_val) { peak_val = val; peak_x = x; peak_y = y; } } } PeakVal = peak_val; } else { // find the peak value within the search region using a smoothing function // also check for saturation for (int y = start_y + 1; y <= end_y - 1; y++) { for (int x = start_x + 1; x <= end_x - 1; x++) { unsigned short p = imgdata[y * rowsize + x]; unsigned int val = 4 * (unsigned int) p + imgdata[(y - 1) * rowsize + (x - 1)] + imgdata[(y - 1) * rowsize + (x + 1)] + imgdata[(y + 1) * rowsize + (x - 1)] + imgdata[(y + 1) * rowsize + (x + 1)] + 2 * imgdata[(y - 1) * rowsize + (x + 0)] + 2 * imgdata[(y + 0) * rowsize + (x - 1)] + 2 * imgdata[(y + 0) * rowsize + (x + 1)] + 2 * imgdata[(y + 1) * rowsize + (x + 0)]; if (val > peak_val) { peak_val = val; peak_x = x; peak_y = y; } if (p > max3[0]) std::swap(p, max3[0]); if (p > max3[1]) std::swap(p, max3[1]); if (p > max3[2]) std::swap(p, max3[2]); } } PeakVal = max3[0]; // raw peak val peak_val /= 16; // smoothed peak value } // meaure noise in the annulus with inner radius A and outer radius B int const A = 7; // inner radius int const B = 12; // outer radius int const A2 = A * A; int const B2 = B * B; // center window around peak value start_x = wxMax(peak_x - B, minx); end_x = wxMin(peak_x + B, maxx); start_y = wxMax(peak_y - B, miny); end_y = wxMin(peak_y + B, maxy); // find the mean and stdev of the background double sum = 0.0; double a = 0.0; double q = 0.0; unsigned int nbg = 0; const unsigned short *row = imgdata + rowsize * start_y; for (int y = start_y; y <= end_y; y++, row += rowsize) { int dy = y - peak_y; int dy2 = dy * dy; for (int x = start_x; x <= end_x; x++) { int dx = x - peak_x; int r2 = dx * dx + dy2; // exclude points not in annulus if (r2 <= A2 || r2 > B2) continue; double const val = (double) row[x]; sum += val; ++nbg; double const k = (double) nbg; double const a0 = a; a += (val - a) / k; q += (val - a0) * (val - a); } } double const mean_bg = sum / (double) nbg; double const sigma2_bg = q / (double) (nbg - 1); double const sigma_bg = sqrt(sigma2_bg); unsigned short thresh; double cx = 0.0; double cy = 0.0; double mass = 0.0; unsigned int n; std::vector<R2M> hfrvec; if (mode == FIND_PEAK) { mass = peak_val; n = 1; thresh = 0; } else { thresh = (unsigned short)(mean_bg + 3.0 * sigma_bg + 0.5); // find pixels over threshold within aperture; compute mass and centroid start_x = wxMax(peak_x - A, minx); end_x = wxMin(peak_x + A, maxx); start_y = wxMax(peak_y - A, miny); end_y = wxMin(peak_y + A, maxy); n = 0; row = imgdata + rowsize * start_y; for (int y = start_y; y <= end_y; y++, row += rowsize) { int dy = y - peak_y; int dy2 = dy * dy; if (dy2 > A2) continue; for (int x = start_x; x <= end_x; x++) { int dx = x - peak_x; // exclude points outside aperture if (dx * dx + dy2 > A2) continue; // exclude points below threshold unsigned short val = row[x]; if (val < thresh) continue; double const d = (double) val - mean_bg; cx += dx * d; cy += dy * d; mass += d; ++n; hfrvec.push_back(R2M(x, y, d)); } } } Mass = mass; // SNR estimate from: Measuring the Signal-to-Noise Ratio S/N of the CCD Image of a Star or Nebula, J.H.Simonetti, 2004 January 8 // http://www.phys.vt.edu/~jhs/phys3154/snr20040108.pdf double const gain = .5; // electrons per ADU, nominal SNR = n > 0 ? mass / sqrt(mass / gain + sigma2_bg * (double) n * (1.0 + 1.0 / (double) nbg)) : 0.0; double const LOW_SNR = 3.0; // a few scattered pixels over threshold can give a false positive // avoid this by requiring the smoothed peak value to be above the threshold if (peak_val <= thresh && SNR >= LOW_SNR) { Debug.Write(wxString::Format("Star::Find false star n=%u nbg=%u bg=%.1f sigma=%.1f thresh=%u peak=%u\n", n, nbg, mean_bg, sigma_bg, thresh, peak_val)); SNR = LOW_SNR - 0.1; } if (mass < 10.0) Result = STAR_LOWMASS; else if (SNR < LOW_SNR) Result = STAR_LOWSNR; else { newX = peak_x + cx / mass; newY = peak_y + cy / mass; HFD = 2.0 * hfr(hfrvec, newX, newY, mass); // even at saturation, the max values may vary a bit due to noise // Call it saturated if the the top three values are within 32 parts per 65535 of max for 16-bit cameras, // or within 1 part per 191 for 8-bit cameras unsigned int d = (unsigned int) (max3[0] - max3[2]); unsigned int mx = (unsigned int) max3[0]; // remove pedestal if (mx >= pImg->Pedestal) mx -= pImg->Pedestal; else mx = 0; // unlikely if (pImg->BitsPerPixel < 12) { if (d * 191U < 1U * mx) Result = STAR_SATURATED; } else { if (d * 65535U < 32U * mx) Result = STAR_SATURATED; } } } catch (const wxString& Msg) { POSSIBLY_UNUSED(Msg); if (Result == STAR_OK) { Result = STAR_ERROR; } } // update state SetXY(newX, newY); m_lastFindResult = Result; bool wasFound = WasFound(Result); if (!IsValid() || Result == STAR_ERROR) { Mass = 0.0; SNR = 0.0; HFD = 0.0; } Debug.Write(wxString::Format("Star::Find returns %d (%d), X=%.2f, Y=%.2f, Mass=%.f, SNR=%.1f, Peak=%hu HFD=%.1f\n", wasFound, Result, newX, newY, Mass, SNR, PeakVal, HFD)); return wasFound; }