コード例 #1
0
    def plot_apertures(self, ax, offset=9, wcs=None):
        if wcs:
            cpix = self._ref_centroids_sky.to_pixel(wcs)
        xlim = ax.get_xlim()
        ylim = ax.get_ylim()

        if self._apertures_obj is not None:
            if wcs:
                apertures_obj = [
                    CircularAperture(cpix, r) for r in self.aperture_radii
                ]
            else:
                apertures_obj = self._apertures_obj
            [apt.plot(ax=ax, alpha=0.25) for apt in apertures_obj]
            for istar, (x, y) in enumerate(apertures_obj[0].positions):
                if (xlim[0] <= x <= xlim[1]) and (ylim[0] <= y <= ylim[1]):
                    yoffset = offset if y < ylim[1] - 10 else -offset
                    ax.text(x + offset, y + yoffset, istar)
        if self._apertures_sky is not None:
            if wcs:
                apertures_sky = CircularAnnulus(cpix, self.aperture_radii[-1],
                                                self.aperture_radii[-1] + 15)
            else:
                apertures_sky = self._apertures_sky
            apertures_sky.plot(ax=ax, alpha=0.25)
コード例 #2
0
ファイル: phot_variability.py プロジェクト: mccbc/ObsTech2
def ap_phot(sources, data, source_r=6., sky_in=15, sky_out=20, plot=False):
    global fig
    centroids = (sources['xcentroid'], sources['ycentroid'])
    source_aperture = CircularAperture(centroids, r=source_r)
    source_area = source_aperture.area()
    source_table = aperture_photometry(data, source_aperture)

    sky_aperture = CircularAnnulus(centroids, r_in=sky_in, r_out=sky_out)
    sky_area = sky_aperture.area()
    sky_table = aperture_photometry(data, sky_aperture)

    sky_subtracted_source = deepcopy(source_table)

    for i in range(np.shape(centroids)[1]):
        sky_value = sky_table[i][3]
        sky_per_pix = sky_value / sky_area
        sky_behind_source = sky_per_pix * source_area
        sky_subtracted_source[i][3] -= sky_behind_source

    if plot:
        fig = plt.figure(figsize=(17, 17))
        plt.imshow(data, cmap='gray', origin='lower', vmin=0, vmax=1500)
        for i in range(np.shape(centroids)[1]):
            plt.annotate(str(source_table[i][0]),
                         xy=(np.float64(source_table[i][1]) + 15.,
                             np.float64(source_table[i][2]) + 15.),
                         color="white")
        source_aperture.plot(color='blue', lw=1.5, alpha=0.5)
        sky_aperture.plot(color="orange", lw=0.5, alpha=0.5)
        plt.tight_layout()
        plt.show()

    return sky_subtracted_source
コード例 #3
0
ファイル: phot80.py プロジェクト: fallingelf/Photopy
def ref2image(data,positions,aper,fig_name):
    apertures = CircularAperture(positions, r=aper[0])
    annulus_apertures = CircularAnnulus(positions, r_in=aper[1], r_out=aper[2])          
    fig = plt.figure(figsize=(20,20));fig.add_subplot(111)
    apertures.plot(color='blue',lw=2,alpha=1)
    annulus_apertures.plot(color='red',lw=2,alpha=0.5)
    norm = ImageNormalize(stretch=HistEqStretch(data))
    plt.imshow(data, cmap='Greys', origin='lower', norm=norm)
    for i in range(len(positions)):
        plt.text(positions[i][0]+10,positions[i][1]+10,str(i+1),fontdict={'size':'50','color':'blue'})
    plt.savefig(fig_name,dpi=150)
    plt.show()
コード例 #4
0
ファイル: phot80.py プロジェクト: fallingelf/Photopy
def imageshow(data,positions,aper=[8,12,20],rim_size=50):
    min_x=int(np.min(positions.T[0]))-rim_size;max_x=int(np.max(positions.T[0]))+rim_size
    min_y=int(np.min(positions.T[1]))-rim_size;max_y=int(np.max(positions.T[1]))+rim_size
    data=data[min_y:max_y,min_x:max_x]
    positions=positions-np.array([min_x,min_y])
    apertures = CircularAperture(positions, r=aper[0])
    annulus_apertures = CircularAnnulus(positions, r_in=aper[1], r_out=aper[2])          
    fig = plt.figure(figsize=(20,20));fig.add_subplot(111)
    apertures.plot(color='blue',lw=2,alpha=1)
    annulus_apertures.plot(color='red',lw=2,alpha=0.5)
    norm = ImageNormalize(stretch=HistEqStretch(data))
    plt.imshow(data, cmap='Greys', origin='lower', norm=norm)
    plt.show()
コード例 #5
0
def aper_phot(img_data,positions, r = 10., r_in = 14., r_out = 18,bkg_sub=False,plot=False):
"""
:params: r: Aperture Radius
:params: r_in: annulus aperture inside radius
:params: r_out: annulus aperture outside radius
:params: bkg_sub: True if background subtraction is needed
:params: plot: True to plot

:out: phot_table: Table with the values of the aperture photometry
"""

	#Background subtraction
	if bkg_sub == True:
		sigma_clip = SigmaClip(sigma=3., iters=10)
		bkg_estimator = MedianBackground()
		bkg = Background2D(img_data, (50, 50), filter_size=(3, 3),sigma_clip=sigma_clip, bkg_estimator=bkg_estimator)
		data_sub = img_data - bkg.background
	else:
		data_sub = img_data

	#Aperture Photometry using a circular aperture and a circular annulus
	apertures = CircularAperture(positions, r=r)
	annulus_apertures = CircularAnnulus(positions,r_in = r_in,r_out = r_out)
	apers = [apertures,annulus_apertures]
	phot_table = aperture_photometry(data_sub, apers)
	bkg_mean = phot_table['aperture_sum_1'] / annulus_apertures.area()
	bkg_sum = bkg_mean * apertures.area()
	final_sum = phot_table['aperture_sum_0'] - bkg_sum
	phot_table['residual_aperture_sum'] = final_sum
	positions = np.array(positions)

	if plot == True:
		#Ploting 
		norm = ImageNormalize(data_sub, interval=ZScaleInterval(),stretch=LinearStretch())
		plt.imshow(data_sub, cmap='Greys', origin='lower',norm=norm)
		apertures.plot(color='blue', lw=1.5, alpha=0.5)
		annulus_apertures.plot(color='green',lw=1.5,alpha=0.5)
		plt.plot(positions[:,0],positions[:,1], ls='none', color = 'red', marker='.', ms=10, lw=1.5)
		plt.xlim(0, data_sub.shape[1]-1)
		plt.ylim(0, data_sub.shape[0]-1)
		plt.show()	

	return phot_table
コード例 #6
0
ファイル: fji.py プロジェクト: felipeji/fji
class AnnSky:
    def __init__(self, data, coord, r_in, r_out, method, subpixels=None):
        from photutils import CircularAnnulus

        self.ap = CircularAnnulus(coord, r_in=r_in, r_out=r_out)

        self.sec_data = SecImData(self.ap,
                                  data,
                                  method=method,
                                  subpixels=subpixels)
        # media sigma clip

    def stat(self, sigma_clip):
        from astropy.stats import sigma_clipped_stats
        mean, median, stddev = sigma_clipped_stats(self.sec_data,
                                                   sigma=sigma_clip)

        return mean, median, stddev

    def plot(self, color='C0', ls='solid', lw=1):
        self.ap.plot(color=color, ls=ls, lw=lw)
コード例 #7
0
ファイル: snr.py プロジェクト: scibuff/astro-phd
def showStars(image):

    stars = findStars(image)

    # take just the 5 brightest objects
    stars.sort('flux')
    stars.reverse()
    list = stars[:3]

    positions = zip(list['xcentroid'], list['ycentroid'])
    apertures = CircularAperture(positions, r=6)
    annuli = CircularAnnulus(positions, r_in=18, r_out=24)
    phot_table = aperture_photometry(image, [apertures, annuli])

    plt.clf()

    plt.imshow(image, cmap='gray', norm=LogNorm())
    annuli.plot(color='green', lw=1, alpha=0.75)
    apertures.plot(color='blue', lw=1, alpha=0.75)
    plt.savefig('%s.png' % path, dpi=300)

    print list
    print phot_table
コード例 #8
0
def photometryimg(positions, img, i):
    
    positionslist = positions.tolist()
    
    aperture = CircularAperture(positionslist, r=5) #2*FWHM
    annulus_aperture = CircularAnnulus(positionslist, r_in=7, r_out=9)#4-5*FWHM+2*FWHM
    apers = [aperture, annulus_aperture]
    
    displayimage(img, 1, i) ###画图1
    aperture.plot(color='blue', lw=0.5)
    annulus_aperture.plot(color='red', lw=0.2)
    plt.pause(0.001)
    plt.clf()
    
    phot_table = aperture_photometry(img, apers)
    bkg_mean = phot_table['aperture_sum_1'] / annulus_aperture.area
    bkg_sum = bkg_mean * aperture.area
    final_sum = phot_table['aperture_sum_0'] - bkg_sum
    phot_table['residual_aperture_sum'] = final_sum       
    posflux = np.column_stack((positions, phot_table['residual_aperture_sum']))  
    #return posflux
    magstar = 25 - 2.5*np.log10(abs(final_sum/1))
    return posflux,magstar
コード例 #9
0
def CircleMaskPhometry(data, location, index=2):
    Mimgdata = np.copy(data)
    Mlocatin = np.copy(location)

    Mapeture = CircularAperture(Mlocatin, r=6)
    Mannuals = CircularAnnulus(Mlocatin, r_in=8., r_out=11.)

    Eannuals_masks = Mannuals.to_mask(method='center')

    bkg_median = []
    for mask in Eannuals_masks:
        Eannuals_data = mask.multiply(Mimgdata)
        Eannulus_data_1d = Eannuals_data[mask.data > 0]
        meandata, median_sigclip, _ = sigma_clipped_stats(Eannulus_data_1d)
        bkg_median.append(median_sigclip)

    bkg_median = np.array(bkg_median)
    phot = aperture_photometry(Mimgdata, Mapeture)
    phot['annulus_median'] = bkg_median
    phot['aper_bkg'] = bkg_median * Mapeture.area
    phot['aper_sum_bkgsub'] = phot['aperture_sum'] - phot['aper_bkg']

    Mpositionflux = np.transpose(
        (phot['xcenter'], phot['ycenter'], phot['aper_sum_bkgsub']))

    displayimage(Mimgdata, 3, index)
    Mapeture.plot(color='blue', lw=0.5)
    Mannuals.plot(color='red', lw=0.2)
    plt.pause(0.001)
    plt.clf()
    #Mannulus_data = Eannuals_masks[0].multiply(Mimgdata)
    #displayimage(Mannulus_data,3,index+1)

    flux_sum = phot['aper_sum_bkgsub']
    magstar = 25 - 2.5 * np.log10(abs(flux_sum / 1))

    return Mpositionflux, magstar
コード例 #10
0
    def run_counting(self):
        if self.images:
            self.find_sources_in_all_images()
            self.do_aperture_flux_measurement_in_all_images()

            print(
                "Choose the reference star on image that popped on the screen!"
            )
            print(
                "Use left mouse button. If you made a mistake simply click on correct star."
            )
            print("Close the window or hit Enter to proceed...\n")

            image = self.images[0]
            positions = self.sources_positions[0]
            object_aperture = CircularAperture(positions, self.aperture_radius)
            background_annulus = CircularAnnulus(positions, self._irad_annulus,
                                                 self._orad_annulus)

            self.ax.imshow(image,
                           norm=colors.PowerNorm(gamma=0.5),
                           cmap='PuBu_r')
            object_aperture.plot(color='white', lw=1)
            background_annulus.plot(color='white', lw=1)
            self.interactive = True
            plt.show()

            # Continue when image is closed
            threshold_flux = self.fluxes_in_all_images[0][
                self.reference_star_idx]
            counts = self.count_stars(threshold_flux)
            for f, c in zip(self.file_names, counts):
                print("{:5d} counts in central circle of image : {}".format(
                    c, f))
            print("\nAverage number of stars in central circle = {:.1f}\n".
                  format(np.mean(counts)))
コード例 #11
0
 def check_adjustments(self):
     if self.images:
         print("Current adjustments are:\n")
         print("Aperture radius = {} pix".format(self.aperture_radius))
         print("FWHM = {} pix".format(self.fwhm))
         print("Threshold = {:.2f}\n".format(self.threshold))
         #-------------------------------------------------------------------
         image = self.images[0]
         positions = None
         if len(self.images) == len(self.sources_positions):
             positions = self.sources_positions[0]
         else:
             positions = self.get_positions(image)
         # ------------------------------------------------------------------
         object_aperture = CircularAperture(positions, self.aperture_radius)
         background_annulus = CircularAnnulus(positions, self._irad_annulus,
                                              self._orad_annulus)
         self.ax.imshow(image,
                        norm=colors.PowerNorm(gamma=0.5),
                        cmap='PuBu_r')
         object_aperture.plot(color='white', lw=1)
         background_annulus.plot(color='white', lw=1)
         self.interactive = False
         plt.show()
