Esempio n. 1
0
void DataSignal :: WriteSmartPeakInfoToXML (RGTextOutput& text, const RGString& indent, const RGString& bracketTag, const RGString& locationTag) {

	int peak;
	Endl endLine;
	RGString suffix;
	
//	if (HasCrossChannelSignalLink ()) {

		if (HasAlleleName () && (!mDoNotCall)) {

			peak = (int) floor (Peak () + 0.5);

			if (mOffGrid)
				suffix = " OL";

			text << indent << "<" << bracketTag << ">" << endLine;
			text << indent << "\t<mean>" << GetMean () << "</mean>" << endLine;
			text << indent << "\t<height>" << peak << "</height>" << endLine;
			text << indent << "\t<BPS>" << GetBioID () << "</BPS>" << endLine;
//			text << indent << "\t<" << locationTag << ">" << (int) floor (GetApproximateBioID () + 0.5) << "</" << locationTag << ">" << endLine;
			text << indent << "\t<" << locationTag << ">" << GetApproximateBioID () << "</" << locationTag << ">" << endLine;
			text << indent << "\t<PeakArea>" << TheoreticalArea () << "</PeakArea>" << endLine;
			text << indent << "\t<allele>" << GetAlleleName () << suffix << "</allele>" << endLine;
			text << indent << "\t<width>" << 4.0 * GetStandardDeviation () << "</width>" << endLine;
			text << indent << "\t<fit>" << GetCurveFit () << "</fit>" << endLine;
			text << indent << "</" + bracketTag << ">" << endLine;
		}
//	}
}
Esempio n. 2
0
bool DataSignal :: ReportSmartNoticeObjects (RGTextOutput& text, const RGString& indent, const RGString& delim) {

	if (NumberOfSmartNoticeObjects () > 0) {

		int msgLevel = GetHighestMessageLevelWithRestrictionSM ();
		RGDListIterator it (*mSmartMessageReporters);
		SmartMessageReporter* nextNotice;
		text.SetOutputLevel (msgLevel);

		if (!text.TestCurrentLevel ()) {

			text.ResetOutputLevel ();
			return false;
		}

		Endl endLine;
		text << endLine;
		text << indent << "Notices for curve with (Mean, Sigma, Peak, 2Content, Fit) = " << delim << delim << delim << delim << delim << delim;
		text << GetMean () << delim << GetStandardDeviation () << delim << Peak () << delim << GetScale (2) << delim << Fit << endLine;

		while (nextNotice = (SmartMessageReporter*) it ())
			text << indent << nextNotice->GetMessage () << nextNotice->GetMessageData () << endLine;

		text.ResetOutputLevel ();
		text.Write (1, "\n");
	}

	else
		return false;

	return true;
}
Esempio n. 3
0
 /** Add a peak to the list
  * @param ipeak :: Peak object to add (copy) into this.
  */
 void PeaksWorkspace::addPeak(const API::IPeak& ipeak)
 {
   if (dynamic_cast<const Peak*>(&ipeak))
   {
     peaks.push_back((const Peak&) ipeak);
   }
   else
   {
     peaks.push_back(Peak(ipeak));
   }
 }
  /** Execute the algorithm.
   */
  void CreatePeaksWorkspace::exec()
  {
    MatrixWorkspace_sptr instWS = getProperty("InstrumentWorkspace");

    PeaksWorkspace_sptr out(new PeaksWorkspace());
    setProperty("OutputWorkspace", out);
    int NumberOfPeaks = getProperty("NumberOfPeaks");

    if (instWS)
    {
      out->setInstrument(instWS->getInstrument());
      // Create some default peaks
      for (int i=0; i < NumberOfPeaks; i++)
      {
        out->addPeak( Peak(out->getInstrument(), out->getInstrument()->getDetectorIDs(true)[0], 1.0) );
      }
    }
  }
