示例#1
0
文件: test.py 项目: cmccully/sep
def test_apertures_exact():
    """Test area as measured by exact aperture modes on array of ones"""

    theta = np.random.uniform(-np.pi/2., np.pi/2., naper)
    ratio = np.random.uniform(0.2, 1.0, naper)
    r = 3.

    for dt in SUPPORTED_IMAGE_DTYPES:
        data = np.ones(data_shape, dtype=dt)
        for r in [0.5, 1., 3.]:
            flux, fluxerr, flag = sep.sum_circle(data, x, y, r, subpix=0)
            assert_allclose(flux, np.pi*r**2)

            rout = r*1.1
            flux, fluxerr, flag = sep.sum_circann(data, x, y, r, rout,
                                                  subpix=0)
            assert_allclose(flux, np.pi*(rout**2 - r**2))

            flux, fluxerr, flag = sep.sum_ellipse(data, x, y, 1., ratio,
                                                  theta, r=r, subpix=0)
            assert_allclose(flux, np.pi*ratio*r**2)

            rout = r*1.1
            flux, fluxerr, flag = sep.sum_ellipann(data, x, y, 1., ratio,
                                                   theta, r, rout, subpix=0)
            assert_allclose(flux, np.pi*ratio*(rout**2 - r**2))
示例#2
0
def select_sources(file, threshold=30, SNR_limit=5):
    objects = []
    name = file.split('/')[-1].split('.')[0]
    fitsfile = pyfits.open(file, uint=False)
    original = fitsfile[1].data
    original = np.asarray(original, dtype=np.float)
    bias = fits.open('/media/data/Bahtinov/2017_03_15/Bias/Masterbias.fits')
    bias = bias[0].data
    original = original - bias
    data, background = image.subtract_background(original)
    sources = sep.extract(data,
                          threshold,
                          err=background.globalrms,
                          gain=1.0,
                          minarea=100)
    sources = np.sort(sources, order='flux')
    BG = sep.sum_ellipann(data,
                          sources['x'],
                          sources['y'],
                          sources['a'],
                          sources['b'],
                          sources['theta'],
                          sources['b'] * 1.5,
                          sources['b'] * 4.5,
                          err=background.globalrms)[0]
    SNR = sources['flux'] / BG
    objects.append([
        sources['x'][np.where(SNR > SNR_limit)],
        sources['x2'][np.where(SNR > SNR_limit)],
        sources['y'][np.where(SNR > SNR_limit)],
        sources['y2'][np.where(SNR > SNR_limit)],
        SNR[np.where(SNR > SNR_limit)]
    ])
    objects = objects[0]
    ratio = data.shape[0] * 1.0 / data.shape[1]
    fig = figure(figsize=(10, ratio * 10))
    axis = fig.add_subplot(111)
    axis.imshow(data, cmap='gray', interpolation='nearest', origin='lower')
    axis.scatter(sources['x'], sources['y'], color='b', s=1.5)
    axis.scatter(objects[0], objects[2], color='r', s=1.5)
    fig.savefig(directory_prefix + 'bahtinov_results/Focusrun/' +
                today_utc_date + '/' + name + '.png')
    plt.close()
    return np.asarray(objects)