コード例 #12
0
def find_zero_point(stack,
                    fil,
                    ext,
                    catalog,
                    data_path=None,
                    show_fwhm=False,
                    show_bkg=False,
                    show_contours=False,
                    r_annulus_in=3.5,
                    r_annulus_out=4.5,
                    log=None):
    '''

    Function used to find the FWHM, zero point, and zero point error for a specific stack and filter.

    Initially finds all sources in specified catalog within the image and matches them to a
    10+ sigma source in the image.
    Then makes a series of cuts to determine the standard stars to use when finding the zero point:

        * Only considers sources with a center count (in adu / pix / sec) between 100 and 300
            - Plots radial profiles of these sources
            - Finds the median FWHM of the image from these sources (returns this value)
        * Removes sources too close to the edge
        * Removes sources that are too close to each other
        * Removes oblong sources (most likely galaxies or two very close stars)

    Checks isophotes of remaining sources to determine if the stack is aligned correctly.
        * Shows the user a sample cutout of a source that reflects the isophotes of the stack as a whole
    If not, then allows the user the option of checking them individual images and possibly remaking the stack
    with `revisit_stack` before continuing.

    If ``show_bkg`` is True, shows the remaining sources to be used when finding the zero point, color coded by
    the median background level.

    Finds the instrumental magnitudes of the remaining sources and compares to the magnitudes from the catalog
    and determines the zero point and zero point error. Returns these values along with the FWHM of the image.

    Parameters
    ----------
    :param stack: str
        Name of the stack to use (include path to the stack).

    :param fil: str
        Name of the filter of the stack.

    :param ext: int
        Extension to use (only used if `revisit_stack` is called).

    :param catalog: str
        Name of catalog to query for magnitudes.

    :param data_path: str, optional
        Path to the original data (only used if `revisit_stack` is called).
        Default is ``None``. If ``None`` and `revisit_stack` is called, error will occur.

    :param show_fwhm: boolean, optional
        Set to ``True`` to view the radial profiles when finding the FWHM of the image.
        Default is ``False``.

    :param show_bkg: boolean, optional
        Set to ``True`` to view the final sources before finding the zero point,
        color coded by the median background level.
        Default is ``False``.

    :param show_contours: boolean, optional
        Set to ``True`` to view the contours when examining the isophotes. Also shows isophotes in `revisit_stack`.
        Default is ``False``.

    :param r_annulus_in: int, optional
        Radius for the inner annulus when performing photometry on the final sources to determine their
        instrumental magnitude.
        Default is ``3.5``.

    :param r_annulus_out: int, optional
        Radius for the oter annulus when performing photometry on the final sources to determine their
        instrumental magnitude.
        Default is ``4.5``.

    :param log:
        In-depth log.
        If no log is inputted, information is printed out instead of being written to ``log``.
        Default is ``None``.

    Returns
    -------

    :return: float, float, float
        First float is the calculated zero point of the image.
        Second float is the calculated zero point error of the image.
        Third float is the calculated FWHM of the image.

    '''

    if log is not None:
        log.info("Computing FWHM of sources in the stack %s" % stack)
    else:
        print("Computing FWHM of sources in the stack %s" % stack)

    with fits.open(stack) as hdr:
        header, data = hdr[0].header, hdr[0].data
    w = wcs.WCS(header)

    # Center in pixel coordinates, change to real coordinates
    real_coords = w.wcs_pix2world(
        np.array([[header['NAXIS1'] / 2, header['NAXIS2'] / 2]], np.float), 1)
    coords = SkyCoord(real_coords[0][0] * u.deg,
                      real_coords[0][1] * u.deg,
                      frame='fk5')

    Vizier.ROW_LIMIT = -1  # So that you get all the rows, not just the first 50

    # Information about the sources from catalog found in the extension (full area, not radially)
    cat_ID, cat_ra, cat_dec, cat_mag, cat_err = find_catalog(catalog, fil)
    cat = Vizier.query_region(coords, width=13.65 * u.arcmin, catalog=cat_ID)
    if len(cat) == 0:
        log.info('No catalog sources at input coordinates!  Exiting...')
        sys.exit()
    else:
        cat = cat[0]
        mask = (~cat[cat_ra].mask) & (~cat[cat_dec].mask) &\
            (~cat[cat_mag].mask) & (~cat[cat_err].mask)
        cat = cat[mask]

    # Find the sources above 10 std in the image
    _, median, std = sigma_clipped_stats(data, sigma=3.0)
    daofind = DAOStarFinder(
        fwhm=7, threshold=10. *
        std)  # Looking for best sources, so raise threshold way up
    dao_sources = daofind(
        np.asarray(data))  # DAOStarFinder sources == dao_sources
    if log is not None:
        log.info(
            "{0} objects found in {1} and {2} sources above 10 std found in the stack"
            .format(len(cat), catalog, len(dao_sources)))
        log.info(
            "DAOStarFinder found {0} sources that are 10 std above the background"
            .format(len(dao_sources)))
    else:
        print(
            "{0} objects found in {1} and {2} sources above 10 std found in the stack"
            .format(len(cat), catalog, len(dao_sources)))
        print(
            "DAOStarFinder found {0} sources that are 10 std above the background"
            .format(len(dao_sources)))

    ra_dao = [
    ]  # Change pixel coordinates to real coordinates for DAOStarFinder sources
    dec_dao = []
    for i, j in enumerate(dao_sources):
        pixel_coords = np.array([[j['xcentroid'], j['ycentroid']]], np.float)
        dao_coords = w.wcs_pix2world(pixel_coords, 1)
        ra_dao.append(dao_coords[0][0])
        dec_dao.append(dao_coords[0][1])

    # Coordinates need to be in real, not pixel
    coords_dao = SkyCoord(ra_dao * u.deg, dec_dao * u.deg,
                          frame='fk5')  # Coordinates from sources
    coords_cat = SkyCoord(cat[cat_ra], cat[cat_dec],
                          frame='fk5')  # Coordinates from catalog

    # Match each catalog source to the closest dao_source
    idx, d2, d3 = coords_cat.match_to_catalog_sky(
        coords_dao)  # Match smaller to larger
    if log is not None:
        log.info("{0} objects have been matched".format(len(idx)))
    else:
        print("{0} objects have been matched".format(len(idx)))

    # Next step: find the radial profiles from intermediate-brightness sources and then find the fwhm of the field
    step_size = 1
    fwhm_orig = 4

    coords = [
        w.wcs_world2pix(np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0]
        for j in idx
    ]  # Positions of DAO

    radii = np.arange(step_size, 2.5 * fwhm_orig,
                      step_size)  # Radii of apertures
    x_data = np.arange(
        0, 2.5 * fwhm_orig,
        step_size)  # Steps (starting at zero, to 2.5 * fwhm, by step_size)

    apers_area = [np.pi * (step_size**2)]  # Circle area = pi*(r**2)
    for r in radii:
        apers_area.append(np.pi *
                          ((r + step_size)**2 -
                           r**2))  # Annulus area = pi*(r_out**2 - r_in**2)

    apertures = [CircularAperture(coords, r=step_size)
                 ]  # For circle aperture around center
    for r in radii:
        apertures.append(CircularAnnulus(coords, r_in=r, r_out=r +
                                         step_size))  # Annuli apertures

    phot_table = aperture_photometry(
        data, apertures
    )  # Each row is a source, with len(x_data) photometry measurements

    if show_fwhm:
        fig, ax = plt.subplots()
    sigmas = []
    new_idx = []
    for i, source in enumerate(
            phot_table
    ):  # Find the sums per area (adu/pix/sec) for each of the sources
        sums = [source[i] / apers_area[i - 3] for i in range(3, len(source))]
        # Only consider moderately bright sources; too bright are saturated and too dim can have larger error values
        if 100 <= sums[0] <= 30000 and sums[len(sums) - 1] < 30000:
            try:  # Try unless it takes too much time...if that happens, it's probably a bad fit and we don't want it
                w_fit = x_data
                f_fit = sums
                g, _ = curve_fit(fix_x0, w_fit, f_fit)
                if show_fwhm:  # Add two plots: radial profile and Gaussian fit
                    ax.plot(x_data, sums, 'o-', alpha=0.5, lw=2,
                            markersize=4)  # Plot the radial profiles
                    ax.plot(x_data,
                            fix_x0(x_data, *g),
                            '^-',
                            alpha=0.5,
                            lw=2,
                            markersize=4)
                sigmas.append(
                    np.abs(g[1])
                )  # Sigma can be either positive or negative; fix_x0 allows both
                new_idx.append(
                    idx[i]
                )  # Append to a list that will be used later for future sources
            except RuntimeError:
                pass

    fwhm_values = [i * 2.35482 for i in sigmas]
    sigmas_post_clipping, _, _ = sigmaclip(sigmas, low=3,
                                           high=3)  # Clip outlier sigma values
    med = np.median(sigmas_post_clipping)  # Median sigma value
    fwhm = med * 2.35482  # FWHM to be used to find the zero point and make cuts
    if log is not None:
        log.info(
            "Median sigma: {0} Median FWHM: {1} Number of sources counted/total found in field: {2}/{3}"
            .format('%5.3f' % med, '%2.3f' % fwhm, len(sigmas_post_clipping),
                    len(dao_sources)))
    else:
        print(
            "Median sigma: {0} Median FWHM: {1} Number of sources counted/total found in field: {2}/{3}"
            .format('%5.3f' % med, '%2.3f' % fwhm, len(sigmas_post_clipping),
                    len(dao_sources)))

    clipped_fwhm_values = [i * 2.35482 for i in sigmas_post_clipping]

    if show_fwhm:
        ax.set_title("Radial profiles of sources 10 std above background"
                     )  # Plot of radial profiles and Gaussian fits
        ax.set_xlabel("Radial distance from centroid of source (pixels)")
        ax.set_ylabel("Count (adu per second per pixel)")
        plt.show()

        fig, ax = plt.subplots(
        )  # Histogram of fwhm_values after sigma clipping
        plt.hist(clipped_fwhm_values, bins=30)
        ax.set_title("FWHM values for Gaussian-fit radial profiles")
        ax.set_xlabel("FWHM value for best-fit Gaussian (pixels)")
        plt.show()

    # Calculate the zero point
    if log is not None:
        log.info(
            "Computing the zero point of the stack {0} with filter {1} and FWHM of {2}"
            .format(stack, fil, '%2.3f' % fwhm))
    else:
        print(
            "Computing the zero point of the stack {0} with filter {1} and FWHM of {2}"
            .format(stack, fil, '%2.3f' % fwhm))

    orig_coords = [
        w.wcs_world2pix(np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0]
        for j in new_idx
    ]  # DAO pos

    # First cut: removing sources near the edge
    edge_error = 100
    first_cut_coords, first_cut_idx, first_fwhm_values = [], [], []
    for i, j in enumerate(orig_coords):
        if not (j[0] < edge_error or data.shape[0] - j[0] < edge_error
                or j[1] < edge_error or data.shape[0] - j[1] < edge_error):
            first_cut_coords.append(orig_coords[i])
            first_cut_idx.append(new_idx[i])
            first_fwhm_values.append(fwhm_values[i])

    # Second cut: removing sources that are not isolated
    # 1: Make sure no two sources are within (r_annulus_out + r_annulus_in) * fwhm of each other
    # 2: If a second source is present in the annulus, the sigma clipping for the mask will cut out the additional
    # counts from these pixels so that the second source will be cut out and not interfere with the zero point
    # If the sources are close enough together, they will either be cut out here or will look oblong and be cut out next
    overlap = []
    for i, j in enumerate(first_cut_coords):
        for k in range(i + 1, len(first_cut_coords)):
            # (r_ann_out + r_ann_in)*fwhm so that the annulus of one doesn't overlap the circular aperture of the other
            if np.sqrt((j[0] - first_cut_coords[k][0])**2 + (j[1] - first_cut_coords[k][1])**2) < \
                    (r_annulus_out + r_annulus_in) * fwhm:
                overlap.append(i)
                overlap.append(k)

    second_cut_coords, second_cut_idx, second_fwhm_values = [], [], []
    for i, j in enumerate(first_cut_coords):
        if i not in overlap:
            second_cut_coords.append(j)
            second_cut_idx.append(first_cut_idx[i])
            second_fwhm_values.append(first_fwhm_values[i])

    # Third cut: removing sources that are not circular
    # Part 1: remove sources with a fwhm 1.2 * median fwhm of the field (1.2 determined by considering 5 stacks)
    third_cut_coords, third_cut_idx, third_fwhm_values = [], [], []
    for i, j in enumerate(second_fwhm_values):
        if not (j > 1.2 * fwhm):
            third_cut_coords.append(second_cut_coords[i])
            third_cut_idx.append(second_cut_idx[i])
            third_fwhm_values.append(j)

    if log is not None:
        log.info(
            "Checking if the stack was performed correctly with {0} sources".
            format(len(third_cut_coords)))  # Final check
    else:
        print("Checking if the stack was performed correctly with {0} sources".
              format(len(third_cut_coords)))  # Final check

    d_x_y = 40
    field_sigmas = cutouts_2d_gaussian(stack,
                                       third_cut_coords,
                                       d_x_y,
                                       fwhm,
                                       show_cutouts=show_contours)
    field_ratio = np.median(field_sigmas)
    if log is not None:
        log.info("Median field ratio (y_sigma/x_sigma): {0}".format(
            '%2.3f' % field_ratio))
    else:
        print("Median field ratio (y_sigma/x_sigma): {0}".format('%2.3f' %
                                                                 field_ratio))

    if not ((1 / 1.2) > field_ratio or 1.2 < field_ratio):
        if log is not None:
            log.info(
                "Good to go! Now calculating the zero point with {0} sources".
                format(len(third_cut_coords)))
        else:
            print(
                "Good to go! Now calculating the zero point with {0} sources".
                format(len(third_cut_coords)))

    else:  # Stack is slightly off. Check to see what the issue is
        if log is not None:
            log.info("Stack seems to be off. Sample cutout shown")
        else:
            print("Stack seems to be off. Sample cutout shown")

        if show_contours:
            for i, j in enumerate(third_cut_coords):
                d = data[int(j[1]) - d_x_y:int(j[1]) + d_x_y,
                         int(j[0]) - d_x_y:int(j[0]) +
                         d_x_y]  # Cutout of source
                x, y = np.meshgrid(
                    np.linspace(0,
                                np.shape(d)[1] - 1,
                                np.shape(d)[1]),
                    np.linspace(0,
                                np.shape(d)[0] - 1,
                                np.shape(d)[0]))

                initial_guess = (100, d_x_y, d_x_y, fwhm / 2.35482,
                                 fwhm / 2.35482, 0, 0
                                 )  # Initial guess is IMPORTANT
                popt, pcov = curve_fit(twoD_Gaussian, (x, y),
                                       d.ravel(),
                                       p0=initial_guess)  # Fit of 2D Gaussian

                if np.abs((popt[4] / popt[3]) - field_ratio) < 0.01:
                    if log is not None:
                        log.info(
                            'x sigma = {0} y sigma = {1} ratio = {2}'.format(
                                '%7.3f' % popt[3], '%7.3f' % popt[4],
                                '%7.3f' % (popt[4] / popt[3])))
                    else:
                        print('x sigma = {0} y sigma = {1} ratio = {2}'.format(
                            '%7.3f' % popt[3], '%7.3f' % popt[4],
                            '%7.3f' % (popt[4] / popt[3])))

                    fitted_data = twoD_Gaussian((x, y), *popt)
                    contours = np.arange(np.min(d), np.max(d), 30.)
                    norm = simple_norm(data, 'sqrt', percent=99)
                    plt.imshow(d, norm=norm)
                    plt.colorbar()
                    plt.contour(x,
                                y,
                                fitted_data.reshape(np.shape(d)),
                                contours,
                                colors='w')
                    plt.show()
                    break  # Breaks out of loop

        if input(
                "Would you like to revisit the stack and look at isophotes within it? Type 'yes' or 'no': "
        ) == "yes":
            try:
                zp, zp_err, fwhm = revisit_stack(stack, fil, ext,
                                                 third_cut_coords, fwhm,
                                                 show_fwhm, show_bkg,
                                                 show_contours, data_path, log)
                return zp, zp_err, fwhm
            except TypeError:
                pass
        elif log is not None:
            log.info(
                "User decided not to remake the stack even though isophotes are not circular"
            )

    final_coords, final_idx = third_cut_coords, third_cut_idx  # Final coordinates and idx to use

    # Annuli of DAO sources
    annulus_apertures = CircularAnnulus(final_coords,
                                        r_in=fwhm * r_annulus_in,
                                        r_out=fwhm * r_annulus_out)
    annulus_masks = annulus_apertures.to_mask(
        method='center')  # Create masks to highlight pixels in annuli

    bkg_median = []
    for mask in annulus_masks:
        annulus_data = mask.multiply(data)
        annulus_data_1d = annulus_data[mask.data > 0]
        _, median_sigclip, stddev = sigma_clipped_stats(annulus_data_1d)
        bkg_median.append(median_sigclip)
    bkg_median = np.array(bkg_median)

    if show_bkg:  # Finds the sources for each upper median background limit and plots them over the image
        groupings = [[], [], [], [], [], [], [], []]
        for i, j in enumerate(bkg_median):
            if j < 0:
                groupings[0].append(i)
            elif j < 0.1:
                groupings[1].append(i)
            elif j < 0.25:
                groupings[2].append(i)
            elif j < 0.5:
                groupings[3].append(i)
            elif j < 0.75:
                groupings[4].append(i)
            elif j < 1:
                groupings[5].append(i)
            elif j < 2.5:
                groupings[6].append(i)
            else:
                groupings[7].append(i)

        positions = [[], [], [], [], [], [], [], []]
        for i, j in enumerate(final_idx):
            if i in groupings[0]:  # Positions of DAO
                positions[0].append(
                    w.wcs_world2pix(
                        np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0])
            elif i in groupings[1]:
                positions[1].append(
                    w.wcs_world2pix(
                        np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0])
            elif i in groupings[2]:
                positions[2].append(
                    w.wcs_world2pix(
                        np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0])
            elif i in groupings[3]:
                positions[3].append(
                    w.wcs_world2pix(
                        np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0])
            elif i in groupings[4]:
                positions[4].append(
                    w.wcs_world2pix(
                        np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0])
            elif i in groupings[5]:
                positions[5].append(
                    w.wcs_world2pix(
                        np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0])
            elif i in groupings[6]:
                positions[6].append(
                    w.wcs_world2pix(
                        np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0])
            elif i in groupings[7]:
                positions[7].append(
                    w.wcs_world2pix(
                        np.array([[ra_dao[j], dec_dao[j]]], np.float), 1)[0])
        colors = [
            'w', 'r', 'orange', 'lime', 'deepskyblue', 'b', 'purple', 'k'
        ]
        bkg_med_list = []
        for i, j in enumerate(positions):
            if len(positions[i]) != 0:
                apertures = CircularAperture(
                    positions[i], 2.5 * fwhm)  # Aperture to perform photometry
                # Annuli of DAO sources
                annulus_apertures = CircularAnnulus(positions[i],
                                                    r_in=fwhm * r_annulus_in,
                                                    r_out=fwhm * r_annulus_out)
                annulus_masks = annulus_apertures.to_mask(
                    method='center'
                )  # Create masks to highlight pixels in annuli

                bkg_median_1 = []
                for mask in annulus_masks:
                    annulus_data = mask.multiply(data)
                    annulus_data_1d = annulus_data[mask.data > 0]
                    _, median_sigclip, stddev = sigma_clipped_stats(
                        annulus_data_1d)
                    bkg_median_1.append(median_sigclip)
                bkg_median_1 = np.array(bkg_median_1)
                bkg_med_list.append(bkg_median_1)
                apertures.plot(color='white', lw=2)
                annulus_apertures.plot(color=colors[i], lw=2)

        if log is not None:
            log.info("Plotting the sources by median background value")
            log.info(
                "Number of sources per list (<0, 0.1, 0.25, 0.5, 0.75, 1, 2.5, >2.5)"
            )
        else:
            print("Plotting the sources by median background value")
            print(
                "Number of sources per list (<0, 0.1, 0.25, 0.5, 0.75, 1, 2.5, >2.5)"
            )
        for i in range(len(bkg_med_list)):
            if log is not None:
                log.info(len(bkg_med_list[i]))
            else:
                print(len(bkg_med_list[i]))

        norm = simple_norm(data, 'sqrt', percent=99)
        plt.suptitle(
            'w < 0 | r < 0.1 | o < 0.25 | g < 0.5 | lb < 0.75 | db < 1 | p < 2.5 | b > 2.5'
        )
        plt.imshow(data, norm=norm)
        plt.colorbar()
        plt.show(
        )  # Plot of matched sources with annuli, color coded by median background value in annulus

    mag_cat = []  # Moving past show_bkg
    magerr_cat = []
    for i, j in enumerate(final_idx):
        mag_cat.append(
            cat[i][cat_mag])  # Reference magnitudes of catalog objects
        magerr_cat.append(cat[i][cat_err])

    final_apertures = CircularAperture(final_coords, 2.5 *
                                       fwhm)  # Aperture to perform photometry
    # Annuli of DAO sources
    final_ann_aps = CircularAnnulus(final_coords,
                                    r_in=fwhm * r_annulus_in,
                                    r_out=fwhm * r_annulus_out)
    final_ann_masks = final_ann_aps.to_mask(
        method='center')  # Create masks to highlight pixels in annuli

    final_bkg_median = []
    for mask in final_ann_masks:  # For each masked annulus, find the median background value and add to list
        annulus_data = mask.multiply(data)
        annulus_data_1d = annulus_data[mask.data > 0]
        _, median_sigclip, stddev = sigma_clipped_stats(annulus_data_1d)
        final_bkg_median.append(median_sigclip)
    final_bkg_median = np.array(final_bkg_median)

    sigma_clip = SigmaClip(
        sigma=3)  # This section is to find the error on the background
    bkg_estimator = MedianBackground()
    bkg = Background2D(data, (120, 120),
                       filter_size=(3, 3),
                       sigma_clip=sigma_clip,
                       bkg_estimator=bkg_estimator)
    error = calc_total_error(data, bkg.background_rms, 1)

    phot_table = aperture_photometry(
        data, final_apertures, error=error)  # Photometry, error accounted for
    bkg_aper = final_bkg_median * final_apertures.area

    flux = np.asarray(phot_table['aperture_sum']) - bkg_aper
    fluxerr = np.sqrt(np.asarray(phot_table['aperture_sum_err'])**2 + bkg_aper)

    ap = absphot.absphot()
    zpt, zpterr = ap.zpt_iteration(flux, fluxerr, mag_cat, magerr_cat)
    print(zpt, zpterr)

    mag_inst = -2.5 * np.log10(
        (np.asarray(phot_table['aperture_sum']) -
         bkg_aper))  # Instrumental magnitudes (and error)

    # Find zero point
    if log is not None:
        log.info(
            'Calculating zero point from {0} stars with sigma clipping and a maximum std error of 0.1.'
            .format(len(mag_inst)))
    else:
        print(
            'Calculating zero point from {0} stars with sigma clipping and a maximum std error of 0.1.'
            .format(len(mag_inst)))

    for i in np.flip(np.linspace(1, 3, num=10)):
        _, zp, zp_err = sigma_clipped_stats(mag_cat - mag_inst, sigma=i)
        if zp_err < 0.1:
            break

    if log is not None:
        log.info('zp = {0} +/- {1}'.format('%2.3f' % zp, '%2.3f' % zp_err))
    else:
        print('zp = {0} +/- {1}'.format('%2.3f' % zp, '%2.3f' % zp_err))

    # Online zp (vega): https://www.ukirt.hawaii.edu/instruments/wfcam/user_guide/performance.html
    return zp, zp_err, fwhm