Esempio n. 5
0
double SuperGaussian :: GetPullupToleranceInBP (double noise) const {

	//pullUpToleranceFactor
	double P = Peak ();

	if (P <= 0.0)
		return (mPullupTolerance + (2.0 * sin (0.5 * acos (Fit)) / 4.47));

	double localFit = Fit;

	if (localFit < 0.1)
		localFit = 0.1;

	double localNoise = noise;
	
	double LN = 0.95 * P;

	if (localNoise > LN)
		localNoise = LN;

	double temp1 = 1.0 / localFit;
	double temp = 2.0 * (temp1 * temp1 - 1.0) * StandardDeviation * log (P / (P - localNoise));
	return (mPullupTolerance + pullUpToleranceFactor * mApproxBioIDPrime * sqrt (temp));
}
Esempio n. 6
0
void PeakDetectorNaiveImpl::findPeaks(const FrequencyData& fd, peakdata::Scan& result) const
{
    result.scanNumber = fd.scanNumber(); 
    result.retentionTime = fd.retentionTime(); 
    result.observationDuration = fd.observationDuration();
    result.calibrationParameters = fd.calibrationParameters();
    result.peakFamilies.clear();

    const double noiseLevel = sqrt(fd.variance());
    const double threshold = noiseLevel * noiseFactor_;

    for (FrequencyData::const_iterator it=fd.data().begin(); it!=fd.data().end(); ++it)
    if (isPeak(it, fd.data(), threshold, detectionRadius_))
    {
        result.peakFamilies.push_back(PeakFamily());
        PeakFamily& peakFamily = result.peakFamilies.back();
        peakFamily.peaks.push_back(Peak());
        Peak& peak = peakFamily.peaks.back();

        peak.frequency = it->x;
        peak.intensity = it->y.real();
        peak.phase = it->y.imag();
    }
}
Esempio n. 7
0
void DataSignal :: WriteSmartTableArtifactInfoToXML (RGTextOutput& text, RGTextOutput& tempText, const RGString& indent, const RGString& bracketTag, const RGString& locationTag) {

	int peak;
	Endl endLine;
	RGString suffix;
	RGString label;
	SmartMessageReporter* notice;
	int i;
	RGString virtualAllele;
	SmartMessageReporter* nextNotice;
	text.SetOutputLevel (1);
	int msgNum;

	smAcceptedOLLeft acceptedOLLeft;
	smAcceptedOLRight acceptedOLRight;

	int reportedMessageLevel = GetHighestMessageLevelWithRestrictionSM ();
	//bool hasThreeLoci;
	//bool needLocus0;

	if ((!DontLook ()) && (NumberOfSmartNoticeObjects () != 0)) {
	
		bool firstNotice = true;
		peak = (int) floor (Peak () + 0.5);
		virtualAllele = GetVirtualAlleleName ();

		text << indent << "<" << bracketTag << ">" << endLine;  // Should be <Artifact>
		text << indent << "\t<Id>" << GetSignalID () << "</Id>" << endLine;
		text << indent << "\t<Level>" << reportedMessageLevel << "</Level>" << endLine;
		text << indent << "\t<RFU>" << peak << "</RFU>" << endLine;
		text << indent << "\t<" << locationTag << ">" << GetApproximateBioID () << "</" << locationTag << ">" << endLine;
		text << indent << "\t<PeakArea>" << TheoreticalArea () << "</PeakArea>" << endLine;
		text << indent << "\t<Time>" << GetMean () << "</Time>" << endLine;
		text << indent << "\t<Fit>" << GetCurveFit () << "</Fit>" << endLine;

		if (!mAllowPeakEdit)
			text << indent << "\t<AllowPeakEdit>false</AllowPeakEdit>" << endLine;
		
		RGDListIterator it (*mSmartMessageReporters);
		i = 0;

		while (notice = (SmartMessageReporter*) it ()) {

			if (!notice->GetDisplayOsirisInfo ())
				continue;

			if (firstNotice) {

				label = indent + "\t<Label>";
				firstNotice = false;
			}

			if (i > 0)
				label << "&#10;";

			label += notice->GetMessage ();
			label += notice->GetMessageData ();
			i++;
		}

		if (i > 0) {

			label << "</Label>";
			text << label << endLine;
		}

		// Now add list of notices...

		it.Reset ();

		while (nextNotice = (SmartMessageReporter*) it ()) {

			msgNum = Notice::GetNextMessageNumber ();
			nextNotice->SetMessageCount (msgNum);
			text << indent << "\t<MessageNumber>" << msgNum << "</MessageNumber>" << endLine;
			tempText << "\t\t<Message>\n";
			tempText << "\t\t\t<MessageNumber>" << msgNum << "</MessageNumber>\n";
			tempText << "\t\t\t<Text>" << nextNotice->GetMessage () << nextNotice->GetMessageData () << "</Text>\n";

			if (nextNotice->HasViableExportInfo ()) {

				if (nextNotice->IsEnabled ())
					tempText << "\t\t\t<Hidden>false</Hidden>\n";

				else
					tempText << "\t\t\t<Hidden>true</Hidden>\n";

				if (!nextNotice->IsCritical ())
					tempText << "\t\t\t<Critical>false</Critical>\n";

				if (nextNotice->IsEnabled ())
					tempText << "\t\t\t<Enabled>true</Enabled>\n";

				else
					tempText << "\t\t\t<Enabled>false</Enabled>\n";

				if (!nextNotice->IsEditable ())
					tempText << "\t\t\t<Editable>false</Editable>\n";

				if (nextNotice->GetDisplayExportInfo ())
					tempText << "\t\t\t<DisplayExportInfo>true</DisplayExportInfo>\n";

				else
					tempText << "\t\t\t<DisplayExportInfo>false</DisplayExportInfo>\n";

				if (!nextNotice->GetDisplayOsirisInfo ())
					tempText << "\t\t\t<DisplayOsirisInfo>false</DisplayOsirisInfo>\n";

				tempText << "\t\t\t<MsgName>" << nextNotice->GetMessageName () << "</MsgName>\n";

				//tempText << "\t\t\t<ExportProtocolList>";
				//tempText << "\t\t\t" << nextNotice->GetExportProtocolInformation ();
				//tempText << "\t\t\t</ExportProtocolList>\n";
			}

			tempText << "\t\t</Message>\n";
		}

		// Now add list of alleles

		//hasThreeLoci = (mLocus != NULL) && (mLeftLocus != NULL) && (mRightLocus != NULL);

		//if (mLocus != NULL) {

		//	if ((mLeftLocus == NULL) && (mRightLocus == NULL))
		//		needLocus0 = true;

		//	else
		//		needLocus0 = false;
		//}

		//else
		//	needLocus0 = false;

		//needLocus0 = (!hasThreeLoci) && ((mLocus != mLeftLocus) || (mLocus != mRightLocus));

		if (mLocus != NULL) {

			//testing
			RGString locusName = mLocus->GetLocusName ();
			suffix = GetAlleleName (0);

			if ((suffix.Length () > 0) || (virtualAllele.Length () > 0)) {

				text << indent << "\t<Allele>" << endLine;

				if (suffix.Length () > 0)
					text << indent << "\t\t<Name>" << suffix << "</Name>" << endLine;

				else
					text << indent << "\t\t<Name>" << virtualAllele << "</Name>" << endLine;

				if (mOffGrid)
					suffix = "true";

				else if (mAcceptedOffGrid)
					suffix = "accepted";

				else
					suffix = "false";

				text << indent << "\t\t<OffLadder>" << suffix << "</OffLadder>" << endLine;
				text << indent << "\t\t<BPS>" << GetBioID (0) << "</BPS>" << endLine;
				text << indent << "\t\t<Locus>" << mLocus->GetLocusName () << "</Locus>" << endLine;
				text << indent << "\t\t<Location>0</Location>" << endLine;
				text << indent << "\t</Allele>" << endLine;
			}
		}

		if ((mLeftLocus != NULL) && (mLeftLocus != mLocus)) {

			if (mAlleleNameLeft.Length () > 0) {

				text << indent << "\t<Allele>" << endLine;
				text << indent << "\t\t<Name>" << mAlleleNameLeft << "</Name>" << endLine;

				if (mIsOffGridLeft)
					suffix = "true";

				else if (GetMessageValue (acceptedOLLeft))
					suffix = "accepted";

				else
					suffix = "false";

				text << indent << "\t\t<OffLadder>" << suffix << "</OffLadder>" << endLine;
				text << indent << "\t\t<BPS>" << GetBioID (-1) << "</BPS>" << endLine;
				text << indent << "\t\t<Locus>" << mLeftLocus->GetLocusName () << "</Locus>" << endLine;
				text << indent << "\t\t<Location>-1</Location>" << endLine;
				text << indent << "\t</Allele>" << endLine;
			}
		}

		if ((mRightLocus != NULL) && (mRightLocus != mLocus)) {

			if (mAlleleNameRight.Length () > 0) {

				text << indent << "\t<Allele>" << endLine;
				text << indent << "\t\t<Name>" << mAlleleNameRight << "</Name>" << endLine;

				if (mIsOffGridRight)
					suffix = "true";

				else if (GetMessageValue (acceptedOLRight))
					suffix = "accepted";

				else
					suffix = "false";

				text << indent << "\t\t<OffLadder>" << suffix << "</OffLadder>" << endLine;
				text << indent << "\t\t<BPS>" << GetBioID (1) << "</BPS>" << endLine;
				text << indent << "\t\t<Locus>" << mRightLocus->GetLocusName () << "</Locus>" << endLine;
				text << indent << "\t\t<Location>1</Location>" << endLine;
				text << indent << "\t</Allele>" << endLine;
			}
		}

		text << indent << "</" + bracketTag << ">" << endLine;
	}

	text.ResetOutputLevel ();
}
Esempio n. 8
0
void DataSignal :: WriteSmartArtifactInfoToXML (RGTextOutput& text, const RGString& indent, const RGString& bracketTag, const RGString& locationTag) {

	int peak;
	Endl endLine;
	RGString suffix;
	RGString label;
	SmartMessageReporter* notice;
	int i;
	RGString virtualAllele;
	int reportedMessageLevel;

	reportedMessageLevel = GetHighestMessageLevelWithRestrictionSM ();
	bool thisIsFirstNotice = true;

	if ((!DontLook ()) && (NumberOfSmartNoticeObjects () != 0)) {
	
		peak = (int) floor (Peak () + 0.5);

		if (mOffGrid)
			suffix = " OL";

		virtualAllele = GetVirtualAlleleName ();

		RGDListIterator it (*mSmartMessageReporters);
		i = 0;

		while (notice = (SmartMessageReporter*) it ()) {

			if (!notice->GetDisplayOsirisInfo ())
				continue;

			if (thisIsFirstNotice) {

				text << indent << "<" << bracketTag << ">" << endLine;
				text << indent << "\t<level>" << reportedMessageLevel << "</level>" << endLine;
				text << indent << "\t<mean>" << GetMean () << "</mean>" << endLine;
				text << indent << "\t<height>" << peak << "</height>" << endLine;
//				text << indent << "\t<" << locationTag << ">" << (int) floor (GetApproximateBioID () + 0.5) << "</" << locationTag << ">" << endLine;
				text << indent << "\t<" << locationTag << ">" << GetApproximateBioID () << "</" << locationTag << ">" << endLine;
				text << indent << "\t<PeakArea>" << TheoreticalArea () << "</PeakArea>" << endLine;

				if (virtualAllele.Length () > 0)
					text << indent << "\t<equivAllele>" << virtualAllele << suffix << "</equivAllele>" << endLine;

				text << indent << "\t<width>" << 4.0 * GetStandardDeviation () << "</width>" << endLine;
				text << indent << "\t<fit>" << GetCurveFit () << "</fit>" << endLine;
				label = indent + "\t<label>";
				thisIsFirstNotice = false;
			}

			if (i > 0)
				label << "&#10;";

			label += notice->GetMessage ();
			label += notice->GetMessageData ();
			i++;
		}

		RGString temp = GetVirtualAlleleName () + suffix;

		if (temp.Length () > 0) {

			if (i > 0)
				label << "&#10;";

			label += "Allele " + temp;
		}

		if (i > 0) {

			label << "</label>";
			text << label << endLine;
			text << indent << "</" + bracketTag << ">" << endLine;
		}

		/*if ((signalLink != NULL) && (!signalLink->xmlArtifactInfoWritten)) {

			signalLink->xmlArtifactInfoWritten = true;
			peak = signalLink->height;

			text << indent << "<" << bracketTag << ">" << endLine;
			text << indent << "\t<mean>" << signalLink->mean << "</mean>" << endLine;
			text << indent << "\t<height>" << peak << "</height>" << endLine;
			text << indent << "\t<" << locationTag << ">" << signalLink->bioID << "</" << locationTag << ">" << endLine;
			text << indent << "\t<fit>" << GetCurveFit () << "</fit>" << endLine;
			label = indent + "\t<label>";
			mNoticeObjectIterator.Reset ();
			i = 0;

			while (notice = (Notice*) mNoticeObjectIterator ()) {

				if (i > 0)
					label << "&#10;";

				label += notice->GetLabel ();
				i++;
			}

			if (temp.Length () > 0) {

				if (i > 0)
					label << "&#10;";

				label += "Allele " + temp;
			}

			label << "</label>";
			text << label << endLine;
			text << indent << "</" + bracketTag << ">" << endLine;
		}*/
	}
}
Esempio n. 9
0
bool Star::AutoFind(const usImage& image, int extraEdgeAllowance, int searchRegion)
{
    if (!image.Subframe.IsEmpty())
    {
        Debug.AddLine("Autofind called on subframe, returning error");
        return false; // not found
    }

    wxBusyCursor busy;

    Debug.AddLine(wxString::Format("Star::AutoFind called with edgeAllowance = %d searchRegion = %d", extraEdgeAllowance, searchRegion));

    // run a 3x3 median first to eliminate hot pixels
    usImage smoothed;
    smoothed.CopyFrom(image);
    Median3(smoothed);

    // convert to floating point
    FloatImg conv(smoothed);

    // downsample the source image
    const int downsample = 1;
    if (downsample > 1)
    {
        FloatImg tmp;
        Downsample(tmp, conv, downsample);
        conv.Swap(tmp);
    }

    // run the PSF convolution
    {
        FloatImg tmp;
        psf_conv(tmp, conv);
        conv.Swap(tmp);
    }

    enum { CONV_RADIUS = 4 };
    int dw = conv.Size.GetWidth();      // width of the downsampled image
    int dh = conv.Size.GetHeight();     // height of the downsampled image
    wxRect convRect(CONV_RADIUS, CONV_RADIUS, dw - 2 * CONV_RADIUS, dh - 2 * CONV_RADIUS);  // region containing valid data

    SaveImage(conv, "PHD2_AutoFind.fit");

    enum { TOP_N = 100 };  // keep track of the brightest stars
    std::set<Peak> stars;  // sorted by ascending intensity

    double global_mean, global_stdev;
    GetStats(&global_mean, &global_stdev, conv, convRect);

    Debug.AddLine("AutoFind: global mean = %.1f, stdev %.1f", global_mean, global_stdev);

    const double threshold = 0.1;
    Debug.AddLine("AutoFind: using threshold = %.1f", threshold);

    // find each local maximum
    int srch = 4;
    for (int y = convRect.GetTop() + srch; y <= convRect.GetBottom() - srch; y++)
    {
        for (int x = convRect.GetLeft() + srch; x <= convRect.GetRight() - srch; x++)
        {
            float val = conv.px[dw * y + x];
            bool ismax = false;
            if (val > 0.0)
            {
                ismax = true;
                for (int j = -srch; j <= srch; j++)
                {
                    for (int i = -srch; i <= srch; i++)
                    {
                        if (i == 0 && j == 0)
                            continue;
                        if (conv.px[dw * (y + j) + (x + i)] > val)
                        {
                            ismax = false;
                            break;
                        }
                    }
                }
            }
            if (!ismax)
                continue;

            // compare local maximum to mean value of surrounding pixels
            const int local = 7;
            double local_mean, local_stdev;
            wxRect localRect(x - local, y - local, 2 * local + 1, 2 * local + 1);
            localRect.Intersect(convRect);
            GetStats(&local_mean, &local_stdev, conv, localRect);

            // this is our measure of star intensity
            double h = (val - local_mean) / global_stdev;

            if (h < threshold)
            {
                //  Debug.AddLine(wxString::Format("AG: local max REJECT [%d, %d] PSF %.1f SNR %.1f", imgx, imgy, val, SNR));
                continue;
            }

            // coordinates on the original image
            int imgx = x * downsample + downsample / 2;
            int imgy = y * downsample + downsample / 2;

            stars.insert(Peak(imgx, imgy, h));
            if (stars.size() > TOP_N)
                stars.erase(stars.begin());
        }
    }

    for (std::set<Peak>::const_reverse_iterator it = stars.rbegin(); it != stars.rend(); ++it)
        Debug.AddLine("AutoFind: local max [%d, %d] %.1f", it->x, it->y, it->val);

    // merge stars that are very close into a single star
    {
        const int minlimitsq = 5 * 5;
    repeat:
        for (std::set<Peak>::const_iterator a = stars.begin(); a != stars.end(); ++a)
        {
            std::set<Peak>::const_iterator b = a;
            ++b;
            for (; b != stars.end(); ++b)
            {
                int dx = a->x - b->x;
                int dy = a->y - b->y;
                int d2 = dx * dx + dy * dy;
                if (d2 < minlimitsq)
                {
                    // very close, treat as single star
                    Debug.AddLine("AutoFind: merge [%d, %d] %.1f - [%d, %d] %.1f", a->x, a->y, a->val, b->x, b->y, b->val);
                    // erase the dimmer one
                    stars.erase(a);
                    goto repeat;
                }
            }
        }
    }

    // exclude stars that would fit within a single searchRegion box
    {
        // build a list of stars to be excluded
        std::set<int> to_erase;
        const int extra = 5; // extra safety margin
        const int fullw = searchRegion + extra;
        for (std::set<Peak>::const_iterator a = stars.begin(); a != stars.end(); ++a)
        {
            std::set<Peak>::const_iterator b = a;
            ++b;
            for (; b != stars.end(); ++b)
            {
                int dx = abs(a->x - b->x);
                int dy = abs(a->y - b->y);
                if (dx <= fullw && dy <= fullw)
                {
                    // stars closer than search region, exclude them both
                    // but do not let a very dim star eliminate a very bright star
                    if (b->val / a->val >= 5.0)
                    {
                        Debug.AddLine("AutoFind: close dim-bright [%d, %d] %.1f - [%d, %d] %.1f", a->x, a->y, a->val, b->x, b->y, b->val);
                    }
                    else
                    {
                        Debug.AddLine("AutoFind: too close [%d, %d] %.1f - [%d, %d] %.1f", a->x, a->y, a->val, b->x, b->y, b->val);
                        to_erase.insert(std::distance(stars.begin(), a));
                        to_erase.insert(std::distance(stars.begin(), b));
                    }
                }
            }
        }
        RemoveItems(stars, to_erase);
    }

    // exclude stars too close to the edge
    {
        enum { MIN_EDGE_DIST = 40 };
        int edgeDist = MIN_EDGE_DIST + extraEdgeAllowance;

        std::set<Peak>::iterator it = stars.begin();
        while (it != stars.end())
        {
            std::set<Peak>::iterator next = it;
            ++next;
            if (it->x <= edgeDist || it->x >= image.Size.GetWidth() - edgeDist ||
                it->y <= edgeDist || it->y >= image.Size.GetHeight() - edgeDist)
            {
                Debug.AddLine("AutoFind: too close to edge [%d, %d] %.1f", it->x, it->y, it->val);
                stars.erase(it);
            }
            it = next;
        }
    }

    // At first I tried running Star::Find on the survivors to find the best
    // star. This had the unfortunate effect of locating hot pixels which
    // the psf convolution so nicely avoids. So, don't do that!  -ag

    // find the brightest non-saturated star. If no non-saturated stars, settle for a saturated star.
    bool allowSaturated = false;
    while (true)
    {
        Debug.AddLine("AutoSelect: finding best star allowSaturated = %d", allowSaturated);

        for (std::set<Peak>::reverse_iterator it = stars.rbegin(); it != stars.rend(); ++it)
        {
            Star tmp;
            tmp.Find(&image, searchRegion, it->x, it->y, FIND_CENTROID);
            if (tmp.WasFound())
            {
                if (tmp.GetError() == STAR_SATURATED && !allowSaturated)
                {
                    Debug.AddLine("Autofind: star saturated [%d, %d] %.1f Mass %.f SNR %.1f", it->x, it->y, it->val, tmp.Mass, tmp.SNR);
                    continue;
                }
                SetXY(it->x, it->y);
                Debug.AddLine("Autofind returns star at [%d, %d] %.1f Mass %.f SNR %.1f", it->x, it->y, it->val, tmp.Mass, tmp.SNR);
                return true;
            }
        }

        if (allowSaturated)
            break; // no stars found

        Debug.AddLine("AutoFind: could not find a non-saturated star!");

        allowSaturated = true;
    }

    Debug.AddLine("Autofind: no star found");
    return false;
}
Esempio n. 10
0
bool Star::AutoFind(const usImage& image, int extraEdgeAllowance, int searchRegion)
{
    if (!image.Subframe.IsEmpty())
    {
        Debug.AddLine("Autofind called on subframe, returning error");
        return false; // not found
    }

    wxBusyCursor busy;

    Debug.Write(wxString::Format("Star::AutoFind called with edgeAllowance = %d searchRegion = %d\n", extraEdgeAllowance, searchRegion));

    // run a 3x3 median first to eliminate hot pixels
    usImage smoothed;
    smoothed.CopyFrom(image);
    Median3(smoothed);

    // convert to floating point
    FloatImg conv(smoothed);

    // downsample the source image
    const int downsample = 1;
    if (downsample > 1)
    {
        FloatImg tmp;
        Downsample(tmp, conv, downsample);
        conv.Swap(tmp);
    }

    // run the PSF convolution
    {
        FloatImg tmp;
        psf_conv(tmp, conv);
        conv.Swap(tmp);
    }

    enum { CONV_RADIUS = 4 };
    int dw = conv.Size.GetWidth();      // width of the downsampled image
    int dh = conv.Size.GetHeight();     // height of the downsampled image
    wxRect convRect(CONV_RADIUS, CONV_RADIUS, dw - 2 * CONV_RADIUS, dh - 2 * CONV_RADIUS);  // region containing valid data

    SaveImage(conv, "PHD2_AutoFind.fit");

    enum { TOP_N = 100 };  // keep track of the brightest stars
    std::set<Peak> stars;  // sorted by ascending intensity

    double global_mean, global_stdev;
    GetStats(&global_mean, &global_stdev, conv, convRect);

    Debug.Write(wxString::Format("AutoFind: global mean = %.1f, stdev %.1f\n", global_mean, global_stdev));

    const double threshold = 0.1;
    Debug.Write(wxString::Format("AutoFind: using threshold = %.1f\n", threshold));

    // find each local maximum
    int srch = 4;
    for (int y = convRect.GetTop() + srch; y <= convRect.GetBottom() - srch; y++)
    {
        for (int x = convRect.GetLeft() + srch; x <= convRect.GetRight() - srch; x++)
        {
            float val = conv.px[dw * y + x];
            bool ismax = false;
            if (val > 0.0)
            {
                ismax = true;
                for (int j = -srch; j <= srch; j++)
                {
                    for (int i = -srch; i <= srch; i++)
                    {
                        if (i == 0 && j == 0)
                            continue;
                        if (conv.px[dw * (y + j) + (x + i)] > val)
                        {
                            ismax = false;
                            break;
                        }
                    }
                }
            }
            if (!ismax)
                continue;

            // compare local maximum to mean value of surrounding pixels
            const int local = 7;
            double local_mean, local_stdev;
            wxRect localRect(x - local, y - local, 2 * local + 1, 2 * local + 1);
            localRect.Intersect(convRect);
            GetStats(&local_mean, &local_stdev, conv, localRect);

            // this is our measure of star intensity
            double h = (val - local_mean) / global_stdev;

            if (h < threshold)
            {
                //  Debug.Write(wxString::Format("AG: local max REJECT [%d, %d] PSF %.1f SNR %.1f\n", imgx, imgy, val, SNR));
                continue;
            }

            // coordinates on the original image
            int imgx = x * downsample + downsample / 2;
            int imgy = y * downsample + downsample / 2;

            stars.insert(Peak(imgx, imgy, h));
            if (stars.size() > TOP_N)
                stars.erase(stars.begin());
        }
    }

    for (std::set<Peak>::const_reverse_iterator it = stars.rbegin(); it != stars.rend(); ++it)
        Debug.Write(wxString::Format("AutoFind: local max [%d, %d] %.1f\n", it->x, it->y, it->val));

    // merge stars that are very close into a single star
    {
        const int minlimitsq = 5 * 5;
    repeat:
        for (std::set<Peak>::const_iterator a = stars.begin(); a != stars.end(); ++a)
        {
            std::set<Peak>::const_iterator b = a;
            ++b;
            for (; b != stars.end(); ++b)
            {
                int dx = a->x - b->x;
                int dy = a->y - b->y;
                int d2 = dx * dx + dy * dy;
                if (d2 < minlimitsq)
                {
                    // very close, treat as single star
                    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));
                    // erase the dimmer one
                    stars.erase(a);
                    goto repeat;
                }
            }
        }
    }

    // exclude stars that would fit within a single searchRegion box
    {
        // build a list of stars to be excluded
        std::set<int> to_erase;
        const int extra = 5; // extra safety margin
        const int fullw = searchRegion + extra;
        for (std::set<Peak>::const_iterator a = stars.begin(); a != stars.end(); ++a)
        {
            std::set<Peak>::const_iterator b = a;
            ++b;
            for (; b != stars.end(); ++b)
            {
                int dx = abs(a->x - b->x);
                int dy = abs(a->y - b->y);
                if (dx <= fullw && dy <= fullw)
                {
                    // stars closer than search region, exclude them both
                    // but do not let a very dim star eliminate a very bright star
                    if (b->val / a->val >= 5.0)
                    {
                        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));
                    }
                    else
                    {
                        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));
                        to_erase.insert(std::distance(stars.begin(), a));
                        to_erase.insert(std::distance(stars.begin(), b));
                    }
                }
            }
        }
        RemoveItems(stars, to_erase);
    }

    // exclude stars too close to the edge
    {
        enum { MIN_EDGE_DIST = 40 };
        int edgeDist = MIN_EDGE_DIST + extraEdgeAllowance;

        std::set<Peak>::iterator it = stars.begin();
        while (it != stars.end())
        {
            std::set<Peak>::iterator next = it;
            ++next;
            if (it->x <= edgeDist || it->x >= image.Size.GetWidth() - edgeDist ||
                it->y <= edgeDist || it->y >= image.Size.GetHeight() - edgeDist)
            {
                Debug.Write(wxString::Format("AutoFind: too close to edge [%d, %d] %.1f\n", it->x, it->y, it->val));
                stars.erase(it);
            }
            it = next;
        }
    }

    // At first I tried running Star::Find on the survivors to find the best
    // star. This had the unfortunate effect of locating hot pixels which
    // the psf convolution so nicely avoids. So, don't do that!  -ag

    // try to identify the saturation point

    //  first, find the peak pixel overall
    unsigned short maxVal = 0;
    for (unsigned int i = 0; i < image.NPixels; i++)
        if (image.ImageData[i] > maxVal)
            maxVal = image.ImageData[i];

    // next see if any of the stars has a flat-top
    bool foundSaturated = false;
    for (std::set<Peak>::reverse_iterator it = stars.rbegin(); it != stars.rend(); ++it)
    {
        Star tmp;
        tmp.Find(&image, searchRegion, it->x, it->y, FIND_CENTROID);
        if (tmp.WasFound() && tmp.GetError() == STAR_SATURATED)
        {
            if ((maxVal - tmp.PeakVal) * 255U > maxVal)
            {
                // false positive saturation, flat top but below maxVal
                Debug.Write(wxString::Format("AutoSelect: false positive saturation peak = %hu, max = %hu\n", tmp.PeakVal, maxVal));
            }
            else
            {
                // a saturated star was found
                foundSaturated = true;
                break;
            }
        }
    }

    unsigned int sat_level; // saturation level, including pedestal
    if (foundSaturated)
    {
        // use the peak overall pixel value as the saturation limit
        Debug.Write(wxString::Format("AutoSelect: using saturation level peakVal = %hu\n", maxVal));
        sat_level = maxVal; // includes pedestal
    }
    else
    {
        // no staurated stars found, can't make any assumption about whether the max val is saturated

        Debug.Write(wxString::Format("AutoSelect: using saturation level from BPP %u and pedestal %hu\n",
            image.BitsPerPixel, image.Pedestal));

        sat_level = ((1U << image.BitsPerPixel) - 1) + image.Pedestal;
        if (sat_level > 65535)
            sat_level = 65535;
    }
    unsigned int diff = sat_level > image.Pedestal ? sat_level - image.Pedestal : 0U;
    // "near-saturation" threshold at 90% saturation
    unsigned short sat_thresh = (unsigned short)((unsigned int) image.Pedestal + 9 * diff / 10);

    Debug.Write(wxString::Format("AutoSelect: BPP = %u, saturation at %u, pedestal %hu, thresh = %hu\n",
        image.BitsPerPixel, sat_level, image.Pedestal, sat_thresh));

    // Final star selection
    //   pass 1: find brightest star with peak value < 90% saturation AND SNR > 6
    //       this pass will reject saturated and nearly-saturated stars
    //   pass 2: find brightest non-saturated star
    //   pass 3: find brightest star, even if saturated

    for (int pass = 1; pass <= 3; pass++)
    {
        Debug.Write(wxString::Format("AutoSelect: finding best star pass %d\n", pass));

        for (std::set<Peak>::reverse_iterator it = stars.rbegin(); it != stars.rend(); ++it)
        {
            Star tmp;
            tmp.Find(&image, searchRegion, it->x, it->y, FIND_CENTROID);
            if (tmp.WasFound())
            {
                if (pass == 1)
                {
                    if (tmp.PeakVal > sat_thresh)
                    {
                        Debug.Write(wxString::Format("Autofind: near-saturated [%d, %d] %.1f Mass %.f SNR %.1f Peak %hu\n", it->x, it->y, it->val, tmp.Mass, tmp.SNR, tmp.PeakVal));
                        continue;
                    }
                    if (tmp.GetError() == STAR_SATURATED || tmp.SNR < 6.0)
                        continue;
                }
                else if (pass == 2)
                {
                    if (tmp.GetError() == STAR_SATURATED)
                    {
                        Debug.Write(wxString::Format("Autofind: star saturated [%d, %d] %.1f Mass %.f SNR %.1f\n", it->x, it->y, it->val, tmp.Mass, tmp.SNR));
                        continue;
                    }
                }

                // star accepted
                SetXY(it->x, it->y);
                Debug.Write(wxString::Format("Autofind returns star at [%d, %d] %.1f Mass %.f SNR %.1f\n", it->x, it->y, it->val, tmp.Mass, tmp.SNR));
                return true;
            }
        }

        if (pass == 1)
            Debug.Write("AutoFind: could not find a star on Pass 1\n");
        else if (pass == 2)
            Debug.Write("AutoFind: could not find a non-saturated star!\n");
    }

    Debug.Write("Autofind: no star found\n");
    return false;
}