示例#3
0
def mass_weighted_prof(data,
                       mass_map,
                       aper,
                       r_inn,
                       r_out,
                       subpix=7,
                       mask=None,
                       return_mass=False):
    """Get the stellar mass weighted properties in annulus.

    Parameters
    ----------
    data : ndarray
        Age or metallicity map, or other quantities that need to be weighted.
    mass_map : ndarray
        Stellar mass map.
    aper : dict
        Basic information of the galaxy
    r_inn : ndarray
        Array of inner radial bins to define the annulus.
    r_out : ndarray
        Array of outer radial bins to define the annulus.
    subpix : int, optional
        Subpixel sampling factor. Default: 5.
    mask : ndarray, optional
        Mask array.
    return_mass : bool, optional
        Return the stellar mass in each radial bins. Default: False

    Returns
    -------
    prof : dict
        A dictionary that contains the mass-weighted and none-weighted profiles.

    """
    # Mass weighted data
    data_w = copy.deepcopy(data * mass_map)
    data_w[np.isnan(data_w)] = 0.0

    # Define a mask...just in case
    if mask is None:
        mask = (mass_map < 1.).astype(np.uint8)

    # Sum(data) term
    sum_data, _, flag = sep.sum_ellipann(data,
                                         aper['x'],
                                         aper['y'],
                                         1.0,
                                         aper['ba'],
                                         aper['theta'],
                                         r_inn,
                                         r_out,
                                         mask=mask,
                                         subpix=subpix)

    # Total number of effective pixels
    n_pix_eff, _, flag = sep.sum_ellipann((1.0 - mask),
                                          aper['x'],
                                          aper['y'],
                                          1.0,
                                          aper['ba'],
                                          aper['theta'],
                                          r_inn,
                                          r_out,
                                          mask=mask,
                                          subpix=subpix)

    # Sum(mass_map * data) term
    sum_data_w, _, _ = sep.sum_ellipann(data_w,
                                        aper['x'],
                                        aper['y'],
                                        1.0,
                                        aper['ba'],
                                        aper['theta'],
                                        r_inn,
                                        r_out,
                                        mask=mask,
                                        subpix=subpix)

    # Sum(mass_map) term
    sum_mass_map, _, _ = sep.sum_ellipann(mass_map,
                                          aper['x'],
                                          aper['y'],
                                          1.0,
                                          aper['ba'],
                                          aper['theta'],
                                          r_inn,
                                          r_out,
                                          mask=mask,
                                          subpix=subpix)

    if return_mass:
        return {
            'prof_w': sum_data_w / sum_mass_map,
            'prof': sum_data / n_pix_eff,
            'mass': sum_mass_map,
            'flag': flag
        }

    return {
        'prof_w': sum_data_w / sum_mass_map,
        'prof': sum_data / n_pix_eff,
        'flag': flag
    }