コード例 #13
0
ファイル: test.py プロジェクト: dharmesh1007/Photometry
            
            
            """ 2.5 APERTURE AND ANNULUS DATA ---------------------------------"""
            # Requires object pixel coordinates and radii of aperture and annulus
            aperture = CircularAperture(obj_loc, r=ap_rad)
            annulus = CircularAnnulus(obj_loc, r_in=an_in, r_out=an_out)
            
            
            """ 2.6 DISPLAY THE APERTURE ON THE IMAGE TO CHECK CENTERING ------"""
            if images == True:
                # DISPLAY INDIVIDUAL TARGETS
                for im_plot in range(0, len(obj_loc)):
                    plt.figure(figsize=(15,15))
                    plt.imshow(imgdta, cmap='nipy_spectral')
                    aperture.plot(color='white', lw=2)
                    annulus.plot(color='red', lw=2)
                    xcent = int(obj_loc[im_plot][0])
                    ycent = int(obj_loc[im_plot][1])
                    plt.xlim(xcent-15, xcent+15)
                    plt.ylim(ycent-15, ycent+15)

                # DISPLAY WHOLE REGION OF AG PEG AND STANDARD STARS
                plt.figure(figsize=(20, 20))
                plt.imshow(imgdta, cmap='nipy_spectral')
                aperture.plot(color='white', lw=2)
                annulus.plot(color='red', lw=2)

            
            """ 2.7 OBJECT MAGNITUDE WITH RESPECT TO EACH STANDARD STAR """
            # Counts of object and standard stars with associated errors.
            star_cts = counts(imgdta, aperture, annulus, 5)
