def A_photometry(image_data,
                 bg_err,
                 factor=1,
                 ape_sum=[],
                 ape_sum_err=[],
                 cx=15,
                 cy=15,
                 r=2.5,
                 a=5,
                 b=5,
                 w_r=5,
                 h_r=5,
                 theta=0,
                 shape='Circular',
                 method='center'):
    '''
    Performs aperture photometry, first by creating the aperture (Circular,
    Rectangular or Elliptical), then it sums up the flux that falls into the 
    aperture.

    Parameters
    ----------

    image_data: 3D array 
        Data cube of images (2D arrays of pixel values).

    bg_err   : 1D array
        Array of uncertainties on pixel value.

    factor   : float (optional)
        Electron count to photon count factor. Default is 1 if none given.

    ape_sum  : 1D array (optional)
        Array of flux to append new flux values to. If 'None', the new values
        will be appended to an empty array

    ape_sum_err: 1D array (optional)
        Array of flux uncertainty to append new flux uncertainty values to. If 
        'None', the new values will be appended to an empty array.

    cx       : int (optional)
        x-coordinate of the center of the aperture. Default is 15.

    cy       : int (optional)
        y-coordinate of the center of the aperture. Default is 15.

    r        : int (optional)
        If phot_meth is 'Aperture' and ap_shape is 'Circular', c_radius is 
        the radius for the circular aperture. Default is 2.5.

    a        : int (optional)
        If phot_meth is 'Aperture' and ap_shape is 'Elliptical', e_semix is
        the semi-major axis for elliptical aperture (x-axis). Default is 5.

    b        : int (optional)
        If phot_meth is 'Aperture' and ap_shape is 'Elliptical', e_semiy is
        the semi-major axis for elliptical aperture (y-axis). Default is 5.

    w_r      : int (optional)
        If phot_meth is 'Aperture' and ap_shape is 'Rectangular', r_widthx is
        the full width for rectangular aperture (x-axis). Default is 5.

    h_r      : int (optional)
        If phot_meth is 'Aperture' and ap_shape is 'Rectangular', r_widthy is
        the full height for rectangular aperture (y-axis). Default is 5.

    theta    : int (optional)
        If phot_meth is 'Aperture' and ap_shape is 'Elliptical' or
        'Rectangular', theta is the angle of the rotation angle in radians 
        of the semimajor axis from the positive x axis. The rotation angle 
        increases counterclockwise. Default is 0.

    shape    : string object (optional)
        If phot_meth is 'Aperture', ap_shape is the shape of the aperture. 
        Possible aperture shapes are 'Circular', 'Elliptical', 'Rectangular'. 
        Default is 'Circular'.

    method   : string object (optional)
        If phot_meth is 'Aperture', apemethod is the method used to 
        determine the overlap of the aperture on the pixel grid. Possible 
        methods are 'exact', 'subpixel', 'center'. Default is 'center'.

    Returns
    -------
    ape_sum  : 1D array
        Array of flux with new flux appended.

    ape_sum_err: 1D array
        Array of flux uncertainties with new flux uncertainties appended.

    '''
    l, h, w = image_data.shape
    position = [cx, cy]
    if (shape == 'Circular'):
        aperture = CircularAperture(position, r=r)
    elif (shape == 'Elliptical'):
        aperture = EllipticalAperture(position, a=a, b=b, theta=theta)
    elif (shape == 'Rectangular'):
        aperture = RectangularAperture(position, w=w_r, h=h_r, theta=theta)
    for i in range(l):
        data_error = calc_total_error(image_data[i, :, :],
                                      bg_err[i],
                                      effective_gain=1)
        phot_table = aperture_photometry(image_data[i, :, :],
                                         aperture,
                                         error=data_error,
                                         pixelwise_error=False)
        if (phot_table['aperture_sum_err'] > 0.000001):
            ape_sum.extend(phot_table['aperture_sum'] * factor)
            ape_sum_err.extend(phot_table['aperture_sum_err'] * factor)
        else:
            ape_sum.extend([np.nan])
            ape_sum_err.extend([np.nan])
    return ape_sum, ape_sum_err
