def objective(threshold, fwhm, sigma_radius, roundlo, roundhi, sharplo,
              sharphi):
    res_table = DAOStarFinder(threshold=median + std * threshold,
                              fwhm=fwhm,
                              sigma_radius=sigma_radius,
                              sharplo=sharplo,
                              sharphi=sharphi,
                              roundlo=roundlo,
                              roundhi=roundhi,
                              exclude_border=True)(img)
    if not res_table:
        return 3000

    xys = structured_to_unstructured(
        np.array(res_table['xcentroid', 'ycentroid']))
    seen_indices = set()
    offsets = []
    for xy in xys:
        dist, index = lookup_tree.query(xy)
        if dist > 2 or index in seen_indices:
            offsets.append(np.nan)
        else:
            offsets.append(dist)
        seen_indices.add(index)

    offsets += [np.nan] * len(seen_indices - set(lookup_tree.indices))
    offsets += [np.nan] * abs(len(ref_table) - len(res_table))
    offsets = np.array(offsets)
    offsets -= np.nanmean(offsets)
    offsets[np.isnan(offsets)] = 3.

    return np.sqrt(np.sum(np.array(offsets)**2))
Example #2
0
def locateStarsInImage(imageArray):
    mean, median, std = sigma_clipped_stats(imageArray, sigma=sigmaParameter)
    daofind = DAOStarFinder(fwhm=fwhmParameter,
                            threshold=thresholdParameter * std)
    sources = daofind(imageArray - median)

    return sources
Example #3
0
File: fji.py Project: felipeji/fji
def Seeing(data, r=11):

    from astropy.stats import sigma_clipped_stats
    from photutils.detection import DAOStarFinder
    from astropy.nddata.utils import Cutout2D

    # Source detection

    mean, median, std = sigma_clipped_stats(data, sigma=3)

    starfind = DAOStarFinder(fwhm=4.0,
                             threshold=5. * std,
                             exclude_border=True,
                             sky=median)
    sources = starfind(data - median)

    x = sources['xcentroid']
    y = sources['ycentroid']

    # FWHM over all the detected sources
    fwhm = []
    for i in range(len(x)):
        cut = Cutout2D(data, [x[i], y[i]], r, mode='partial')
        sec = cut.data
        xc, yc = cut.to_cutout_position([x[i], y[i]])
        fwhm.append(FWHM(sec, xc, yc))

    return np.array(fwhm)
Example #4
0
def getnewtargetlist(imagedata):
    _logger.info("Redoing target list")
    mean, median, std = sigma_clipped_stats(imagedata[200:-200, 200:-200],
                                            sigma=3.0)
    daofind = DAOStarFinder(fwhm=3, threshold=5. * std)
    sources = daofind(imagedata - median)
    retTables = Table(
        [sources['xcentroid'], sources['ycentroid'], sources['flux']],
        names=['x', 'y', 'FLUX'])
    return retTables
Example #5
0
def daodetect(image,nsigma=1.5,fwhm=3.0):
    """ Detection with DAOFinder."""

    threshold = np.median(image.error)*nsigma
    daofind = DAOStarFinder(fwhm=fwhm, threshold=threshold, sky=0.0)  
    objects = daofind(image.data-image.sky, mask=image.mask)
    # homogenize the columns
    objects['xcentroid'].name = 'x'
    objects['ycentroid'].name = 'y'        
    return objects
Example #6
0
def bar():
    # TODO fails with friggin index error in model again:
    config = Config()
    image, input_table = read_or_generate_image('gauss_cluster_N1000')
    mean, median, std = sigma_clipped_stats(image, sigma=config.clip_sigma)
    star_guesses = make_stars_guess(image,
                                    DAOStarFinder(median * 3, 4.),
                                    cutout_size=51)
    config.epsf_guess = make_epsf_combine(star_guesses)
    config.epsfbuilder_iters = 4
Example #7
0
def astrometry_extract(data,
                       fwhm=3.,
                       ksigma=5.,
                       csigma=3.,
                       indexing=1,
                       bintable=True):
    ''' Extracts the star positions (0-indexing), but not extended ones.
    Note
    ----
    This is just a convenience function for DAOStarFinder. First used for the
    astrometry client. This is why the xy positions are sorted by flux.

    Parameters
    ----------
    data: ndarray
        The array containing the pixel values
    fwhm: float
        The estimated FWHM of stellar objects in the image.
    ksigma, csigma: float
        The threshold for the detection will be calculated by median plus
        ``ksigma`` times standard deviation, where the median and standard
        deviation is calculated from the ``csigma``-sigma clipping on to the
        original image (``data``).
    indexing: int, float
        Whether to use 0 or 1 indexing. The user may use any floating number
        for their own indexing, although 0 or 1 is the most usual case.
    bintable: bool
        Whether to convert to FITS BINTABLE format. This is required for astrometry.net

    Example
    -------
    >>> xy = extracter(orig.data, fwhm=4, ksigma=5, csigma=3) + 1  # 1-indexing
    >>> np.savetxt(srcpath, xy, fmt='%d')
    >>> plt.plot(*xy.T, 'rx', ms=10)
    '''
    avg, med, std = sigma_clipped_stats(data, sigma=csigma, iters=1)
    finder = DAOStarFinder(fwhm=fwhm,
                           threshold=med + ksigma * std,
                           exclude_border=True)
    sources = finder(data)
    sources.sort(["flux"])
    xy = np.vstack((sources["xcentroid"].round().astype(int).tolist(),
                    sources["ycentroid"].round().astype(int).tolist())).T
    xy += indexing

    if bintable:
        x = fits.Column(name='x', format='Iw', array=xy[:, 0])
        y = fits.Column(name='y', format='Iw', array=xy[:, 1])
        xy = fits.BinTableHDU.from_columns([x, y])
        return xy

    return xy
def photutils_daostarfinder(img, thresholds, fwhms):
    mask = np.zeros_like(img, dtype=np.bool)
    results = []

    for t, f in zip(thresholds, fwhms):
        dao = DAOStarFinder(threshold=t, fwhm=f)
        with warnings.catch_warnings():
            warnings.filterwarnings(
                'ignore', category=NoDetectionsWarning
            )
            dao_dataframe = dao.find_stars(img, mask=mask)
        dao_dataframe = dao.find_stars(img, mask=mask)
        if dao_dataframe is not None:
            dao_dataframe = dao_dataframe.to_pandas()
            results += [dao_dataframe]
        
            _mask = np.zeros_like(img, dtype=np.bool)
            
            y_limit, x_limit = np.array(img.shape) - 1
            spot_mask_positions = (dao_dataframe
                .loc[:, ['xcentroid', 'ycentroid']]
                .transform(np.round).astype('int')
                .query('({y} >= 0) & ({y} <= @y_limit)'.format(y='ycentroid'))
                .query('({x} >= 0) & ({x} <= @x_limit)'.format(x='xcentroid'))
            )
            _mask[
                spot_mask_positions.ycentroid,
                spot_mask_positions.xcentroid
            ] = True
            # fwhm ~= 2.355 sigma
            mask += binary_dilation(_mask, selem=disk(2*f))
    
    if len(results) > 0:
        return pd.concat(results)
    else:
        return pd.DataFrame(columns=['xcentroid', 'ycentroid'])