コード例 #14
0
def do_aperture_photometry(filename):

    fwhm,filename=iraf_fwhm()

    xpix,ypix=source_list(filename)

    #ast=AstrometryNet()
    #ast.api_key= 'iqmqwvazpvolmjmn'


    data,header=fits.getdata(filename,header=True)
    exposure_time=header['EXPOSURE']

    sigma_clip = SigmaClip(sigma=3., maxiters=10) 
    bkg_estimator = MedianBackground() 
    bkg = Background2D(data, (10,10), filter_size=(3, 3),sigma_clip=sigma_clip, bkg_estimator=bkg_estimator)
    back=bkg.background # this is the background we need for the background subtraction.
    back2=np.median(bkg.background)


    mask = data == 0
    unit = u.electron / u.s


    xdf_image = CCDData(data, unit=unit, meta=header, mask=mask)
    norm_image = ImageNormalize(vmin=1e-4, vmax=5e-2, stretch=LogStretch(), clip=False)
    xdf_image_clipped = np.clip(xdf_image, 1e-4, None)

    mean, median, std = sigma_clipped_stats(xdf_image.data, sigma=3.0, maxiters=20, mask=xdf_image.mask)

    print('Finding the sources')

    #daofind = DAOStarFinder(fwhm=fwhm, threshold=5*std) # 3 sigma above the background.
    #sources = daofind(data - median)

    #sources_findpeaks = find_peaks(xdf_image.data, mask=xdf_image.mask, threshold=30.*std, box_size=30, centroid_func=centroid_2dg)

    #print('We have found:',len(sources),' sources')
    #print(sources)

    #print(sources['xcentroid'], sources['ycentroid'],sources['fwhm'])
    #positions=sources['xcentroid'], sources['ycentroid']
    positions=np.genfromtxt('co_ordinates_list_1.txt',unpack=True,usecols=(0,1))
    #print(positions)
    radii=[ fwhm,2*fwhm, 4*fwhm, 6*fwhm]

    #positions=(sources['xcentroid'], sources['ycentroid'])
    apertures = [CircularAperture(positions, r=r) for r in radii] 

    an_ap = CircularAnnulus(positions, r_in=8*fwhm, r_out=10*fwhm)
    #apers = [apertures, annulus_apertures]


    #bkg_sigma=mad_std(data)
    effective_gain=exposure_time
    error=calc_total_error(data,back,effective_gain)


    #error=0.1*data
    phot_table = aperture_photometry(data, apertures,error=error)
    phot_table2=aperture_photometry(data,an_ap)


    bkg_mean = phot_table2['aperture_sum'] / an_ap.area()
    bkg_sum = bkg_mean * an_ap.area()


    final_sum0=phot_table['aperture_sum_0']-bkg_sum
    final_sum1=phot_table['aperture_sum_1']-bkg_sum
    final_sum2=phot_table['aperture_sum_2']-bkg_sum
    final_sum3=phot_table['aperture_sum_3']-bkg_sum



    mag_0=-2.5*np.log10(final_sum0/exposure_time)+25
    mag_1=-2.5*np.log10(final_sum1/exposure_time)+25
    mag_2=-2.5*np.log10(final_sum2/exposure_time)+25
    mag_3=-2.5*np.log10(final_sum3/exposure_time)+25

    fig=plt.figure()
    plt.imshow(data,cmap='gray',origin='lower',vmin=50,vmax=400)
    colors=['red','green','yellow','blue']
    for i in range(len(apertures)):
        apertures[i].plot(color=colors[i], alpha=0.7) 
    
    an_ap.plot(color='green', alpha=0.7) 
    plt.show()

    for i in range (len(phot_table)):
        print(mag_0[i],mag_1[i],mag_2[i],mag_3[i])



    '''



    mag=-2.5*np.log10(final_sum/30)+25

    flux=final_sum
    flux_err=phot_table['aperture_sum_err_0']
    mag_err=1.09*flux_err/flux

    x=[phot.value for phot in phot_table['xcenter']]
    y=[phot.value for phot in phot_table['ycenter']]

    #with open('result.dat', 'w') as f:
    #with open('out.txt', 'w') as f:
    for i in range(len(x)):
        print(x[i],y[i],'\t',mag[i],mag_err[i])


    outfile=' '
    for i in range (len(phot_table)):
        outfile+=x[i]+ " "+ y[i]+" "+ mag[i]+" " +mag_err[i]
        outfile+='\n'

    out=open('result.txt','w')
    out.write(outfile,overwrite=True)
    out.close()

    '''

    '''
コード例 #15
0
ファイル: photometry.py プロジェクト: fschmnn/pnlf
def measure_single_flux(img,
                        positions,
                        aperture_size,
                        model='Moffat',
                        alpha=None,
                        gamma=None,
                        fwhm=None,
                        bkg=True,
                        plot=False):
    '''Measure the flux for a single object
    
    The Background is subtracted from an annulus. No error is reported

    Parameters
    ----------

    img : ndarray
        array with the image data

    position : tuple
        position (x,y) of the source

    aperture_size : float
        aperture size in units of FWHM
    
    alpha : float
        power index of the Moffat
    
    gamma : float
        other parameter for the Moffat

    bkg : bool
        determines if the background is subtracted
    '''

    if model == 'Moffat':
        fwhm = 2 * gamma * np.sqrt(2**(1 / alpha) - 1)

    r = aperture_size * (fwhm) / 2
    aperture = CircularAperture(positions, r=r)
    phot = aperture_photometry(img, aperture)

    r_in = 4 * fwhm / 2
    r_out = np.sqrt(3 * r**2 + r_in**2)
    annulus_aperture = CircularAnnulus(positions, r_in=r_in, r_out=r_out)
    mask = annulus_aperture.to_mask(method='center')

    annulus_data = mask.multiply(img)
    annulus_data_1d = annulus_data[mask.data > 0]
    _, median_sigclip, _ = sigma_clipped_stats(
        annulus_data_1d[~np.isnan(annulus_data_1d)], sigma=3, maxiters=1)

    phot['bkg_median'] = np.array(median_sigclip)
    phot['bkg_local'] = phot['bkg_median'] * aperture.area
    if bkg:
        phot['flux'] = phot['aperture_sum'] - phot['bkg_local']
    else:
        phot['flux'] = phot['aperture_sum']

    if model == 'Moffat':
        correction = light_in_moffat(r, alpha, gamma)
    elif model == 'Gaussian':
        correction = light_in_gaussian(r, fwhm)
    else:
        raise ValueError(f'unkown model {model}')
    phot['flux'] /= correction

    if plot:
        from astropy.visualization import simple_norm

        norm = simple_norm(img, 'sqrt', percent=99)
        plt.imshow(img, norm=norm)
        aperture.plot(color='orange', lw=2)
        annulus_aperture.plot(color='red', lw=2)
        plt.show()

    #return phot
    return phot['flux'][0]
コード例 #16
0
    annuli.plot(color='blue', lw=2)
    plt.imshow(quadRU_data,
               origin='lower',
               norm=norm,
               cmap='BrBG',
               clim=(0, 1000))
    plt.show()
    plt.clf()

##use CircularAnnulus
annuli_c = CircularAnnulus(star_coords, r_in=5.5, r_out=7)
print("Circular Annuli", annuli_c)
phot_table_circ = aperture_photometry(quadRU_data, annuli_c, method='exact')
print("phot_table_circ", phot_table_circ)
if show_images:
    annuli_c.plot(color='blue', lw=2)
    plt.imshow(quadRU_data,
               origin='lower',
               norm=norm,
               cmap='BrBG',
               clim=(0, 1000))
    plt.show()
    plt.clf()

##background subtraction

##ellip annuli background subtraction
apers = [apertures, annuli, annuli_c]
phot_table = aperture_photometry(quadRU_data, apers)
background_mean_ellip = phot_table['aperture_sum_1'] / annuli.area()
background_sum_ellip = background_mean_ellip * apertures.area()
コード例 #17
0
ファイル: measure_sensit.py プロジェクト: danielasmus/vipe
def measure_sensit(fin=None,
                   ext=0,
                   im=None,
                   pos=None,
                   std=True,
                   rmax=11,
                   sky_rin=30,
                   sky_rout=40,
                   flux=None,
                   exptime=None,
                   head=None,
                   showplot=True,
                   verbose=False):
    """
    comute the maximum S/N of an image in a similar way as the ESO pipeline does
    """

    if fin != None:

        hdu = fits.open(fin)
        im = hdu[ext].data

        if head == None:
            head = hdu[ext].header

        hdu.close()

    # --- position to measure the S/N
    if pos == None:
        pos = 0.5 * np.asarray(im.shape)

#    print(pos)
#    from aper import aper

# --- define circular apertures from 1 px to rmax px
    aperrads = np.arange(1, rmax)
    apers = [CircularAperture((pos[1], pos[0]), r=a) for a in aperrads]

    # --- estimate the background level and standard deviation
    # --- define a sky annulus
    annul = CircularAnnulus((pos[1], pos[0]), r_in=sky_rin, r_out=sky_rout)
    # --- make a mask corresponding to the annulus
    annul_mask = annul.to_mask(
        method='center'
    )  # in older versions of the photutils a [0] is reqiured here
    # --- apply the annulus mask to the image
    annul_data = annul_mask.multiply(im)
    # --- extract the data from that annulus
    bg = annul_data[annul_mask.data > 0]
    bgstd = np.nanstd(bg)
    bgmed = np.nanmedian(bg)

    counts = [
        aperture_photometry(im, a, error=im * 0 + bgstd)['aperture_sum'][0] -
        bgmed * a.area for a in apers
    ]
    ecnts = [
        aperture_photometry(im, a, error=im * 0 + bgstd)['aperture_sum_err'][0]
        for a in apers
    ]

    sn = np.array(counts) / np.array(ecnts)

    if verbose:
        print("Aper rad. | counts | error | S/N")

        for r in aperrads:
            print(r, counts[r - 1], ecnts[r - 1], sn[r - 1])

    # sn = np.zeros(rmax)

    # for radius in range(1, rmax):

    #     (mag, magerr, flux, fluxerr, sky, skyerr, badflag,
    #      outstr) = aper(im, pos[0], pos[1], phpadu=1000.0, apr=radius,
    #                     skyrad=[30, 40], exact=True)

    #     print(flux, fluxerr, flux/fluxerr)
    #     sn[radius-1] = flux/fluxerr

    # --- best S/N and corresponding aperture radius
    snbest = np.max(sn)
    rbest = np.argmax(sn) + 1

    # --- check if the file was a standard which allow computation of
    # sensitivity
    sens = -1

    # --- in case the image is of a standard star try to determine the reached
    #     sensitivity
    if std:
        try:
            if flux == None:
                flux = head["HIERARCH ESO QC JYVAL"]
            if exptime == None:
                if "HIERARCH ESO QC EXPTIME" in head:
                    exptime = head["HIERARCH ESO QC EXPTIME"]
                elif "HIERARCH ESO SEQ TIME" in head:
                    exptime = head["HIERARCH ESO SEQ TIME"]

            sens = flux * 1000.0 * 10.0 * np.sqrt(exptime / 3600.0) / snbest

        except:
            print(
                "MEASURE_SENSITIVITY: ERROR: Can not compute sensitivity because flux/time information missing"
            )

    if showplot:
        fig = plt.figure(num=10, figsize=(8, 4))

        ax0 = fig.add_subplot(1, 2, 1)

        ax0.plot(aperrads, sn)
        ax0.set_xlabel("aperture radius [px]")
        ax0.set_ylabel("S/N")

        ax1 = fig.add_subplot(1, 2, 2)

        pim = im - np.nanmin(im)

        ax1.imshow(pim,
                   origin='lower',
                   norm=LogNorm(),
                   interpolation='nearest',
                   cmap='gnuplot2',
                   vmin=np.nanpercentile(pim, 0.1),
                   vmax=np.nanmax(pim))

        apers[rbest].plot(color='lime', lw=1)
        annul.plot(color='cyan', lw=1)

        ax1.set_xlabel('x [px]')
        ax1.set_ylabel('y [px]')

        plt.show()
        plt.close(10)

    if verbose:
        print("\n")
        print("MEASURE_SENSITIVITY:\n" + " - background level: " +
              "{:.2f}".format(bgmed) + "\n" + " - background STD: " +
              "{:.2f}".format(bgstd) + "\n" + " - best S/N: " +
              "{:.1f}".format(snbest) + "\n" + " - for radius [px]: " +
              str(rbest) + "\n" + " - counts: " +
              "{:.1f}".format(counts[rbest - 1]) + "\n" + " - ecounts: " +
              "{:.1f}".format(ecnts[rbest - 1]))
        if sens != -1:
            print(" - STD flux density [Jy]: " + "{:.2f}".format(flux) + "\n" +
                  " - exposure time [s]: " + "{:.0f}".format(exptime) + "\n" +
                  " - sensitivity: " + "{:.2f}".format(sens) + "\n")
        else:
            print(
                " - no sensitivty determined because flux/time info missing. ")

    return (snbest, sens)
コード例 #18
0
def ap_an_phot(image, sources, source_ap, sky_ap, delta_ann=3.0, coords='xy',
            centroid=False, show_plot=False, **kargs):
    """Performs circular aperture fotometry on a image, given a table with the
    coordinates (x,y) or (ra,dec) of the sources. Background is estimated from a
    annular aperture and substracted

    Input:
        image:      (str) name of the .fits file with the image
        sources:    table with the Positions of the sources in the image.
        source_ap:  Radius of the circular aperture in pixels
        sky_ap:     inner radius of the annulus to calculate the sky.
        delta_ann:  Width of the annulus to calculate the sky
        centroid:   (Boolean) if True the source position centroid are
                    recalculated.
        sky_coord   (str) type of coordinates to use, either xy or radec.
                    If xy sources table must have 'x' and 'y' cols.
                    If ra dec sources table must to have 'ra' and 'dec' cols.
        show_plot:  (Boolean) If True a plot of the aperture is displayed.
    Output:
        phot_table: Table with the resulting photometry
    """

    data = fits.getdata(image)
    x, y = sources.colnames

    if coords == 'radec':
        sources = SkyCoord(sources[x], sources[y], **kargs)
        # convert to pixel coordinates
        header = fits.getheader(image)
        wcs = WCS(header)
        sources = Table(sources.to_pixel(wcs), names=[x, y])
    elif coords == 'xy':
        warnings.warn('Image pixels start at 1 while python index stats at 0',
                      AstropyUserWarning)
        # Check if the following correction is necessary
        # sources[x] -= 1
        # sources[y] -= 1

    if centroid:
        sources = centroid_sources(data, sources[x], sources[y], box_size=30)
        sources = Table(sources, names=[x, y])

    # create apertures
    sources = [(a, b) for a, b in zip(sources[x], sources[y])]
    source_aperture = CircularAperture(sources, r=source_ap)

    sky_aperture = CircularAnnulus(sources, r_in=sky_ap, r_out=sky_ap +
                                   delta_ann)

    if show_plot:
        index = [str(i+1) for i in range(len(sources))]
        norm = simple_norm(data, 'sqrt', percent=99)
        plt.figure()
        plt.imshow(data, norm=norm)
        source_aperture.plot(color='white', lw=2)
        sky_aperture.plot(color='red', lw=2)
        for a, b in sources:
            plt.text(a, b, index, color="purple", fontsize=12)
        plt.show()

    # get background using sigma clipped median
    annulus_masks = sky_aperture.to_mask(method='center')
    bkg_median = []
    for mask in annulus_masks:
        annulus_data = mask.multiply(data)
        annulus_data_1d = annulus_data[mask.data > 0]
        _, median_sigclip, _ = sigma_clipped_stats(annulus_data_1d)
        bkg_median.append(median_sigclip)
    bkg_median = np.array(bkg_median)

    phot_table = aperture_photometry(data, source_aperture)
    phot_table['annulus_median'] = bkg_median
    phot_table['aper_bkg'] = bkg_median * source_aperture.area()
    phot_table['aper_sum_bkgsub'] = phot_table['aperture_sum'] - \
        phot_table['aper_bkg']

    return phot_table
コード例 #19
0
ファイル: fluxExtract.py プロジェクト: ashbake723/camal
def fluxExtract(science,bias,dark,flats,hdr,plotap=False,rad=18,plotname=None):
    ' Returns final flux of source '
    # Calibrate Image. Flatten? add bias and flat to input
    flagg = 0
    
    # Defaul shape of sbig 2x2 binning
    xsize,ysize = 1266,1676
    if xsize != np.shape(bias)[0]:
        print 'WARNING: check that size of science file matches assumptions'

    if flats == 0:
        flat = np.ones((xsize,ysize))
    else:
        flat = flats[hdr['FILTER']]
    
    if dark == 0:
        dark = np.ones((xsize,ysize))
    else:
        dark = dark[str(hdr['EXPTIME'])]

    if np.mean(bias) == 0:
        bias = np.zeros((xsize,ysize))
    else:
        bias = bias

    # Size of science to trim calib frames (which are always full)
    # Science frames either full or sub: 400x400 around center ()
    centx, centy = int(1663/2), int(1252/2) # use config file in future, x and y are switched from thesky vs pyfits???
    subframe_size = np.shape(science)
    dx,dy         = subframe_size[0]/2,subframe_size[0]/2
    
    l,b,r,t = centx - dx, centy+dy, centx+dx, centy-dy # top and bottom switched

    data = (science - bias[t:b,l:r] - dark[t:b,l:r])/flat[t:b,l:r]
    
    if np.mean(data) < 0:
        flagg += 1
        
    # Get source x,y position
    x,y = sourceFinder(science)
    positions = (x,y)
    
    if x==0:
        flagg += 1
    
    if y==0:
        flagg += 1
        
    # Define Apertures
    apertures = CircularAperture(positions, r=rad)
    annulus_apertures = CircularAnnulus(positions, r_in=rad+5, r_out=rad+20)

    # Get fluxes
    rawflux_table = aperture_photometry(data, apertures)
    bkgflux_table = aperture_photometry(data, annulus_apertures)
    bkg_mean = bkgflux_table['aperture_sum'] / annulus_apertures.area()
    bkg_sum = bkg_mean * apertures.area()
    final_sum = rawflux_table['aperture_sum'] - bkg_sum

    # Plot
    if plotap == True:
        plt.ioff()
        plt.figure(-999)
        plt.clf()
        plt.imshow(np.log(data), origin='lower')
        apertures.plot(color='red', lw=1.5, alpha=0.5)
        annulus_apertures.plot(color='orange', lw=1.5, alpha=0.5)
        plt.savefig(plotname)

    return final_sum[0],flagg,x,y,bkg_mean[0]
コード例 #20
0

tp.lightcurve(info)

position = (xnew,ynew)



aperture = CircularAperture(position, r=radius)

bkg_aperture = CircularAnnulus(position, r_in=15., r_out=20.)

# perform the photometry; the default method is 'exact'
phot = aperture_photometry(image, aperture)
bkg = aperture_photometry(image, bkg_aperture)

# calculate the mean background level (per pixel) in the annuli
bkg_mean = bkg['aperture_sum'] / bkg_aperture.area()
bkg_mean
bkg_sum = bkg_mean * aperture.area()

# plot the apertures
plt.imshow(scale_image(image, scale='sqrt', percent=98.), 
           origin='lower',cmap='gray')
aperture.plot(color='blue')
bkg_aperture.plot(color='cyan', hatch='//', alpha=0.8)
plt.xlim(xnew-100,xnew+100)
plt.ylim(ynew-100,ynew+100)

phot['bkg_sum'] = bkg_sum
コード例 #21
0
            i = 0
            for x, y in positions:

                label = "{:s}".format(radec['name'][i].replace(
                    field, '').replace('_', '').replace('-', ''))
                plt.annotate(
                    label,  # this is the text
                    (x, y),  # this is the point to label
                    textcoords="offset points",  # how to position the text
                    color='blue',
                    xytext=(-10, 5),
                    ha='center')  # horizontal alignment
                i = i + 1
            plt.imshow(science, norm=norm_science, cmap='Greys')
            apertures.plot(color='yellow', lw=1.5, alpha=0.7)
            annulus_apertures.plot(color='blue', lw=1.5, alpha=0.5)
            plt.savefig('stars_positions_' + field + '_' + filter_name +
                        '.png',
                        format='png',
                        pdi=300)
            """ IRAF definitions of mag and mag_err
        flux = sum - area * msky
        mag = zmag - 2.5 * log10 (flux) + 2.5 * log10 (itime)
        merr = 1.0857 * err / flux
        err = sqrt (flux / gain + area * stdev**2 + area**2 * stdev**2 / nsky)
        """
            # Implement a mask that track the bad pixels and/or saturated pixels
            # from the dq array, in order to exclude those regions from the data.
            #tic = time.perf_counter()
            bkg_median = []
            bkg_stddev = []
コード例 #22
0
#the aperture photometry bit. Defines an aperture with radius (not width, oops, I'll sort that) of star_w, and
#an annulus around the star to pick up background noise.

positions = (sources['xcentroid'], sources['ycentroid'])
apertures = CircularAperture(positions, r=aperture_w / 2)
annuli = CircularAnnulus(positions, r_in=10., r_out=12.)
totals = [apertures, annuli]
phot_table = aperture_photometry(region, totals)
for col in phot_table.colnames:
    phot_table[col].info.format = '%.8g'

#calculates the mean background from the annulus, then corrects the
#aperture brightness for this

bkg_mean = (phot_table['aperture_sum_1']) / annuli.area()
bkg_sum = bkg_mean * apertures.area()
final_sum = phot_table['aperture_sum_0'] - bkg_sum
phot_table['final_sum'] = final_sum
print phot_table['final_sum']

#plots it so I know what's going on (plus it's pretty, inferno is a great colourmap)

plt.figure(figsize=(10, 10), dpi=80)
plt.imshow(region,
           cmap='inferno',
           origin='left',
           aspect='equal',
           norm=LogNorm())
apertures.plot(color='green', lw=1.5, alpha=1)
annuli.plot(color='white', lw=.5, alpha=1)
plt.show()
コード例 #23
0
    g = [None] * 100
    bkg = [None] * 100

    for i in range(100):
        h[i] = p + p * i
        aperture1[i] = CircularAperture(positions, r=h[i])
        apers1[i] = [aperture1[i]]
        f[i] = aperture_photometry(data, apers1[i], method='exact')
        bkg[i] = median_sigclip * aperture1[i].area
        g[i] = f[i]['aperture_sum_0'] - bkg[i]

    #plots figure with apertures
    plt.imshow(data, cmap='gray')
    plt.gca().invert_yaxis()
    aperture.plot(color='red', lw=2)
    annulus_aperture.plot(color='green', lw=2)
    plt.xlim(aa - c - 10, aa + c + 10)
    plt.ylim(bb - c - 10, bb + c + 10)
    plt.show()

    #plots curve of growth
    plt.plot(h, g, 'k-')
    plt.title('Curve of Growth for aperture')
    plt.xlabel('Radius (pix)')
    plt.ylabel('Counts')
    plt.show()
    print('End of aperture photometry, thank you!')

else:
    print('End of CPM position measurement, thank you!')
コード例 #24
0
def find_target_phot(stack, fil, fwhm, zp, zp_err, show_phot=False, log=None, log2=None,
                     ra=None, dec=None, x=None, y=None):

    '''

    Function to perform photometry on a target at a specific RA and Dec. Usually used after `find_zero_point`
    (this finds the ``zp``, ``zp_err``, and ``fwhm``).
    During this process, the user has the options to change the target's location and radii used in
    aperture photometry (if ``show_plots`` is ``True``, user can see cutouts of target and its radial profile).
    Attempts to find the magnitude of the target using aperture photometry.


    If magnitude cannot be found at a 3 sigma confidence interval, limiting magnitude is found at the same position.
    Limiting magnitude is found by adding fake sources of increasing magnitude at the target's position until it is
    more than 3 sigma above the background.

    :param stack: str
        Name of the stack to use (include path to the stack).

    :param fil: str
        Name of the filter of the stack.

    :param fwhm: float
        FWHM of image (most likely found through `find_zero_point`).

    :param zp: float
        Zero point of image (most likely found through `find_zero_point`).

    :param zp_err: float
        Error on the zero point of image (most likely found through `find_zero_point`).

    :param show_phot: boolean, optional
        Option to see the cutout of the source with apertures along with its radial profile.
        Default is ``False``.

    :param log: log, optional
        In-depth log.
        If no log is inputted, information is printed out instead of being written to ``log``.
        Default is ``None``.

    :param log2: log, optional
        Overview log.
        If no log is inputted, information is printed out instead of being written to ``log2``.
        Default is ``None``.

    :param ra: float, optional
        RA coordinate of target.
        Default is ``None``.

    :param dec: float, optional
        Dec coordinate of target.
        Default is ``None``.

    :param x: float, optional
        X pixel coordinate (not RA).
        Default is ``None``.

    :param y: float, optional
        Y pixel coordiante (not Dec).
        Default is ``None``.

    Returns
    -------

    :return: float, float
        Three options:
            * Returns the magnitude of the target (in AB) and its error.
            * Returns tne limiting magnitude at the target's location (in AB) and its error.
            * Returns ``None``, ``None`` if a fake source of amplitude 5 (adu / sec / pixel) is not three
                sigma above the background.

    '''

    with fits.open(stack) as hdr:
        header, data = hdr[0].header, hdr[0].data
        w = wcs.WCS(header)  # Parse the WCS keywords in the primary HDU

    if ra is not None:
        coords = np.array([[ra, dec]], np.float)  # Array of coordinates: [[RA, Dec]] in [deg, deg]
        pixel_coords = w.wcs_world2pix(coords, 1)[0]     # Find the pixel coordinates in the image
        x = pixel_coords[0]
        y = pixel_coords[1]
    elif x is not None:
        coords = np.array([[x, y]], np.float)  # Array of coordinates: [[RA, Dec]] in [deg, deg]
        pixel_coords = w.wcs_pix2world(coords, 1)[0]     # Find the pixel coordinates in the image
        ra = pixel_coords[0]
        dec = pixel_coords[1]
    else:
        print("No coordinates inputted")

    d_x_y = 125     # Pixels above and below the target when showing image...125 corresponds to a 50" x 50" cutout
    if log is not None:
        log.info("Pixel coordinates: (%.3f, %.3f)" % (x, y))
    else:
        print("Pixel coordinates: (%.3f, %.3f)" % (x, y))
    if input('Would you like to choose the cutout size? Default is 125x125 pixels (50"x50"): ') == "yes":
        try:
            d_x_y = int(input("Choose the radius, in arcsec: "))*2.5
        except TypeError:
            pass

    sigma_clip = SigmaClip(sigma=3)  # This section is to find the error on the background
    bkg_estimator = MedianBackground()
    bkg = Background2D(data, (120, 120), filter_size=(3, 3), sigma_clip=sigma_clip, bkg_estimator=bkg_estimator)
    error = calc_total_error(data, bkg.background_rms, 1)  # Used later as well for limiting magnitude

    if log is not None:
        log.info("You will now get to pick the position you would like. "
                 "Decide what pixel coordinates you would like to use")
        log.info("Blue circle in image is the distance the radial profile extends to")
    else:
        print("You will now get to pick the position you would like. "
              "Decide what pixel coordinates you would like to use")
        print("Blue circle in image is the distance the radial profile extends to")
    correct_position = "no"
    while correct_position == "no":
        if show_phot:
            radial_profile(data, x, y, 0.7, fwhm, rad=6)        # Radial profile
            plt.axvline(6*fwhm)

            print("Double click to set the new center. Do nothing if you are ok with current coordinates.")
            fig = plt.figure(2)
            new_coords = []
            fig.canvas.mpl_connect('button_press_event', lambda event: onclick(event, new_coords=new_coords))
            ap_in = CircularAperture((x, y), 1)  # Aperture to perform photometry
            ap_out = CircularAperture((x, y), 6*fwhm)  # Aperture to perform photometry
            ap_in.plot(color='r', lw=1)      # Plot of target with apertures and annulus
            ap_out.plot(color='b', lw=2)
            norm = simple_norm(data, 'sqrt', percent=99)
            plt.imshow(data, norm=norm)
            plt.xlim(int(x) - d_x_y, int(x) + d_x_y)
            plt.ylim(int(y) - d_x_y, int(y) + d_x_y)
            plt.colorbar()
            plt.show()

            if len(new_coords) != 0:
                x, y = new_coords[len(new_coords) - 2], new_coords[len(new_coords) - 1]
                real_coords = w.wcs_pix2world(np.array([[x, y]], np.float), 1)[0]  # Find the pixel coords in the image
                ra, dec = real_coords[0], real_coords[1]

        if input("Are you ok with current coordinates? RA = %.5f Dec = %.5f Type 'yes' or 'no': " % (ra, dec)) != "yes":
            pass
        else:
            correct_position = "yes"
            if log is not None:
                log.info("Coordinates chosen: (%.3f, %.3f) at RA = %.5f and Dec = %.5f" % (x, y, ra, dec))
            else:
                print("Coordinates chosen: (%.3f, %.3f) at RA = %.5f and Dec = %.5f" % (x, y, ra, dec))
            if log2 is not None:
                log2.info("Final coordinates: (%.3f, %.3f) at RA = %.5f and Dec = %.5f" % (x, y, ra, dec))

    if log is not None:
        log.info("You will now get to choose the radii for the circular aperture and the r_in and r_out of the annulus")
    else:
        print("You will now get to choose the radii for the circular aperture and the r_in and r_out of the annulus")
    correct_radii = "no"
    while correct_radii == "no":
        if log is not None:
            log.info("Automatic radii picked by comparing to FWHM of field: rad = %.3f, r_in = %.3f, r_out = %.3f" %
                     (2.5 * fwhm, 3.5 * fwhm, 4.5 * fwhm))
        else:
            print("Automatic radii picked by comparing to FWHM of field: rad = %.3f, r_in = %.3f, r_out = %.3f" %
                  (2.5 * fwhm, 3.5 * fwhm, 4.5 * fwhm))
        if input("Would you like to use these radii? Type 'yes or 'no': ") == "yes":
            rad, r_in, r_out = 2.5*fwhm, 3.5*fwhm, 4.5*fwhm
        else:
            log.info("FWHM = %.3f" % fwhm)
            rad = float(input("Pick a radius (in pixels) for the circular aperture: "))
            r_in = float(input("Pick an inner radius (in pixels) for the background annulus: "))
            r_out = float(input("Pick an outer radius (in pixels) for the background annulus: "))
            if r_in >= r_out:
                r_out = r_in + 1
                if log is not None:
                    log.info("You selected an invalid r_out value. Automatically set to %s" % r_out)
                else:
                    print("You selected an invalid r_out value. Automatically set to %s" % r_out)

        aperture = CircularAperture((x, y), rad)  # Aperture to perform photometry
        annulus_aperture = CircularAnnulus((x, y), r_in=r_in, r_out=r_out)       # Annulus of target
        annulus_mask = annulus_aperture.to_mask(method='center')  # Create masks to highlight pixels in annuli

        annulus_data = annulus_mask.multiply(data)
        annulus_data_1d = annulus_data[annulus_mask.data > 0]
        _, median_sigclip, stddev = sigma_clipped_stats(annulus_data_1d)

        phot_table = aperture_photometry(data, aperture, error=error)  # Photometry, error accounted for
        bkg_aper = median_sigclip * aperture.area

        if show_phot:
            # Radial profile out to 6 * fwhm (large just to be safe)
            radial_profile(data, x, y, 0.7, fwhm, rad=6)
            plt.axvline(rad, c='r')
            plt.axvline(r_in)
            plt.axvline(r_out)
            plt.figure(2)
            aperture.plot(color='white', lw=2)      # Plot of target with apertures and annulus
            annulus_aperture.plot(color='b', lw=2)
            norm = simple_norm(data, 'sqrt', percent=99)
            plt.imshow(data, norm=norm)
            plt.xlim(int(x) - d_x_y, int(x) + d_x_y)
            plt.ylim(int(y) - d_x_y, int(y) + d_x_y)
            plt.colorbar()
            plt.show()

        correct_radii = input("Are you ok with the previously selected radii? Type 'yes' or 'no': ")
    if log is not None:
        log.info("Radii chosen: %.3f, %.3f, %.3f" % (rad, r_in, r_out))
    else:
        print("Radii chosen: %.3f, %.3f, %.3f" % (rad, r_in, r_out))
    if log2 is not None:
        log2.info("Final radii: %.3f, %.3f, %.3f" % (rad, r_in, r_out))

    three_sigma = 3  # Change according to what sigma you'd like to use
    if (phot_table['aperture_sum']) - bkg_aper > 0:
        mag = -2.5 * np.log10((phot_table['aperture_sum']) - bkg_aper)  # Instrumental magnitude
        mag_err = 2.5 / np.log(10.) * (phot_table['aperture_sum_err'])/(phot_table['aperture_sum'])
        mag += zp  # Apparent magnitude (Vega)
        mag_err = np.sqrt(mag_err ** 2 + zp_err ** 2)
        if log is not None:
            log.info("Magnitude of target = %.3f +/- %.3f AB mag" % (mag + AB_conversion(fil), mag_err))  # Final mag
        if log2 is not None:
            log2.info("Magnitude of target = %.3f +/- %.3f AB mag" % (mag + AB_conversion(fil), mag_err))  # Final mag
        else:
            print("Magnitude of target = %.3f +/- %.3f AB mag" % (mag + AB_conversion(fil), mag_err))  # Final magnitude

        if np.abs(mag_err) < 1/three_sigma:     # Good! No limiting magnitude needed
            return float(mag + AB_conversion(fil)), float(mag_err)
        if log is not None:
            log.info("Target was not %s sigma above the background." % three_sigma)
        else:
            print("Target was not %s sigma above the background." % three_sigma)
        if log2 is not None:
            log2.info("Target not found")

    else:
        if log is not None:
            log.info("Target's aperture sum was less than background aperture sum. Target was not found")
        else:
            print("Target's aperture sum was less than background aperture sum. Target was not found")
        if log2 is not None:
            log2.info("Target not found")

    # Finding the limiting magnitude where the target was supposed to be located
    if log is not None:
        log.info("Now finding the limiting magnitude to %s sigma." % three_sigma)
    else:
        print("Now finding the limiting magnitude to %s sigma." % three_sigma)
    d_x_y = 50
    d = data[int(y)-d_x_y:int(y)+d_x_y, int(x)-d_x_y:int(x)+d_x_y]      # Cutout of source; upside down from normal
    x_mesh, y_mesh = np.meshgrid(np.linspace(0, np.shape(d)[1] - 1, np.shape(d)[1]),
                                 np.linspace(0, np.shape(d)[0] - 1, np.shape(d)[0]))

    amplitudes = np.arange(0, 5, 0.1)       # Amplitudes to consider
    for i, j in enumerate(amplitudes):
        gauss_data = twoD_Gaussian((x_mesh, y_mesh), j, d_x_y, d_x_y, fwhm/2.35482, fwhm/2.35482, 0, 0)  # Fake source

        norm = simple_norm(data, 'sqrt', percent=99)        # Find this before changing data
        d += gauss_data.reshape(np.shape(d))    # Adding the Gaussian to the data

        # Photometry on source with resulting limiting magnitude
        aperture = CircularAperture((x, y), rad)  # Aperture to perform photometry
        annulus_aperture = CircularAnnulus((x, y), r_in=r_in, r_out=r_out)  # Annulus of target
        annulus_mask = annulus_aperture.to_mask(method='center')  # Create masks to highlight pixels in annuli

        annulus_data = annulus_mask.multiply(data)
        annulus_data_1d = annulus_data[annulus_mask.data > 0]
        _, median_sigclip, stddev = sigma_clipped_stats(annulus_data_1d)

        phot_table = aperture_photometry(data, aperture, error=error)  # Photometry, error accounted for (found earlier)
        bkg_aper = median_sigclip * aperture.area

        if (phot_table['aperture_sum']) - bkg_aper > 0:
            mag_err = 2.5 / np.log(10.) * (phot_table['aperture_sum_err']) / (phot_table['aperture_sum'])
            if np.abs(mag_err) < 1/three_sigma:
                if log is not None:
                    log.info("Amplitude: %.3f Magnitude Error: %.3f" % (j, mag_err))
                else:
                    print("Amplitude: %.3f Magnitude Error: %.3f" % (j, mag_err))

                if show_phot:
                    plt.figure(2)
                    d -= gauss_data.reshape(np.shape(d))
                    aperture.plot(color='white', lw=2)      # Old data plot
                    annulus_aperture.plot(color='b', lw=2)
                    plt.imshow(data, norm=norm)
                    plt.xlim(int(x) - d_x_y, int(x) + d_x_y)
                    plt.ylim(int(y) - d_x_y, int(y) + d_x_y)
                    plt.colorbar()
                    plt.title("Target without fake source added in")

                    plt.figure(3)
                    d += gauss_data.reshape(np.shape(d))
                    aperture.plot(color='white', lw=2)      # New data plot
                    annulus_aperture.plot(color='b', lw=2)
                    plt.imshow(data, norm=norm)
                    plt.xlim(int(x) - d_x_y, int(x) + d_x_y)
                    plt.ylim(int(y) - d_x_y, int(y) + d_x_y)
                    plt.colorbar()
                    plt.title("Target after fake source has been added in")

                    # Radial profile...only need for good fake source (same process as used to find the FWHM)
                    radial_profile(data, x, y, 0.7, fwhm, rad=r_out/fwhm)
                    plt.axvline(rad, c='r')
                    plt.axvline(r_in)
                    plt.axvline(r_out)
                    plt.show()

                mag = -2.5 * np.log10((phot_table['aperture_sum']) - bkg_aper)  # Instrumental magnitude
                mag += zp  # Apparent magnitude (Vega)
                mag_err = np.sqrt(mag_err ** 2 + zp_err ** 2)

                # Limiting magnitude at the position of the target
                if log is not None:
                    log.info("Limiting magnitude = %.3f +/- %.3f AB mag" % (mag + AB_conversion(fil), mag_err))
                else:
                    print("Limiting magnitude = %.3f +/- %.3f AB mag" % (mag + AB_conversion(fil), mag_err))
                if log2 is not None:
                    log2.info("Limiting magnitude = %.3f +/- %.3f AB mag" % (mag + AB_conversion(fil), mag_err))
                return float(mag + AB_conversion(fil)), float(mag_err)

        d -= gauss_data.reshape(np.shape(d))    # 'Reset' data for next run if limiting magnitude not found

    if log is not None:
        log.info("False source with amplitude of 5 (adu/sec/pixel) was not %s sigma above the background."
                 % three_sigma)
        log.info("Limiting magnitude not found")
    else:
        print("False source with amplitude of 5 (adu/sec/pixel) was not %s sigma above the background." % three_sigma)
        print("Limiting magnitude not found")
    if log2 is not None:
        log2.info("Limiting magnitude not found")
    return None, None
                       overwrite=True,
                       format='ascii.fast_csv')
        DAOcoord = (DAOfound['xcentroid'], DAOfound['ycentroid'])
        DAOannul = CircAn(positions=DAOcoord, r_in=4 * FWHM, r_out=6 * FWHM)

        # Save apertures as circular, 4 pixel radius, at each (X, Y)
        DAOapert = CircAp(DAOcoord, r=4.)
        #print('DAOapert\n ', DAOapert)

        DAOimgXY = np.array(DAOcoord)
        #print('DAOimgXY \n', DAOimgXY)

        plt.figure(figsize=(16, 12))
        ax = plt.gca()
        im = plt.imshow(img, vmax=thresh * 4, origin='lower')
        DAOannul.plot(color='red', lw=2., alpha=0.7)
        divider = make_axes_locatable(ax)
        cax = divider.append_axes("right", size="3%", pad=0.05)
        plt.colorbar(im, cax=cax)
        plt.savefig(f_name[:-4] +
                    '_DAOstarfinder_Annulus_result_all_stars.png',
                    overwrite=True)
        #plt.show()

        #%%
        #print('Star ID    msky  sky_std  nsky nrej')
        #for star_ID in range(2, 10) : # for debug
        img_uint16 = np.array(img * 65536.0, dtype=np.uint16)
        ronoise = hdu[0].header['RDNOISE']
        #gain =  0.46599999070167542     # e/ADU
        gain = hdu[0].header['GAIN']
コード例 #26
0
def photometry(fileid):
    """
	Run photometry.

	.. codeauthor:: Rasmus Handberg <*****@*****.**>
	"""

    # Settings:
    ref_mag_limit = 17  # Lower limit on reference target brightness
    ref_target_dist_limit = 30  # Reference star must be further than this away to be included

    logger = logging.getLogger(__name__)
    tic = default_timer()

    # Use local copy of archive if configured to do so:
    config = load_config()

    # Get datafile dict from API:
    datafile = api.get_datafile(fileid)
    logger.debug("Datafile: %s", datafile)
    targetid = datafile['targetid']
    photfilter = datafile['photfilter']

    archive_local = config.get('photometry', 'archive_local', fallback=None)
    if archive_local is not None:
        datafile['archive_path'] = archive_local
    if not os.path.isdir(datafile['archive_path']):
        raise FileNotFoundError("ARCHIVE is not available")

    # Get the catalog containing the target and reference stars:
    # TODO: Include proper-motion to the time of observation
    catalog = api.get_catalog(targetid, output='table')
    target = catalog['target'][0]

    # Extract information about target:
    target_name = str(target['target_name'])
    target_coord = coords.SkyCoord(ra=target['ra'],
                                   dec=target['decl'],
                                   unit='deg',
                                   frame='icrs')

    # Folder to save output:
    # TODO: Change this!
    output_folder_root = config.get('photometry', 'output', fallback='.')
    output_folder = os.path.join(output_folder_root, target_name,
                                 '%04d' % fileid)
    os.makedirs(output_folder, exist_ok=True)

    # Also write any logging output to the
    formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
    _filehandler = logging.FileHandler(os.path.join(output_folder,
                                                    'photometry.log'),
                                       mode='w')
    _filehandler.setFormatter(formatter)
    _filehandler.setLevel(logging.INFO)
    logger.addHandler(_filehandler)

    # The paths to the science image:
    filepath = os.path.join(datafile['archive_path'], datafile['path'])

    # TODO: Download datafile using API to local drive:
    # TODO: Is this a security concern?
    #if archive_local:
    #	api.download_datafile(datafile, archive_local)

    # Translate photometric filter into table column:
    if photfilter == 'gp':
        ref_filter = 'g_mag'
    elif photfilter == 'rp':
        ref_filter = 'r_mag'
    elif photfilter == 'ip':
        ref_filter = 'i_mag'
    elif photfilter == 'zp':
        ref_filter = 'z_mag'
    elif photfilter == 'B':
        ref_filter = 'B_mag'
    elif photfilter == 'V':
        ref_filter = 'V_mag'
    else:
        logger.warning(
            "Could not find filter '%s' in catalogs. Using default gp filter.",
            photfilter)
        ref_filter = 'g_mag'

    references = catalog['references']
    references.sort(ref_filter)

    # Load the image from the FITS file:
    image = load_image(filepath)

    # Calculate pixel-coordinates of references:
    row_col_coords = image.wcs.all_world2pix(
        np.array([[ref['ra'], ref['decl']] for ref in references]), 0)
    references['pixel_column'] = row_col_coords[:, 0]
    references['pixel_row'] = row_col_coords[:, 1]

    # Calculate the targets position in the image:
    target_pixel_pos = image.wcs.all_world2pix(
        [[target['ra'], target['decl']]], 0)[0]

    # Clean out the references:
    hsize = 10
    x = references['pixel_column']
    y = references['pixel_row']
    references = references[
        (np.sqrt((x - target_pixel_pos[0])**2 +
                 (y - target_pixel_pos[1])**2) > ref_target_dist_limit)
        & (references[ref_filter] < ref_mag_limit)
        & (x > hsize) & (x < (image.shape[1] - 1 - hsize))
        & (y > hsize) & (y < (image.shape[0] - 1 - hsize))]

    #==============================================================================================
    # BARYCENTRIC CORRECTION OF TIME
    #==============================================================================================

    ltt_bary = image.obstime.light_travel_time(target_coord, ephemeris='jpl')
    image.obstime = image.obstime.tdb + ltt_bary

    #==============================================================================================
    # BACKGROUND ESITMATION
    #==============================================================================================

    fig, ax = plt.subplots(1, 2, figsize=(20, 18))
    plot_image(image.clean, ax=ax[0], scale='log', cbar='right', title='Image')
    plot_image(image.mask,
               ax=ax[1],
               scale='linear',
               cbar='right',
               title='Mask')
    fig.savefig(os.path.join(output_folder, 'original.png'),
                bbox_inches='tight')
    plt.close(fig)

    # Estimate image background:
    # Not using image.clean here, since we are redefining the mask anyway
    bkg = Background2D(
        image.clean,
        (128, 128),
        filter_size=(5, 5),
        #mask=image.mask | (image.clean > background_cutoff),
        sigma_clip=SigmaClip(sigma=3.0),
        bkg_estimator=SExtractorBackground(),
        exclude_percentile=50.0)
    image.background = bkg.background

    # Create background-subtracted image:
    image.subclean = image.clean - image.background

    # Plot background estimation:
    fig, ax = plt.subplots(1, 3, figsize=(20, 6))
    plot_image(image.clean, ax=ax[0], scale='log', title='Original')
    plot_image(image.background, ax=ax[1], scale='log', title='Background')
    plot_image(image.subclean,
               ax=ax[2],
               scale='log',
               title='Background subtracted')
    fig.savefig(os.path.join(output_folder, 'background.png'),
                bbox_inches='tight')
    plt.close(fig)

    # TODO: Is this correct?!
    image.error = calc_total_error(image.clean, bkg.background_rms, 1.0)

    #==============================================================================================
    # DETECTION OF STARS AND MATCHING WITH CATALOG
    #==============================================================================================

    logger.info("References:\n%s", references)

    radius = 10
    fwhm_guess = 6.0
    fwhm_min = 3.5
    fwhm_max = 13.5

    # Extract stars sub-images:
    #stars = extract_stars(
    #	NDData(data=image.subclean, mask=image.mask),
    #	stars_for_epsf,
    #	size=size
    #)

    # Set up 2D Gaussian model for fitting to reference stars:
    g2d = models.Gaussian2D(amplitude=1.0,
                            x_mean=radius,
                            y_mean=radius,
                            x_stddev=fwhm_guess * gaussian_fwhm_to_sigma)
    g2d.amplitude.bounds = (0.1, 2.0)
    g2d.x_mean.bounds = (0.5 * radius, 1.5 * radius)
    g2d.y_mean.bounds = (0.5 * radius, 1.5 * radius)
    g2d.x_stddev.bounds = (fwhm_min * gaussian_fwhm_to_sigma,
                           fwhm_max * gaussian_fwhm_to_sigma)
    g2d.y_stddev.tied = lambda model: model.x_stddev
    g2d.theta.fixed = True

    gfitter = fitting.LevMarLSQFitter()

    fwhms = np.full(len(references), np.NaN)
    for i, (x, y) in enumerate(
            zip(references['pixel_column'], references['pixel_row'])):
        x = int(np.round(x))
        y = int(np.round(y))
        x0, y0, width, height = x - radius, y - radius, 2 * radius, 2 * radius
        cutout = slice(y0 - 1, y0 + height), slice(x0 - 1, x0 + width)

        curr_star = image.subclean[cutout] / np.max(image.subclean[cutout])
        npix = len(curr_star)

        ypos, xpos = np.mgrid[:npix, :npix]
        gfit = gfitter(g2d, x=xpos, y=ypos, z=curr_star)

        fwhms[i] = gfit.x_fwhm

    mask = ~np.isfinite(fwhms) | (fwhms <= fwhm_min) | (fwhms >= fwhm_max)
    masked_fwhms = np.ma.masked_array(fwhms, mask)

    fwhm = np.mean(sigma_clip(masked_fwhms, maxiters=20, sigma=2.0))
    logger.info("FWHM: %f", fwhm)

    # Use DAOStarFinder to search the image for stars, and only use reference-stars where a
    # star was actually detected close to the references-star coordinate:
    cleanout_references = (len(references) > 50)

    if cleanout_references:
        daofind_tbl = DAOStarFinder(100, fwhm=fwhm, roundlo=-0.5,
                                    roundhi=0.5).find_stars(image.subclean,
                                                            mask=image.mask)
        indx_good = np.zeros(len(references), dtype='bool')
        for k, ref in enumerate(references):
            dist = np.sqrt((daofind_tbl['xcentroid'] -
                            ref['pixel_column'])**2 +
                           (daofind_tbl['ycentroid'] - ref['pixel_row'])**2)
            if np.any(dist <= fwhm / 4):  # Cutoff set somewhat arbitrary
                indx_good[k] = True

        references = references[indx_good]

    fig, ax = plt.subplots(1, 1, figsize=(20, 18))
    plot_image(image.subclean,
               ax=ax,
               scale='log',
               cbar='right',
               title=target_name)
    ax.scatter(references['pixel_column'],
               references['pixel_row'],
               c='r',
               alpha=0.3)
    if cleanout_references:
        ax.scatter(daofind_tbl['xcentroid'],
                   daofind_tbl['ycentroid'],
                   c='g',
                   alpha=0.3)
    ax.scatter(target_pixel_pos[0], target_pixel_pos[1], marker='+', c='r')
    fig.savefig(os.path.join(output_folder, 'positions.png'),
                bbox_inches='tight')
    plt.close(fig)

    #==============================================================================================
    # CREATE EFFECTIVE PSF MODEL
    #==============================================================================================

    # Make cutouts of stars using extract_stars:
    # Scales with FWHM
    size = int(np.round(29 * fwhm / 6))
    if size % 2 == 0:
        size += 1  # Make sure it's a uneven number
    size = max(size, 15)  # Never go below 15 pixels
    hsize = (size - 1) / 2

    x = references['pixel_column']
    y = references['pixel_row']
    mask_near_edge = ((x > hsize) & (x < (image.shape[1] - 1 - hsize))
                      & (y > hsize) & (y < (image.shape[0] - 1 - hsize)))

    stars_for_epsf = Table()
    stars_for_epsf['x'] = x[mask_near_edge]
    stars_for_epsf['y'] = y[mask_near_edge]

    # Store which stars were used in ePSF in the table:
    logger.info("Number of stars used for ePSF: %d", len(stars_for_epsf))
    references['used_for_epsf'] = mask_near_edge

    # Extract stars sub-images:
    stars = extract_stars(NDData(data=image.subclean, mask=image.mask),
                          stars_for_epsf,
                          size=size)

    # Plot the stars being used for ePSF:
    nrows = 5
    ncols = 5
    imgnr = 0
    for k in range(int(np.ceil(len(stars_for_epsf) / (nrows * ncols)))):
        fig, ax = plt.subplots(nrows=nrows,
                               ncols=ncols,
                               figsize=(20, 20),
                               squeeze=True)
        ax = ax.ravel()
        for i in range(nrows * ncols):
            if imgnr > len(stars_for_epsf) - 1:
                ax[i].axis('off')
            else:
                plot_image(stars[imgnr], ax=ax[i], scale='log', cmap='viridis')
            imgnr += 1

        fig.savefig(os.path.join(output_folder,
                                 'epsf_stars%02d.png' % (k + 1)),
                    bbox_inches='tight')
        plt.close(fig)

    # Build the ePSF:
    epsf = EPSFBuilder(oversampling=1.0,
                       maxiters=500,
                       fitter=EPSFFitter(fit_boxsize=2 * fwhm),
                       progress_bar=True)(stars)[0]

    logger.info('Successfully built PSF model')

    fig, ((ax1, ax2), (ax3, ax4)) = plt.subplots(2, 2, figsize=(15, 15))
    plot_image(epsf.data, ax=ax1, cmap='viridis')

    fwhms = []
    for a, ax in ((0, ax3), (1, ax2)):
        # Collapse the PDF along this axis:
        profile = epsf.data.sum(axis=a)
        itop = profile.argmax()
        poffset = profile[itop] / 2

        # Run a spline through the points, but subtract half of the peak value, and find the roots:
        # We have to use a cubic spline, since roots() is not supported for other splines
        # for some reason
        profile_intp = UnivariateSpline(np.arange(0, len(profile)),
                                        profile - poffset,
                                        k=3,
                                        s=0,
                                        ext=3)
        lr = profile_intp.roots()
        axis_fwhm = lr[1] - lr[0]

        fwhms.append(axis_fwhm)

        x_fine = np.linspace(-0.5, len(profile) - 0.5, 500)

        ax.plot(profile, 'k.-')
        ax.plot(x_fine, profile_intp(x_fine) + poffset, 'g-')
        ax.axvline(itop)
        ax.axvspan(lr[0], lr[1], facecolor='g', alpha=0.2)
        ax.set_xlim(-0.5, len(profile) - 0.5)

    # Let's make the final FWHM the largest one we found:
    fwhm = np.max(fwhms)
    logger.info("Final FWHM based on ePSF: %f", fwhm)

    #ax2.axvspan(itop - fwhm/2, itop + fwhm/2, facecolor='b', alpha=0.2)
    #ax3.axvspan(itop - fwhm/2, itop + fwhm/2, facecolor='b', alpha=0.2)
    ax4.axis('off')
    fig.savefig(os.path.join(output_folder, 'epsf.png'), bbox_inches='tight')
    plt.close(fig)

    #==============================================================================================
    # COORDINATES TO DO PHOTOMETRY AT
    #==============================================================================================

    coordinates = np.array([[ref['pixel_column'], ref['pixel_row']]
                            for ref in references])

    # Add the main target position as the first entry:
    if datafile.get('template') is None:
        coordinates = np.concatenate(([target_pixel_pos], coordinates), axis=0)

    #==============================================================================================
    # APERTURE PHOTOMETRY
    #==============================================================================================

    # Define apertures for aperture photometry:
    apertures = CircularAperture(coordinates, r=fwhm)
    annuli = CircularAnnulus(coordinates, r_in=1.5 * fwhm, r_out=2.5 * fwhm)

    apphot_tbl = aperture_photometry(image.subclean, [apertures, annuli],
                                     mask=image.mask,
                                     error=image.error)

    logger.debug("Aperture Photometry Table:\n%s", apphot_tbl)
    logger.info('Apperature Photometry Success')

    #==============================================================================================
    # PSF PHOTOMETRY
    #==============================================================================================

    # Are we fixing the postions?
    epsf.fixed.update({'x_0': False, 'y_0': False})

    # Create photometry object:
    photometry = BasicPSFPhotometry(group_maker=DAOGroup(fwhm),
                                    bkg_estimator=SExtractorBackground(),
                                    psf_model=epsf,
                                    fitter=fitting.LevMarLSQFitter(),
                                    fitshape=size,
                                    aperture_radius=fwhm)

    psfphot_tbl = photometry(image=image.subclean,
                             init_guesses=Table(coordinates,
                                                names=['x_0', 'y_0']))

    logger.debug("PSF Photometry Table:\n%s", psfphot_tbl)
    logger.info('PSF Photometry Success')

    #==============================================================================================
    # TEMPLATE SUBTRACTION AND TARGET PHOTOMETRY
    #==============================================================================================

    if datafile.get('template') is not None:
        # Find the pixel-scale of the science image:
        pixel_area = proj_plane_pixel_area(image.wcs.celestial)
        pixel_scale = np.sqrt(pixel_area) * 3600  # arcsec/pixel
        #print(image.wcs.celestial.cunit) % Doesn't work?
        logger.info("Science image pixel scale: %f", pixel_scale)

        # Run the template subtraction, and get back
        # the science image where the template has been subtracted:
        diffimage = run_imagematch(datafile,
                                   target,
                                   star_coord=coordinates,
                                   fwhm=fwhm,
                                   pixel_scale=pixel_scale)

        # Include mask from original image:
        diffimage = np.ma.masked_array(diffimage, image.mask)

        # Create apertures around the target:
        apertures = CircularAperture(target_pixel_pos, r=fwhm)
        annuli = CircularAnnulus(target_pixel_pos,
                                 r_in=1.5 * fwhm,
                                 r_out=2.5 * fwhm)

        # Create two plots of the difference image:
        fig, ax = plt.subplots(1, 1, squeeze=True, figsize=(20, 20))
        plot_image(diffimage, ax=ax, cbar='right', title=target_name)
        ax.plot(target_pixel_pos[0],
                target_pixel_pos[1],
                marker='+',
                color='r')
        fig.savefig(os.path.join(output_folder, 'diffimg.png'),
                    bbox_inches='tight')
        apertures.plot(color='r')
        annuli.plot(color='k')
        ax.set_xlim(target_pixel_pos[0] - 50, target_pixel_pos[0] + 50)
        ax.set_ylim(target_pixel_pos[1] - 50, target_pixel_pos[1] + 50)
        fig.savefig(os.path.join(output_folder, 'diffimg_zoom.png'),
                    bbox_inches='tight')
        plt.close(fig)

        # Run aperture photometry on subtracted image:
        target_apphot_tbl = aperture_photometry(diffimage, [apertures, annuli],
                                                mask=image.mask,
                                                error=image.error)

        # Run PSF photometry on template subtracted image:
        target_psfphot_tbl = photometry(diffimage,
                                        init_guesses=Table(
                                            target_pixel_pos,
                                            names=['x_0', 'y_0']))

        # Combine the output tables from the target and the reference stars into one:
        apphot_tbl = vstack([target_apphot_tbl, apphot_tbl], join_type='exact')
        psfphot_tbl = vstack([target_psfphot_tbl, psfphot_tbl],
                             join_type='exact')

    # Build results table:
    tab = references.copy()
    tab.insert_row(
        0, {
            'starid': 0,
            'ra': target['ra'],
            'decl': target['decl'],
            'pixel_column': target_pixel_pos[0],
            'pixel_row': target_pixel_pos[1]
        })
    for key in ('pm_ra', 'pm_dec', 'gaia_mag', 'gaia_bp_mag', 'gaia_rp_mag',
                'H_mag', 'J_mag', 'K_mag', 'g_mag', 'r_mag', 'i_mag', 'z_mag'):
        tab[0][key] = np.NaN

    # Subtract background estimated from annuli:
    flux_aperture = apphot_tbl['aperture_sum_0'] - (
        apphot_tbl['aperture_sum_1'] / annuli.area()) * apertures.area()
    flux_aperture_error = np.sqrt(apphot_tbl['aperture_sum_err_0']**2 +
                                  (apphot_tbl['aperture_sum_err_1'] /
                                   annuli.area() * apertures.area())**2)

    # Add table columns with results:
    tab['flux_aperture'] = flux_aperture / image.exptime
    tab['flux_aperture_error'] = flux_aperture_error / image.exptime
    tab['flux_psf'] = psfphot_tbl['flux_fit'] / image.exptime
    tab['flux_psf_error'] = psfphot_tbl['flux_unc'] / image.exptime
    tab['pixel_column_psf_fit'] = psfphot_tbl['x_fit']
    tab['pixel_row_psf_fit'] = psfphot_tbl['y_fit']
    tab['pixel_column_psf_fit_error'] = psfphot_tbl['x_0_unc']
    tab['pixel_row_psf_fit_error'] = psfphot_tbl['y_0_unc']

    # Theck that we got valid photometry:
    if not np.isfinite(tab[0]['flux_psf']) or not np.isfinite(
            tab[0]['flux_psf_error']):
        raise Exception("Target magnitude is undefined.")

    #==============================================================================================
    # CALIBRATE
    #==============================================================================================

    # Convert PSF fluxes to magnitudes:
    mag_inst = -2.5 * np.log10(tab['flux_psf'])
    mag_inst_err = (2.5 / np.log(10)) * (tab['flux_psf_error'] /
                                         tab['flux_psf'])

    # Corresponding magnitudes in catalog:
    mag_catalog = tab[ref_filter]

    # Mask out things that should not be used in calibration:
    use_for_calibration = np.ones_like(mag_catalog, dtype='bool')
    use_for_calibration[0] = False  # Do not use target for calibration
    use_for_calibration[~np.isfinite(mag_inst)
                        | ~np.isfinite(mag_catalog)] = False

    # Just creating some short-hands:
    x = mag_catalog[use_for_calibration]
    y = mag_inst[use_for_calibration]
    yerr = mag_inst_err[use_for_calibration]

    # Fit linear function with fixed slope, using sigma-clipping:
    model = models.Linear1D(slope=1, fixed={'slope': True})
    fitter = fitting.FittingWithOutlierRemoval(fitting.LinearLSQFitter(),
                                               sigma_clip,
                                               sigma=3.0)
    best_fit, sigma_clipped = fitter(model, x, y, weights=1.0 / yerr**2)

    # Extract zero-point and estimate its error:
    # I don't know why there is not an error-estimate attached directly to the Parameter?
    zp = -1 * best_fit.intercept.value  # Negative, because that is the way zeropoints are usually defined
    zp_error = nanstd(y[~sigma_clipped] - best_fit(x[~sigma_clipped]))

    # Add calibrated magnitudes to the photometry table:
    tab['mag'] = mag_inst + zp
    tab['mag_error'] = np.sqrt(mag_inst_err**2 + zp_error**2)

    fig, ax = plt.subplots(1, 1)
    ax.errorbar(x, y, yerr=yerr, fmt='k.')
    ax.scatter(x[sigma_clipped], y[sigma_clipped], marker='x', c='r')
    ax.plot(x, best_fit(x), color='g', linewidth=3)
    ax.set_xlabel('Catalog magnitude')
    ax.set_ylabel('Instrumental magnitude')
    fig.savefig(os.path.join(output_folder, 'calibration.png'),
                bbox_inches='tight')
    plt.close(fig)

    #==============================================================================================
    # SAVE PHOTOMETRY
    #==============================================================================================

    # Descriptions of columns:
    tab['flux_aperture'].unit = u.count / u.second
    tab['flux_aperture_error'].unit = u.count / u.second
    tab['flux_psf'].unit = u.count / u.second
    tab['flux_psf_error'].unit = u.count / u.second
    tab['pixel_column'].unit = u.pixel
    tab['pixel_row'].unit = u.pixel
    tab['pixel_column_psf_fit'].unit = u.pixel
    tab['pixel_row_psf_fit'].unit = u.pixel
    tab['pixel_column_psf_fit_error'].unit = u.pixel
    tab['pixel_row_psf_fit_error'].unit = u.pixel

    # Meta-data:
    tab.meta['version'] = __version__
    tab.meta['fileid'] = fileid
    tab.meta['template'] = None if datafile.get(
        'template') is None else datafile['template']['fileid']
    tab.meta['photfilter'] = photfilter
    tab.meta['fwhm'] = fwhm
    tab.meta['obstime-bmjd'] = float(image.obstime.mjd)
    tab.meta['zp'] = zp
    tab.meta['zp_error'] = zp_error

    # Filepath where to save photometry:
    photometry_output = os.path.join(output_folder, 'photometry.ecsv')

    # Write the final table to file:
    tab.write(photometry_output,
              format='ascii.ecsv',
              delimiter=',',
              overwrite=True)

    toc = default_timer()
    logger.info("Photometry took: %f seconds", toc - tic)

    return photometry_output