示例#4
0
def aperture_photometry(img: Union[ndarray, MaskedArray], sources: ndarray,
                        background: Optional[Union[ndarray,
                                                   MaskedArray]] = None,
                        background_rms: Optional[Union[ndarray,
                                                       MaskedArray]] = None,
                        texp: float = 1,
                        gain: Union[float, ndarray, MaskedArray] = 1,
                        sat_level: float = 63000,
                        a: Optional[float] = None, b: Optional[float] = None,
                        theta: Optional[float] = 0,
                        a_in: Optional[float] = None,
                        a_out: Optional[float] = None,
                        b_out: Optional[float] = None,
                        theta_out: Optional[float] = None,
                        k: float = 0,
                        k_in: Optional[float] = None,
                        k_out: Optional[float] = None,
                        radius: float = 6,
                        fix_aper: bool = False,
                        fix_ell: bool = True,
                        fix_rot: bool = True,
                        apcorr_tol: float = 0.0001) -> ndarray:
    """
    Do automatic or fixed aperture photometry

    :param img: input 2D image array
    :param sources: record array of sources extracted with
        :func:`skylib.extraction.extract_sources`; should contain at least "x"
        and "y" columns
    :param background: optional sky background map; if omitted, extract
        background from the annulus around the aperture, see `a_in` below
    :param background_rms: optional sky background RMS map; if omitted,
        calculate RMS over the annulus around the aperture
    :param texp: exposure time in seconds
    :param gain: electrons to data units conversion factor; used to estimate
        photometric errors; for variable-gain images (e.g. mosaics), must be
        an array of the same shape as the input data
    :param sat_level: saturation level in ADUs; used to select only
        non-saturated stars for adaptive aperture photometry and aperture
        correction
    :param a: fixed aperture radius or semi-major axis in pixels; default: use
        automatic photometry with a = a_iso*k, where a_iso is the isophotal
        semi-major axis
    :param b: semi-minor axis in pixels when using a fixed aperture; default:
        same as `a`
    :param theta: rotation angle of semi-major axis in degrees CCW when using
        a fixed aperture and `b` != `a`; default: 0
    :param a_in: inner annulus radius or semi-major axis in pixels; used
        to estimate the background if `background` or `background_rms` are not
        provided, ignored otherwise; default: `a`*`k_in`
    :param a_out: outer annulus radius or semi-major axis in pixels; default:
        `a`*`k_out`
    :param b_out: outer annulus semi-minor axis in pixels; default: `b`*`k_out`
    :param theta_out: annulus orientation in degrees CCW; default: same
        as `theta`
    :param k: automatic aperture radius in units of isophotal radius; 0 means
        find the optimal radius based on SNR; default: 0
    :param k_in: inner annulus radius in units of aperture radius (fixed
        aperture, i.e. `a` is provided) or isophotal radius (adaptive aperture);
        default: 1.5*`k` or 3.75 if `k` is undefined and `a` = None
    :param k_out: outer annulus radius in units of aperture radius (fixed
        aperture) or isophotal radius (adaptive aperture); default: 2*`k` or
        5 if `k` is undefined and `a` = None
    :param radius: isophotal analysis radius in pixels used to compute automatic
        aperture if ellipse parameters (a,b,theta) are missing
    :param fix_aper: use the same aperture radius for all sources when doing
        automatic photometry; calculated as flux-weighted median of aperture
        sizes based on isophotal parameters
    :param fix_ell: use the same major to minor aperture axis ratio for all
        sources during automatic photometry; calculated as flux-weighted median
        of all ellipticities
    :param fix_rot: use the same aperture position angle for all sources during
        automatic photometry; calculated as flux-weighted median
        of all orientations
    :param apcorr_tol: growth curve stopping tolerance for aperture correction;
        0 = disable aperture correction

    :return: record array containing the input sources, with the following
        fields added or updated: "flux", "flux_err", "mag", "mag_err", "aper_a",
        "aper_b", "aper_theta", "aper_a_in", "aper_a_out", "aper_b_out",
        "aper_theta_out", "aper_area", "background_area", "background",
        "background_rms", "phot_flag"
    """
    if not len(sources):
        return array([])

    img = sep_compatible(img)

    texp = float(texp)
    if isscalar(gain):
        gain = float(gain)
    k = float(k)
    if k <= 0.1:
        k = 0  # temporary fix for k = 0 not being allowed in AgA
    if k_in:
        k_in = float(k_in)
    if k_out:
        k_out = float(k_out)

    x, y = sources['x'] - 1, sources['y'] - 1
    area_img = ones(img.shape, dtype=int32)
    if isinstance(img, MaskedArray):
        mask = img.mask
        img = img.data
    else:
        mask = None

    have_background = background is not None and background_rms is not None
    if have_background:
        background = sep_compatible(background)
        if isinstance(background, MaskedArray):
            if mask is None:
                mask = background.mask
            else:
                mask |= background.mask
            background = background.data

        background_rms = sep_compatible(background_rms)
        if isinstance(background_rms, MaskedArray):
            if mask is None:
                mask = background_rms.mask
            else:
                mask |= background_rms.mask
            background_rms = background_rms.data

    # Will need this to fill the newly added source table columns
    z = zeros(len(sources), float)

    fixed_aper = bool(a)
    if fixed_aper:
        # Use the same fixed aperture and annulus parameters for all sources
        a = float(a)
        if b:
            b = float(b)
        else:
            b = a
        if theta:
            theta = float(theta % 180)*pi/180
            if theta > pi/2:
                theta -= pi
        else:
            theta = 0

        if not have_background:
            if theta_out:
                theta_out = float(theta_out % 180)*pi/180
                if theta_out > pi/2:
                    theta_out -= pi
            elif theta_out != 0:
                theta_out = theta
            if a_in:
                a_in = float(a_in)
            else:
                a_in = a*k_in if k_in else a*1.5*k if k else a*3.75
            if a_out:
                a_out = float(a_out)
            else:
                a_out = a*k_out if k_out else a*2*k if k else a*5
            if b_out:
                b_out = float(b_out)
            else:
                b_out = a_out*b/a
    else:
        # Use automatic apertures derived from ellipse axes; will need image
        # with background subtracted
        if background is None:
            # Estimate background on the fly
            tmp_back, tmp_rms = estimate_background(img, size=64)
        else:
            tmp_back, tmp_rms = background, background_rms
        img_back = img - tmp_back
        for name in ['a', 'b', 'theta', 'flux']:
            if name not in sources.dtype.names:
                sources = append_fields(sources, name, z, usemask=False)
        a, b, theta = sources['a'], sources['b'], sources['theta']
        flux = sources['flux']
        bad = (a <= 0) | (b <= 0) | (flux <= 0)
        if bad.any():
            # Do isophotal analysis to compute ellipse parameters if missing
            yy, xx = indices(img.shape)
            for i in bad.nonzero()[0]:
                ap = (xx - sources[i]['x'])**2 + (yy - sources[i]['y'])**2 <= \
                    radius**2
                if ap.any():
                    yi, xi = ap.nonzero()
                    ap_data = img_back[ap].astype(float)
                    f = ap_data.sum()
                    if f > 0:
                        cx = (xi*ap_data).sum()/f
                        cy = (yi*ap_data).sum()/f
                        x2 = (xi**2*ap_data).sum()/f - cx**2
                        y2 = (yi**2*ap_data).sum()/f - cy**2
                        xy = (xi*yi*ap_data).sum()/f - cx*cy
                    else:
                        cx, cy = xi.mean(), yi.mean()
                        x2 = (xi**2).mean() - cx**2
                        y2 = (yi**2).mean() - cy**2
                        xy = (xi*yi).mean() - cx*cy
                    if x2 == y2:
                        thetai = 0
                    else:
                        thetai = arctan(2*xy/(x2 - y2))/2
                        if y2 > x2:
                            thetai += pi/2
                    m1 = (x2 + y2)/2
                    m2 = sqrt(max((x2 - y2)**2/4 + xy**2, 0))
                    ai = max(1/12, sqrt(max(m1 + m2, 0)))
                    bi = max(1/12, sqrt(max(m1 - m2, 0)))
                    if ai/bi > 2:
                        # Prevent too elongated apertures usually occurring for
                        # faint objects
                        bi = ai
                else:
                    # Cannot obtain a,b,theta from isophotal analysis, assume
                    # circular aperture
                    ai, bi, thetai, f = radius, radius, 0, 0
                a[i] = sources[i]['a'] = ai
                b[i] = sources[i]['b'] = bi
                theta[i] = sources[i]['theta'] = thetai
                flux[i] = sources[i]['flux'] = f
        bad = (a < b).nonzero()
        a[bad], b[bad] = b[bad], a[bad]
        theta[bad] += pi/2
        theta %= pi
        theta[theta > pi/2] -= pi
        elongation = a/b

        # Obtain the optimal aperture radius from the brightest non-saturated
        # source
        if not k:
            for i in argsort(flux)[::-1]:
                if sep.sum_ellipse(
                        img >= sat_level, [x[i]], [y[i]], a[i], b[i], theta[i],
                        1, subpix=0)[0][0]:
                    # Saturated source
                    continue
                try:
                    # noinspection PyTypeChecker
                    res = minimize(
                        calc_flux_err, [a[i]*1.6],
                        (img_back, x[i], y[i], elongation[i], theta[i], tmp_rms,
                         mask, gain), bounds=[(1, None)], tol=1e-5)
                except ValueError:
                    continue
                if not res.success:
                    continue
                k = res.x[0]/a[i]
                break
            if not k:
                raise ValueError(
                    'Not enough data for automatic aperture factor; use '
                    'explicit aperture factor')

        # Calculate weighted median of aperture sizes, elongations, and/or
        # orientations if requested
        r = sqrt(a*b)*k
        if r.size > 1 and any([fix_aper, fix_ell, fix_rot]):
            flux[flux < 0] = 0
            if not flux.any():
                raise ValueError(
                    'Not enough data for weighted median in fixed-aperture '
                    'automatic photometry; use static fixed-aperture or fully '
                    'adaptive automatic photometry instead')
            if fix_aper:
                r = weighted_median(r, flux)
            if fix_ell:
                elongation = weighted_median(elongation, flux)
            if fix_rot:
                theta = weighted_median(theta, flux, period=pi)
                if theta > pi/2:
                    theta -= pi

        # Calculate the final aperture and annulus sizes
        sqrt_el = sqrt(elongation)
        a, b = r*sqrt_el, r/sqrt_el
        if not have_background:
            if not k_in:
                k_in = 1.5*k
            if not k_out:
                k_out = 2*k
            a_in = a*(k_in/k)
            a_out, b_out = a*(k_out/k), b*(k_out/k)
            theta_out = theta

    # Calculate mean and RMS of background; to get the pure sigma, set error
    # to 1 and don't pass the gain
    if have_background:
        if fixed_aper and a == b:
            bk_area = sep.sum_circle(area_img, x, y, a, mask=mask, subpix=0)[0]
            bk_mean, bk_sigma = sep.sum_circle(
                background, x, y, a, err=1, mask=mask, subpix=0)[:2]
        else:
            bk_area = sep.sum_ellipse(
                area_img, x, y, a, b, theta, 1, mask=mask, subpix=0)[0]
            bk_mean, bk_sigma = sep.sum_ellipse(
                background, x, y, a, b, theta, 1, err=1, mask=mask,
                subpix=0)[:2]
        error = background_rms
    elif fixed_aper and a_out == b_out:
        bk_area = sep.sum_circann(
            area_img, x, y, a_in, a_out, mask=mask, subpix=0)[0]
        bk_mean, bk_sigma = sep.sum_circann(
            img, x, y, a_in, a_out, err=1, mask=mask, subpix=0)[:2]
        error = bk_sigma
    else:
        bk_area = sep.sum_ellipann(
            area_img, x, y, a_out, b_out, theta_out, a_in/a_out, 1, mask=mask,
            subpix=0)[0]
        bk_mean, bk_sigma = sep.sum_ellipann(
            img, x, y, a_out, b_out, theta_out, a_in/a_out, 1, err=1, mask=mask,
            subpix=0)[:2]
        error = bk_sigma

    if have_background:
        area = bk_area
    elif fixed_aper and a == b:
        area = sep.sum_circle(area_img, x, y, a, mask=mask, subpix=0)[0]
    else:
        area = sep.sum_ellipse(
            area_img, x, y, a, b, theta, 1, mask=mask, subpix=0)[0]

    if fixed_aper and a == b:
        # Fixed circular aperture
        if ndim(error) == 1:
            # Separate scalar error for each source
            flux, flux_err = empty([2, len(sources)], dtype=float)
            flags = empty(len(sources), dtype=int)
            for i, (_x, _y, _err) in enumerate(zip(x, y, error)):
                flux[i], flux_err[i], flags[i] = sep.sum_circle(
                    img, [_x], [_y], a, err=_err, mask=mask, gain=gain,
                    subpix=0)
        else:
            flux, flux_err, flags = sep.sum_circle(
                img, x, y, a, err=error, mask=mask, gain=gain, subpix=0)
    else:
        # Variable or elliptic aperture
        if ndim(error) == 1:
            # Separate scalar error for each source
            flux, flux_err = empty([2, len(sources)], dtype=float)
            flags = empty(len(sources), dtype=int)
            if isscalar(a):
                a = full_like(x, a)
            if isscalar(b):
                b = full_like(x, b)
            if isscalar(theta):
                theta = full_like(x, theta)
            for i, (_x, _y, _err, _a, _b, _theta) in enumerate(zip(
                    x, y, error, a, b, theta)):
                flux[i], flux_err[i], flags[i] = sep.sum_ellipse(
                    img, [_x], [_y], _a, _b, _theta, 1, err=_err, mask=mask,
                    gain=gain, subpix=0)
        else:
            flux, flux_err, flags = sep.sum_ellipse(
                img, x, y, a, b, theta, 1, err=error, mask=mask, gain=gain,
                subpix=0)

    # Convert background sum to mean and subtract background from fluxes
    if have_background:
        # Background area equals aperture area
        flux -= bk_mean
        bk_mean = bk_mean/area
    else:
        # Background area equals annulus area
        bk_mean = bk_mean/bk_area
        flux -= bk_mean*area

    # Convert ADUs to electrons
    flux *= gain
    flux_err *= gain
    bk_mean *= gain
    bk_sigma *= gain

    # Calculate aperture correction for all aperture sizes from the brightest
    # source
    aper_corr = {}
    if apcorr_tol > 0:
        for i in argsort(flux)[::-1]:
            xi, yi = x[i], y[i]
            if isscalar(a):
                ai = a
            else:
                ai = a[i]
            if isscalar(b):
                bi = b
            else:
                bi = b[i]
            if isscalar(theta):
                thetai = theta
            else:
                thetai = theta[i]
            if ndim(error) == 1:
                err = error[i]
            else:
                err = error

            if ai == bi:
                nsat = sep.sum_circle(
                    img >= sat_level, [xi], [yi], ai, subpix=0)[0][0]
            else:
                nsat = sep.sum_ellipse(
                    img >= sat_level, [xi], [yi], ai, bi, thetai, 1,
                    subpix=0)[0][0]
            if nsat:
                # Saturated source
                continue

            # Obtain total flux by increasing aperture size until it grows
            # either more than before (i.e. a nearby source in the aperture) or
            # less than the threshold (i.e. the growth curve reached saturation)
            f0 = f_prev = flux[i]
            dap = 0
            f_tot = df_prev = None
            while True:
                dap += 0.1
                if ai == bi:
                    f, f_err, fl = sep.sum_circle(
                        img, [xi], [yi], ai + dap, err=err, mask=mask,
                        gain=gain, subpix=0)
                    area_i = sep.sum_circle(
                        area_img, [xi], [yi], ai + dap, mask=mask,
                        subpix=0)[0][0]
                else:
                    f, f_err, fl = sep.sum_ellipse(
                        img, [xi], [yi], ai + dap, bi*(1 + dap/ai), thetai, 1,
                        err=err, mask=mask, gain=gain, subpix=0)
                    area_i = sep.sum_ellipse(
                        area_img, [xi], [yi], ai + dap, bi + dap, thetai, 1,
                        mask=mask, subpix=0)[0][0]
                f, fl = f[0], fl[0]
                if fl:
                    break
                f = (f - bk_mean[i]*area_i)*gain
                if f <= 0:
                    break
                df = f/f_prev
                if df_prev is not None and df > df_prev:
                    # Increasing growth, nearby source hit
                    f_tot = f_prev
                    break
                if df < 1 + apcorr_tol:
                    # Growth stopped to within the tolerance
                    f_tot = f
                    break
                f_prev, df_prev = f, df
            if f_tot is None:
                continue

            # Calculate fluxes for the chosen source for all unique aperture
            # sizes used for other sources
            fluxes_for_ap = {ai: f0}
            if not isscalar(a):
                for aj in set(a) - {ai}:
                    bj = aj*bi/ai
                    if aj == bj:
                        f, fl = sep.sum_circle(
                            img, [xi], [yi], aj, err=err, mask=mask, gain=gain,
                            subpix=0)[::2]
                        area_j = sep.sum_circle(
                            area_img, [xi], [yi], aj, mask=mask, subpix=0)[0][0]
                    else:
                        f, fl = sep.sum_ellipse(
                            img, [xi], [yi], aj, bj, thetai, 1, err=err,
                            mask=mask, gain=gain, subpix=0)[::2]
                        area_j = sep.sum_ellipse(
                            area_img, [xi], [yi], aj, bj, thetai, 1,
                            mask=mask, subpix=0)[0][0]
                    f, fl = f[0], fl[0]
                    f = (f - bk_mean[i]*area_j)*gain
                    if fl or f <= 0:
                        continue
                    fluxes_for_ap[aj] = f

            # Calculate aperture corrections
            for aj, f in fluxes_for_ap.items():
                if f < f_tot:
                    aper_corr[aj] = -2.5*log10(f_tot/f)

            break

    if 'flux' in sources.dtype.names:
        sources['flux'] = flux
    else:
        sources = append_fields(sources, 'flux', flux, usemask=False)

    if 'flux_err' in sources.dtype.names:
        sources['flux_err'] = flux_err
    else:
        sources = append_fields(sources, 'flux_err', flux_err, usemask=False)

    if 'flag' in sources.dtype.names:
        sources['flag'] |= flags
    else:
        sources = append_fields(sources, 'flag', flags, usemask=False)

    for name in ['mag', 'mag_err', 'aper_a', 'aper_b', 'aper_theta',
                 'aper_a_in', 'aper_a_out', 'aper_b_out', 'aper_theta_out',
                 'aper_area', 'background_area', 'background',
                 'background_rms']:
        if name not in sources.dtype.names:
            sources = append_fields(sources, name, z, usemask=False)

    good = (flux > 0).nonzero()
    if len(good[0]):
        sources['mag'][good] = -2.5*log10(flux[good]/texp)
        sources['mag_err'][good] = 2.5*log10(1 + flux_err[good]/flux[good])

    sources['aper_a'] = a
    sources['aper_b'] = b
    sources['aper_theta'] = theta

    if not have_background:
        sources['aper_a_in'] = a_in
        sources['aper_a_out'] = a_out
        sources['aper_b_out'] = b_out
        sources['aper_theta_out'] = theta_out

    sources['aper_area'] = area
    sources['background_area'] = bk_area
    sources['background'] = bk_mean
    sources['background_rms'] = bk_sigma

    # Apply aperture correction
    for i in good[0]:
        sources['mag'][i] += aper_corr.get(sources['aper_a'][i], 0)

    return sources