Example #9
0
def get_finder(image: np.ndarray, config: Config) -> photutils.StarFinderBase:
    """construct a StarFinder from a given configuration and image(needed for threshold)"""

    mean, median, std = sigma_clipped_stats(image, sigma=config.clip_sigma)
    threshold = median + config.threshold_factor * std

    finder = DAOStarFinder(threshold=threshold,
                           fwhm=config.fwhm_guess,
                           sigma_radius=config.sigma_radius,
                           sharplo=config.sharplo,
                           sharphi=config.sharphi,
                           roundlo=config.roundlo,
                           roundhi=config.roundhi,
                           exclude_border=config.exclude_border)
    return finder
Example #10
0
def psfphotometry(imagefile,
                  ra=None,
                  dec=None,
                  x=None,
                  y=None,
                  fwhm=5.0,
                  zp=0.0,
                  gain=1.0,
                  doDifferential=False,
                  xfield=None,
                  yfield=None,
                  xfirst=None,
                  yfirst=None):

    hdulist = fits.open(imagefile)
    header = fits.getheader(imagefile)

    if x == None:
        w = WCS(header)
        x0, y0 = w.wcs_world2pix(ra, dec, 1)
        gain = 1.0
    else:
        x0, y0 = x, y

    if len(hdulist) > 3:
        image = hdulist[1].data
    elif len(hdulist) == 2:
        image = hdulist[0].data
    else:
        image = hdulist[0].data
    image_shape = image.shape

    #daogroup = DAOGroup(crit_separation=8)
    daogroup = DAOGroup(crit_separation=25)

    mmm_bkg = MMMBackground()
    #iraffind = IRAFStarFinder(threshold=2.0*mmm_bkg(image),
    #                          fwhm=4.0)
    fitter = LevMarLSQFitter()
    gaussian_prf = IntegratedGaussianPRF(flux=1, sigma=1.7)
    gaussian_prf.sigma.fixed = False
    gaussian_prf.flux.fixed = False

    psffile = imagefile.replace(".fits", ".psf")
    fid = open(psffile, 'w')

    if len(image_shape) == 3:

        nhdu, xshape, yshape = image.shape
        dateobs = utcparser(hdulist[0].header["UTCSTART"])
        mjd = dateobs.mjd

        if "KINCYCTI" in hdulist[0].header:
            mjdall = mjd + np.arange(
                nhdu) * hdulist[0].header["KINCYCTI"] / 86400.0
        else:
            mjdall = mjd + np.arange(
                nhdu) * hdulist[0].header["EXPTIME"] / 86400.0

        mjds, mags, magerrs, fluxes, fluxerrs = [], [], [], [], []
        for jj in range(nhdu):
            if np.mod(jj, 10) == 0:
                print('PSF fitting: %d/%d' % (jj, nhdu))

            image = hdulist[0].data[jj, :, :]
            mjd = mjdall[jj]

            n, median, std = sigma_clipped_stats(image, sigma=3.0)
            daofind = DAOStarFinder(fwhm=2.0, threshold=2. * std)

            #phot_obj = IterativelySubtractedPSFPhotometry(finder=daofind,
            #                                              group_maker=daogroup,
            #                                              bkg_estimator=mmm_bkg,
            #                                              psf_model=gaussian_prf,
            #                                              fitter=fitter,
            #                                              fitshape=(21, 21),
            #                                              niters=10)

            image = image - np.median(image)
            image_slice = np.zeros(image.shape)

            slsize = 25
            xmin = np.max([0, int(x0 - slsize)])
            xmax = np.min([int(x0 + slsize), image.shape[0]])
            ymin = np.max([0, int(y0 - slsize)])
            ymax = np.min([int(y0 + slsize), image.shape[1]])
            image_slice[ymin:ymax, xmin:xmax] = 1

            if doDifferential:
                xmin_f = np.max([0, int(xfield - slsize)])
                xmax_f = np.min([int(xfield + slsize), image.shape[0]])
                ymin_f = np.max([0, int(yfield - slsize)])
                ymax_f = np.min([int(yfield + slsize), image.shape[1]])
                image_slice[ymin_f:ymax_f, xmin_f:xmax_f] = 1

            image = image * image_slice

            if (xfirst is None) or (yfirst is None):
                phot_obj = BasicPSFPhotometry(finder=daofind,
                                              group_maker=daogroup,
                                              psf_model=gaussian_prf,
                                              fitter=fitter,
                                              fitshape=(21, 21),
                                              bkg_estimator=mmm_bkg)
                phot_results = phot_obj(image)
            else:
                gaussian_prf = IntegratedGaussianPRF(flux=1, sigma=1.7)
                gaussian_prf.sigma.fixed = False
                gaussian_prf.flux.fixed = False
                gaussian_prf.x_0.fixed = False
                gaussian_prf.y_0.fixed = False

                phot_obj = BasicPSFPhotometry(group_maker=daogroup,
                                              psf_model=gaussian_prf,
                                              fitter=fitter,
                                              fitshape=(21, 21),
                                              bkg_estimator=mmm_bkg)

                pos = Table(names=['x_0', 'y_0'],
                            data=[[xfirst, xfield], [yfirst, yfield]])
                phot_results_tmp = phot_obj(image, init_guesses=pos)
                resimage = phot_obj.get_residual_image()

                pos = Table(names=['x_0', 'y_0'], data=[[x0], [y0]])

                gaussian_prf = IntegratedGaussianPRF(flux=1, sigma=1.7)
                gaussian_prf.sigma.fixed = False
                gaussian_prf.flux.fixed = False
                gaussian_prf.x_0.fixed = True
                gaussian_prf.y_0.fixed = True

                phot_obj = BasicPSFPhotometry(group_maker=daogroup,
                                              psf_model=gaussian_prf,
                                              fitter=fitter,
                                              fitshape=(7, 7),
                                              bkg_estimator=mmm_bkg)

                phot_results = phot_obj(resimage, init_guesses=pos)

                phot_results = vstack([phot_results_tmp, phot_results])

            #if True:
            if False:
                #sources = iraffind(image)
                sources = daofind(image)
                import matplotlib.pyplot as plt

                positions = np.transpose(
                    (sources['ycentroid'], sources['xcentroid']))
                apertures = CircularAperture(positions, r=4.)
                fig, axs = plt.subplots(1, 2)
                plt.axes(axs[0])
                plt.imshow(image.T,
                           origin='lower',
                           cmap='viridis',
                           aspect=1,
                           interpolation='nearest',
                           vmin=np.percentile(image[image > 0], 10),
                           vmax=np.percentile(image[image > 0], 90))
                apertures.plot(color='red')
                plt.xlim([ymin, ymax])
                plt.ylim([xmin, xmax])

                resimage = phot_obj.get_residual_image()
                plt.axes(axs[1])
                plt.imshow(resimage.T,
                           origin='lower',
                           cmap='viridis',
                           aspect=1,
                           interpolation='nearest',
                           vmin=0,
                           vmax=np.percentile(resimage[resimage > 0], 90))
                apertures.plot(color='red')
                plt.xlim([ymin, ymax])
                plt.ylim([xmin, xmax])
                plt.savefig('test_%d.png' % jj)
                plt.close()

                fig, axs = plt.subplots(1, 2)
                plt.axes(axs[0])
                plt.imshow(image.T,
                           origin='lower',
                           cmap='viridis',
                           aspect=1,
                           interpolation='nearest',
                           vmin=np.percentile(image[image > 0], 10),
                           vmax=np.percentile(image[image > 0], 90))
                apertures.plot(color='red')
                plt.xlim([ymin_f, ymax_f])
                plt.ylim([xmin_f, xmax_f])

                resimage = phot_obj.get_residual_image()
                plt.axes(axs[1])
                plt.imshow(resimage.T,
                           origin='lower',
                           cmap='viridis',
                           aspect=1,
                           interpolation='nearest',
                           vmin=np.percentile(resimage[resimage > 0], 10),
                           vmax=np.percentile(resimage[resimage > 0], 90))
                apertures.plot(color='red')
                plt.xlim([ymin_f, ymax_f])
                plt.ylim([xmin_f, xmax_f])
                plt.savefig('test_f_%d.png' % jj)
                plt.close()

            #phot_results.pprint_all()

            #print(stop)

            dist = np.sqrt((phot_results["x_fit"] - x0)**2 +
                           (phot_results["y_fit"] - y0)**2)
            idx = np.argmin(dist)
            flux = phot_results[idx]["flux_fit"]
            fluxerr = phot_results[idx]["flux_unc"]
            magerr = 1.0857 * fluxerr / flux  #1.0857 = 2.5/log(10)
            mag = zp - 2.5 * np.log10(flux)

            if doDifferential:
                dist = np.sqrt((phot_results["x_fit"] - xfield)**2 +
                               (phot_results["y_fit"] - yfield)**2)
                idy = np.argmin(dist)
                flux_field = phot_results[idy]["flux_fit"]
                fluxerr_field = phot_results[idy]["flux_unc"]
                magerr_field = 1.0857 * fluxerr_field / flux_field  #1.0857 = 2.5/log(10)
                mag_field = zp - 2.5 * np.log10(flux_field)

                mag = mag - mag_field
                magerr = np.sqrt(magerr**2 + magerr_field**2)
                fluxerr = np.sqrt((fluxerr / flux)**2 +
                                  (fluxerr_field / flux_field)**2)
                flux = flux / flux_field
                fluxerr = flux * fluxerr

            #print(phot_results[idy]["flux_fit"], phot_results[idx]["flux_fit"])

            mjds.append(mjd)
            mags.append(mag)
            magerrs.append(magerr)
            fluxes.append(flux)
            fluxerrs.append(fluxerr)

            fid.write('%.5f %.5f %.5f %.5f %.5f\n' %
                      (dateobs.mjd, mag, magerr, flux, fluxerr))
        fid.close()

        return np.array(mjds), np.array(mags), np.array(magerrs), np.array(
            fluxes), np.array(fluxerrs)

    else:
        mjds, mags, magerrs, fluxes, fluxerrs = [], [], [], [], []
        for ii, hdu in enumerate(hdulist):
            if ii == 0: continue
            header = hdulist[ii].header
            image = hdulist[ii].data
            if not "DATE" in header:
                print("Warning: 'DATE missing from %s hdu %d/%d" %
                      (imagefile, ii, len(hdulist)))
                continue

            dateobs = Time(header["DATE"])

            phot_results = phot_obj(image)

            dist = np.sqrt((phot_results["x_fit"] - x0)**2 +
                           (phot_results["y_fit"] - y0)**2)
            idx = np.argmin(dist)
            flux = phot_results[idx]["flux_fit"]
            fluxerr = phot_results[idx]["flux_unc"]
            magerr = 1.0857 * fluxerr / flux  #1.0857 = 2.5/log(10)
            mag = zp - 2.5 * np.log10(flux)

            mjds.append(dateobs.mjd)
            mags.append(mag)
            magerrs.append(magerr)
            fluxes.append(flux)
            fluxerrs.append(fluxerr)

            fid.write('%.5f %.5f %.5f %.5f %.5f\n' %
                      (dateobs.mjd, mag, magerr, flux, fluxerr))
        fid.close()

        return np.array(mjds), np.array(mags), np.array(magerrs), np.array(
            fluxes), np.array(fluxerrs)