Example #2
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
Example #3
0
def find_zero_point(stack,
                    fil,
                    ext,
                    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 2MASS (HJK) or UKIDSS (Y) 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 (2MASS or
    UKIDSS) 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 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 2MASS found in the extension (full area, not radially)
    if (fil != 'y') or (fil != 'Y'):
        two_mass = Vizier.query_region(coords,
                                       width=13.65 * u.arcmin,
                                       catalog='II/246')[0]
    else:
        two_mass = Vizier.query_region(
            coords, width=13.65 * u.arcmin,
            catalog='II/319')[0]  # Todo UKIDSS not 2MASS

    # 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(
            "%s objects found in 2MASS and %s sources above 10 std found in the stack"
            % (len(two_mass), len(dao_sources)))
        log.info(
            "DAOStarFinder found %s sources that are 10 std above the background"
            % len(dao_sources))
    else:
        print(
            "%s objects found in 2MASS and %s sources above 10 std found in the stack"
            % (len(two_mass), len(dao_sources)))
        print(
            "DAOStarFinder found %s sources that are 10 std above the background"
            % 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_two_mass = SkyCoord(two_mass['ra'], two_mass['dec'], frame='fk5')    # Coordinates from 2MASS catalog
    coords_two_mass = SkyCoord(two_mass['RAJ2000'],
                               two_mass['DEJ2000'],
                               frame='fk5')  # Coordinates from 2MASS catalog

    # Match each 2MASS sources to the closest dao_source
    idx, d2, d3 = coords_two_mass.match_to_catalog_sky(
        coords_dao)  # Match smaller to larger
    if log is not None:
        log.info("%s objects have been matched" % len(idx))
    else:
        print("%s objects have been matched" % len(idx))

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

    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] <= 300 and sums[len(sums) - 1] < 15:
            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: %.3f Median FWHM: %.3f Number of sources counted/total found in field: %s/%s"
            % (med, fwhm, len(sigmas_post_clipping), len(dao_sources)))
    else:
        print(
            "Median sigma: %.3f Median FWHM: %.3f Number of sources counted/total found in field: %s/%s"
            % (med, 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 %s with filter %s and FWHM of %.3f"
            % (stack, fil, fwhm))
    else:
        print(
            "Computing the zero point of the stack %s with filter %s and FWHM of %.3f"
            % (stack, fil, 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 %s sources" %
            len(third_cut_coords))  # Final check
    else:
        print("Checking if the stack was performed correctly with %s sources" %
              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): %.3f" % field_ratio)
    else:
        print("Median field ratio (y_sigma/x_sigma): %.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 %s sources" %
                len(third_cut_coords))
        else:
            print(
                "Good to go! Now calculating the zero point with %s sources" %
                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 = %.3f y sigma = %.3f ratio = %.3f' %
                                 (popt[3], popt[4], popt[4] / popt[3]))
                    else:
                        print('x sigma = %.3f y sigma = %.3f ratio = %.3f' %
                              (popt[3], popt[4], 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_two_mass = []  # Moving past show_bkg
    for i, j in enumerate(final_idx):
        # mag_two_mass.append(two_mass[i][fil.lower() + '_m'])  # Reference magnitudes of 2MASS objects
        mag_two_mass.append(
            two_mass[i][fil.upper() +
                        'mag'])  # Reference magnitudes of 2MASS objects

    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

    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 %i stars with sigma clipping and a maximum std error of 0.1.'
            % len(mag_inst))
    else:
        print(
            'Calculating zero point from %i stars with sigma clipping and a maximum std error of 0.1.'
            % len(mag_inst))

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

    if log is not None:
        log.info('zp = %.3f +/- %.3f.' % (zp, zp_err))
    else:
        print('zp = %.3f +/- %.3f.' % (zp, zp_err))

    # Online zp (vega): https://www.ukirt.hawaii.edu/instruments/wfcam/user_guide/performance.html
    return zp, zp_err, fwhm
Example #4
0
def do_aperture_photometry(filename,count, fwhm,date):

    #fwhm,files=iraf_fwhm()

    #xpix,ypix=source_list(files)

    #ast=AstrometryNet()
    #ast.api_key= 'iqmqwvazpvolmjmn'
    '''
    choice=input("Enter the mode. Please use the keywords\n 'single' for single image, 'multiple'  for multiple images: \n\n")

    if (choice=='single'):
        print('single mode')
    elif (choice ==' multiple'):
        print(' multiple image mode')
    else:
        print('Enter valid choice!!!')
    '''
    data,header=fits.getdata(filename,header=True)
    #exposure=header['EXPOSURE']
    exposure=300
    #print('Exposure is',exposure)

    sigma_clip = SigmaClip(sigma=3, maxiters=10)
    bkg_estimator = SExtractorBackground()
    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)
    #print('median background is',back2)

    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-back)

    #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.txt',unpack=True,usecols=(0,1))
    #print(positions)
    radii=[ fwhm,2*fwhm, 3*fwhm,4*fwhm,5*fwhm]

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

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


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


    #error=0.1*data
    phot_table = aperture_photometry(data-back, apertures,error=error)
    phot_table2=aperture_photometry(data-back,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
    final_sum4=phot_table['aperture_sum_4']-bkg_sum

    mag_back=-2.5*np.log10(bkg_mean/exposure)+22
    mag_0=-2.5*np.log10(final_sum0/exposure)+22
    mag_1=-2.5*np.log10(final_sum1/exposure)+22
    mag_2=-2.5*np.log10(final_sum2/exposure)+22
    mag_3=-2.5*np.log10(final_sum3/exposure)+22
    mag_4=-2.5*np.log10(final_sum4/exposure)+22

    #print(mag_back,mag_0,mag_1,mag_2,mag_3,mag_4)


    flux_err_0=phot_table['aperture_sum_err_0']
    mag_err_0=1.09*flux_err_0/final_sum0

    flux_err_1=phot_table['aperture_sum_err_1']
    mag_err_1=1.09*flux_err_1/final_sum1

    flux_err_2=phot_table['aperture_sum_err_2']
    mag_err_2=1.09*flux_err_2/final_sum2

    flux_err_3=phot_table['aperture_sum_err_3']
    mag_err_3=1.09*flux_err_3/final_sum3

    flux_err_4=phot_table['aperture_sum_err_4']
    mag_err_4=1.09*flux_err_4/final_sum4
    '''
    fig=plt.figure()
    plt.imshow(data,cmap='gray',origin='lower',vmin=mean-4*std,vmax=mean+4*std)
    colors=['red','salmon','yellow','blue','cyan']
    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()
    '''
    with open ('{}_r.dat'.format(date),'w') as r:
        for i in range (len(phot_table)):
            #print(final_sum0[i],final_sum1[i],final_sum2[i],final_sum3[i],final_sum4[i],final_sum5[i],file=r)
            print(mag_back[i],mag_0[i],mag_err_0[i],mag_1[i],mag_err_1[i],mag_2[i],mag_err_2[i],mag_3[i],mag_err_3[i],mag_4[i],mag_err_4[i],file=r)

    print('No. {} file has been processed'.format(count+1))

    '''



    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()

    '''

    '''
Example #5
0
def readimageanddatared(image_name,
                        fits_data_slice,
                        output_name,
                        plot_title,
                        fits_cut,
                        fileloc=0):
    '''
	Function: Read images and create simple plots
	'''

    print('>>> Running readimageanddatared')
    if fileloc == 0:
        image_file = fits.open(image_name + '.fits')
    else:
        image_file = fits.open('./All_Files/' + image_name + '.fits')
    image_file.info()
    image_org_wcs = wcs.WCS(image_file[fits_data_slice].header)

    # Create cut out
    if fits_cut[0] > 0:
        image_sci_full = image_file[fits_data_slice].data
        image_sci_cutout = Cutout2D(image_sci_full, (fits_cut[0], fits_cut[1]),
                                    (fits_cut[2] * 2, fits_cut[3] * 2),
                                    wcs=image_org_wcs)
        image_sci = image_sci_cutout.data
        image_wcs = image_sci_cutout.wcs

    else:
        image_sci_full = image_file[fits_data_slice].data
        image_sci = image_sci_full
        image_wcs = image_org_wcs

    # Create rms normalized results (based on region chosen)
    image_sigma = mad_std(image_sci)
    image_sci_rmsnorm = image_sci / image_sigma
    image_sci_full_rmsnorm = image_sci_full / image_sigma

    # Create arrays for plotting (setting negatives to zero)
    image_sci_rmsnorm_plot = image_sci_rmsnorm
    image_sci_full_rmsnorm_plot = image_sci_full_rmsnorm
    image_sci_rmsnorm_plot[image_sci_rmsnorm_plot < 0] = 0
    image_sci_full_rmsnorm_plot[image_sci_full_rmsnorm_plot < 0] = 0

    # Create background image
    bkg = Background2D(image_sci, (7, 7))

    # Calculate Error Array
    image_exptime = float(image_file[0].header['EXPTIME'])
    print('From Header: Exposure Time = {}'.format(image_exptime))
    image_error = calc_total_error(image_sci, bkg.background_rms,
                                   image_exptime)

    # Plot Full File + Rectangle
    fig, ax = plt.subplots(1, figsize=(20, 20))
    plt.imshow(np.sqrt(image_sci_full_rmsnorm),
               origin='lower',
               cmap='Greys_r',
               vmin=sn_vmin,
               vmax=sn_vmax)
    plt.plot(fits_cut[0], fits_cut[1], 'rs')
    rect = patches.Rectangle(
        (fits_cut[0] - fits_cut[2], fits_cut[1] - fits_cut[3]),
        fits_cut[2] * 2,
        fits_cut[3] * 2,
        linewidth=1,
        edgecolor='r',
        facecolor='none')
    ax.add_patch(rect)
    plt.title(plot_title)
    plt.axis([
        fits_cut[0] - 1000, fits_cut[0] + 1000, fits_cut[1] - 1000,
        fits_cut[1] + 1000
    ])
    plt.savefig(output_name + '01_rms_full.jpg')
    plt.close()

    # Plot cut region
    fig, ax = plt.subplots(1, figsize=(20, 20))
    plt.imshow(np.sqrt(image_sci_rmsnorm_plot),
               origin='lower',
               cmap='Greys_r',
               vmin=sn_vmin,
               vmax=sn_vmax)
    plt.title(plot_title)
    plt.savefig(output_name + '02_rms_cut.jpg')
    plt.close()

    # Plot cut region (background image)
    fig, ax = plt.subplots(1, figsize=(20, 20))
    plt.imshow(bkg.background, origin='lower', cmap='Greys_r')
    plt.title(plot_title)
    plt.savefig(output_name + '03_background.jpg')
    plt.close()

    print('Size of Output Array: {}'.format(len(image_sci)))
    print('{} Pixel Value Range: {:.2e} to {:.2e}'.format(
        image_name, np.nanmin(image_sci), np.nanmax(image_sci)))
    print('{} S/N Value Range: {:.2e} to {:.2e}'.format(
        image_name, np.nanmin(image_sci_rmsnorm),
        np.nanmax(image_sci_rmsnorm)))

    return image_sci, image_sigma, image_wcs, image_error, image_exptime
Example #6
0
def photometry(header, data, name, RA_bound, DEC_bound, thresh_factor,
               results_file, im=True):
    """
    Input: the header of a reduced object's .fits file, the image data of the 
    file, the name to be used when creating the segmented image and a csv 
    containing all detected sources, a threshold factor to be used in image 
    segmentation, 2 arrays giving the RA and DEC (in degrees) boundaries on the 
    desired source, the name of the results textfile to which the photometry 
    will be appended, and a boolean indicating whether or not to save the image 
    of the segmentation test (optional; defaultTrue)
    Output: None
    
    Obtains a stack of images in the form of a header and data from a .fits 
    file. Estimates the background photon count of this image. Sets a threshold 
    above which we declare a cluster of pixels to be a source: this threshold 
    is defined as the background + the input thresh_factor*the RMS deviation of 
    the background. 
    
    Scans the input image and looks for sources which are at least 7 pixels in 
    area and above this threshold. Saves an image of the sources found in the 
    original field to the working directory. Uses a pixel coordinate to WCS 
    coordinate transformation, via a previously known reference pixel (see 
    PESTO_lib.WCS_merge()) to obtain WCS coords of all detected sources. 
    Outputs a .csv containing the properties of all detected sources.
    
    A tab-delimited results file is appended to. The number of images used in 
    the stack, the total exposure time of the stack and its error, the 
    timestamp (averaged across all images) and its error are already included.    
    IF a source is found, this script appends the x and y minima of the 
    source's centroid, the pixel area of the source, the photon count, and the 
    error on the photon count. If not, the flag NO SOURCE FOUND is appended to 
    the image. 
    """
    
    import numpy as np   
    import numpy.ma as ma 
    import os
    from astropy.stats import (SigmaClip, gaussian_fwhm_to_sigma, 
                               sigma_clipped_stats)
    from astropy.convolution import Gaussian2DKernel
    from astropy.table import Table, Column
    from astroquery.vizier import Vizier
    from astropy.coordinates import SkyCoord
    import astropy.units as u
    from photutils import Background2D, MedianBackground
    from photutils.utils import calc_total_error
    from photutils import detect_sources
    
    # perform 20 iterations of sigma clipping where needed 
    sigma_clip = SigmaClip(sigma=3.0, iters=20) 
    # background is estimated as the median of the entire image 
    bkg_estimator = MedianBackground() 
    mask = (data == 0) # mask all pixels where the ADU is 0  
    bkg = Background2D(data, (50,50), filter_size=(3,3), sigma_clip=sigma_clip, 
                       bkg_estimator=bkg_estimator, mask=mask)

    ### find sources using image segmentation

    # set the threshold for source detection 
    threshold = bkg.background + (thresh_factor*bkg.background_rms)
    sigma = 3.0*gaussian_fwhm_to_sigma
    kernel = Gaussian2DKernel(sigma, x_size=3.0, y_size=3.0)
    kernel.normalize()
    segm = detect_sources(data, threshold, npixels=7, filter_kernel=kernel)
    segm.remove_masked_labels(mask)
    
    try: 
        segm.remove_border_labels(10, partial_overlap=True, relabel=True)
    except: 
        print("The background threshold factor is too large; sources are "+
              "being ignored during image segmentation.\nPlease try a smaller"+
              " value.\n")
        return
 
    # pictures to see what's going on
    if(im):
        import matplotlib.pyplot as plt
        plt.switch_backend('agg') # stop matplotlib from trying to show image
        from astropy.visualization import SqrtStretch
        from astropy.visualization.mpl_normalize import ImageNormalize
        
        norm = ImageNormalize(stretch=SqrtStretch()) # normalize the image 
        fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(90, 90)) # 2 plots 

	     # show the (data-background) of the image with a greyscale colourmap 
        # using the above normalization
        ax1.imshow(data-bkg.background, origin='lower', cmap='Greys_r', 
                   norm=norm) 
        ax2.imshow(segm, origin='lower', cmap=segm.cmap(random_state=12345)) 
        plt.savefig('segmentationtest_'+name+'.png')
    
    # find source properties (centroid, source pixel area, etc.) 
    from photutils import source_properties
    from astropy.wcs import WCS
    
    # calculate the error on the photon counts
#    tf = open('/data/irulan/omm_transients/'+results_file,'r')
#    contents = tf.readlines()
#    tf.close()
#    tf_last = contents[len(contents)-1]
#    tf_data = tf_last.split("\t")  
#    # gain*exposure*stack: 
#    effective_gain = 13.522*(float(tf_data[1])/1000.0)*float(tf_data[0])  
    effective_gain = 13.522
    # compute photon count error :
    error = calc_total_error(data, bkg.background_rms, effective_gain) 
    
    cat = source_properties(data-bkg.background, segm, wcs='all_pix2world', 
                            error=error)
    segm_tbl = cat.to_table() # contruct a table of source properties 
    
    # WCS object
    w = WCS(header)
    # get WCS of all sources, add to segm_tbl, and write the table
    ra, dec = w.all_pix2world(segm_tbl['xcentroid'], segm_tbl['ycentroid'],1)
    segm_tbl["ra"] = ra
    segm_tbl["dec"] = dec
    segm_tbl.write('segmentation_table_'+name+'.csv', format = 'csv', 
              overwrite=True)
    
    # build a new table with only the parameters we care about 
    tbl = Table()
    tbl["id"] = segm_tbl["id"] # id 
    tbl["xcentroid"] = segm_tbl["xcentroid"] # x coord
    tbl["ycentroid"] = segm_tbl["ycentroid"] # y coord 
    tbl["area"] = segm_tbl["area"] # area in pixels
    tbl["ra"] = ra # ra 
    tbl["dec"] = dec # dec
    tbl["pc"] = segm_tbl["source_sum"] # flux 
    tbl["pc_err"] = segm_tbl["source_sum_err"] # error on flux 
    tbl["mag_fit"] = -2.5*np.log10(tbl["pc"]) # instrumental magnitude
    tbl["mag_fit_unc"] = 2.5/(tbl["pc"]*np.log(10)) # error on magnitude 
    
    ### query Vizier to match sources and do aperture photometry
    
    # set the catalogue and filter of the image
    ref_catalog = "II/349/ps1"
    ref_catalog_name = "PS1" # PanStarrs 1
    filt = header["filtre"][0]
    # get the centre of the image and its RA, Dec
    x_size = data.shape[1]
    y_size = data.shape[0]
    ra_centre, dec_centre = np.array(w.all_pix2world(x_size/2.0, 
                                                     y_size/2.0, 1))
    # set radius to search in, minimum, maximum magnitudes
    minmag = 10.0
    maxmag = 22.0 
    max_emag = 0.3 # maximum error on magnitude 
    pixscale = np.mean(np.abs([header["CDELT1"], header["CDELT2"]])) # in deg
    pixscale = pixscale*3600.0 # in arcsec
    radius = pixscale*y_size/60.0 # radius in arcmin 
    
    # query print statement
    #print('Querying Vizier %s around RA %.4f, Dec %.4f with a radius of %.4f arcmin\n'%(
    #        ref_catalog, ra_centre, dec_centre, radius))
    
    # querying 
    v = Vizier(columns=["*"], column_filters={
            filt+"mag":str(minmag)+".."+str(maxmag),
            "e_"+filt+"mag":"<"+str(max_emag)}, row_limit=-1) # no row limit
    
    Q = v.query_region(SkyCoord(ra=ra_centre, dec=dec_centre, 
                    unit = (u.deg, u.deg)), radius = str(radius)+'m', 
                    catalog=ref_catalog, cache=False)
    cat_coords = w.all_world2pix(Q[0]['RAJ2000'], Q[0]['DEJ2000'], 1)
    # mask out edge sources
    x_lims = [int(0.05*x_size), int(0.95*x_size)] 
    y_lims = [int(0.05*y_size), int(0.95*y_size)]
    mask = (cat_coords[0] > x_lims[0]) & (
            cat_coords[0] < x_lims[1]) & (
            cat_coords[1] > y_lims[0]) & (
            cat_coords[1] < y_lims[1])
    good_cat_sources = Q[0][mask] # sources in catalogue 
    
    # cross-matching coords of sources found by astrometry
    source_coords = SkyCoord(ra=tbl['ra'], dec=tbl['dec'], frame='fk5', 
                             unit='degree')
    # and coords of valid sources in the queried catalog 
    cat_source_coords = SkyCoord(ra=good_cat_sources['RAJ2000'], 
                                     dec=good_cat_sources['DEJ2000'], 
                                     frame='fk5', unit='degree')
        
    # indices of matching sources (within 5.0 pix of each other) 
    idx_image, idx_cat, d2d, d3d = cat_source_coords.search_around_sky(
            source_coords, 5.0*pixscale*u.arcsec)
    
    # compute magnitude offsets and zero point
    mag_offsets = ma.array(good_cat_sources[filt+'mag'][idx_cat] - 
                      tbl['mag_fit'][idx_image])
    zp_mean, zp_med, zp_std = sigma_clipped_stats(mag_offsets) # zero point
    
    mag_calib = tbl['mag_fit'] + zp_mean # compute magnitudes 
    mag_calib.name = 'mag_calib'
    mag_calib_unc = np.sqrt(tbl['mag_fit_unc']**2 + zp_std**2) # propagate errs
    mag_calib_unc.name = 'mag_calib_unc'
    tbl['mag_calib'] = mag_calib
    tbl['mag_calib_unc'] = mag_calib_unc
    
    #print(zp_mean)
    #print(zp_std)
    
    # add flag indicating if source is in catalog
    #in_cat = []
    #for i in range(len(tbl)):
    #    if i in idx_image:
    #        in_cat.append(True)
    #    else:
    #        in_cat.append(False)
    #in_cat_col = Column(data=in_cat, name="in "+ref_catalog_name)
    #tbl["in "+ref_catalog_name] = in_cat_col
    
    #return tbl

    # boundaries on the desired source
    RA_min, RA_max = RA_bound
    DEC_min, DEC_max = DEC_bound

    # parse a list of all sources for a source within the RA, Dec bounds
    cwd = os.getcwd() # current working dir
    for i in range(len(tbl['id'])):
        # if source is found:
        if (RA_min <= tbl[i]['ra'] <= RA_max) and (
                DEC_min <= tbl[i]['dec'] <= DEC_max):                      
            print("\nFound a source.\n")

            # Write xcentroid and ycentroid, the pixel area of the source, 
            # the photon count, photon count error, calibrated magnitude, 
            # calibrated magnitude error, and filter used to the file.
            # If xcentroid and ycentroid change drastically from one stack to 
            # another, the sources are not the same or astrometric calibration 
            # may have failed. 
            xcentroid = tbl["xcentroid"].data[i]
            ycentroid = tbl["ycentroid"].data[i]
            area = tbl["area"].data[i]
            pc = tbl["pc"].data[i]
            pc_err = tbl["pc_err"].data[i] 
            mag = tbl["mag_calib"].data[i]
            mag_err = tbl["mag_calib_unc"][i]
            # line to write to the file: 
            line = str(xcentroid)+"\t"+str(ycentroid)+"\t"+str(area)
            line += "\t"+str(pc)+"\t"+str(pc_err) 
            line += "\t"+str(mag)+"\t"+str(mag_err)+"\t"+filt+"\n"
            tf = open(cwd+"/"+results_file,'a')
            tf.write(line)
            tf.close()
            return tbl

    # if no source is found:
    line = "NO SOURCE FOUND.\n" 
    print("No source found.\n")
    tf = open(cwd+"/"+results_file,'a')
    tf.write(line)
    tf.close()

    return tbl
Example #7
0
def phot(images,
         positions=None,
         aperture_radius=6.0,
         annulus=8.0,
         dannulus=10.0,
         method='exact',
         bg_method='median',
         bg_box_size=50,
         gain=1.1,
         output=None):
    """
    Basic circular aperture photometry of given images.

    Parameters
    ----------
    images : generator or list of 'ccdproc.CCDData'
        Images to be combined.

    positions : list
        The positions should be either a single tuple of (x, y), a list of (x, y) tuples, or
        an array with shape Nx2, where N is the number of positions.

        Default is 'None'.


    aperture_radius : list or float
        The aperture radius of a source.

        Default is '3.0'.

    annulus : float
        The circular inner annulus aperture radius of a source.

        Default is '6.0'.

    dannulus : float
        The circular outer annulus aperture radius of a source.

        Default is '8.0'.

    method : {'exact', 'center', 'subpixel'}, optional
        The method used to determine the overlap of the aperture on the
        pixel grid.  Not all options are available for all aperture
        types.  Note that the more precise methods are generally slower.
        The following methods are available:

            * ``'exact'`` (default):
                The the exact fractional overlap of the aperture and
                each pixel is calculated.  The returned mask will
                contain values between 0 and 1.

            * ``'center'``:
                A pixel is considered to be entirely in or out of the
                aperture depending on whether its center is in or out of
                the aperture.  The returned mask will contain values
                only of 0 (out) and 1 (in).

            * ``'subpixel'``:
                A pixel is divided into subpixels (see the ``subpixels``
                keyword), each of which are considered to be entirely in
                or out of the aperture depending on whether its center
                is in or out of the aperture.  If ``subpixels=1``, this
                method is equivalent to ``'center'``.  The returned mask
                will contain values between 0 and 1.

    bg_method: {'mean', 'median'}, optional
        The statistic used to calculate the background.
        All measurements are sigma clipped.

    bg_box_size: int
    Selecting the box size requires some care by the user. The box size
    should generally be larger than the typical size of sources in the
    image, but small enough to encapsulate any background variations. For
    best results, the box size should also be chosen so that the data are
    covered by an integer number of boxes in both dimensions. More information:
    https://github.com/Gabriel-p/photpy/blob/master/IRAF_compare/aperphot.py

    gain: float
        CCD gain value.

    output : None or str
        If it is None, function returns just Table.
        If it is 'str', function creates photometry result file.


    Returns
    -------
    'A table object or file'

    Examples
    --------
    >>> from tuglib.io import FitsCollection
    >>> from tuglib.analysis import phot
    >>> location = '/Users/ykilic/Desktop/data/'
    >>> images = FitsCollection(location, file_extension="fit")
    >>> fits_images = images.ccds(IMAGETYP='object')
    >>> phot(fits_images, positions=[(1400.65, 1545.78)], aperture_radius=10.0, annulus=12.0, dannulus=15)
    """

    if not isinstance(images, (list, types.GeneratorType)):
        raise TypeError("'images' should be a 'ccdproc.CCDData' object.")

    if positions is not None:
        if not isinstance(positions, (list, type(None))):
            raise TypeError("'positions' should be 'list' or 'None' object.")

    if not isinstance(aperture_radius, (list, float, int)):
        raise TypeError("'radius' should be a 'list' or 'float' object.")

    if not isinstance(annulus, (float, int)):
        raise TypeError("'annulus' should be a 'int' or 'float' object.")

    if not isinstance(dannulus, (float, int)):
        raise TypeError("'dannulus' should be a 'int' or 'float' object.")

    if not isinstance(method, str):
        raise TypeError("'method' should be a 'str' object.")

    if not isinstance(bg_method, str):
        raise TypeError("'bg_method' should be a 'str' object.")

    if not isinstance(bg_box_size, int):
        raise TypeError("'bg_box_size' should be a 'int' or 'float' object.")

    if not isinstance(gain, (float, int)):
        raise TypeError("'gain' should be a 'float' object.")

    if not isinstance(output, (type(None), type(str))):
        raise TypeError("'output' should be 'None' or 'str' objects.")

    if isinstance(images, types.GeneratorType):
        try:
            ccds = list(images)
        except IndexError:
            return None
    else:
        ccds = images

    ccds_phot_list = list()

    for ccd in ccds:
        exptime = ccd.meta['EXPTIME']

        box_xy = (bg_box_size, bg_box_size)
        sigma_clip = SigmaClip(sigma=3.)
        bkg_estimator = MedianBackground()
        bkg = Background2D(ccd,
                           box_xy,
                           filter_size=(3, 3),
                           sigma_clip=sigma_clip,
                           bkg_estimator=bkg_estimator)

        ccd.uncertainty = StdDevUncertainty(
            calc_total_error(ccd.data, bkg.background, gain))

        aperture = CircularAperture(positions, r=aperture_radius)
        annulus_aperture = CircularAnnulus(positions,
                                           r_in=annulus,
                                           r_out=dannulus)
        annulus_masks = annulus_aperture.to_mask(method=method)

        bkg_phot = []
        for mask in annulus_masks:
            annulus_data = mask.multiply(ccd)
            annulus_data_1d = annulus_data[mask.data > 0]
            mean_sigclip, median_sigclip, stddev_sigclip = sigma_clipped_stats(
                annulus_data_1d)

            if bg_method == "mean":
                sigclip = mean_sigclip
            elif bg_method == "median":
                sigclip = median_sigclip
            else:
                sigclip = median_sigclip

            bkg_phot.append(sigclip)

        bkg_phot = np.array(sigclip)
        phot_table = aperture_photometry(ccd, aperture)
        phot_table['annulus_{}'.format(bg_method)] = bkg_phot * u.adu
        phot_table['aper_bkg'] = bkg_phot * aperture.area * u.adu
        phot_table['residual_aperture_sum'] = phot_table[
            'aperture_sum'] - phot_table['aper_bkg']

        phot_table['flux'] = phot_table['residual_aperture_sum'] / float(
            exptime)
        phot_table['flux'].info.format = '%.3f'
        phot_table['JD'] = float(ccd.meta['JD'])
        phot_table['JD'].info.format = '%.8f'
        phot_table['JD'].unit = 'd'
        phot_table['aperture_sum'].info.format = '%.3f'
        phot_table['aperture_sum_err'].info.format = '%.3f'
        phot_table['annulus_{}'.format(bg_method)].info.format = '%.3f'
        phot_table['aper_bkg'].info.format = '%.3f'
        phot_table['residual_aperture_sum'].info.format = '%.3f'
        phot_table = flux2mag(phot_table)

        phot_table['mag_err'] = 1.0857 * (
            phot_table['aperture_sum_err'] /
            phot_table['residual_aperture_sum']) * u.mag
        phot_table['mag_err'].info.format = '%.3f'
        ccds_phot_list.append(phot_table)

    return vstack(ccds_phot_list)
Example #8
0
def A_photometry(bg_err,
                 cx=15,
                 cx_med=15,
                 cy=15,
                 cy_med=15,
                 r=[2.5],
                 a=[5],
                 b=[5],
                 w_r=[5],
                 h_r=[5],
                 theta=[0],
                 scale=1,
                 shape='Circular',
                 methods=['center', 'exact'],
                 moveCentroids=[True],
                 i=0,
                 img_data=None):
    """Performs aperture photometry, first by creating the aperture then summing the flux within the aperture.

    Note that this will implicitly use the global variable image_stack (3D array) to allow for parallel computing
        with large (many GB) datasets.

    Args:
        bg_err (1D array): Array of uncertainties on pixel value.
        cx (int/array, optional): x-coordinate(s) of the center of the aperture. Default is 15.
        cy (int/array, optional): y-coordinate(s) of the center of the aperture. Default is 15.
        r (int/array, optional): If shape is 'Circular', the radii to try for aperture photometry
            in units of pixels. Default is just 2.5 pixels.
        a (int/array, optional): If shape is 'Elliptical', the semi-major axes to try for elliptical aperture
            in units of pixels. Default is 5.
        b (int/array, optional): If shape is 'Elliptical', the semi-minor axes to try for elliptical aperture
            in units of pixels. Default is 5.
        w_r (int/array, optional): If shape is 'Rectangular', the full widths to try for rectangular aperture (x-axis).
            Default is 5.
        h_r (int/array, optional): If shape is 'Rectangular', the full heights to try for rectangular aperture (y-axis).
            Default is 5.
        theta (int/array, optional): If shape is 'Elliptical' or 'Rectangular', the rotation angles in radians
            of the semimajor axis from the positive x axis. The rotation angle increases counterclockwise. Default is 0.
        scale (int, optional): If the image is oversampled, scaling factor for centroid and bounds,
            i.e, give centroid in terms of the pixel value of the initial image.
        shape (string, optional): shape is the shape of the aperture. Possible aperture shapes are 'Circular',
            'Elliptical', 'Rectangular'. Default is 'Circular'.
        methods (iterable, optional): The methods used to determine the overlap of the aperture on the pixel grid. Possible 
            methods are 'exact', 'subpixel', 'center'. Default is ['center', 'exact'].
        i (int, optional): The current frame number being examined.
        img_data (3D array): The image stack being analyzed if not using the global variable to allow for parallel computing.

    Returns:
        tuple: results (2D array) Array of flux and flux errors, of shape (nMethods*nSizes, 2), where the nSizes loop
            is nested inside the nMethods loop which is itself nested inside the moveCentoids loop.

    """

    # Access the global variable
    if img_data is None:
        global image_stack
    else:
        image_stack = img_data

    if not isinstance(r, Iterable):
        r = [r]
    if not isinstance(cx, Iterable):
        cx = [cx]
    if not isinstance(cy, Iterable):
        cy = [cy]
    if not isinstance(a, Iterable):
        a = [a]
    if not isinstance(b, Iterable):
        b = [b]
    if not isinstance(w_r, Iterable):
        w_r = [w_r]
    if not isinstance(h_r, Iterable):
        h_r = [h_r]
    if not isinstance(theta, Iterable):
        theta = [theta]
    if not isinstance(methods, Iterable):
        methods = [methods]

    data_error = calc_total_error(image_stack[i, :, :],
                                  bg_err[i],
                                  effective_gain=1)

    results = []

    for moveCentroid in moveCentroids:
        # Set up aperture(s)
        apertures = []
        if not moveCentroid:
            position = [cx_med * scale, cy_med * scale]
        else:
            position = [cx[i] * scale, cy[i] * scale]
        if (shape == 'Circular'):
            for j in range(len(r)):
                apertures.append(CircularAperture(position, r=r[j] * scale))
        elif (shape == 'Elliptical'):
            for j in range(len(a)):
                apertures.append(
                    EllipticalAperture(position,
                                       a=a[j] * scale,
                                       b=b[j] * scale,
                                       theta=theta[j]))
        elif (shape == 'Rectangular'):
            for j in range(len(w_r)):
                apertures.append(
                    RectangularAperture(position,
                                        w=w_r[j] * scale,
                                        h=h_r[j] * scale,
                                        theta=theta[j]))

        for method in methods:
            phot_table = aperture_photometry(image_stack[i, :, :],
                                             apertures,
                                             error=data_error,
                                             method=method)
            results.extend([
                float(phot_table[f'aperture_sum_{j}'])
                for j in range(len(apertures))
            ])

    return np.array(results)
         else:
             record_phot = False
             skip_phot = False
     else:
         record_phot = False
         skip_phot = False
 if skip_phot:
     logging.shutdown()
     sys.exit(-1)
 sigma_clip = SigmaClip(sigma=3)
 bkg_estimator = MedianBackground()
 bkg = Background2D(sci_med, (120, 120),
                    filter_size=(3, 3),
                    sigma_clip=sigma_clip,
                    bkg_estimator=bkg_estimator)
 error = calc_total_error(sci_med, bkg.background_rms, 1)
 fwhm = np.float(input('FWHM of stars? '))
 log.info('Using FWHM of ' + str(fwhm))
 apertures = CircularAperture(positions, r=fwhm * 2.5)
 annulus_apertures = CircularAnnulus(positions,
                                     r_in=fwhm * 3,
                                     r_out=fwhm * 4)
 annulus_masks = annulus_apertures.to_mask(method='center')
 bkg_median = []
 for mask in annulus_masks:
     annulus_data = mask.multiply(sci_med)
     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)
 phot = aperture_photometry(sci_med, apertures, error=error)
Example #10
0
def extract_photometry(filename,
                       approx_location,
                       centering_width=80,
                       ap_rad=7.0,
                       in_rad=8.0,
                       out_rad=15.0):
    """
    Does aperture photometry on the reduced image in filename, at the location 
    specified by approx_location
    
    Parameters
    ----------
    filename : str
        Name of the reduced image.
    approx_location : `~astropy.coordinates.SkyCoord`
        `astropy.coordinates.SkyCoord` with the RA and Dec of the object you 
        want to extract. Passed to generate_regions.
    centering_width : int, optional
        Size of box around source region to find the centroid of in pixels. 
        Passed to generate_regions.
    ap_rad : float, optional
        Radius of source region in arcseconds. Passed to generate_regions.
    in_rad : float, optional
        Inner radius of background annulus in arcseconds. Passed to generate_regions.
    out_rad : float, optional
        Outer radius of background annulus in arcseconds. Passed to generate_regions.
    
        
    Returns
    -------
    phot_table : `~astropy.table.Table`
        A table containing the photometry information. This is the original filename, 
        the time of the start of the observation, the exposure time, the time associated 
        with the data point, the filter, the center of the extraction region, the radius 
        of the source region, the area of the source region in pixels, the source counts, 
        the source count error, the center of the background region, the inner radius of 
        the background region, the outer radius of the background region, the area of the 
        background region, the background counts, the background count error, then the net
        counts, net count error, and the instrumental magnitude and instrumental magnitude
        error.
        
    """
    #Open up the file
    hdu = fits.open(filename)[0]

    #Let's do some bookkeeping
    #Get the TAI time from the header, convert to an astropy.time.Time object
    time_header = hdu.header['DATE-OBS']
    obs_time_obj = Time(time_header, scale='tai')
    #Get the actual value of the MJD out of the astropy object
    obs_time = obs_time_obj.mjd

    exp_time = hdu.header['EXPTIME'] * u.second
    time_obj = obs_time_obj + (exp_time / 2.0)
    #Get the value of the MJD out of the astropy object again.
    time = time_obj.mjd

    #Get the name of the filter
    filt = hdu.header['FILTER']

    #Now for some actual photometry
    #Give me the data!
    data = hdu.data

    #Major source of error in the image assumed to be readout noise and photon shot noise.
    #The latter is handled by calc_total_error. The former is just an array the same size
    #as the data, filled with the read out noise.
    ron = hdu.header['GTRON11']
    error_arr = np.full(data.shape, ron)
    #Calculate the total error array from the data + gain (Poisson noise) and readout noise.
    gain = hdu.header['GTGAIN11']
    error = calc_total_error(data, error_arr, gain)

    #Generate source regions
    src, bkg = generate_regions(hdu, approx_location, centering_width, ap_rad,
                                in_rad, out_rad)
    #grab centers of source and background regions
    src_center = (src.positions.ra.value, src.positions.dec.value)
    bkg_center = (bkg.positions.ra.value, bkg.positions.dec.value)

    apers = [src, bkg]

    #Do some aperture photometry!
    phot_table = aperture_photometry(hdu, apers, error=error)

    #Calculate grab counts
    src_cts = phot_table['aperture_sum_0'].data[0]
    src_cts_err = phot_table['aperture_sum_err_0'].data[0]

    bkg_cts = phot_table['aperture_sum_1'].data[0]
    bkg_cts_err = phot_table['aperture_sum_err_1'].data[0]

    #We need source and background region areas, convert sky regions to pix regions
    wcs = WCS(hdu)
    src_pix = src.to_pixel(wcs)
    bkg_pix = bkg.to_pixel(wcs)

    #Calculate region areas
    src_area = src_pix.area()
    bkg_area = bkg_pix.area()

    #Scale the background counts
    bkg_scaled = bkg_cts * (src_area / bkg_area)
    bkg_scaled_err = bkg_cts_err * (src_area / bkg_area)

    #Net flux = Source - Bkg
    net_cts = src_cts - bkg_scaled
    net_cts_err = np.sqrt(src_cts_err**2.0 + bkg_scaled_err**2.0)

    inst_mag = -2.5 * np.log10(net_cts / exp_time.value)
    inst_mag_err = 2.5 * net_cts_err / (net_cts * np.log(10.0))

    if type(bkg_area[0]) == float:

        print(type(bkg_area))
        out_table = Table(
            [[filename], [obs_time], [exp_time.value], [time], [filt],
             [src_center], [ap_rad], [src_area], [src_cts], [src_cts_err],
             [bkg_center], [in_rad], [out_rad], [bkg_area], [bkg_cts],
             [bkg_cts_err], [net_cts[0]], [net_cts_err[0]], [inst_mag[0]],
             [inst_mag_err[0]]],
            names=[
                'Filename', 'Obs_start', 'Exptime', 'Time', 'Filter',
                'Src_center', 'Src_rad', 'Src_area', 'Src_cts', 'Src_cts_err',
                'Bkg_center', 'Bkg_in_rad', 'Bkg_out_rad', 'Bkg_area',
                'Bkg_cts', 'Bkg_cts_err', 'Net_cts', 'Net_cts_err', 'Inst_mag',
                'Inst_mag_err'
            ])
        return out_table

    out_table = Table([[filename], [obs_time], [exp_time.value], [time],
                       [filt], [src_center], [ap_rad], [src_area], [src_cts],
                       [src_cts_err], [bkg_center], [in_rad], [out_rad],
                       [bkg_area[0]], [bkg_cts], [bkg_cts_err], [net_cts[0]],
                       [net_cts_err[0]], [inst_mag[0]], [inst_mag_err[0]]],
                      names=[
                          'Filename', 'Obs_start', 'Exptime', 'Time', 'Filter',
                          'Src_center', 'Src_rad', 'Src_area', 'Src_cts',
                          'Src_cts_err', 'Bkg_center', 'Bkg_in_rad',
                          'Bkg_out_rad', 'Bkg_area', 'Bkg_cts', 'Bkg_cts_err',
                          'Net_cts', 'Net_cts_err', 'Inst_mag', 'Inst_mag_err'
                      ])

    print(type(bkg_area))

    return out_table
Example #11
0
def A_photometry(image_data,
                 bg_err,
                 factor=1,
                 ape_sum=None,
                 ape_sum_err=None,
                 cx=15,
                 cy=15,
                 r=2.5,
                 a=5,
                 b=5,
                 w_r=5,
                 h_r=5,
                 theta=0,
                 shape='Circular',
                 method='center'):
    """
    Performs aperture photometry, first by creating the aperture (Circular,
    Rectangular or Elliptical), then it sums up the flux that falls into the 
    aperture.

    Args:
        image_data (3D array): Data cube of images (2D arrays of pixel values).
        bg_err (1D array): Array of uncertainties on pixel value.
        factor (float, optional): Electron count to photon count factor. Default is 1 if none given.
        ape_sum (1D array, optional): Array of flux to append new flux values to.
            If None, the new values will be appended to an empty array
        ape_sum_err (1D array, optional): Array of flux uncertainty to append new flux uncertainty values to.
            If None, the new values will be appended to an empty array.
        cx (int, optional): x-coordinate of the center of the aperture. Default is 15.
        cy (int, optional): y-coordinate of the center of the aperture. Default is 15.
        r (int, optional): If shape is 'Circular', r is the radius for the circular aperture. Default is 2.5.
        a (int, optional): If shape is 'Elliptical', a is the semi-major axis for elliptical aperture (x-axis). Default is 5.
        b (int, optional): If shape is 'Elliptical', b is the semi-major axis for elliptical aperture (y-axis). Default is 5.
        w_r (int, optional): If shape is 'Rectangular', w_r is the full width for rectangular aperture (x-axis). Default is 5.
        h_r (int, optional): If shape is 'Rectangular', h_r is the full height for rectangular aperture (y-axis). Default is 5.
        theta (int, optional): If shape is 'Elliptical' or 'Rectangular', theta is the angle of the rotation angle in radians
            of the semimajor axis from the positive x axis. The rotation angle increases counterclockwise. Default is 0.
        shape (string, optional): shape is the shape of the aperture. Possible aperture shapes are 'Circular',
            'Elliptical', 'Rectangular'. Default is 'Circular'.
        method (string, optional): The method used to determine the overlap of the aperture on the pixel grid. Possible 
            methods are 'exact', 'subpixel', 'center'. Default is 'center'.

    Returns:
        tuple: ape_sum (1D array) Array of flux with new flux appended.
            ape_sum_err (1D array) Array of flux uncertainties with new flux uncertainties appended.

    """

    if ape_sum is None:
        ape_sum = []
    if ape_sum_err is None:
        ape_sum_err = []

    l, h, w = image_data.shape
    tmp_sum = []
    tmp_err = []
    movingCentroid = (isinstance(cx, Iterable) or isinstance(cy, Iterable))
    if not movingCentroid:
        position = [cx, cy]
        if (shape == 'Circular'):
            aperture = CircularAperture(position, r=r)
        elif (shape == 'Elliptical'):
            aperture = EllipticalAperture(position, a=a, b=b, theta=theta)
        elif (shape == 'Rectangular'):
            aperture = RectangularAperture(position, w=w_r, h=h_r, theta=theta)
    for i in range(l):
        if movingCentroid:
            position = [cx[i], cy[i]]
            if (shape == 'Circular'):
                aperture = CircularAperture(position, r=r)
            elif (shape == 'Elliptical'):
                aperture = EllipticalAperture(position, a=a, b=b, theta=theta)
            elif (shape == 'Rectangular'):
                aperture = RectangularAperture(position,
                                               w=w_r,
                                               h=h_r,
                                               theta=theta)
        data_error = calc_total_error(image_data[i, :, :],
                                      bg_err[i],
                                      effective_gain=1)
        phot_table = aperture_photometry(
            image_data[i, :, :], aperture, error=data_error,
            method=method)  #, pixelwise_error=False)
        tmp_sum.extend(phot_table['aperture_sum'] * factor)
        tmp_err.extend(phot_table['aperture_sum_err'] * factor)
    # removing outliers
    tmp_sum = sigma_clip(tmp_sum, sigma=4, maxiters=2, cenfunc=np.ma.median)
    tmp_err = sigma_clip(tmp_err, sigma=4, maxiters=2, cenfunc=np.ma.median)
    ape_sum.extend(tmp_sum)
    ape_sum_err.extend(tmp_err)
    return ape_sum, ape_sum_err
Example #12
0
def A_photometry(image_data,
                 bg_err,
                 factor=1,
                 ape_sum=[],
                 ape_sum_err=[],
                 cx=15,
                 cy=15,
                 r=2.5,
                 a=5,
                 b=5,
                 w_r=5,
                 h_r=5,
                 theta=0,
                 shape='Circular',
                 method='center'):
    '''
	Performs aperture photometry, first by creating the aperture (Circular,
	Rectangular or Elliptical), then it sums up the flux that falls into the 
	aperture.

    Parameters
    ==========

    image_data: 3D array 
    	Data cube of images (2D arrays of pixel values).

    bg_err   : 1D array
    	Array of uncertainties on pixel value.

    factor   : float (optional)
    	Electron count to photon count factor. Default is 1 if none given.

    ape_sum  : 1D array (optional)
    	Array of flux to append new flux values to. If 'None', the new values
    	will be appended to an empty array

    ape_sum_err: 1D array (optional)
    	Array of flux uncertainty to append new flux uncertainty values to. If 
    	'None', the new values will be appended to an empty array.

    cx       : float or 1D array (optional)
    	x-coordinate of the center of the aperture. Dimension must be equal to 
    	dimension of cy. Default is 15.

    cy       : float or 1D array (optional)
    	y-coordinate of the center of the aperture. Default is 15.

    r        : int (optional)
    	If phot_meth is 'Aperture' and ap_shape is 'Circular', c_radius is 
    	the radius for the circular aperture. Default is 2.5.

	a        : int (optional)
		If phot_meth is 'Aperture' and ap_shape is 'Elliptical', e_semix is
		the semi-major axis for elliptical aperture (x-axis). Default is 5.

	b        : int (optional)
		If phot_meth is 'Aperture' and ap_shape is 'Elliptical', e_semiy is
		the semi-major axis for elliptical aperture (y-axis). Default is 5.

	w_r      : int (optional)
		If phot_meth is 'Aperture' and ap_shape is 'Rectangular', r_widthx is
		the full width for rectangular aperture (x-axis). Default is 5.

	h_r      : int (optional)
		If phot_meth is 'Aperture' and ap_shape is 'Rectangular', r_widthy is
		the full height for rectangular aperture (y-axis). Default is 5.

    theta    : int (optional)
    	If phot_meth is 'Aperture' and ap_shape is 'Elliptical' or
    	'Rectangular', theta is the angle of the rotation angle in radians 
    	of the semimajor axis from the positive x axis. The rotation angle 
    	increases counterclockwise. Default is 0.

	shape    : string object (optional)
    	If phot_meth is 'Aperture', ap_shape is the shape of the aperture. 
    	Possible aperture shapes are 'Circular', 'Elliptical', 'Rectangular'. 
    	Default is 'Circular'.

    method   : string object (optional)
    	If phot_meth is 'Aperture', apemethod is the method used to 
    	determine the overlap of the aperture on the pixel grid. Possible 
    	methods are 'exact', 'subpixel', 'center'. Default is 'exact'.

    Returns
    -------
    ape_sum  : 1D array
    	Array of flux with new flux appended.

    ape_sum_err: 1D array
    	Array of flux uncertainties with new flux uncertainties appended.

	'''
    l, h, w = image_data.shape
    # central position of aperture
    #position = np.c_[cx, cy] # remove when uncommenting below
    if (type(cx) is list):
        position = np.c_[cx, cy]
    else:
        position = np.c_[(cx * np.ones(l)), (cy * np.ones(l))]
    tmp_sum = []
    tmp_err = []
    # performing aperture photometry
    for i in range(l):
        #aperture = CircularAperture(position[i], r=r) # remove when uncommenting below
        if (shape == 'Circular'):
            aperture = CircularAperture(position[i], r=r)
        elif (shape == 'Elliptical'):
            aperture = EllipticalAperture(position[i], a=a, b=b, theta=theta)
        elif (shape == 'Rectangular'):
            aperture = RectangularAperture(position[i],
                                           w=w_r,
                                           h=h_r,
                                           theta=theta)
        data_error = calc_total_error(image_data[i, :, :],
                                      bg_err[i],
                                      effective_gain=1)
        phot_table = aperture_photometry(image_data[i, :, :],
                                         aperture,
                                         error=data_error,
                                         pixelwise_error=False,
                                         method=method)
        tmp_sum.extend(phot_table['aperture_sum'] * factor)
        tmp_err.extend(phot_table['aperture_sum_err'] * factor)
    # removing outliers
    tmp_sum = sigma_clip(tmp_sum, sigma=4, iters=2, cenfunc=np.ma.median)
    tmp_err = sigma_clip(tmp_err, sigma=4, iters=2, cenfunc=np.ma.median)
    ape_sum.extend(tmp_sum)
    ape_sum_err.extend(tmp_err)
    return ape_sum, ape_sum_err
def single_star_photometry(img,
                           xc,
                           yc,
                           bkg,
                           filename,
                           images_info,
                           upper_limit=False):

    shortfile = filename.split('.fits')[0]
    ttexp = images_info.loc[0, 'EXPTIME']
    print('exptime = ' + str(ttexp))
    nanmask = pd.isnull(img)
    ap = 0.4 / images_info.loc[0, 'PIXSCALE']
    filt = images_info.loc[images_info['FILENAME'] == filename,
                           'FILTER'].values[0]
    vegazpt = config.VEGA_ZEROPTS[filt]
    pos = [float(xc), float(yc)]
    apertures = CircularAperture(pos, r=ap)

    sigma_clip = SigmaClip(sigma=3.)
    bkg_estimator = MedianBackground()
    bkgimg = Background2D(img, (20, 20),
                          filter_size=(3, 3),
                          sigma_clip=sigma_clip,
                          bkg_estimator=bkg_estimator)
    error = calc_total_error(
        img,
        bkgimg.background,
        effective_gain=images_info.loc[images_info['FILENAME'] == filename,
                                       'CCDGAIN'].values[0])
    #  error = calc_total_error(img, bkgimg.background, effective_gain=config.GAIN[instrument])

    #For sky annuli background
    #sky_inner = 0.8/images_info.loc[0,'PIXSCALE']
    #sky_outer = 0.9/images_info.loc[0,'PIXSCALE']
    #skyannuli = CircularAnnulus(pos, r_in=sky_inner, r_out=sky_outer)
    #phot_apers = [apertures, skyannuli]
    #phot_table2 = Table(aperture_photometry(img, phot_apers, method='exact', error=error, mask=nanmask)).to_pandas()

    #For multiapertures background
    phot = aperture_photometry(img, apertures, method='exact',
                               mask=nanmask)  #error=error,
    phot = Table(phot).to_pandas()

    phot = phot.rename(columns={'aperture_sum': 'APERTURE_FLUX'
                                })  #,'aperture_sum_err':'APERTURE_FLUX_ERR'
    phot['BKG_FLUX'] = bkg[0]
    phot['BKG_FLUX_ERR'] = bkg[1]
    phot['APERTURE_FLUX_ERR'] = np.sqrt(phot['APERTURE_FLUX'] / ttexp +
                                        2 * phot['BKG_FLUX_ERR']**2)
    phot = phot.apply(pd.to_numeric, errors='ignore')
    if upper_limit == True:
        print('this is true')
        print('background flux error = ' + str(bkg[1]))
        phot['STAR_FLUX'] = 3 * bkg[1]
        #3 sigma = 3 * 1.4826 * MAD since distribution is Gaussian - No more MAD, just STD
        phot['STAR_FLUX_ERR'] = np.nan
    else:
        #no need to divide over area because aperture areas for both star flux and backgroun flux are the same
        phot['STAR_FLUX'] = phot['APERTURE_FLUX'] - phot['BKG_FLUX']
        phot['STAR_FLUX_ERR'] = np.sqrt(phot['APERTURE_FLUX_ERR']**2 +
                                        phot['BKG_FLUX_ERR']**2)

        #bkg_mean = phot_table2['aperture_sum_1'] / skyannuli.area
        #bkg_starap_sum = bkg_mean * apertures.area
        #final_sum = phot_table2['aperture_sum_0']-bkg_starap_sum
        #phot_table2['bg_subtracted_star_counts'] = final_sum
        #bkg_mean_err = phot_table2['aperture_sum_err_1'] / skyannuli.area
        #bkg_sum_err = bkg_mean_err * apertures.area
        #phot_table2['bg_sub_star_cts_err'] = np.sqrt((phot_table2['aperture_sum_err_0']**2)+(bkg_sum_err**2))

        #phot['STAR_FLUX_ANNULI'] = final_sum
        #phot['STAR_FLUX_ERR_ANNULI'] = phot_table2['bg_sub_star_cts_err']

    phot['VEGAMAG'] = vegazpt - 2.5 * np.log10(phot['STAR_FLUX'])
    phot['VEGAMAG_UNC'] = 1.0857 * phot['STAR_FLUX_ERR'] / phot['STAR_FLUX']
    #phot['VEGAMAG_UNC_APPHOT_ONLY'] = 1.0857 * phot['APERTURE_FLUX_ERR'] / phot['STAR_FLUX']
    #phot['VEGAMAG_ANNULI'] = vegazpt - 2.5 * np.log10(phot['STAR_FLUX_ANNULI'])
    #phot['VEGAMAG_UNC_ANNULI'] = 1.0857 * phot['STAR_FLUX_ERR_ANNULI'] / phot['STAR_FLUX_ANNULI']
    phot.to_csv('phot_table_' + str(shortfile) + '.csv')

    return phot
Example #14
0
def reduce(fitsfile, outfile, makeplots=False):
    from photutils import (Background2D, DAOStarFinder, CircularAperture,
                           aperture_photometry, MedianBackground,
                           make_source_mask, SigmaClip)
    from photutils.utils import calc_total_error
    from astropy.stats import sigma_clipped_stats

    # background variables
    bkg_boxsize = 100
    bkg_filter_size = (11, 11)
    bkg_edge_method = 'pad'
    bkg_estimator = MedianBackground()

    # sigma clipped stats variables
    sig = 5.
    iters = 5

    # star finder variables
    fwhm = 1.5
    # threshold = 5. * std

    # aperture radius (in pixels)
    # plate scale ~0.034 × 0.030"/pixel
    # infinate aperture is 4"
    # 15 pixels ~ 0.45"
    apr_rad = 5

    # load data and header
    hdu = fits.open(fitsfile)
    data = hdu[0].data
    w = wcs.WCS(hdu[0].header)
    nanmask = np.isnan(data)
    # subtract background
    bkg = Background2D(data, bkg_boxsize, filter_size=bkg_filter_size,
                       edge_method=bkg_edge_method,
                       bkg_estimator=bkg_estimator,
                       mask=nanmask)

    data_sub = data - bkg.background

    # calculate background stats
    mean, median, std = sigma_clipped_stats(data_sub, sigma=sig, iters=iters)
    threshold = 5. * std

    daofind = DAOStarFinder(fwhm=fwhm, threshold=threshold, exclude_border=True)
    sources = daofind(data_sub)

    positions = (sources['xcentroid'], sources['ycentroid'])
    #
    apertures = CircularAperture(positions, r=apr_rad)

    effective_gain = hdu[0].header['EXPTIME']

    error = calc_total_error(data_sub, bkg.background, effective_gain)
    phot_table = aperture_photometry(data_sub, apertures, error=error,
                                     mask=nanmask)

    vegamag, vegamagerr = calcmag(phot_table['aperture_sum'],
                                  phot_table['aperture_sum_err'])
    ra, dec = w.all_pix2world(phot_table['xcenter'], phot_table['ycenter'], 1)

    phot_table['ra'] = ra
    phot_table['dec'] = dec
    phot_table['vegamag'] = vegamag
    phot_table['vegamagerr'] = vegamagerr
    fmts = {c: '%.6f' for c in ['xcenter', 'ycenter', 'aperture_sum',
                                'aperture_sum_err', 'ra', 'dec', 'vegamag',
                                'vegamagerr']}
    phot_table.write(outfile, format='ascii.commented_header', formats=fmts,
                     overwrite=True)
    if makeplots:
        outfig = outfile + '.pdf'
        axs = photplots(data, bkg.background, bkg.background_rms, data_sub,
                        fitsfile, outfig)
        apertures.plot(color='r', lw=1.5, alpha=0.5, ax=axs[0, 1])
        plt.savefig(outfig)
    return vegamag, vegamagerr, ra, dec
Example #15
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
Example #16
0
def apr_red(fitsfile, outfile, makeplots=False):
    """
    aperture photometry on a fitsfild
    """
    from photutils import (Background2D, DAOStarFinder, CircularAperture,
                           aperture_photometry, MeanBackground)
    from photutils.utils import calc_total_error
    from astropy.stats import sigma_clipped_stats
    # Open the fits file
    hdu = fits.open(fitsfile)
    # Take out the data -- this is often not the 0 extention. If this breaks,
    # try this instead:
    # data = hdu[1].data
    data = hdu[0].data
    # the header is almost always the 0th ext.
    w = wcs.WCS(hdu[0].header)
    nanmask = np.isnan(data)
    data[nanmask] = -99
    newdata = data.copy()
    # you might not need to make the image square -- I did it for my mosaic.
    # square-ify: add -99 values to fill a square
    nx, ny = np.shape(data)
    if nx != ny:
        if ny > nx:
            filler = np.zeros((ny - nx, ny)) - 99
            newdata = np.vstack((data, filler))
        else:
            filler = np.zeros((nx, nx - ny)) - 99
            newdata = np.hstack((data, filler))
    else:
        newdata = data.copy()
    # See photutils documentation, box dimension is the nearest factor to 150px
    boxdim = nearest_factor(ny, 150)
    mask = (newdata == -99)
    # sigma_clip = SigmaClip(sigma=3., iters=10)
    bkg = Background2D(newdata, (boxdim, boxdim),
                       filter_size=(9, 9),
                       mask=mask,
                       bkg_estimator=MeanBackground())
    # data - bg
    data_sub = newdata - bkg.background

    mean, median, std = sigma_clipped_stats(data_sub,
                                            sigma=8.0,
                                            iters=10,
                                            mask=mask)

    daofind = DAOStarFinder(fwhm=4.0, threshold=10. * std + median)
    sources = daofind(data_sub)

    positions = (sources['xcentroid'], sources['ycentroid'])
    # ACS handbook says 4. is inf.
    apertures = CircularAperture(positions, r=4.)

    effective_gain = hdu[0].header['EXPTIME']

    error = calc_total_error(data_sub, bkg.background, effective_gain)
    phot_table = aperture_photometry(data_sub,
                                     apertures,
                                     error=error,
                                     mask=mask)

    vegamag = -2.5 * np.log10(phot_table['aperture_sum'] * PHOTFLAM) - VEGAmag0
    # vegamagerr = \
    #     -2.5 * np.log10(phot_table['aperture_sum_err'] * PHOTFLAM) - VEGAmag0
    vegamagerr = verrtest(phot_table['aperture_sum'],
                          phot_table['aperture_sum_err'])

    # see astropy.wcs for info:
    ra, dec = w.all_pix2world(phot_table['xcenter'], phot_table['ycenter'], 1)

    phot_table['ra'] = ra
    phot_table['dec'] = dec
    phot_table['vegamag'] = vegamag
    phot_table['vegamagerr'] = vegamagerr

    fmts = {
        c: '%.6f'
        for c in [
            'xcenter', 'ycenter', 'aperture_sum', 'aperture_sum_err', 'ra',
            'dec', 'vegamag', 'vegamagerr'
        ]
    }
    phot_table.write(outfile, format='ascii.commented_header', formats=fmts)
    if makeplots:
        outfig = outfile + '.pdf'
        axs = photplots(newdata, bkg.background, bkg.background_rms, data_sub,
                        fitsfile, outfig)
        apertures.plot(color='red', lw=1.5, alpha=0.5, ax=axs[0, 1])
        plt.savefig(outfig)
    return vegamag, vegamagerr, ra, dec