#  0  PRIMARY       1 PrimaryHDU     258   ()
#  1  SCI           1 ImageHDU        85   (2048, 2048)   float32
#  2  ERR           1 ImageHDU        10   (2048, 2048)   float32
#  3  DQ            1 ImageHDU        11   (2048, 2048)   int32 (rescales to uint32)
#  4  AREA          1 ImageHDU         9   (2048, 2048)   float32
#  5  VAR_POISSON    1 ImageHDU         9   (2048, 2048)   float32
#  6  VAR_RNOISE    1 ImageHDU         9   (2048, 2048)   float32
#  7  VAR_FLAT      1 ImageHDU         9   (2048, 2048)   float32
#  8  ASDF          1 BinTableHDU     11   1R x 1C   [17197B]

# The data is the hdu[1]
data = hdu[1].data

# Let's run a quick fitting using DAOStarFinder
mean, median, std = sigma_clipped_stats(data, sigma=3.0)
daofind = DAOStarFinder(fwhm=3.0, threshold=5. * std)
sources = daofind(data - median)

# And now, let's make some cuts to remove objects that are too faint or too bright
flux_min = 5
flux_max = 50
flux_range = np.where((sources['flux'] > flux_min)
                      & (sources['flux'] < flux_max))[0]

init_tbl = Table()
init_tbl['x_0'] = sources['xcentroid'][flux_range]
init_tbl['y_0'] = sources['ycentroid'][flux_range]
init_tbl['flux_0'] = sources['flux'][flux_range]

# And now, let's make a plot of the original image.
plt.figure(figsize=(9, 9))
Example #12
0
def make_tweakreg_catalog(model,
                          kernel_fwhm,
                          snr_threshold,
                          sharplo=0.2,
                          sharphi=1.0,
                          roundlo=-1.0,
                          roundhi=1.0,
                          brightest=None,
                          peakmax=None):
    """
    Create a catalog of point-line sources to be used for image
    alignment in tweakreg.

    Parameters
    ----------
    model : `ImageModel`
        The input `ImageModel` of a single image.  The input image is
        assumed to be background subtracted.

    kernel_fwhm : float
        The full-width at half-maximum (FWHM) of the 2D Gaussian kernel
        used to filter the image before thresholding.  Filtering the
        image will smooth the noise and maximize detectability of
        objects with a shape similar to the kernel.

    snr_threshold : float
        The signal-to-noise ratio per pixel above the ``background`` for
        which to consider a pixel as possibly being part of a source.

    sharplo : float, optional
        The lower bound on sharpness for object detection.

    sharphi : float, optional
        The upper bound on sharpness for object detection.

    roundlo : float, optional
        The lower bound on roundness for object detection.

    roundhi : float, optional
        The upper bound on roundness for object detection.

    brightest : int, None, optional
        Number of brightest objects to keep after sorting the full object list.
        If ``brightest`` is set to `None`, all objects will be selected.

    peakmax : float, None, optional
        Maximum peak pixel value in an object. Only objects whose peak pixel
        values are *strictly smaller* than ``peakmax`` will be selected.
        This may be used to exclude saturated sources. By default, when
        ``peakmax`` is set to `None`, all objects will be selected.

        .. warning::
            `DAOStarFinder` automatically excludes objects whose peak
            pixel values are negative. Therefore, setting ``peakmax`` to a
            non-positive value would result in exclusion of all objects.

    Returns
    -------
    catalog : `~astropy.Table`
        An astropy Table containing the source catalog.
    """
    if not isinstance(model, ImageModel):
        raise TypeError('The input model must be an ImageModel.')

    threshold_img = detect_threshold(model.data, nsigma=snr_threshold)
    # TODO:  use threshold image based on error array
    threshold = threshold_img[0, 0]  # constant image

    daofind = DAOStarFinder(fwhm=kernel_fwhm,
                            threshold=threshold,
                            sharplo=sharplo,
                            sharphi=sharphi,
                            roundlo=roundlo,
                            roundhi=roundhi,
                            brightest=brightest,
                            peakmax=peakmax)

    # Mask the non-imaging area (e.g. MIRI)
    mask = (dqflags.pixel['NON_SCIENCE'] & model.dq).astype(bool)

    sources = daofind(model.data, mask=mask)

    columns = ['id', 'xcentroid', 'ycentroid', 'flux']
    if sources:
        catalog = sources[columns]
    else:
        catalog = Table(names=columns,
                        dtype=(np.int_, np.float_, np.float_, np.float_))

    return catalog
Example #13
0
    starfinder_result = run_optimizer(starfinder_optimizer,
                                      starfinder_obj,
                                      n_evaluations=1400)
    x = starfinder_result.x

    print(
        list(zip(starfinder_result.space.dimension_names,
                 starfinder_result.x)))
    mean, median, std = sigma_clipped_stats(img, sigma=3)
    threshold = median + x[0] * std

    finder = DAOStarFinder(threshold=threshold,
                           fwhm=x[1],
                           sigma_radius=x[2],
                           roundlo=x[3],
                           roundhi=x[4],
                           sharplo=x[5],
                           sharphi=x[6],
                           exclude_border=True)

    result_table = finder(img)

    plt.imshow(img, norm=LogNorm(), cmap='inferno')
    plt.plot(input_table['x'],
             input_table['y'],
             'o',
             fillstyle='none',
             markeredgewidth=0.5,
             markeredgecolor='red',
             label=f'reference N={len(input_table)}')
    plt.plot(result_table['xcentroid'],
#plt.imshow(image)
#plt.show()

fitsStars = fits.open("Images/FitsImages/L_2019-04-08_22-59-51_c.fits")
imgStars = fitsStars[0].data.astype(np.float64)

#bkg = MMMBackground()
#background = bkg(imgStars)
#gaussian_prf = PRF()
#gaussian_prf.sigma.fixed = False
#photTester = DAOP(8,background,5,gaussian_prf,(11,11))

daogroup = DAOGroup(crit_separation=8)
mmm_bkg = MMMBackground()
iraffind = DAOStarFinder(threshold=2 * mmm_bkg(imgStars), fwhm=4.5)
fitter = LevMarLSQFitter()
gaussian_prf = IntegratedGaussianPRF(sigma=2.05)
gaussian_prf.sigma.fixed = False
photTester = IterativelySubtractedPSFPhotometry(finder=iraffind,
                                                group_maker=daogroup,
                                                bkg_estimator=mmm_bkg,
                                                psf_model=gaussian_prf,
                                                fitter=fitter,
                                                fitshape=(11, 11),
                                                niters=2)

photResults = photTester(imgStars)
print(photResults['x_fit', 'y_fit', 'flux_fit'])

finalImg = photTester.get_residual_image()
Example #15
0
def fwhm(image,syntax,sigma_lvl = None,fwhm = None):


    import warnings
    warnings.simplefilter(action='ignore', category=FutureWarning)

    '''
    Find full width half maxiumu of an image via gaussian fitting to isolated bright stars

    Can be very tempromental for bad images
    '''

    from astropy.stats import sigma_clipped_stats
    from photutils.detection import DAOStarFinder
    from autophot.packages.functions import gauss_fwhm,gauss_2d
    import numpy as np
    import pandas as pd
    import sys,os
    import lmfit
    from astropy.stats import sigma_clip
    import logging

    logger = logging.getLogger(__name__)


    if sigma_lvl != None:
        min_source_no  = 0
        max_source_no  = np.inf
    else:
        max_source_no = syntax['max_source_lim']
        min_source_no = syntax['min_source_lim']

    if sigma_lvl == None:
        threshold_value  = syntax['threshold_value']
        int_fwhm = syntax['fwhm_guess']
    else:
        threshold_value = sigma_lvl
        if fwhm != None:
            int_fwhm = fwhm

        else:

            int_fwhm = syntax['fwhm_guess']

    if fwhm != None:
        syntax['int_scale'] = syntax['scale']

    isolated_sources = []

    img_seg = [0]
    try:
        for idx in range(len(list(img_seg))):

            mean, median, std = sigma_clipped_stats(image,
                                                    sigma=syntax['fwhm_sigma'],
                                                    maxiters=syntax['fwhm_iters'])

            if sigma_lvl == None:
                logger.debug('Image stats: Mean %.3f :: Median %.3f :: std %.3f' % (mean,median,std))
                syntax['global_mean'] = mean
                syntax['global_median'] = median
                syntax['global_std'] = std

            # decrease
            m = 0

            # increase
            n = 0

            # backstop
            failsafe = 0

            decrease_increment = False

            # How much to drop/increase each iteration each
            fudge_factor = syntax['fudge_factor']

            bkg_detect_check=[]

            search_image = image.copy()

            # Remove target by masking with area with that of median image value - just so it's not picked up
            if syntax['target_name'] != None and fwhm == None:

                logger.info('Target location : (x,y) -> (%.3f,%.3f)' % (syntax['target_x_pix'] , syntax['target_y_pix']))

                search_image[int(syntax['target_y_pix'])-syntax['int_scale']: int(syntax['target_y_pix']) + syntax['int_scale'],
                             int(syntax['target_x_pix'])-syntax['int_scale']: int(syntax['target_x_pix']) + syntax['int_scale']] =  syntax['global_median'] * np.ones((int(2*syntax['int_scale']),int(2*syntax['int_scale'])))


            while True:
                    try:
                        # If iterations get to big - terminate
                        if failsafe>syntax['source_max_iter']:
                            logger.info(' Source detection gives up!')
                            break
                        else:
                            failsafe +=1

                        # check if threshold value is still good
                        threshold_value_check = threshold_value + n - m

                        # if <=0 reverse previous drop and and fine_fudge factor
                        if threshold_value_check  <= syntax['lim_SNR']:
                                logger.warning('Threshold value has gone below threshold - increasing by smaller increment ')

                                # revert privious decrease
                                decrease_increment = True
                                n=syntax['fine_fudge_factor']
                                # m = 0 to stop any further decrease

                                threshold_value += m
                                m = 0

                        else:

                            threshold_value = round(threshold_value + n - m,3)

                        # if threshold goes negative usesmaller fudge factor
                        if decrease_increment:
                            fudge_factor = syntax['fine_fudge_factor']




                        daofind = DAOStarFinder(fwhm      = np.ceil(int_fwhm),
                                                threshold = threshold_value*std,
                                                sharplo   =  0.2,sharphi = 1.0,
                                                roundlo   = -1.0,roundhi = 1.0
                                                )

                        sources = daofind(search_image - median)

                        if sources == None:
                            logger.warning('Sources == None')
                            m = fudge_factor
                            continue

                        sources = sources.to_pandas()

                        logger.info('Number of sources before cleaning - [s = %.1f]: %d ' % (threshold_value,len(sources)))

                        # f_x = len(sources) / (threshold_value*std)

                        # relative_change = f_x - np.nanmedian(f_x) / np.nanmedian(f_x)

                        if len(sources) == 0:
                            logger.warning('No sources')
                            m = fudge_factor

                            continue

                        # bkg_detect_check.append(relative_change)

                        try:
                            sources['xcentroid']
                            'Make sure some are detceted, if not try again '
                        except Exception as e:
                            logger.exception(e)
                            break


                        if len(sources) > 10000 and m !=0:
                            logger.warning('Picking up noise')
                            fudge_factor = syntax['fine_fudge_factor']
                            n = syntax['fine_fudge_factor']
                            m = 0
                            decrease_increment = True
                            continue

                        elif len(sources) > max_source_no:
                            logger.warning('Too many sources')

                            if m != 0 :
                                decrease_increment = True
                                n = syntax['fine_fudge_factor']
                                fudge_factor = syntax['fine_fudge_factor']
                            else:
                                n = fudge_factor

                            continue

                        elif len(sources) < min_source_no:
                            logger.warning('Too few sources')
                            m = fudge_factor
                            continue

                        elif len(sources) == 0:
                            logger.warning('No sources')
                            m = fudge_factor
                            continue


                        if len(sources) > 30:

                            if syntax['remove_boundary_sources']:

                                with_boundary = len(sources)

                                sources = sources[sources['xcentroid'] < image.shape[1] - syntax['pix_bound'] ]
                                sources = sources[sources['xcentroid'] > syntax['pix_bound'] ]
                                sources = sources[sources['ycentroid'] < image.shape[0] - syntax['pix_bound'] ]
                                sources = sources[sources['ycentroid'] > syntax['pix_bound'] ]

                                logger.debug('Removed %d sources near boundary' % (with_boundary - len(sources)))

                        x = np.array(sources['xcentroid'])
                        y = np.array(sources['ycentroid'])

                        iso_temp = []
                        pix_dist = []

                        if len(sources) < min_source_no:
                            logger.warning('Less than min source after boundary removal')
                            m = fudge_factor
                            continue

                        if sigma_lvl != None or len(sources) < 10:
                            isolated_sources = pd.DataFrame({'x_pix':x,'y_pix':y})

                        else:

                            for idx in range(len(x)):
                                try:

                                    x0 = x[idx]
                                    y0 = y[idx]

                                    dist = np.sqrt((x0-np.array(x))**2+(y0-np.array(y))**2)

                                    dist = dist[np.where(dist!=0)]

                                    isolated_dist = 0
                                    if syntax['isolate_sources']:

                                        isolated_dist = syntax['iso_scale']

                                    if len(dist) == 0:
                                        dist = [0]

                                    if min(list(dist)) >  isolated_dist:

                                        df = np.array((float(x0),float(y0)))

                                        iso_temp.append(df)

                                        pix_dist.append(dist)
                                except Exception as e:
                                    logger.exception(e)
                                    pass

                            if len(iso_temp) == 0:
                                logger.warning('Less than min source after isolating sources')
                                m = fudge_factor
                                continue

                            isolated_sources= pd.DataFrame(data = iso_temp)
                            isolated_sources.columns = ['x_pix','y_pix']
                            isolated_sources.reset_index()



                        x_rc = []
                        y_rc = []

                        sigma=[]
                        medianlst=[]

                        x_pix = np.arange(0,2 * syntax['int_scale'])
                        y_pix = np.arange(0,2 * syntax['int_scale'])

                        image_copy = image.copy()

                        for idx in isolated_sources.index:

                            try:


                                x0 = isolated_sources['x_pix'].loc[[idx]]
                                y0 = isolated_sources['y_pix'].loc[[idx]]

                                close_up = image_copy[int(y0)- syntax['int_scale']: int(y0) + syntax['int_scale'],
                                                      int(x0)- syntax['int_scale']: int(x0) + syntax['int_scale']]

                                if close_up.shape != (int(2*syntax['int_scale']),int(2*syntax['int_scale'])):
                                    sigma.append(np.nan)
                                    x_rc.append(np.nan)
                                    y_rc.append(np.nan)
                                    medianlst.append(np.nan)
                                    logger.warning('wrong close-up size')
                                    continue

                                mean, median_val, std = sigma_clipped_stats(close_up,
                                                                            sigma=syntax['fwhm_sigma'],
                                                                            maxiters=syntax['fwhm_iters'])
                                medianlst.append(median_val)
                                xx, yy = np.meshgrid(x_pix, y_pix)


                                if syntax['remove_sat']:
                                    try:
                                        saturation_lvl = syntax['sat_lvl']
                                    except:
                                        saturation_lvl = 2**16

                                    if np.nanmax(close_up) >= saturation_lvl:
                                        sigma.append(np.nan)
                                        x_rc.append(np.nan)
                                        y_rc.append(np.nan)
                                        continue


                                try:

                                     pars = lmfit.Parameters()
                                     pars.add('A',value = np.nanmax(close_up),min = 0)
                                     pars.add('x0',value = close_up.shape[0]/2)
                                     pars.add('y0',value = close_up.shape[0]/2)
                                     pars.add('sigma',value = 3,max = syntax['max_fit_fwhm'] / 2*np.sqrt(2*np.log(2)) )
                                     pars.add('sky',value = np.nanmedian(close_up))

                                     def residual(p):
                                         p = p.valuesdict()
                                         return (close_up - gauss_2d((xx,yy),p['x0'],p['y0'],p['sky'],p['A'],p['sigma']).reshape(close_up.shape)).flatten()

                                     mini = lmfit.Minimizer(residual, pars,nan_policy = 'omit')
                                     result = mini.minimize(method = 'least_squares')

                                     sigma_fit = abs(result.params['sigma'].value)

                                     sigma.append(sigma_fit)

                                     x_rc.append(result.params['x0'] - syntax['int_scale'] + x0)
                                     y_rc.append(result.params['y0'] - syntax['int_scale'] + y0)


                                except Exception as e:
                                    logger.exception(e)

                                    sigma.append(np.nan)
                                    x_rc.append(np.nan)
                                    y_rc.append(np.nan)

                                    pass

                            except Exception as e:
                                logger.exception(e)

                                sigma.append(np.nan)
                                x_rc.append(np.nan)
                                y_rc.append(np.nan)
                                medianlst.append(median_val)

                                continue

                        if sigma_lvl == None:
                            isolated_sources['sigma'] = pd.Series(sigma)
                            isolated_sources['median'] = pd.Series(medianlst)


                            if isolated_sources['sigma'].values == np.array([]):
                                logger.info('> No sigma values taken <')
                                continue
                            try:

                                if len(isolated_sources) > 30:
                                    isolate_mask = sigma_clip(isolated_sources['sigma'].values, sigma=1.5).mask

                                    isolated_sources = isolated_sources[~isolate_mask]

                            except:

                                isolated_sources = []

                            if len(isolated_sources) < min_source_no:
                                logger.warning('Less than min source after sigma clipping: %d' % len(isolated_sources))
                                threshold_value += m
                                if n ==0:
                                    decrease_increment = True
                                    n = syntax['fine_fudge_factor']
                                    fudge_factor = syntax['fine_fudge_factor']
                                else:
                                    n = fudge_factor
                                # m = fudge_factor
                                m=0
                                # n = fudge_factor
                                continue

                            logger.info('Isolated sources found [ %.1f sigma ]: %d' % (threshold_value,len(isolated_sources)))

                            sigma = np.nanmedian(isolated_sources['sigma'])
                            mean_fwhm = gauss_fwhm(sigma)

                            syntax['scale'] = int(np.ceil(syntax['scale_multipler'] * mean_fwhm))
                        else:
                            isolated_sources['sigma'] = pd.Series(sigma)
                            mean_fwhm = fwhm

                        break

                    except Exception as e:
                        logger.exception(e)
                        continue

        return mean_fwhm,isolated_sources,syntax

    except Exception as e:
        logger.exception(e)
        return np.nan
def psf_fit(data_array, data_file, psf_grid, fheader, imagemodel):

    # Let's run a quick fitting using DAOStarFinder
    mean, median, std = sigma_clipped_stats(data_array, sigma=3.0)
    daofind = DAOStarFinder(fwhm=3.0, threshold=5. * std)
    sources = daofind(data_array - median)

    # And now, let's make some cuts to remove objects that are too faint or too bright
    flux_min = 5  #5 #0
    flux_max = 50  #50 #1000
    flux_range = np.where((sources['flux'] > flux_min)
                          & (sources['flux'] < flux_max))[0]

    init_tbl = Table()
    init_tbl['x_0'] = sources['xcentroid'][flux_range]
    init_tbl['y_0'] = sources['ycentroid'][flux_range]
    init_tbl['flux_0'] = sources['flux'][flux_range]

    # And now, let's make a plot of the original image.
    plt.figure(figsize=(9, 9))
    imshow_norm(data_array,
                interval=PercentileInterval(99.),
                stretch=SqrtStretch())
    plt.colorbar()
    plt.savefig(data_file + '.png', dpi=300)
    plt.clf()

    # And now, let's make a plot of the image showing the positions of the objects.
    plt.figure(figsize=(9, 9))
    imshow_norm(data_array,
                interval=PercentileInterval(99.),
                stretch=SqrtStretch())
    plt.scatter(init_tbl['x_0'], init_tbl['y_0'], s=10, color='black')
    plt.colorbar()
    plt.savefig(data_file + '_with_daostarfinder_objects.png', dpi=300)
    plt.clf()

    eval_xshape = int(np.ceil(psf_grid.data.shape[2] / psf_grid.oversampling))
    eval_yshape = int(np.ceil(psf_grid.data.shape[1] / psf_grid.oversampling))

    # And now, let's run the PSF Photometry
    sigma_psf = 3.
    daogroup = DBSCANGroup(2.0 * sigma_psf * gaussian_sigma_to_fwhm)
    mmm_bkg = MMMBackground()
    fit_shape = (eval_yshape, eval_xshape)
    phot = BasicPSFPhotometry(daogroup,
                              mmm_bkg,
                              psf_grid,
                              fit_shape,
                              finder=None,
                              aperture_radius=3.)

    # This is the part that takes the longest, so I print out the date/time before and after.
    now = datetime.now()
    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
    print("Starting the fit: date and time = ", dt_string)

    tbl = phot(data_array, init_guesses=init_tbl)

    now = datetime.now()
    dt_string = now.strftime("%d/%m/%Y %H:%M:%S")
    print("Ending the fit: date and time = ", dt_string)

    # Now I format the output
    tbl['x_fit'].format = '%.1f'
    tbl['y_fit'].format = '%.1f'
    tbl['flux_fit'].format = '%.4e'
    tbl['flux_unc'].format = '%.4e'
    tbl['x_0_unc'].format = '%.4e'
    tbl['y_0_unc'].format = '%.4e'

    diff = phot.get_residual_image()
    hdu_out = fits.PrimaryHDU(diff, header=fheader)
    hdul_out = fits.HDUList([hdu_out])
    hdul_out.writeto(data_file + '_residual.fits')

    # And create a residual image from the fit
    plt.figure(figsize=(9, 9))
    imshow_norm(diff, interval=PercentileInterval(99.), stretch=SqrtStretch())
    #plt.scatter(tbl['x_fit'], tbl['y_fit'], s=80, facecolors='none', edgecolors='r')
    plt.colorbar()
    plt.savefig(data_file + '_residual.png', dpi=300)
    plt.clf()

    # Calculate the RA and DEC values from the x_fit and y_fit values.
    RA_fit = np.zeros(len(tbl['x_fit']))
    DEC_fit = np.zeros(len(tbl['x_fit']))
    RA_fit, DEC_fit = imagemodel.meta.wcs(tbl['x_fit'], tbl['y_fit'])

    tbl.add_column(DEC_fit, index=0, name='DEC_fit')
    tbl.add_column(RA_fit, index=0, name='RA_fit')

    # And write out the table to a file.
    tbl.write(data_file + '_psf_fit_output.fits')
Example #17
0
print('\n')

imheader = hdu_list[0].header
imdata = hdu_list[0].data
hdu_list.close()

#%% get image background statistics
mean, median, std = sigma_clipped_stats(imdata, sigma=3.0)
print(
    f'Image Background: mean = {mean:.5g}, median = {median:.5g}, standard deviation = {std:.3g}\n'
)

seeing = 6.0

#%% find the sources
daofind = DAOStarFinder(fwhm=seeing, sky=median, threshold=5. * std)
sources = daofind.find_stars(imdata - median)

print('\nPrint source locations:')
sources['id', 'xcentroid',
        'ycentroid'].pprint()  #print out positions of sources
print('\n')

#%% perform aperture photometry
# extract source postions from table; transpose is needed for proper orientation
positions = np.transpose((sources['xcentroid'], sources['ycentroid']))
# define the aperture
r_a = 3 * seeing
apertures = CircularAperture(positions, r=r_a)
# define the annulus
r_in = r_a + 3
Example #18
0
                    time.sleep(0.5)
            for job in jobs:
                optimizer.tell(job.args, job.result.get())
        except KeyboardInterrupt:
            pass

    res = optimizer.get_result()

    with open(result_filename, 'wb') as f:
        dill.dump(optimizer, f)

    threshold, fwhm, sigma_radius, roundlo, roundhi, sharplo, sharphi = res.x
    res_table = DAOStarFinder(threshold=median + std * threshold,
                              fwhm=fwhm,
                              sigma_radius=sigma_radius,
                              sharplo=sharplo,
                              sharphi=sharphi,
                              roundlo=roundlo,
                              roundhi=roundhi,
                              exclude_border=True)(img)

    plt.ion()
    plt.imshow(img, norm=LogNorm())
    plt.plot(res_table['xcentroid'],
             res_table['ycentroid'],
             'ro',
             markersize=0.5)

    plot_evaluations(res, dimensions=dimensions)
    plt.figure()
    ax = plot_convergence(res)
    ax.set_yscale('log')
Example #19
0
def baz():
    config = Config.instance()

    # throw away border pixels to make psf fit into original image

    psf = read_or_generate_helper('anisocado_psf', config)
    psf = center_cutout(
        psf, (51, 51))  # cutout center of psf or else it takes forever to fit

    config.output_folder = 'output_cheating_astrometry'
    filename = 'scopesim_grid_16_perturb0'

    image, input_table = read_or_generate_image(filename, config)

    origin = np.array(psf.shape) / 2
    # type: ignore
    epsf = photutils.psf.EPSFModel(psf,
                                   flux=None,
                                   origin=origin,
                                   oversampling=1,
                                   normalize=False)
    epsf = photutils.psf.prepare_psf_model(epsf, renormalize_psf=False)

    image, input_table = read_or_generate_image(filename, config)

    mean, median, std = sigma_clipped_stats(image, sigma=config.clip_sigma)
    threshold = median + config.threshold_factor * std

    fwhm = estimate_fwhm(epsf.psfmodel)

    finder = DAOStarFinder(threshold=threshold, fwhm=fwhm)

    grouper = DAOGroup(config.separation_factor * fwhm)

    shape = (epsf.psfmodel.shape / epsf.psfmodel.oversampling).astype(np.int64)

    epsf.fwhm = astropy.modeling.Parameter(
        'fwhm', 'this is not the way to add this I think')
    epsf.fwhm.value = fwhm
    bkgrms = MADStdBackgroundRMS()

    photometry = BasicPSFPhotometry(finder=finder,
                                    group_maker=grouper,
                                    bkg_estimator=bkgrms,
                                    psf_model=epsf,
                                    fitter=LevMarLSQFitter(),
                                    fitshape=shape)

    result_table = photometry(image)
    star_guesses = make_stars_guess(image, finder, 51)

    plot_filename = os.path.join(config.output_folder,
                                 filename + '_photometry_vs_sources')
    plot_image_with_source_and_measured(image,
                                        input_table,
                                        result_table,
                                        output_path=plot_filename)

    if len(result_table) != 0:
        plot_filename = os.path.join(config.output_folder,
                                     filename + '_measurement_offset')
        plot_xy_deviation(input_table, result_table, output_path=plot_filename)
    else:
        print(f"No sources found for {filename} with {config}")

    plt.figure()
    plt.imshow(epsf.psfmodel.data)
    save(os.path.join(config.output_folder, filename + '_epsf'), plt.gcf())

    plt.figure()
    plt.imshow(concat_star_images(star_guesses))
    save(os.path.join(config.output_folder, filename + '_star_guesses'),
         plt.gcf())

    res = PhotometryResult(image, input_table, result_table, epsf,
                           star_guesses)
def create_dao_like_coordlists(fitsfile,
                               sourcelist_filename,
                               make_region_file=False,
                               dao_fwhm=3.5,
                               bkgsig_sf=2.):
    """Make daofind-like coordinate lists

    Parameters
    ----------
    fitsfile : string
        Name of the drizzle-combined filter product to used to generate photometric sourcelists.

    sourcelist_filename : string
        Name of optionally generated ds9-compatible region file

    dao_fwhm : float
        (`~photutils.detection.DAOstarfinder` param 'fwhm') The full-width half-maximum (FWHM) of the major axis of the
        Gaussian kernel in units of pixels. Default value = 3.5.

    make_region_file : Boolean
        Generate ds9-compatible region file? Default value = True

    bkgsig_sf : float
        multiplictive scale factor applied to background sigma value to compute DAOfind input parameter
        'threshold'. Default value = 2.

    Returns
    -------
    sources : astropy table
        Table containing x, y coordinates of identified sources
    """
    hdulist = fits.open(fitsfile)
    image = hdulist['SCI'].data
    image -= np.nanmedian(image)
    bkg_sigma = mad_std(image, ignore_nan=True)
    daofind = DAOStarFinder(fwhm=dao_fwhm, threshold=bkgsig_sf * bkg_sigma)
    sources = daofind(image)
    hdulist.close()

    for col in sources.colnames:
        sources[col].info.format = '%.8g'  # for consistent table output

    # Write out ecsv file
    tbl_length = len(sources)
    sources.write(sourcelist_filename, format="ascii.ecsv")
    log.info("Created coord list  file '{}' with {} sources".format(
        sourcelist_filename, tbl_length))

    if make_region_file:
        out_table = sources.copy()
        # Remove all other columns besides xcentroid and ycentroid
        out_table.keep_columns(['xcentroid', 'ycentroid'])

        # Add offset of 1.0 in X and Y to line up sources in region file with image displayed in ds9.
        out_table['xcentroid'].data[:] += np.float64(1.0)
        out_table['ycentroid'].data[:] += np.float64(1.0)

        reg_filename = sourcelist_filename.replace(".ecsv", ".reg")
        out_table.write(reg_filename, format="ascii")
        log.info("Created region file '{}' with {} sources".format(
            reg_filename, len(out_table)))

    return (sources)
Example #21
0
    def psf_photometry(self,
                       mbi,
                       label=None,
                       subtract_galaxy=True,
                       sersic_model=None,
                       save_residual_images=False,
                       **kwargs):

        self.mbi = mbi
        self._setup_psf()

        if subtract_galaxy:
            self.phot_image = self.subtract_galaxy(label, sersic_model,
                                                   **kwargs)
        else:
            self.phot_image = mbi.image

        if self.use_hsc_bright_mask['phot']:
            logger.info('applying hsc bright object mask')
            for b in mbi.bands:
                mask = mbi.get_hsc_bright_object_mask(b).astype(bool)
                self.phot_image[b][mask] = 0

        catalog = LsstStruct()
        self.stddev = LsstStruct()

        if save_residual_images:
            self.residual_image = LsstStruct()

        for band in mbi.bands:
            if len(mbi.stats) > 0:
                self.stddev[band] = mbi.stats[band].stdev
            else:
                self.stddev[band] = self.bkgrms(self.phot_image[band])
            daogroup = DAOGroup(self.crit_separation * self.psf_fwhm[band])
            self.daofinder_opt['fwhm'] = self.psf_fwhm[band]
            self.phot_opts['aperture_radius'] = self.aperture_radius
            self.phot_opts['aperture_radius'] *= self.psf_fwhm[band]

            daofind = DAOStarFinder(self.threshold * self.stddev[band],
                                    exclude_border=True,
                                    **self.daofinder_opt)

            logger.info('performing ' + band + '-band psf photometry')

            photometry = IterativelySubtractedPSFPhotometry(
                finder=daofind,
                group_maker=daogroup,
                psf_model=self.psf_model[band],
                **self.phot_opts)

            with warnings.catch_warnings():
                message = '.*The fit may be unsuccessful;.*'
                warnings.filterwarnings('ignore',
                                        message=message,
                                        category=AstropyUserWarning)
                catalog[band] = photometry(image=self.phot_image[band])

            if save_residual_images:
                logger.info('generating residual image')
                self.residual_image[band] = subtract_psf(
                    mbi.image[band], self.psf_model[band], catalog[band])
        return catalog
Example #22
0
def find(image,
         fwhm,
         method='daophot',
         background='1D',
         frame='diff',
         diag=False):
    '''
        Find all stars above the sky background level using DAOFind-like algorithm

        Required inputs:
        image = 2D array of image on which to perform find
        fwhm = FWHM in pixels (1)

        Optional inputs:
        method = Either 'daophot' or 'peaks' to select different finding algorithms
        background = '2D' or '1D' to select 2- or 1-D background estimators
        frame = 'diff' or 'single' to set background behaviour for difference or single frames

    Example
    -------
    >>> np.random.seed(0)
    >>> im = np.ones((10,10)) + np.random.uniform(size=(10,10))
    >>> im *= u.ph
    >>> im[5,5] += 5 * u.ph
    >>> star_tbl, bkg_image, threshold = find(im, 1, method='peaks', background='1D', frame='single')
    >>> np.equal(len(star_tbl), 1)
    True
    '''
    from photutils.detection import DAOStarFinder, find_peaks
    from astropy.stats import sigma_clipped_stats

    if frame == 'diff':
        # Determine background RMS:
        bkg_image, sky = estimate_background(image,
                                             method=background,
                                             sigma=5,
                                             diag=diag)
        find_image = image
    elif frame == 'single':
        # Create and subtract a background image and determine background RMS:
        bkg_image, sky = estimate_background(image,
                                             method=background,
                                             sigma=2,
                                             diag=diag)
        find_image = image - bkg_image

    # Look for sources at twice the background RMS level
    threshold = 2 * sky

    # Make sure the image and threshold units are the same
    threshold = threshold.to(image.unit)

    # Find stars
    if method == 'daophot':
        finder = DAOStarFinder(threshold.value, fwhm)
        star_tbl = finder.find_stars(find_image.value)
        star_tbl['x'], star_tbl['y'] = \
            star_tbl['xcentroid'], star_tbl['ycentroid']
    elif method == 'peaks':
        star_tbl = find_peaks(find_image.value, threshold.value, box_size=3)
        star_tbl['x'], star_tbl['y'] = \
            star_tbl['x_peak'], star_tbl['y_peak']

    # Remove entries outside the image frame (mainly an issue with daophot), then reset the ID column:
    index = ((star_tbl['x'] < 0) | (star_tbl['y'] < 0) |
             (star_tbl['x'] > image.shape[0]) |
             (star_tbl['y'] > image.shape[1]))
    star_tbl.remove_rows(index)
    star_tbl['id'] = np.arange(len(star_tbl)) + 1

    if diag:
        print("Sky background rms: {}".format(sky))
        print("Found {} stars".format(len(star_tbl)))

    return star_tbl, bkg_image, threshold