Beispiel #1
0
def _Generate_Profile(IMG, results, R, E, Ee, PA, PAe, options):

    # Create image array with background and mask applied
    try:
        if np.any(results['mask']):
            mask = results['mask']
        else:
            mask = None
    except:
        mask = None
    dat = IMG - results['background']
    zeropoint = options['ap_zeropoint'] if 'ap_zeropoint' in options else 22.5

    sb = []
    sbE = []
    cogdirect = []
    sbfix = []
    sbfixE = []

    count_neg = 0
    medflux = np.inf
    end_prof = None
    for i in range(len(R)):
        isobandwidth = R[i] * (options['ap_isoband_width']
                               if 'ap_isoband_width' in options else 0.025)
        if medflux > (results['background noise'] *
                      (options['ap_isoband_start'] if 'ap_isoband_start'
                       in options else 2)) or isobandwidth < 0.5:
            isovals = _iso_extract(
                dat,
                R[i],
                E[i],
                PA[i],
                results['center'],
                mask=mask,
                rad_interp=(options['ap_iso_interpolate_start']
                            if 'ap_iso_interpolate_start' in options else 5) *
                results['psf fwhm'],
                sigmaclip=options['ap_isoclip']
                if 'ap_isoclip' in options else False,
                sclip_iterations=options['ap_isoclip_iterations']
                if 'ap_isoclip_iterations' in options else 10,
                sclip_nsigma=options['ap_isoclip_nsigma']
                if 'ap_isoclip_nsigma' in options else 5)
            isovalsfix = _iso_extract(
                dat,
                R[i],
                results['init ellip'],
                results['init pa'],
                results['center'],
                mask=mask,
                rad_interp=(options['ap_iso_interpolate_start']
                            if 'ap_iso_interpolate_start' in options else 5) *
                results['psf fwhm'],
                sigmaclip=options['ap_isoclip']
                if 'ap_isoclip' in options else False,
                sclip_iterations=options['ap_isoclip_iterations']
                if 'ap_isoclip_iterations' in options else 10,
                sclip_nsigma=options['ap_isoclip_nsigma']
                if 'ap_isoclip_nsigma' in options else 5)
        else:
            isovals = _iso_between(
                dat,
                R[i] - isobandwidth,
                R[i] + isobandwidth,
                E[i],
                PA[i],
                results['center'],
                mask=mask,
                sigmaclip=options['ap_isoclip']
                if 'ap_isoclip' in options else False,
                sclip_iterations=options['ap_isoclip_iterations']
                if 'ap_isoclip_iterations' in options else 10,
                sclip_nsigma=options['ap_isoclip_nsigma']
                if 'ap_isoclip_nsigma' in options else 5)
            isovalsfix = _iso_between(
                dat,
                R[i] - isobandwidth,
                R[i] + isobandwidth,
                results['init ellip'],
                results['init pa'],
                results['center'],
                mask=mask,
                sigmaclip=options['ap_isoclip']
                if 'ap_isoclip' in options else False,
                sclip_iterations=options['ap_isoclip_iterations']
                if 'ap_isoclip_iterations' in options else 10,
                sclip_nsigma=options['ap_isoclip_nsigma']
                if 'ap_isoclip_nsigma' in options else 5)
        isotot = np.sum(
            _iso_between(dat,
                         0,
                         R[i],
                         E[i],
                         PA[i],
                         results['center'],
                         mask=mask))
        medflux = _average(
            isovals, options['ap_isoaverage_method']
            if 'ap_isoaverage_method' in options else 'median')
        scatflux = _scatter(
            isovals, options['ap_isoaverage_method']
            if 'ap_isoaverage_method' in options else 'median')
        medfluxfix = _average(
            isovalsfix, options['ap_isoaverage_method']
            if 'ap_isoaverage_method' in options else 'median')
        scatfluxfix = _scatter(
            isovalsfix, options['ap_isoaverage_method']
            if 'ap_isoaverage_method' in options else 'median')

        sb.append(
            flux_to_sb(medflux, options['ap_pixscale'], zeropoint
                       ) if medflux > 0 else 99.999)
        sbE.append((2.5 * scatflux / (np.sqrt(len(isovals)) * medflux *
                                      np.log(10))) if medflux > 0 else 99.999)
        sbfix.append(
            flux_to_sb(medfluxfix, options['ap_pixscale'], zeropoint
                       ) if medfluxfix > 0 else 99.999)
        sbfixE.append((2.5 * scatfluxfix /
                       (np.sqrt(len(isovalsfix)) * np.median(isovalsfix) *
                        np.log(10))) if medfluxfix > 0 else 99.999)
        cogdirect.append(
            flux_to_mag(isotot, zeropoint) if isotot > 0 else 99.999)
        if medflux <= 0:
            count_neg += 1
        if 'ap_truncate_evaluation' in options and options[
                'ap_truncate_evaluation'] and count_neg >= 2:
            end_prof = i + 1
            break

    # Compute Curve of Growth from SB profile
    cog, cogE = SBprof_to_COG_errorprop(R[:end_prof] * options['ap_pixscale'],
                                        np.array(sb),
                                        np.array(sbE),
                                        1. - E[:end_prof],
                                        Ee[:end_prof],
                                        N=100,
                                        method=0,
                                        symmetric_error=True)
    cogE[cog > 99] = 99.999
    cogfix, cogfixE = SBprof_to_COG_errorprop(R[:end_prof] *
                                              options['ap_pixscale'],
                                              np.array(sbfix),
                                              np.array(sbfixE),
                                              1. - E[:end_prof],
                                              Ee[:end_prof],
                                              N=100,
                                              method=0,
                                              symmetric_error=True)
    cogfixE[cogfix > 99] = 99.999

    # For each radius evaluation, write the profile parameters
    params = [
        'R', 'SB', 'SB_e', 'totmag', 'totmag_e', 'ellip', 'ellip_e', 'pa',
        'pa_e', 'totmag_direct', 'SB_fix', 'SB_fix_e', 'totmag_fix',
        'totmag_fix_e'
    ]

    SBprof_data = dict((h, None) for h in params)
    SBprof_units = {
        'R': 'arcsec',
        'SB': 'mag*arcsec^-2',
        'SB_e': 'mag*arcsec^-2',
        'totmag': 'mag',
        'totmag_e': 'mag',
        'ellip': 'unitless',
        'ellip_e': 'unitless',
        'pa': 'deg',
        'pa_e': 'deg',
        'totmag_direct': 'mag',
        'SB_fix': 'mag*arcsec^-2',
        'SB_fix_e': 'mag*arcsec^-2',
        'totmag_fix': 'mag',
        'totmag_fix_e': 'mag'
    }
    SBprof_format = {
        'R': '%.4f',
        'SB': '%.4f',
        'SB_e': '%.4f',
        'totmag': '%.4f',
        'totmag_e': '%.4f',
        'ellip': '%.3f',
        'ellip_e': '%.3f',
        'pa': '%.2f',
        'pa_e': '%.2f',
        'totmag_direct': '%.4f',
        'SB_fix': '%.4f',
        'SB_fix_e': '%.4f',
        'totmag_fix': '%.4f',
        'totmag_fix_e': '%.4f'
    }

    SBprof_data['R'] = list(R[:end_prof] * options['ap_pixscale'])
    SBprof_data['SB'] = list(sb)
    SBprof_data['SB_e'] = list(sbE)
    SBprof_data['totmag'] = list(cog)
    SBprof_data['totmag_e'] = list(cogE)
    SBprof_data['ellip'] = list(E[:end_prof])
    SBprof_data['ellip_e'] = list(Ee[:end_prof])
    SBprof_data['pa'] = list(PA[:end_prof] * 180 / np.pi)
    SBprof_data['pa_e'] = list(PAe[:end_prof] * 180 / np.pi)
    SBprof_data['totmag_direct'] = list(cogdirect)
    SBprof_data['SB_fix'] = list(sbfix)
    SBprof_data['SB_fix_e'] = list(sbfixE)
    SBprof_data['totmag_fix'] = list(cogfix)
    SBprof_data['totmag_fix_e'] = list(cogfixE)

    if 'ap_doplot' in options and options['ap_doplot']:
        CHOOSE = np.logical_and(
            np.array(SBprof_data['SB']) < 99,
            np.array(SBprof_data['SB_e']) < 1)
        errscale = 1.
        if np.all(np.array(SBprof_data['SB_e'])[CHOOSE] < 0.5):
            errscale = 1 / np.max(np.array(SBprof_data['SB_e'])[CHOOSE])
        lnlist = []
        lnlist.append(
            plt.errorbar(np.array(SBprof_data['R'])[CHOOSE],
                         np.array(SBprof_data['SB'])[CHOOSE],
                         yerr=errscale * np.array(SBprof_data['SB_e'])[CHOOSE],
                         elinewidth=1,
                         linewidth=0,
                         marker='.',
                         markersize=5,
                         color='r',
                         label='Surface Brightness (err$\\cdot$%.1f)' %
                         errscale))
        plt.errorbar(np.array(SBprof_data['R'])[np.logical_and(
            CHOOSE,
            np.arange(len(CHOOSE)) % 4 == 0)],
                     np.array(SBprof_data['SB'])[np.logical_and(
                         CHOOSE,
                         np.arange(len(CHOOSE)) % 4 == 0)],
                     yerr=np.array(SBprof_data['SB_e'])[np.logical_and(
                         CHOOSE,
                         np.arange(len(CHOOSE)) % 4 == 0)],
                     elinewidth=1,
                     linewidth=0,
                     marker='.',
                     markersize=5,
                     color='limegreen')
        # plt.errorbar(np.array(SBprof_data['R'])[CHOOSE], np.array(SBprof_data['totmag'])[CHOOSE], yerr = np.array(SBprof_data['totmag_e'])[CHOOSE],
        #              elinewidth = 1, linewidth = 0, marker = '.', markersize = 5, color = 'orange', label = 'Curve of Growth')
        plt.xlabel('Semi-Major-Axis [arcsec]', fontsize=16)
        plt.ylabel('Surface Brightness [mag arcsec$^{-2}$]', fontsize=16)
        bkgrdnoise = -2.5 * np.log10(
            results['background noise']) + zeropoint + 2.5 * np.log10(
                options['ap_pixscale']**2)
        lnlist.append(
            plt.axhline(
                bkgrdnoise,
                color='purple',
                linewidth=0.5,
                linestyle='--',
                label='1$\\sigma$ noise/pixel: %.1f mag arcsec$^{-2}$' %
                bkgrdnoise))
        plt.gca().invert_yaxis()
        plt.tick_params(labelsize=14)
        # ax2 = plt.gca().twinx()
        # lnlist += ax2.plot(np.array(SBprof_data['R'])[CHOOSE], np.array(SBprof_data['pa'])[CHOOSE]/180, color = 'b', label = 'PA/180')
        # lnlist += ax2.plot(np.array(SBprof_data['R'])[CHOOSE], np.array(SBprof_data['ellip'])[CHOOSE], color = 'orange', linestyle = '--', label = 'ellipticity')
        labs = [l.get_label() for l in lnlist]
        plt.legend(lnlist, labs, fontsize=11)
        # ax2.set_ylabel('Position Angle, Ellipticity', fontsize = 16)
        # ax2.tick_params(labelsize = 14)
        plt.tight_layout()
        if not ('ap_nologo' in options and options['ap_nologo']):
            AddLogo(plt.gcf())
        plt.savefig(
            '%sphotometry_%s.jpg' %
            (options['ap_plotpath'] if 'ap_plotpath' in options else '',
             options['ap_name']),
            dpi=options['ap_plotdpi'] if 'ap_plotdpi' in options else 300)
        plt.close()

        useR = np.array(SBprof_data['R'])[CHOOSE] / options['ap_pixscale']
        useE = np.array(SBprof_data['ellip'])[CHOOSE]
        usePA = np.array(SBprof_data['pa'])[CHOOSE]
        ranges = [[
            max(0, int(results['center']['x'] - useR[-1] * 1.2)),
            min(dat.shape[1], int(results['center']['x'] + useR[-1] * 1.2))
        ],
                  [
                      max(0, int(results['center']['y'] - useR[-1] * 1.2)),
                      min(dat.shape[0],
                          int(results['center']['y'] + useR[-1] * 1.2))
                  ]]
        LSBImage(dat[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]],
                 results['background noise'])
        for i in range(len(useR)):
            plt.gca().add_patch(
                Ellipse(
                    (results['center']['x'] - ranges[0][0],
                     results['center']['y'] - ranges[1][0]),
                    2 * useR[i],
                    2 * useR[i] * (1. - useE[i]),
                    usePA[i],
                    fill=False,
                    linewidth=((i + 1) / len(useR))**2,
                    color='limegreen' if (i % 4 == 0) else 'r',
                    linestyle='-' if useR[i] < results['fit R'][-1] else '--'))
        if not ('ap_nologo' in options and options['ap_nologo']):
            AddLogo(plt.gcf())
        plt.savefig(
            '%sphotometry_ellipse_%s.jpg' %
            (options['ap_plotpath'] if 'ap_plotpath' in options else '',
             options['ap_name']),
            dpi=options['ap_plotdpi'] if 'ap_plotdpi' in options else 300)
        plt.close()

    return {
        'prof header': params,
        'prof units': SBprof_units,
        'prof data': SBprof_data,
        'prof format': SBprof_format
    }
Beispiel #2
0
def Axial_Profiles(IMG, results, options):

    mask = results['mask'] if 'mask' in results else None
    pa = results['init pa'] + ((options['ap_axialprof_pa'] * np.pi /
                                180) if 'ap_axialprof_pa' in options else 0.)
    dat = IMG - results['background']
    zeropoint = options['ap_zeropoint'] if 'ap_zeropoint' in options else 22.5

    if 'prof data' in results:
        Rproflim = results['prof data']['R'][-1] / options['ap_pixscale']
    else:
        Rproflim = min(IMG.shape) / 2

    R = [0]
    while R[-1] < Rproflim:
        if 'ap_samplestyle' in options and options[
                'ap_samplestyle'] == 'linear':
            step = options[
                'ap_samplelinearscale'] if 'ap_samplelinearscale' in options else 0.5 * results[
                    'psf fwhm']
        else:
            step = R[-1] * (options['ap_samplegeometricscale']
                            if 'ap_samplegeometricscale' in options else 0.1)
        R.append(R[-1] + max(1, step))

    sb = {}
    sbE = {}
    for rd in [1, -1]:
        for ang in [1, -1]:
            key = (rd, ang)
            sb[key] = []
            sbE[key] = []
            branch_pa = (pa + ang * np.pi / 2) % (2 * np.pi)
            for pi, pR in enumerate(R):
                sb[key].append([])
                sbE[key].append([])
                width = (R[pi] - R[pi - 1]) if pi > 0 else 1.
                flux, XX = _iso_line(
                    dat, R[-1], width, branch_pa, {
                        'x':
                        results['center']['x'] +
                        ang * rd * pR * np.cos(pa + (0 if ang > 0 else np.pi)),
                        'y':
                        results['center']['y'] +
                        ang * rd * pR * np.sin(pa + (0 if ang > 0 else np.pi))
                    })
                for oi, oR in enumerate(R):
                    length = (R[oi] - R[oi - 1]) if oi > 0 else 1.
                    CHOOSE = np.logical_and(XX > (oR - length / 2), XX <
                                            (oR + length / 2))
                    if np.sum(CHOOSE) == 0:
                        sb[key][-1].append(99.999)
                        sbE[key][-1].append(99.999)
                        continue
                    medflux = _average(
                        flux[CHOOSE], options['ap_isoaverage_method']
                        if 'ap_isoaverage_method' in options else 'median')
                    scatflux = _scatter(
                        flux[CHOOSE], options['ap_isoaverage_method']
                        if 'ap_isoaverage_method' in options else 'median')
                    sb[key][-1].append(
                        flux_to_sb(medflux, options['ap_pixscale'], zeropoint
                                   ) if medflux > 0 else 99.999)
                    sbE[key][-1].append((
                        2.5 * scatflux /
                        (np.sqrt(np.sum(CHOOSE)) * medflux *
                         np.log(10))) if medflux > 0 else 99.999)

    with open(
            '%s%s_axial_profile_AP.prof' %
        ((options['ap_saveto'] if 'ap_saveto' in options else ''),
         options['ap_name']), 'w') as f:
        f.write('R')
        for rd in [1, -1]:
            for ang in [1, -1]:
                for pR in R:
                    f.write(',sb[%.3f:%s90],sbE[%.3f:%s90]' %
                            (rd * pR * options['ap_pixscale'],
                             '+' if ang > 0 else '-', rd * pR *
                             options['ap_pixscale'], '+' if ang > 0 else '-'))
        f.write('\n')
        f.write('arcsec')
        for rd in [1, -1]:
            for ang in [1, -1]:
                for pR in R:
                    f.write(',mag*arcsec^-2,mag*arcsec^-2')
        f.write('\n')
        for oi, oR in enumerate(R):
            f.write('%.4f' % (oR * options['ap_pixscale']))
            for rd in [1, -1]:
                for ang in [1, -1]:
                    key = (rd, ang)
                    for pi, pR in enumerate(R):
                        f.write(',%.4f,%.4f' %
                                (sb[key][pi][oi], sbE[key][pi][oi]))
            f.write('\n')

    if 'ap_doplot' in options and options['ap_doplot']:

        #colours = {(1,1): 'cool', (1,-1): 'summer', (-1,1): 'autumn', (-1,-1): 'winter'}
        count = 0
        for rd in [1, -1]:
            for ang in [1, -1]:
                key = (rd, ang)
                #cmap = matplotlib.cm.get_cmap('viridis_r')
                norm = matplotlib.colors.Normalize(vmin=0,
                                                   vmax=R[-1] *
                                                   options['ap_pixscale'])
                for pi, pR in enumerate(R):
                    if pi % 3 != 0:
                        continue
                    CHOOSE = np.logical_and(
                        np.array(sb[key][pi]) < 99,
                        np.array(sbE[key][pi]) < 1)
                    plt.errorbar(np.array(R)[CHOOSE] * options['ap_pixscale'],
                                 np.array(sb[key][pi])[CHOOSE],
                                 yerr=np.array(sbE[key][pi])[CHOOSE],
                                 elinewidth=1,
                                 linewidth=0,
                                 marker='.',
                                 markersize=3,
                                 color=autocmap.reversed()(norm(
                                     pR * options['ap_pixscale'])))
                plt.xlabel('%s-axis position on line [arcsec]' %
                           ('Major' if 'ap_axialprof_parallel' in options
                            and options['ap_axialprof_parallel'] else 'Minor'),
                           fontsize=16)
                plt.ylabel('Surface Brightness [mag arcsec$^{-2}$]',
                           fontsize=16)
                # cb1 = matplotlib.colorbar.ColorbarBase(plt.gca(), cmap=cmap,
                #                                        norm=norm)
                cb1 = plt.colorbar(
                    matplotlib.cm.ScalarMappable(norm=norm,
                                                 cmap=autocmap.reversed()))
                cb1.set_label(
                    '%s-axis position of line [arcsec]' %
                    ('Minor' if 'ap_axialprof_parallel' in options
                     and options['ap_axialprof_parallel'] else 'Major'),
                    fontsize=16)
                # plt.colorbar()
                bkgrdnoise = -2.5 * np.log10(
                    results['background noise']) + zeropoint + 2.5 * np.log10(
                        options['ap_pixscale']**2)
                plt.axhline(
                    bkgrdnoise,
                    color='purple',
                    linewidth=0.5,
                    linestyle='--',
                    label='1$\\sigma$ noise/pixel: %.1f mag arcsec$^{-2}$' %
                    bkgrdnoise)
                plt.gca().invert_yaxis()
                plt.legend(fontsize=15)
                plt.tick_params(labelsize=14)
                plt.title('%sR : pa%s90' %
                          ('+' if rd > 0 else '-', '+' if ang > 0 else '-'),
                          fontsize=15)
                plt.tight_layout()
                if not ('ap_nologo' in options and options['ap_nologo']):
                    AddLogo(plt.gcf())
                plt.savefig('%saxial_profile_q%i_%s.jpg' %
                            (options['ap_plotpath'] if 'ap_plotpath' in options
                             else '', count, options['ap_name']),
                            dpi=options['ap_plotdpi']
                            if 'ap_plotdpi' in options else 300)
                plt.close()
                count += 1

        CHOOSE = np.array(results['prof data']['SB_e']) < 0.2
        firstbad = np.argmax(np.logical_not(CHOOSE))
        if firstbad > 3:
            CHOOSE[firstbad:] = False
        outto = np.array(results['prof data']
                         ['R'])[CHOOSE][-1] * 1.5 / options['ap_pixscale']
        ranges = [[
            max(0, int(results['center']['x'] - outto - 2)),
            min(IMG.shape[1], int(results['center']['x'] + outto + 2))
        ],
                  [
                      max(0, int(results['center']['y'] - outto - 2)),
                      min(IMG.shape[0],
                          int(results['center']['y'] + outto + 2))
                  ]]
        LSBImage(dat[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]],
                 results['background noise'])
        count = 0
        cmap = matplotlib.cm.get_cmap('hsv')
        colorind = (np.linspace(0, 1 - 1 / 4, 4) + 0.1) % 1
        colours = list(cmap(c)
                       for c in colorind)  #['b', 'r', 'orange', 'limegreen']
        for rd in [1, -1]:
            for ang in [1, -1]:
                key = (rd, ang)
                branch_pa = (pa + ang * np.pi / 2) % (2 * np.pi)
                for pi, pR in enumerate(R):
                    if pi % 3 != 0:
                        continue
                    start = np.array([
                        results['center']['x'] +
                        ang * rd * pR * np.cos(pa + (0 if ang > 0 else np.pi)),
                        results['center']['y'] +
                        ang * rd * pR * np.sin(pa + (0 if ang > 0 else np.pi))
                    ])
                    end = start + R[-1] * np.array(
                        [np.cos(branch_pa),
                         np.sin(branch_pa)])
                    start -= np.array([ranges[0][0], ranges[1][0]])
                    end -= np.array([ranges[0][0], ranges[1][0]])
                    plt.plot(
                        [start[0], end[0]], [start[1], end[1]],
                        linewidth=0.5,
                        color=colours[count],
                        label=('%sR : pa%s90' %
                               ('+' if rd > 0 else '-',
                                '+' if ang > 0 else '-')) if pi == 0 else None)
                count += 1
        plt.legend()
        plt.xlim([0, ranges[0][1] - ranges[0][0]])
        plt.ylim([0, ranges[1][1] - ranges[1][0]])
        if not ('ap_nologo' in options and options['ap_nologo']):
            AddLogo(plt.gcf())
        plt.savefig(
            '%saxial_profile_lines_%s.jpg' %
            (options['ap_plotpath'] if 'ap_plotpath' in options else '',
             options['ap_name']),
            dpi=options['ap_plotdpi'] if 'ap_plotdpi' in options else 300)
        plt.close()

    return IMG, {}
Beispiel #3
0
def Center_Peak(IMG, results, options):

    current_center = {"x": IMG.shape[1] / 2, "y": IMG.shape[0] / 2}
    dat = IMG - results["background"]
    if "ap_guess_center" in options:
        current_center = deepcopy(options["ap_guess_center"])
        logging.info("%s: Center initialized by user: %s" %
                     (options["ap_name"], str(current_center)))
    if "ap_set_center" in options:
        logging.info("%s: Center set by user: %s" %
                     (options["ap_name"], str(options["ap_set_center"])))
        sb0 = flux_to_sb(
            _iso_extract(dat, 0.0, {
                "ellip": 0.0,
                "pa": 0.0
            }, options["ap_set_center"])[0],
            options["ap_pixscale"],
            options["ap_zeropoint"] if "zeropoint" in options else 22.5,
        )
        return IMG, {
            "center":
            deepcopy(options["ap_set_center"]),
            "auxfile central sb":
            "central surface brightness: %.4f mag arcsec^-2" % sb0,
        }

    searchring = int(
        (options["ap_centeringring"] if "ap_centeringring" in options else 10)
        * results["psf fwhm"])
    xx, yy = np.meshgrid(np.arange(searchring), np.arange(searchring))
    xx = xx.flatten()
    yy = yy.flatten()
    A = np.array([
        np.ones(xx.shape),
        xx,
        yy,
        xx**2,
        yy**2,
        xx * yy,
        xx * yy**2,
        yy * xx**2,
        xx**2 * yy**2,
    ]).T
    ranges = [
        [
            max(0, int(current_center["x"] - searchring / 2)),
            min(IMG.shape[1], int(current_center["x"] + searchring / 2)),
        ],
        [
            max(0, int(current_center["y"] - searchring / 2)),
            min(IMG.shape[0], int(current_center["y"] + searchring / 2)),
        ],
    ]
    chunk = np.clip(
        dat[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]].T,
        a_min=results["background noise"] / 5,
        a_max=None,
    )

    poly2dfit = np.linalg.lstsq(A, np.log10(chunk.flatten()), rcond=None)
    current_center = {
        "x": -poly2dfit[0][2] / (2 * poly2dfit[0][4]) + ranges[0][0],
        "y": -poly2dfit[0][1] / (2 * poly2dfit[0][3]) + ranges[1][0],
    }

    sb0 = flux_to_sb(
        _iso_extract(dat, 0.0, {
            "ellip": 0.0,
            "pa": 0.0
        }, current_center)[0],
        options["ap_pixscale"],
        options["ap_zeropoint"] if "zeropoint" in options else 22.5,
    )
    return IMG, {
        "center":
        current_center,
        "auxfile center":
        "center x: %.2f pix, y: %.2f pix" %
        (current_center["x"], current_center["y"]),
        "auxfile central sb":
        "central surface brightness: %.4f mag arcsec^-2" % sb0,
    }
Beispiel #4
0
def Center_HillClimb(IMG, results, options):
    """Follow locally increasing brightness (robust to PSF size objects) to find peak.

    Using 10 circular isophotes out to 10 times the PSF length, the first FFT coefficient
    phases are averaged to find the direction of increasing flux. Flux values are sampled
    along this direction and a quadratic fit gives the maximum. This is iteratively
    repeated until the step size becomes very small.

    Parameters
    -----------------
    ap_guess_center : dict, default None
      user provided starting point for center fitting. Center should
      be formatted as:

      .. code-block:: python

        {'x':float, 'y': float}

      , where the floats are the center coordinates in pixels. If not
      given, Autoprof will default to a guess of the image center.

    ap_set_center : dict, default None
      user provided fixed center for rest of calculations. Center
      should be formatted as:

      .. code-block:: python

        {'x':float, 'y': float}

      , where the floats are the center coordinates in pixels. If not
      given, Autoprof will default to a guess of the image center.

    ap_centeringring : int, default 10
      Size of ring to use when finding galaxy center, in units of
      PSF. Larger rings will be robust to features (i.e., foreground
      stars), while smaller rings may be needed for small galaxies.

    Notes
    ----------
    :References:
    - 'background'
    - 'background noise'
    - 'psf fwhm'

    Returns
    -------
    IMG : ndarray
      Unaltered galaxy image

    results : dict
      .. code-block:: python

        {'center': {'x': , # x coordinate of the center (pix)
                    'y': }, # y coordinate of the center (pix)

         'auxfile center': # optional, message for aux file to record galaxy center (string)
         'auxfile centeral sb': # optional, central surface brightness value (float)

        }

    """

    current_center = {"x": IMG.shape[1] / 2, "y": IMG.shape[0] / 2}
    dat = IMG - results["background"]
    if "ap_guess_center" in options:
        current_center = deepcopy(options["ap_guess_center"])
        logging.info("%s: Center initialized by user: %s" %
                     (options["ap_name"], str(current_center)))
    if "ap_set_center" in options:
        logging.info("%s: Center set by user: %s" %
                     (options["ap_name"], str(options["ap_set_center"])))
        sb0 = flux_to_sb(
            _iso_extract(dat, 0.0, {
                "ellip": 0.0,
                "pa": 0.0
            }, options["ap_set_center"])[0],
            options["ap_pixscale"],
            options["ap_zeropoint"] if "zeropoint" in options else 22.5,
        )
        return IMG, {
            "center":
            deepcopy(options["ap_set_center"]),
            "auxfile central sb":
            "central surface brightness: %.4f mag arcsec^-2" % sb0,
        }

    searchring = (int(options["ap_centeringring"])
                  if "ap_centeringring" in options else 10)
    sampleradii = np.linspace(1, searchring, searchring) * results["psf fwhm"]

    track_centers = []
    small_update_count = 0
    total_count = 0
    while small_update_count <= 5 and total_count <= 100:
        total_count += 1
        phases = []
        isovals = []
        coefs = []
        for r in sampleradii:
            isovals.append(
                _iso_extract(dat,
                             r, {
                                 "ellip": 0.0,
                                 "pa": 0.0
                             },
                             current_center,
                             more=True))
            coefs.append(
                fft(
                    np.clip(
                        isovals[-1][0],
                        a_max=np.quantile(isovals[-1][0], 0.85),
                        a_min=None,
                    )))
            phases.append((-np.angle(coefs[-1][1])) % (2 * np.pi))
        direction = Angle_Median(phases) % (2 * np.pi)
        levels = []
        level_locs = []
        for i, r in enumerate(sampleradii):
            floc = np.argmin(np.abs((isovals[i][1] % (2 * np.pi)) - direction))
            rloc = np.argmin(
                np.abs((isovals[i][1] % (2 * np.pi)) - ((direction + np.pi) %
                                                        (2 * np.pi))))
            smooth = np.abs(
                ifft(coefs[i][:min(10, len(coefs[i]))], n=len(coefs[i])))
            levels.append(smooth[floc])
            level_locs.append(r)
            levels.insert(0, smooth[rloc])
            level_locs.insert(0, -r)
        try:
            p = np.polyfit(level_locs, levels, deg=2)
            if p[0] < 0 and len(levels) > 3:
                dist = np.clip(-p[1] / (2 * p[0]),
                               a_min=min(level_locs),
                               a_max=max(level_locs))
            else:
                dist = level_locs[np.argmax(levels)]
        except:
            dist = results["psf fwhm"]
        current_center["x"] += dist * np.cos(direction)
        current_center["y"] += dist * np.sin(direction)
        if abs(dist) < (0.5 * results["psf fwhm"]):
            small_update_count += 1
        else:
            small_update_count = 0
        track_centers.append([current_center["x"], current_center["y"]])

    # refine center
    ranges = [
        [
            max(0, int(current_center["x"] - results["psf fwhm"] * 5)),
            min(dat.shape[1],
                int(current_center["x"] + results["psf fwhm"] * 5)),
        ],
        [
            max(0, int(current_center["y"] - results["psf fwhm"] * 5)),
            min(dat.shape[0],
                int(current_center["y"] + results["psf fwhm"] * 5)),
        ],
    ]

    res = minimize(
        _hillclimb_loss,
        x0=[
            current_center["x"] - ranges[0][0],
            current_center["y"] - ranges[1][0]
        ],
        args=(
            dat[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]],
            results["psf fwhm"],
            results["background noise"],
        ),
        method="Nelder-Mead",
    )
    if res.success:
        current_center["x"] = res.x[0] + ranges[0][0]
        current_center["y"] = res.x[1] + ranges[1][0]
    track_centers.append([current_center["x"], current_center["y"]])

    sb0 = flux_to_sb(
        _iso_extract(dat, 0.0, {
            "ellip": 0.0,
            "pa": 0.0
        }, current_center)[0],
        options["ap_pixscale"],
        options["ap_zeropoint"] if "zeropoint" in options else 22.5,
    )
    return IMG, {
        "center":
        current_center,
        "auxfile center":
        "center x: %.2f pix, y: %.2f pix" %
        (current_center["x"], current_center["y"]),
        "auxfile central sb":
        "central surface brightness: %.4f mag arcsec^-2" % sb0,
    }
Beispiel #5
0
def Center_Forced(IMG, results, options):
    """Extracts previously fit center coordinates.

    Extracts the center coordinates from an aux file for a previous
    AutoProf fit. Can instead simply be given a set center value, just
    like other centering methods. A given center will override teh
    fitted aux file center.

    Parameters
    -----------------
    ap_guess_center : dict, default None
      user provided starting point for center fitting. Center should
      be formatted as:

      .. code-block:: python

        {'x':float, 'y': float}

      , where the floats are the center coordinates in pixels. If not
      given, Autoprof will default to a guess of the image center.

    ap_set_center : dict, default None
      user provided fixed center for rest of calculations. Center
      should be formatted as:

      .. code-block:: python

        {'x':float, 'y': float}

      , where the floats are the center coordinates in pixels. If not
      given, Autoprof will default to a guess of the image center.

    ap_forcing_profile : string, default None
      (required for forced photometry) file path to .prof file
      providing forced photometry PA and ellip values to apply to
      *ap_image_file*.

    Notes
    ----------
    :References:
    - 'background'
    - 'background noise'

    Returns
    -------
    IMG : ndarray
      Unaltered galaxy image

    results : dict
      .. code-block:: python

        {'center': {'x': , # x coordinate of the center (pix)
                    'y': }, # y coordinate of the center (pix)

         'auxfile center': # optional, message for aux file to record galaxy center (string)
         'auxfile centeral sb': # optional, central surface brightness value (float)

        }

    """
    current_center = {"x": IMG.shape[1] / 2, "y": IMG.shape[0] / 2}
    if "ap_guess_center" in options:
        current_center = deepcopy(options["ap_guess_center"])
        logging.info("%s: Center initialized by user: %s" %
                     (options["ap_name"], str(current_center)))
    if "ap_set_center" in options:
        logging.info("%s: Center set by user: %s" %
                     (options["ap_name"], str(options["ap_set_center"])))
        sb0 = flux_to_sb(
            _iso_extract(
                IMG - results["background"],
                0.0,
                {
                    "ellip": 0.0,
                    "pa": 0.0
                },
                options["ap_set_center"],
            )[0],
            options["ap_pixscale"],
            options["ap_zeropoint"] if "zeropoint" in options else 22.5,
        )
        return IMG, {
            "center":
            deepcopy(options["ap_set_center"]),
            "auxfile central sb":
            "central surface brightness: %.4f mag arcsec^-2" % sb0,
        }

    try:
        with open(options["ap_forcing_profile"][:-4] + "aux", "r") as f:
            for line in f.readlines():
                if line[:6] == "center":
                    x_loc = line.find("x:")
                    y_loc = line.find("y:")
                    current_center = {
                        "x": float(line[x_loc + 3:line.find("pix")]),
                        "y": float(line[y_loc + 3:line.rfind("pix")]),
                    }
                    break
            else:
                logging.warning(
                    "%s: Forced center failed! Using image center (or guess)."
                    % options["ap_name"])
    except:
        logging.warning(
            "%s: Forced center failed! Using image center (or guess)." %
            options["ap_name"])
    sb0 = flux_to_sb(
        _iso_extract(IMG - results["background"], 0.0, {
            "ellip": 0.0,
            "pa": 0.0
        }, current_center)[0],
        options["ap_pixscale"],
        options["ap_zeropoint"] if "zeropoint" in options else 22.5,
    )
    return IMG, {
        "center":
        current_center,
        "auxfile center":
        "center x: %.2f pix, y: %.2f pix" %
        (current_center["x"], current_center["y"]),
        "auxfile central sb":
        "central surface brightness: %.4f mag arcsec^-2" % sb0,
    }
Beispiel #6
0
def Center_OfMass(IMG, results, options):
    """Find the light weighted galaxy center.

    Iteratively computes the light weighted centroid within a window,
    moves to the new center and computes the light weighted centroid
    again.  The size of the search area is 10PSF by default. The
    iterative process will continue until the center is updated by
    less than 1/10th of the PSF size or when too mny iterations have
    been reached.

    Parameters
    -----------------
    ap_guess_center : dict, default None
      user provided starting point for center fitting. Center should
      be formatted as:

      .. code-block:: python

        {'x':float, 'y': float}

      , where the floats are the center coordinates in pixels. If not
      given, Autoprof will default to a guess of the image center.

    ap_set_center : dict, default None
      user provided fixed center for rest of calculations. Center
      should be formatted as:

      .. code-block:: python

        {'x':float, 'y': float}

      , where the floats are the center coordinates in pixels. If not
      given, Autoprof will default to a guess of the image center.

    ap_centeringring : int, default 10
      Size of ring to use when finding galaxy center, in units of
      PSF. Larger rings will allow for the starting position to be
      further from the true galaxy center.  Smaller rings will include
      fewer spurious objects, and can stop the centroid from being
      distracted by larger nearby objects/galaxies.

    Notes
    ----------
    :References:
    - 'background'
    - 'psf fwhm'

    Returns
    -------
    IMG : ndarray
      Unaltered galaxy image

    results : dict
      .. code-block:: python

        {'center': {'x': , # x coordinate of the center (pix)
                    'y': }, # y coordinate of the center (pix)

         'auxfile center': # optional, message for aux file to record galaxy center (string)
         'auxfile centeral sb': # optional, central surface brightness value (float)

        }

    """

    current_center = {"x": IMG.shape[1] / 2, "y": IMG.shape[0] / 2}
    dat = IMG - results["background"]
    if "ap_guess_center" in options:
        current_center = deepcopy(options["ap_guess_center"])
        logging.info("%s: Center initialized by user: %s" %
                     (options["ap_name"], str(current_center)))
    if "ap_set_center" in options:
        logging.info("%s: Center set by user: %s" %
                     (options["ap_name"], str(options["ap_set_center"])))
        sb0 = flux_to_sb(
            _iso_extract(dat, 0.0, {
                "ellip": 0.0,
                "pa": 0.0
            }, options["ap_set_center"])[0],
            options["ap_pixscale"],
            options["ap_zeropoint"] if "zeropoint" in options else 22.5,
        )
        return IMG, {
            "center":
            deepcopy(options["ap_set_center"]),
            "auxfile central sb":
            "central surface brightness: %.4f mag arcsec^-2" % sb0,
        }

    searchring = int(
        (options["ap_centeringring"] if "ap_centeringring" in options else 10)
        * results["psf fwhm"])
    xx, yy = np.meshgrid(np.arange(searchring), np.arange(searchring))
    N_updates = 0
    while N_updates < 100:
        N_updates += 1
        old_center = deepcopy(current_center)
        ranges = [
            [
                max(0, int(current_center["x"] - searchring / 2)),
                min(IMG.shape[1], int(current_center["x"] + searchring / 2)),
            ],
            [
                max(0, int(current_center["y"] - searchring / 2)),
                min(IMG.shape[0], int(current_center["y"] + searchring / 2)),
            ],
        ]
        current_center = {
            "x":
            ranges[0][0] + np.sum(
                (dat[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]] *
                 xx)) /
            np.sum(dat[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]]),
            "y":
            ranges[1][0] + np.sum(
                (dat[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]] *
                 yy)) /
            np.sum(dat[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]]),
        }
        if (abs(current_center["x"] - old_center["x"]) <
                0.1 * results["psf fwhm"]
                and abs(current_center["y"] - old_center["y"]) <
                0.1 * results["psf fwhm"]):
            break

    sb0 = flux_to_sb(
        _iso_extract(dat, 0.0, {
            "ellip": 0.0,
            "pa": 0.0
        }, current_center)[0],
        options["ap_pixscale"],
        options["ap_zeropoint"] if "zeropoint" in options else 22.5,
    )
    return IMG, {
        "center":
        current_center,
        "auxfile center":
        "center x: %.2f pix, y: %.2f pix" %
        (current_center["x"], current_center["y"]),
        "auxfile central sb":
        "central surface brightness: %.4f mag arcsec^-2" % sb0,
    }
Beispiel #7
0
def Isophote_Extract_Photutils(IMG, results, options):
    """Wrapper of photutils method for extracting SB profiles.

    This simply gives users access to the photutils isophote
    extraction methods. The one exception is that SB values are taken
    as the median instead of the mean, as recomended in the photutils
    documentation. See: `photutils
    <https://photutils.readthedocs.io/en/stable/isophote.html>`_ for
    more information.

    Parameters
    ----------
    ap_zeropoint : float, default 22.5
      Photometric zero point. For converting flux to mag units.

    ap_fluxunits : str, default "mag"
      units for outputted photometry. Can either be "mag" for log
      units, or "intensity" for linear units.
    
    ap_plot_sbprof_ylim : tuple, default None
      Tuple with axes limits for the y-axis in the SB profile
      diagnostic plot. Be careful when using intensity units
      since this will change the ideal axis limits.
    
    ap_plot_sbprof_xlim : tuple, default None
      Tuple with axes limits for the x-axis in the SB profile
      diagnostic plot.
    
    ap_plot_sbprof_set_errscale : float, default None
      Float value by which to scale errorbars on the SB profile
      this makes them more visible in cases where the statistical
      errors are very small.
    
    Notes
    ----------
    :References:
    - 'background'
    - 'background noise'
    - 'psf fwhm'
    - 'center'
    - 'init R' (optional)
    - 'init ellip' (optional)
    - 'init pa' (optional)
    - 'fit R' (optional)
    - 'fit ellip' (optional)
    - 'fit pa' (optional)
    - 'fit photutils isolist' (optional)

    Returns
    -------
    IMG : ndarray
      Unaltered galaxy image

    results : dict
      .. code-block:: python

        {'prof header': , # List object with strings giving the items in the header of the final SB profile (list)
         'prof units': , # dict object that links header strings to units (given as strings) for each variable (dict)
         'prof data': # dict object linking header strings to list objects containing the rows for a given variable (dict)

        }

    """

    zeropoint = options["ap_zeropoint"] if "ap_zeropoint" in options else 22.5
    fluxunits = options["ap_fluxunits"] if "ap_fluxunits" in options else "mag"

    if fluxunits == "intensity":
        params = [
            "R",
            "I",
            "I_e",
            "totflux",
            "totflux_e",
            "ellip",
            "ellip_e",
            "pa",
            "pa_e",
            "a3",
            "a3_e",
            "b3",
            "b3_e",
            "a4",
            "a4_e",
            "b4",
            "b4_e",
        ]
        SBprof_units = {
            "R": "arcsec",
            "I": "flux*arcsec^-2",
            "I_e": "flux*arcsec^-2",
            "totflux": "flux",
            "totflux_e": "flux",
            "ellip": "unitless",
            "ellip_e": "unitless",
            "pa": "deg",
            "pa_e": "deg",
            "a3": "unitless",
            "a3_e": "unitless",
            "b3": "unitless",
            "b3_e": "unitless",
            "a4": "unitless",
            "a4_e": "unitless",
            "b4": "unitless",
            "b4_e": "unitless",
        }
    else:
        params = [
            "R",
            "SB",
            "SB_e",
            "totmag",
            "totmag_e",
            "ellip",
            "ellip_e",
            "pa",
            "pa_e",
            "a3",
            "a3_e",
            "b3",
            "b3_e",
            "a4",
            "a4_e",
            "b4",
            "b4_e",
        ]
        SBprof_units = {
            "R": "arcsec",
            "SB": "mag*arcsec^-2",
            "SB_e": "mag*arcsec^-2",
            "totmag": "mag",
            "totmag_e": "mag",
            "ellip": "unitless",
            "ellip_e": "unitless",
            "pa": "deg",
            "pa_e": "deg",
            "a3": "unitless",
            "a3_e": "unitless",
            "b3": "unitless",
            "b3_e": "unitless",
            "a4": "unitless",
            "a4_e": "unitless",
            "b4": "unitless",
            "b4_e": "unitless",
        }
    SBprof_data = dict((h, []) for h in params)
    res = {}
    dat = IMG - results["background"]
    if not "fit R" in results and not "fit photutils isolist" in results:
        logging.info(
            "%s: photutils fitting and extracting image data" % options["ap_name"]
        )
        geo = EllipseGeometry(
            x0=results["center"]["x"],
            y0=results["center"]["y"],
            sma=results["init R"] / 2,
            eps=results["init ellip"],
            pa=results["init pa"],
        )
        ellipse = Photutils_Ellipse(dat, geometry=geo)

        isolist = ellipse.fit_image(fix_center=True, linear=False)
        res.update(
            {
                "fit photutils isolist": isolist,
                "auxfile fitlimit": "fit limit semi-major axis: %.2f pix"
                % isolist.sma[-1],
            }
        )
    elif not "fit photutils isolist" in results:
        logging.info("%s: photutils extracting image data" % options["ap_name"])
        list_iso = []
        for i in range(len(results["fit R"])):
            if results["fit R"][i] <= 0:
                continue
            # Container for ellipse geometry
            geo = EllipseGeometry(
                sma=results["fit R"][i],
                x0=results["center"]["x"],
                y0=results["center"]["y"],
                eps=results["fit ellip"][i],
                pa=results["fit pa"][i],
            )
            # Extract the isophote information
            ES = EllipseSample(dat, sma=results["fit R"][i], geometry=geo)
            ES.update(fixed_parameters=None)
            list_iso.append(Isophote(ES, niter=30, valid=True, stop_code=0))

        isolist = IsophoteList(list_iso)
        res.update(
            {
                "fit photutils isolist": isolist,
                "auxfile fitlimit": "fit limit semi-major axis: %.2f pix"
                % isolist.sma[-1],
            }
        )
    else:
        isolist = results["fit photutils isolist"]

    for i in range(len(isolist.sma)):
        SBprof_data["R"].append(isolist.sma[i] * options["ap_pixscale"])
        if fluxunits == "intensity":
            SBprof_data["I"].append(
                np.median(isolist.sample[i].values[2]) / options["ap_pixscale"] ** 2
            )
            SBprof_data["I_e"].append(isolist.int_err[i])
            SBprof_data["totflux"].append(isolist.tflux_e[i])
            SBprof_data["totflux_e"].append(isolist.rms[i] / np.sqrt(isolist.npix_e[i]))
        else:
            SBprof_data["SB"].append(
                flux_to_sb(
                    np.median(isolist.sample[i].values[2]),
                    options["ap_pixscale"],
                    zeropoint,
                )
            )
            SBprof_data["SB_e"].append(
                2.5 * isolist.int_err[i] / (isolist.intens[i] * np.log(10))
            )
            SBprof_data["totmag"].append(flux_to_mag(isolist.tflux_e[i], zeropoint))
            SBprof_data["totmag_e"].append(
                2.5
                * isolist.rms[i]
                / (np.sqrt(isolist.npix_e[i]) * isolist.tflux_e[i] * np.log(10))
            )
        SBprof_data["ellip"].append(isolist.eps[i])
        SBprof_data["ellip_e"].append(isolist.ellip_err[i])
        SBprof_data["pa"].append(isolist.pa[i] * 180 / np.pi)
        SBprof_data["pa_e"].append(isolist.pa_err[i] * 180 / np.pi)
        SBprof_data["a3"].append(isolist.a3[i])
        SBprof_data["a3_e"].append(isolist.a3_err[i])
        SBprof_data["b3"].append(isolist.b3[i])
        SBprof_data["b3_e"].append(isolist.b3_err[i])
        SBprof_data["a4"].append(isolist.a4[i])
        SBprof_data["a4_e"].append(isolist.a4_err[i])
        SBprof_data["b4"].append(isolist.b4[i])
        SBprof_data["b4_e"].append(isolist.b4_err[i])
        for k in SBprof_data.keys():
            if not np.isfinite(SBprof_data[k][-1]):
                SBprof_data[k][-1] = 99.999
    res.update(
        {"prof header": params, "prof units": SBprof_units, "prof data": SBprof_data}
    )

    if "ap_doplot" in options and options["ap_doplot"]:
        if fluxunits == "intensity":
            Plot_I_Profile(
                dat,
                np.array(SBprof_data["R"]),
                np.array(SBprof_data["I"]),
                np.array(SBprof_data["I_e"]),
                np.array(SBprof_data["ellip"]),
                np.array(SBprof_data["pa"]),
                results,
                options,
            )
        else:
            Plot_SB_Profile(
                dat,
                np.array(SBprof_data["R"]),
                np.array(SBprof_data["SB"]),
                np.array(SBprof_data["SB_e"]),
                np.array(SBprof_data["ellip"]),
                np.array(SBprof_data["pa"]),
                results,
                options,
            )

    return IMG, res
Beispiel #8
0
def Radial_Profiles(IMG, results, options):

    mask = results['mask'] if 'mask' in results else None
    nwedges = options[
        'ap_radialprofiles_nwedges'] if 'ap_radialprofiles_nwedges' in options else 4
    wedgeangles = np.linspace(0, 2 * np.pi * (1 - 1. / nwedges), nwedges)

    zeropoint = options['ap_zeropoint'] if 'ap_zeropoint' in options else 22.5

    R = np.array(results['prof data']['R']) / options['ap_pixscale']
    SBE = np.array(results['prof data']['SB_e'])
    if 'ap_radialprofiles_variable_pa' in options and options[
            'ap_radialprofiles_variable_pa']:
        pa = np.array(results['prof data']['pa']) * np.pi / 180
    else:
        pa = np.ones(len(R)) * (
            (options['ap_radialprofiles_pa'] * np.pi /
             180) if 'ap_radialprofiles_pa' in options else results['init pa'])
    dat = IMG - results['background']

    maxwedgewidth = options[
        'ap_radialprofiles_width'] if 'ap_radialprofiles_width' in options else 15.
    maxwedgewidth *= np.pi / 180
    if 'ap_radialprofiles_expwidth' in options and options[
            'ap_radialprofiles_expwidth']:
        wedgewidth = maxwedgewidth * np.exp(R / R[-1] - 1)
    else:
        wedgewidth = np.ones(len(R)) * maxwedgewidth

    if wedgewidth[-1] * nwedges > 2 * np.pi:
        logging.warning(
            '%s: Radial sampling wedges are overlapping! %i wedges with a maximum width of %.3f rad'
            % (nwedges, wedgewidth[-1]))

    sb = list([] for i in wedgeangles)
    sbE = list([] for i in wedgeangles)

    for i in range(len(R)):
        if R[i] < 100:
            isovals = list(
                _iso_extract(dat,
                             R[i],
                             0,
                             0,
                             results['center'],
                             more=True,
                             minN=int(5 * 2 * np.pi / wedgewidth[i]),
                             mask=mask))
        else:
            isobandwidth = R[i] * (options['ap_isoband_width']
                                   if 'ap_isoband_width' in options else 0.025)
            isovals = list(
                _iso_between(dat,
                             R[i] - isobandwidth,
                             R[i] + isobandwidth,
                             0,
                             0,
                             results['center'],
                             more=True,
                             mask=mask))
        isovals[1] -= pa[i]

        for sa_i in range(len(wedgeangles)):
            aselect = np.abs(Angle_TwoAngles(wedgeangles[sa_i],
                                             isovals[1])) < (wedgewidth[i] / 2)
            if np.sum(aselect) == 0:
                sb[sa_i].append(99.999)
                sbE[sa_i].append(99.999)
                continue
            medflux = _average(
                isovals[0][aselect], options['ap_isoaverage_method']
                if 'ap_isoaverage_method' in options else 'median')
            scatflux = _scatter(
                isovals[0][aselect], options['ap_isoaverage_method']
                if 'ap_isoaverage_method' in options else 'median')
            sb[sa_i].append(
                flux_to_sb(medflux, options['ap_pixscale'], zeropoint
                           ) if medflux > 0 else 99.999)
            sbE[sa_i].append((2.5 * scatflux /
                              (np.sqrt(np.sum(aselect)) * medflux *
                               np.log(10))) if medflux > 0 else 99.999)

    newprofheader = results['prof header']
    newprofunits = results['prof units']
    newprofformat = results['prof format']
    newprofdata = results['prof data']
    for sa_i in range(len(wedgeangles)):
        p1, p2 = ('SB_rad[%.1f]' % (wedgeangles[sa_i] * 180 / np.pi),
                  'SB_rad_e[%.1f]' % (wedgeangles[sa_i] * 180 / np.pi))
        newprofheader.append(p1)
        newprofheader.append(p2)
        newprofunits[p1] = 'mag*arcsec^-2'
        newprofunits[p2] = 'mag*arcsec^-2'
        newprofformat[p1] = '%.4f'
        newprofformat[p2] = '%.4f'
        newprofdata[p1] = sb[sa_i]
        newprofdata[p2] = sbE[sa_i]

    if 'ap_doplot' in options and options['ap_doplot']:
        CHOOSE = SBE < 0.2
        firstbad = np.argmax(np.logical_not(CHOOSE))
        if firstbad > 3:
            CHOOSE[firstbad:] = False
        ranges = [
            [
                max(0, int(results['center']['x'] - 1.5 * R[CHOOSE][-1] - 2)),
                min(IMG.shape[1],
                    int(results['center']['x'] + 1.5 * R[CHOOSE][-1] + 2))
            ],
            [
                max(0, int(results['center']['y'] - 1.5 * R[CHOOSE][-1] - 2)),
                min(IMG.shape[0],
                    int(results['center']['y'] + 1.5 * R[CHOOSE][-1] + 2))
            ]
        ]
        # cmap = matplotlib.cm.get_cmap('tab10' if nwedges <= 10 else 'viridis')
        # colorind = np.arange(nwedges)/10
        cmap = matplotlib.cm.get_cmap('hsv')
        colorind = (np.linspace(0, 1 - 1 / nwedges, nwedges) + 0.1) % 1
        for sa_i in range(len(wedgeangles)):
            CHOOSE = np.logical_and(
                np.array(sb[sa_i]) < 99,
                np.array(sbE[sa_i]) < 1)
            plt.errorbar(np.array(R)[CHOOSE] * options['ap_pixscale'],
                         np.array(sb[sa_i])[CHOOSE],
                         yerr=np.array(sbE[sa_i])[CHOOSE],
                         elinewidth=1,
                         linewidth=0,
                         marker='.',
                         markersize=5,
                         color=cmap(colorind[sa_i]),
                         label='Wedge %.2f' %
                         (wedgeangles[sa_i] * 180 / np.pi))
        plt.xlabel('Radius [arcsec]', fontsize=16)
        plt.ylabel('Surface Brightness [mag arcsec$^{-2}$]', fontsize=16)
        bkgrdnoise = -2.5 * np.log10(
            results['background noise']) + zeropoint + 2.5 * np.log10(
                options['ap_pixscale']**2)
        plt.axhline(bkgrdnoise,
                    color='purple',
                    linewidth=0.5,
                    linestyle='--',
                    label='1$\\sigma$ noise/pixel:\n%.1f mag arcsec$^{-2}$' %
                    bkgrdnoise)
        plt.gca().invert_yaxis()
        plt.legend(fontsize=15)
        plt.tick_params(labelsize=14)
        plt.tight_layout()
        if not ('ap_nologo' in options and options['ap_nologo']):
            AddLogo(plt.gcf())
        plt.savefig(
            '%sradial_profiles_%s.jpg' %
            (options['ap_plotpath'] if 'ap_plotpath' in options else '',
             options['ap_name']),
            dpi=options['ap_plotdpi'] if 'ap_plotdpi' in options else 300)
        plt.close()

        LSBImage(dat[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]],
                 results['background noise'])

        # plt.imshow(np.clip(dat[ranges[1][0]: ranges[1][1], ranges[0][0]: ranges[0][1]],
        #                    a_min = 0,a_max = None), origin = 'lower', cmap = 'Greys_r', norm = ImageNormalize(stretch=LogStretch()))
        cx, cy = (results['center']['x'] - ranges[0][0],
                  results['center']['y'] - ranges[1][0])
        for sa_i in range(len(wedgeangles)):
            endx, endy = (R * np.cos(wedgeangles[sa_i] + pa),
                          R * np.sin(wedgeangles[sa_i] + pa))
            plt.plot(endx + cx, endy + cy, color='w', linewidth=1.1)
            plt.plot(endx + cx,
                     endy + cy,
                     color=cmap(colorind[sa_i]),
                     linewidth=0.7)
            endx, endy = (R * np.cos(wedgeangles[sa_i] + pa + wedgewidth / 2),
                          R * np.sin(wedgeangles[sa_i] + pa + wedgewidth / 2))
            plt.plot(endx + cx, endy + cy, color='w', linewidth=0.7)
            plt.plot(endx + cx,
                     endy + cy,
                     color=cmap(colorind[sa_i]),
                     linestyle='--',
                     linewidth=0.5)
            endx, endy = (R * np.cos(wedgeangles[sa_i] + pa - wedgewidth / 2),
                          R * np.sin(wedgeangles[sa_i] + pa - wedgewidth / 2))
            plt.plot(endx + cx, endy + cy, color='w', linewidth=0.7)
            plt.plot(endx + cx,
                     endy + cy,
                     color=cmap(colorind[sa_i]),
                     linestyle='--',
                     linewidth=0.5)

        plt.xlim([0, ranges[0][1] - ranges[0][0]])
        plt.ylim([0, ranges[1][1] - ranges[1][0]])
        if not ('ap_nologo' in options and options['ap_nologo']):
            AddLogo(plt.gcf())
        plt.savefig(
            '%sradial_profiles_wedges_%s.jpg' %
            (options['ap_plotpath'] if 'ap_plotpath' in options else '',
             options['ap_name']),
            dpi=options['ap_plotdpi'] if 'ap_plotdpi' in options else 300)
        plt.close()

    return IMG, {
        'prof header': newprofheader,
        'prof units': newprofunits,
        'prof data': newprofdata,
        'prof format': newprofformat
    }
Beispiel #9
0
def _Generate_Profile(IMG, results, R, parameters, options):

    # Create image array with background and mask applied
    try:
        if np.any(results["mask"]):
            mask = results["mask"]
        else:
            mask = None
    except:
        mask = None
    dat = IMG - results["background"]
    zeropoint = options["ap_zeropoint"] if "ap_zeropoint" in options else 22.5
    fluxunits = options["ap_fluxunits"] if "ap_fluxunits" in options else "mag"

    for p in range(len(parameters)):
        # Indicate no Fourier modes if supplied parameters does not include it
        if not "m" in parameters[p]:
            parameters[p]["m"] = None
        if not "C" in parameters[p]:
            parameters[p]["C"] = None
        # If no ellipticity error supplied, assume zero
        if not "ellip err" in parameters[p]:
            parameters[p]["ellip err"] = 0.0
        # If no position angle error supplied, assume zero
        if not "pa err" in parameters[p]:
            parameters[p]["pa err"] = 0.0

    sb = []
    sbE = []
    pixels = []
    maskedpixels = []
    cogdirect = []
    sbfix = []
    sbfixE = []
    measFmodes = []

    count_neg = 0
    medflux = np.inf
    end_prof = len(R)
    compare_interp = []
    for i in range(len(R)):
        if "ap_isoband_fixed" in options and options["ap_isoband_fixed"]:
            isobandwidth = (
                options["ap_isoband_width"] if "ap_isoband_width" in options else 0.5
            )
        else:
            isobandwidth = R[i] * (
                options["ap_isoband_width"] if "ap_isoband_width" in options else 0.025
            )
        isisophoteband = False
        if (
            medflux
            > (
                results["background noise"]
                * (options["ap_isoband_start"] if "ap_isoband_start" in options else 2)
            )
            or isobandwidth < 0.5
        ):
            isovals = _iso_extract(
                dat,
                R[i],
                parameters[i],
                results["center"],
                mask=mask,
                more=True,
                rad_interp=(
                    options["ap_iso_interpolate_start"]
                    if "ap_iso_interpolate_start" in options
                    else 5
                )
                * results["psf fwhm"],
                interp_method=(
                    options["ap_iso_interpolate_method"]
                    if "ap_iso_interpolate_method" in options
                    else "lanczos"
                ),
                interp_window=(
                    int(options["ap_iso_interpolate_window"])
                    if "ap_iso_interpolate_window" in options
                    else 5
                ),
                sigmaclip=options["ap_isoclip"] if "ap_isoclip" in options else False,
                sclip_iterations=options["ap_isoclip_iterations"]
                if "ap_isoclip_iterations" in options
                else 10,
                sclip_nsigma=options["ap_isoclip_nsigma"]
                if "ap_isoclip_nsigma" in options
                else 5,
            )
        else:
            isisophoteband = True
            isovals = _iso_between(
                dat,
                R[i] - isobandwidth,
                R[i] + isobandwidth,
                parameters[i],
                results["center"],
                mask=mask,
                more=True,
                sigmaclip=options["ap_isoclip"] if "ap_isoclip" in options else False,
                sclip_iterations=options["ap_isoclip_iterations"]
                if "ap_isoclip_iterations" in options
                else 10,
                sclip_nsigma=options["ap_isoclip_nsigma"]
                if "ap_isoclip_nsigma" in options
                else 5,
            )
        isotot = np.sum(
            _iso_between(dat, 0, R[i], parameters[i], results["center"], mask=mask)
        )
        medflux = _average(
            isovals[0],
            options["ap_isoaverage_method"]
            if "ap_isoaverage_method" in options
            else "median",
        )
        scatflux = _scatter(
            isovals[0],
            options["ap_isoaverage_method"]
            if "ap_isoaverage_method" in options
            else "median",
        )
        if (
            "ap_iso_measurecoefs" in options
            and not options["ap_iso_measurecoefs"] is None
        ):
            if (
                mask is None
                and (not "ap_isoclip" in options or not options["ap_isoclip"])
                and not isisophoteband
            ):
                coefs = fft(isovals[0])
            else:
                N = max(15, int(0.9 * 2 * np.pi * R[i]))
                theta = np.linspace(0, 2 * np.pi * (1.0 - 1.0 / N), N)
                coefs = fft(np.interp(theta, isovals[1], isovals[0], period=2 * np.pi))
            measFmodes.append(
                {
                    "a": [np.imag(coefs[0]) / len(coefs)]
                    + list(
                        np.imag(coefs[np.array(options["ap_iso_measurecoefs"])])
                        / (np.abs(coefs[0]))
                    ),
                    "b": [np.real(coefs[0]) / len(coefs)]
                    + list(
                        np.real(coefs[np.array(options["ap_iso_measurecoefs"])])
                        / (np.abs(coefs[0]))
                    ),
                }
            )

        pixels.append(len(isovals[0]))
        maskedpixels.append(isovals[2])
        if fluxunits == "intensity":
            sb.append(medflux / options["ap_pixscale"] ** 2)
            sbE.append(scatflux / np.sqrt(len(isovals[0])))
            cogdirect.append(isotot)
        else:
            sb.append(
                flux_to_sb(medflux, options["ap_pixscale"], zeropoint)
                if medflux > 0
                else 99.999
            )
            sbE.append(
                (2.5 * scatflux / (np.sqrt(len(isovals[0])) * medflux * np.log(10)))
                if medflux > 0
                else 99.999
            )
            cogdirect.append(flux_to_mag(isotot, zeropoint) if isotot > 0 else 99.999)
        if medflux <= 0:
            count_neg += 1
        if (
            "ap_truncate_evaluation" in options
            and options["ap_truncate_evaluation"]
            and count_neg >= 2
        ):
            end_prof = i + 1
            break

    # Compute Curve of Growth from SB profile
    if fluxunits == "intensity":
        cog, cogE = Fmode_fluxdens_to_fluxsum_errorprop(
            R[:end_prof] * options["ap_pixscale"],
            np.array(sb),
            np.array(sbE),
            parameters[:end_prof],
            N=100,
            symmetric_error=True,
        )

        if cog is None:
            cog = -99.999 * np.ones(len(R))
            cogE = -99.999 * np.ones(len(R))
        else:
            cog[np.logical_not(np.isfinite(cog))] = -99.999
            cogE[cog < 0] = -99.999
    else:
        cog, cogE = SBprof_to_COG_errorprop(
            R[:end_prof] * options["ap_pixscale"],
            np.array(sb),
            np.array(sbE),
            parameters[:end_prof],
            N=100,
            symmetric_error=True,
        )
        if cog is None:
            cog = 99.999 * np.ones(len(R))
            cogE = 99.999 * np.ones(len(R))
        else:
            cog[np.logical_not(np.isfinite(cog))] = 99.999
            cogE[cog > 99] = 99.999

    # For each radius evaluation, write the profile parameters
    if fluxunits == "intensity":
        params = [
            "R",
            "I",
            "I_e",
            "totflux",
            "totflux_e",
            "ellip",
            "ellip_e",
            "pa",
            "pa_e",
            "pixels",
            "maskedpixels",
            "totflux_direct",
        ]

        SBprof_units = {
            "R": "arcsec",
            "I": "flux*arcsec^-2",
            "I_e": "flux*arcsec^-2",
            "totflux": "flux",
            "totflux_e": "flux",
            "ellip": "unitless",
            "ellip_e": "unitless",
            "pa": "deg",
            "pa_e": "deg",
            "pixels": "count",
            "maskedpixels": "count",
            "totflux_direct": "flux",
        }
    else:
        params = [
            "R",
            "SB",
            "SB_e",
            "totmag",
            "totmag_e",
            "ellip",
            "ellip_e",
            "pa",
            "pa_e",
            "pixels",
            "maskedpixels",
            "totmag_direct",
        ]

        SBprof_units = {
            "R": "arcsec",
            "SB": "mag*arcsec^-2",
            "SB_e": "mag*arcsec^-2",
            "totmag": "mag",
            "totmag_e": "mag",
            "ellip": "unitless",
            "ellip_e": "unitless",
            "pa": "deg",
            "pa_e": "deg",
            "pixels": "count",
            "maskedpixels": "count",
            "totmag_direct": "mag",
        }

    SBprof_data = dict((h, None) for h in params)
    SBprof_data["R"] = list(R[:end_prof] * options["ap_pixscale"])
    SBprof_data["I" if fluxunits == "intensity" else "SB"] = list(sb)
    SBprof_data["I_e" if fluxunits == "intensity" else "SB_e"] = list(sbE)
    SBprof_data["totflux" if fluxunits == "intensity" else "totmag"] = list(cog)
    SBprof_data["totflux_e" if fluxunits == "intensity" else "totmag_e"] = list(cogE)
    SBprof_data["ellip"] = list(parameters[p]["ellip"] for p in range(end_prof))
    SBprof_data["ellip_e"] = list(parameters[p]["ellip err"] for p in range(end_prof))
    SBprof_data["pa"] = list(parameters[p]["pa"] * 180 / np.pi for p in range(end_prof))
    SBprof_data["pa_e"] = list(
        parameters[p]["pa err"] * 180 / np.pi for p in range(end_prof)
    )
    SBprof_data["pixels"] = list(pixels)
    SBprof_data["maskedpixels"] = list(maskedpixels)
    SBprof_data[
        "totflux_direct" if fluxunits == "intensity" else "totmag_direct"
    ] = list(cogdirect)

    if "ap_iso_measurecoefs" in options and not options["ap_iso_measurecoefs"] is None:
        whichcoefs = [0] + list(options["ap_iso_measurecoefs"])
        for i in list(range(len(whichcoefs))):
            aa, bb = "a%i" % whichcoefs[i], "b%i" % whichcoefs[i]
            params += [aa, bb]
            SBprof_units.update(
                {
                    aa: "flux" if whichcoefs[i] == 0 else "a%i/F0" % whichcoefs[i],
                    bb: "flux" if whichcoefs[i] == 0 else "b%i/F0" % whichcoefs[i],
                }
            )
            SBprof_data[aa] = list(F["a"][i] for F in measFmodes)
            SBprof_data[bb] = list(F["b"][i] for F in measFmodes)

    if any(not p["m"] is None for p in parameters):
        for m in range(len(parameters[0]["m"])):
            AA, PP = "A%i" % parameters[0]["m"][m], "Phi%i" % parameters[0]["m"][m]
            params += [AA, PP]
            SBprof_units.update({AA: "unitless", PP: "deg"})
            SBprof_data[AA] = list(p["Am"][m] for p in parameters[:end_prof])
            SBprof_data[PP] = list(p["Phim"][m] for p in parameters[:end_prof])
    if any(not p["C"] is None for p in parameters):
        params += ["C"]
        SBprof_units["C"] = "unitless"
        SBprof_data["C"] = list(p["C"] for p in parameters[:end_prof])

    if "ap_doplot" in options and options["ap_doplot"]:
        Plot_Phase_Profile(
            np.array(SBprof_data["R"]), parameters[:end_prof], results, options
        )
        if fluxunits == "intensity":
            Plot_I_Profile(
                dat,
                np.array(SBprof_data["R"]),
                np.array(SBprof_data["I"]),
                np.array(SBprof_data["I_e"]),
                parameters[:end_prof],
                results,
                options,
            )
        else:
            Plot_SB_Profile(
                dat,
                np.array(SBprof_data["R"]),
                np.array(SBprof_data["SB"]),
                np.array(SBprof_data["SB_e"]),
                parameters[:end_prof],
                results,
                options,
            )

    return {"prof header": params, "prof units": SBprof_units, "prof data": SBprof_data}
Beispiel #10
0
def Slice_Profile(IMG, results, options):

    dat = IMG - (results['background'] if 'background' in results else 0.)
    zeropoint = options['ap_zeropoint'] if 'ap_zeropoint' in options else 22.5
    
    use_anchor = results['center'] if 'center' in results else {'x': IMG.shape[1]/2, 'y': IMG.shape[0]/2}
    if 'ap_slice_anchor' in options:
        use_anchor = options['ap_slice_anchor']
    else:
        logging.warning('%s: ap_slice_anchor not specified by user, using: %s' % (options['ap_name'], str(use_anchor)))

    use_pa = results['init pa'] if 'init pa' in results else 0.
    if 'ap_slice_pa' in options:
        use_pa = options['ap_slice_pa']*np.pi/180
    else:
        logging.warning('%s: ap_slice_pa not specified by user, using: %.2f' % (options['ap_name'], use_pa))

    use_length = results['init R'] if 'init R' in results else min(IMG.shape)
    if 'ap_slice_length' in options:
        use_length = options['ap_slice_length']
    else:
        logging.warning('%s: ap_slice_length not specified by user, using: %.2f' % (options['ap_name'], use_length))

    use_width = 10.
    if 'ap_slice_width' in options:
        use_width = options['ap_slice_width']
    else:
        logging.warning('%s: ap_slice_width not specified by user, using: %.2f' % (options['ap_name'], use_width))

    use_step = results['psf fwhm'] if 'psf fwhm' in results else max(2., use_length/100)
    if 'ap_slice_step' in options:
        use_step = options['ap_slice_step']
    else:
        logging.warning('%s: ap_slice_step not specified by user, using: %.2f' % (options['ap_name'], use_step))
    
    F, X = _iso_line(dat, use_length, use_width, use_pa, use_anchor, more = False)

    windows = np.arange(0, use_length, use_step)

    R = (windows[1:] + windows[:-1])/2
    sb = []
    sb_e = []
    sb_sclip = []
    sb_sclip_e = []
    for i in range(len(windows)-1):
        isovals = F[np.logical_and(X >= windows[i], X < windows[i+1])]
        isovals_sclip = Sigma_Clip_Upper(isovals, iterations = 10, nsigma = 5)

        medflux = _average(isovals, options['ap_isoaverage_method'] if 'ap_isoaverage_method' in options else 'median')
        scatflux = _scatter(isovals, options['ap_isoaverage_method'] if 'ap_isoaverage_method' in options else 'median')
        medflux_sclip = _average(isovals_sclip, options['ap_isoaverage_method'] if 'ap_isoaverage_method' in options else 'median')
        scatflux_sclip = _scatter(isovals_sclip, options['ap_isoaverage_method'] if 'ap_isoaverage_method' in options else 'median')

        sb.append(flux_to_sb(medflux, options['ap_pixscale'], zeropoint) if medflux > 0 else 99.999)
        sb_e.append((2.5*scatflux / (np.sqrt(len(isovals))*medflux*np.log(10))) if medflux > 0 else 99.999)
        sb_sclip.append(flux_to_sb(medflux_sclip, options['ap_pixscale'], zeropoint) if medflux_sclip > 0 else 99.999)
        sb_sclip_e.append((2.5*scatflux_sclip / (np.sqrt(len(isovals))*medflux_sclip*np.log(10))) if medflux_sclip > 0 else 99.999)
    
    with open('%s%s_slice_profile_AP.prof' % ((options['ap_saveto'] if 'ap_saveto' in options else ''), options['ap_name']), 'w') as f:
        f.write('# flux sum: %f\n' % (np.sum(F[np.logical_and(X >= 0, X <= use_length)])))
        f.write('# flux mean: %f\n' % (_average(F[np.logical_and(X >= 0, X <= use_length)], 'mean')))
        f.write('# flux median: %f\n' % (_average(F[np.logical_and(X >= 0, X <= use_length)], 'median')))
        f.write('# flux mode: %f\n' % (_average(F[np.logical_and(X >= 0, X <= use_length)], 'mode')))
        f.write('# flux std: %f\n' % (np.std(F[np.logical_and(X >= 0, X <= use_length)])))
        f.write('# flux 16-84%% range: %f\n' % (iqr(F[np.logical_and(X >= 0, X <= use_length)], rng = [16,84])))
        f.write('R,sb,sb_e,sb_sclip,sb_sclip_e\n')
        f.write('arcsec,mag*arcsec^-2,mag*arcsec^-2,mag*arcsec^-2,mag*arcsec^-2\n')
        for i in range(len(R)):
            f.write('%.4f,%.4f,%.4f,%.4f,%.4f\n' % (R[i]*options['ap_pixscale'], sb[i], sb_e[i], sb_sclip[i], sb_sclip_e[i]))

    if 'ap_doplot' in options and options['ap_doplot']:
        CHOOSE = np.array(sb_e) < 0.5
        plt.errorbar(np.array(R)[CHOOSE]*options['ap_pixscale'], np.array(sb)[CHOOSE], yerr = np.array(sb_e)[CHOOSE],
                     elinewidth = 1, linewidth = 0, marker = '.', markersize = 3, color = 'r')
        plt.xlabel('Position on line [arcsec]', fontsize = 16)
        plt.ylabel('Surface Brightness [mag arcsec$^{-2}$]', fontsize = 16)
        if 'background noise' in results:
            bkgrdnoise = -2.5*np.log10(results['background noise']) + zeropoint + 2.5*np.log10(options['ap_pixscale']**2)
            plt.axhline(bkgrdnoise, color = 'purple', linewidth = 0.5, linestyle = '--', label = '1$\\sigma$ noise/pixel: %.1f mag arcsec$^{-2}$' % bkgrdnoise)
        plt.gca().invert_yaxis()
        plt.legend(fontsize = 15)
        plt.tick_params(labelsize = 14)
        plt.tight_layout()
        if not ('ap_nologo' in options and options['ap_nologo']):
            AddLogo(plt.gcf())
        plt.savefig('%sslice_profile_%s.jpg' % (options['ap_plotpath'] if 'ap_plotpath' in options else '', options['ap_name']), dpi = options['ap_plotdpi'] if 'ap_plotdpi'in options else 300)
        plt.close()
        
        ranges = [[max(0,int(use_anchor['x']+0.5*use_length*np.cos(use_pa)-use_length*0.7)), min(IMG.shape[1],int(use_anchor['x']+0.5*use_length*np.cos(use_pa)+use_length*0.7))],
                  [max(0,int(use_anchor['y']+0.5*use_length*np.sin(use_pa)-use_length*0.7)), min(IMG.shape[0],int(use_anchor['y']+0.5*use_length*np.sin(use_pa)+use_length*0.7))]]
        LSBImage(dat[ranges[1][0]: ranges[1][1], ranges[0][0]: ranges[0][1]], results['background noise'] if 'background noise' in results else iqr(dat, rng = (31.731/2, 100 - 31.731/2))/2)
        
        XX, YY = np.meshgrid(np.arange(ranges[0][1] - ranges[0][0], dtype = float), np.arange(ranges[1][1] - ranges[1][0], dtype = float))
        XX -= use_anchor['x'] - float(ranges[0][0])
        YY -= use_anchor['y'] - float(ranges[1][0])
        XX, YY = (XX*np.cos(-use_pa) - YY*np.sin(-use_pa), XX*np.sin(-use_pa) + YY*np.cos(-use_pa))
        ZZ = np.ones(XX.shape)
        ZZ[np.logical_not(np.logical_and(np.logical_and(YY <= use_width/2, YY >= -use_width/2),
                                         np.logical_and(XX >= 0, XX <= use_length)))] = np.nan
        plt.imshow(ZZ, origin = 'lower', cmap = 'Reds_r', alpha = 0.6)
        plt.tight_layout()
        if not ('ap_nologo' in options and options['ap_nologo']):
            AddLogo(plt.gcf())
        plt.savefig('%sslice_profile_window_%s.jpg' % (options['ap_plotpath'] if 'ap_plotpath' in options else '', options['ap_name']), dpi = options['ap_plotdpi'] if 'ap_plotdpi'in options else 300)
        plt.close()
        
    return IMG, {}
Beispiel #11
0
def Slice_Profile(IMG, results, options):
    """Extract a very basic SB profile along a line.

    A line of pixels can be identified by the user in image
    coordinates to extract an SB profile. Primarily intended for
    diagnostic purposes, this allows users to see very specific
    pixels. While this tool can be used for examining the disk
    structure (such as for edge on galaxies), users will likely prefer
    the more powerful
    :func:`~pipeline_steps.Axial_Profiles.Axial_Profiles` and
    :func:`~pipeline_steps.Radial_Profiles.Radial_Profiles` methods
    for such analysis.

    Parameters
    -----------------
    ap_slice_anchor : dict, default None
      Coordinates for the starting point of the slice as a dictionary
      formatted "{'x': x-coord, 'y': y-coord}" in pixel units.

    ap_slice_pa : float, default None
      Position angle of the slice in degrees, counter-clockwise
      relative to the x-axis.

    ap_slice_length : float, default None
      Length of the slice from anchor point in pixel units. By
      default, use init ellipse semi-major axis length

    ap_slice_width : float, default 10
      Width of the slice in pixel units.

    ap_slice_step : float, default None
      Distance between samples for the profile along the
      slice. By default use the PSF.

    ap_isoaverage_method : string, default 'median'
      Select the method used to compute the averafge flux along an
      isophote. Choose from 'mean', 'median', and 'mode'.  In general,
      median is fast and robust to a few outliers. Mode is slow but
      robust to more outliers. Mean is fast and accurate in low S/N
      regimes where fluxes take on near integer values, but not robust
      to outliers. The mean should be used along with a mask to remove
      spurious objects such as foreground stars or galaxies, and
      should always be used with caution.

    ap_saveto : string, default None
      Directory in which to save profile

    ap_name : string, default None
      Name of the current galaxy, used for making filenames.

    ap_zeropoint : float, default 22.5
      Photometric zero point. For converting flux to mag units.

    Notes
    ----------
    :References:
    - 'background' (optional)
    - 'background noise' (optional)
    - 'center' (optional)
    - 'init R' (optional)
    - 'init pa' (optional)

    Returns
    -------
    IMG : ndarray
      Unaltered galaxy image

    results : dict
      .. code-block:: python

        {}

    """

    dat = IMG - (results["background"] if "background" in results else np.median(IMG))
    zeropoint = options["ap_zeropoint"] if "ap_zeropoint" in options else 22.5

    use_anchor = (
        results["center"]
        if "center" in results
        else {"x": IMG.shape[1] / 2, "y": IMG.shape[0] / 2}
    )
    if "ap_slice_anchor" in options:
        use_anchor = options["ap_slice_anchor"]
    else:
        logging.warning(
            "%s: ap_slice_anchor not specified by user, using: %s"
            % (options["ap_name"], str(use_anchor))
        )

    use_pa = results["init pa"] if "init pa" in results else 0.0
    if "ap_slice_pa" in options:
        use_pa = options["ap_slice_pa"] * np.pi / 180
    else:
        logging.warning(
            "%s: ap_slice_pa not specified by user, using: %.2f"
            % (options["ap_name"], use_pa)
        )

    use_length = results["init R"] if "init R" in results else min(IMG.shape)
    if "ap_slice_length" in options:
        use_length = options["ap_slice_length"]
    else:
        logging.warning(
            "%s: ap_slice_length not specified by user, using: %.2f"
            % (options["ap_name"], use_length)
        )

    use_width = 10.0
    if "ap_slice_width" in options:
        use_width = options["ap_slice_width"]
    else:
        logging.warning(
            "%s: ap_slice_width not specified by user, using: %.2f"
            % (options["ap_name"], use_width)
        )

    use_step = (
        results["psf fwhm"] if "psf fwhm" in results else max(2.0, use_length / 100)
    )
    if "ap_slice_step" in options:
        use_step = options["ap_slice_step"]
    else:
        logging.warning(
            "%s: ap_slice_step not specified by user, using: %.2f"
            % (options["ap_name"], use_step)
        )

    F, X = _iso_line(dat, use_length, use_width, use_pa, use_anchor, more=False)

    windows = np.arange(0, use_length, use_step)

    R = (windows[1:] + windows[:-1]) / 2
    sb = []
    sb_e = []
    sb_sclip = []
    sb_sclip_e = []
    for i in range(len(windows) - 1):
        isovals = F[np.logical_and(X >= windows[i], X < windows[i + 1])]
        isovals_sclip = Sigma_Clip_Upper(isovals, iterations=10, nsigma=5)

        medflux = _average(
            isovals,
            options["ap_isoaverage_method"]
            if "ap_isoaverage_method" in options
            else "median",
        )
        scatflux = _scatter(
            isovals,
            options["ap_isoaverage_method"]
            if "ap_isoaverage_method" in options
            else "median",
        )
        medflux_sclip = _average(
            isovals_sclip,
            options["ap_isoaverage_method"]
            if "ap_isoaverage_method" in options
            else "median",
        )
        scatflux_sclip = _scatter(
            isovals_sclip,
            options["ap_isoaverage_method"]
            if "ap_isoaverage_method" in options
            else "median",
        )

        sb.append(
            flux_to_sb(medflux, options["ap_pixscale"], zeropoint)
            if medflux > 0
            else 99.999
        )
        sb_e.append(
            (2.5 * scatflux / (np.sqrt(len(isovals)) * medflux * np.log(10)))
            if medflux > 0
            else 99.999
        )
        sb_sclip.append(
            flux_to_sb(medflux_sclip, options["ap_pixscale"], zeropoint)
            if medflux_sclip > 0
            else 99.999
        )
        sb_sclip_e.append(
            (
                2.5
                * scatflux_sclip
                / (np.sqrt(len(isovals)) * medflux_sclip * np.log(10))
            )
            if medflux_sclip > 0
            else 99.999
        )

    with open(
        "%s%s_slice_profile.prof"
        % (
            (options["ap_saveto"] if "ap_saveto" in options else ""),
            options["ap_name"],
        ),
        "w",
    ) as f:
        f.write(
            "# flux sum: %f\n" % (np.sum(F[np.logical_and(X >= 0, X <= use_length)]))
        )
        f.write(
            "# flux mean: %f\n"
            % (_average(F[np.logical_and(X >= 0, X <= use_length)], "mean"))
        )
        f.write(
            "# flux median: %f\n"
            % (_average(F[np.logical_and(X >= 0, X <= use_length)], "median"))
        )
        f.write(
            "# flux mode: %f\n"
            % (_average(F[np.logical_and(X >= 0, X <= use_length)], "mode"))
        )
        f.write(
            "# flux std: %f\n" % (np.std(F[np.logical_and(X >= 0, X <= use_length)]))
        )
        f.write(
            "# flux 16-84%% range: %f\n"
            % (iqr(F[np.logical_and(X >= 0, X <= use_length)], rng=[16, 84]))
        )
        f.write("R,sb,sb_e,sb_sclip,sb_sclip_e\n")
        f.write("arcsec,mag*arcsec^-2,mag*arcsec^-2,mag*arcsec^-2,mag*arcsec^-2\n")
        for i in range(len(R)):
            f.write(
                "%.4f,%.4f,%.4f,%.4f,%.4f\n"
                % (
                    R[i] * options["ap_pixscale"],
                    sb[i],
                    sb_e[i],
                    sb_sclip[i],
                    sb_sclip_e[i],
                )
            )

    if "ap_doplot" in options and options["ap_doplot"]:
        CHOOSE = np.array(sb_e) < 0.5
        plt.errorbar(
            np.array(R)[CHOOSE] * options["ap_pixscale"],
            np.array(sb)[CHOOSE],
            yerr=np.array(sb_e)[CHOOSE],
            elinewidth=1,
            linewidth=0,
            marker=".",
            markersize=3,
            color="r",
        )
        plt.xlabel("Position on line [arcsec]", fontsize=16)
        plt.ylabel("Surface Brightness [mag arcsec$^{-2}$]", fontsize=16)
        if "background noise" in results:
            bkgrdnoise = (
                -2.5 * np.log10(results["background noise"])
                + zeropoint
                + 2.5 * np.log10(options["ap_pixscale"] ** 2)
            )
            plt.axhline(
                bkgrdnoise,
                color="purple",
                linewidth=0.5,
                linestyle="--",
                label="1$\\sigma$ noise/pixel: %.1f mag arcsec$^{-2}$" % bkgrdnoise,
            )
        plt.gca().invert_yaxis()
        plt.legend(fontsize=15)
        plt.tick_params(labelsize=14)
        plt.tight_layout()
        if not ("ap_nologo" in options and options["ap_nologo"]):
            AddLogo(plt.gcf())
        plt.savefig(
            "%sslice_profile_%s.jpg"
            % (
                options["ap_plotpath"] if "ap_plotpath" in options else "",
                options["ap_name"],
            ),
            dpi=options["ap_plotdpi"] if "ap_plotdpi" in options else 300,
        )
        plt.close()

        ranges = [
            [
                max(
                    0,
                    int(
                        use_anchor["x"]
                        + 0.5 * use_length * np.cos(use_pa)
                        - use_length * 0.7
                    ),
                ),
                min(
                    IMG.shape[1],
                    int(
                        use_anchor["x"]
                        + 0.5 * use_length * np.cos(use_pa)
                        + use_length * 0.7
                    ),
                ),
            ],
            [
                max(
                    0,
                    int(
                        use_anchor["y"]
                        + 0.5 * use_length * np.sin(use_pa)
                        - use_length * 0.7
                    ),
                ),
                min(
                    IMG.shape[0],
                    int(
                        use_anchor["y"]
                        + 0.5 * use_length * np.sin(use_pa)
                        + use_length * 0.7
                    ),
                ),
            ],
        ]
        LSBImage(
            dat[ranges[1][0] : ranges[1][1], ranges[0][0] : ranges[0][1]],
            results["background noise"]
            if "background noise" in results
            else iqr(dat, rng=(31.731 / 2, 100 - 31.731 / 2)) / 2,
        )

        XX, YY = np.meshgrid(
            np.arange(ranges[0][1] - ranges[0][0], dtype=float),
            np.arange(ranges[1][1] - ranges[1][0], dtype=float),
        )
        XX -= use_anchor["x"] - float(ranges[0][0])
        YY -= use_anchor["y"] - float(ranges[1][0])
        XX, YY = (
            XX * np.cos(-use_pa) - YY * np.sin(-use_pa),
            XX * np.sin(-use_pa) + YY * np.cos(-use_pa),
        )
        ZZ = np.ones(XX.shape)
        ZZ[
            np.logical_not(
                np.logical_and(
                    np.logical_and(YY <= use_width / 2, YY >= -use_width / 2),
                    np.logical_and(XX >= 0, XX <= use_length),
                )
            )
        ] = np.nan
        plt.imshow(ZZ, origin="lower", cmap="Reds_r", alpha=0.6)
        plt.tight_layout()
        if not ("ap_nologo" in options and options["ap_nologo"]):
            AddLogo(plt.gcf())
        plt.savefig(
            "%sslice_profile_window_%s.jpg"
            % (
                options["ap_plotpath"] if "ap_plotpath" in options else "",
                options["ap_name"],
            ),
            dpi=options["ap_plotdpi"] if "ap_plotdpi" in options else 300,
        )
        plt.close()

    return IMG, {}
Beispiel #12
0
def Axial_Profiles(IMG, results, options):
    """Extracts SB profiles perpendicular to the major (or minor) axis.

    For some applications, such as examining edge on galaxies, it is
    beneficial to observe the vertical structure in a disk. This can
    be achieved with the Axial Profiles method. It will construct a
    series of lines, each one with a starting point on the major axis
    of the galaxy and radiating perpendicular from it. The location of
    these lines are, by default, geometrically spaced so that they can
    gather more light in the fainter outskirts. Along a given line,
    and SB profile is extracted, with the distance between points on
    the profile also increasing geometrically, allowing more light
    collection. The outputted profile is formatted similar to a
    regular SB profile, except that there are many SB profiles with
    each one having a corresponding distance from the center and
    quadrant of the image. A diagnostic image is generated to aid in
    identifying where each profile is extracted.
    
    Parameters
    -----------------
    ap_axialprof_pa : float, default 0
      user set position angle at which to align the axial profiles
      relative to the global position angle+90, in degrees. A common
      choice would be "90" which would then sample along the
      semi-major axis instead of the semi-minor axis.

    ap_zeropoint : float, default 22.5
      Photometric zero point

    ap_samplestyle : string, default 'geometric'
      indicate if isophote sampling radii should grow linearly or
      geometrically. Can also do geometric sampling at the center and
      linear sampling once geometric step size equals linear. Options
      are: 'linear', 'geometric', and 'geometric-linear'.

    ap_isoaverage_method : string, default 'median'
      Select the method used to compute the averafge flux along an
      isophote. Choose from 'mean', 'median', and 'mode'.  In general,
      median is fast and robust to a few outliers. Mode is slow but
      robust to more outliers. Mean is fast and accurate in low S/N
      regimes where fluxes take on near integer values, but not robust
      to outliers. The mean should be used along with a mask to remove
      spurious objects such as foreground stars or galaxies, and
      should always be used with caution.

    Notes
    ----------
    :References:
    - 'mask' (optional)
    - 'background'
    - 'psf fwhm'
    - 'center'
    - 'prof data' (optional)
    - 'init pa'

    Returns
    -------
    IMG : ndarray
      Unaltered galaxy image

    results : dict
      No results provided as this method writes its own profile

      .. code-block:: python

        {}

    """

    mask = results["mask"] if "mask" in results else None
    pa = results["init pa"] + (
        (options["ap_axialprof_pa"] * np.pi / 180)
        if "ap_axialprof_pa" in options
        else 0.0
    )
    dat = IMG - results["background"]
    zeropoint = options["ap_zeropoint"] if "ap_zeropoint" in options else 22.5

    if "prof data" in results:
        Rproflim = results["prof data"]["R"][-1] / options["ap_pixscale"]
    else:
        Rproflim = min(IMG.shape) / 2

    R = [0]
    while R[-1] < Rproflim:
        if "ap_samplestyle" in options and options["ap_samplestyle"] == "linear":
            step = (
                options["ap_samplelinearscale"]
                if "ap_samplelinearscale" in options
                else 0.5 * results["psf fwhm"]
            )
        else:
            step = R[-1] * (
                options["ap_samplegeometricscale"]
                if "ap_samplegeometricscale" in options
                else 0.1
            )
        R.append(R[-1] + max(1, step))

    sb = {}
    sbE = {}
    for rd in [1, -1]:
        for ang in [1, -1]:
            key = (rd, ang)
            sb[key] = []
            sbE[key] = []
            branch_pa = (pa + ang * np.pi / 2) % (2 * np.pi)
            for pi, pR in enumerate(R):
                sb[key].append([])
                sbE[key].append([])
                width = (R[pi] - R[pi - 1]) if pi > 0 else 1.0
                flux, XX = _iso_line(
                    dat,
                    R[-1],
                    width,
                    branch_pa,
                    {
                        "x": results["center"]["x"]
                        + ang * rd * pR * np.cos(pa + (0 if ang > 0 else np.pi)),
                        "y": results["center"]["y"]
                        + ang * rd * pR * np.sin(pa + (0 if ang > 0 else np.pi)),
                    },
                )
                for oi, oR in enumerate(R):
                    length = (R[oi] - R[oi - 1]) if oi > 0 else 1.0
                    CHOOSE = np.logical_and(
                        XX > (oR - length / 2), XX < (oR + length / 2)
                    )
                    if np.sum(CHOOSE) == 0:
                        sb[key][-1].append(99.999)
                        sbE[key][-1].append(99.999)
                        continue
                    medflux = _average(
                        flux[CHOOSE],
                        options["ap_isoaverage_method"]
                        if "ap_isoaverage_method" in options
                        else "median",
                    )
                    scatflux = _scatter(
                        flux[CHOOSE],
                        options["ap_isoaverage_method"]
                        if "ap_isoaverage_method" in options
                        else "median",
                    )
                    sb[key][-1].append(
                        flux_to_sb(medflux, options["ap_pixscale"], zeropoint)
                        if medflux > 0
                        else 99.999
                    )
                    sbE[key][-1].append(
                        (
                            2.5
                            * scatflux
                            / (np.sqrt(np.sum(CHOOSE)) * medflux * np.log(10))
                        )
                        if medflux > 0
                        else 99.999
                    )

    with open(
        "%s%s_axial_profile.prof"
        % (
            (options["ap_saveto"] if "ap_saveto" in options else ""),
            options["ap_name"],
        ),
        "w",
    ) as f:
        f.write("R")
        for rd in [1, -1]:
            for ang in [1, -1]:
                for pR in R:
                    f.write(
                        ",sb[%.3f:%s90],sbE[%.3f:%s90]"
                        % (
                            rd * pR * options["ap_pixscale"],
                            "+" if ang > 0 else "-",
                            rd * pR * options["ap_pixscale"],
                            "+" if ang > 0 else "-",
                        )
                    )
        f.write("\n")
        f.write("arcsec")
        for rd in [1, -1]:
            for ang in [1, -1]:
                for pR in R:
                    f.write(",mag*arcsec^-2,mag*arcsec^-2")
        f.write("\n")
        for oi, oR in enumerate(R):
            f.write("%.4f" % (oR * options["ap_pixscale"]))
            for rd in [1, -1]:
                for ang in [1, -1]:
                    key = (rd, ang)
                    for pi, pR in enumerate(R):
                        f.write(",%.4f,%.4f" % (sb[key][pi][oi], sbE[key][pi][oi]))
            f.write("\n")

    if "ap_doplot" in options and options["ap_doplot"]:
        Plot_Axial_Profiles(dat, R, sb, sbE, pa, results, options)

    return IMG, {}
Beispiel #13
0
def Radial_Profiles(IMG, results, options):
    """Extracts SB profiles along lines radiating from the galaxy center.

    For some applications, such as examining edge on galaxies, it is
    beneficial to observe the structure in a disk as well as (or
    instead of) the average isophotal profile. This can done with
    radial profiles which sample along lines radiating form the galaxy
    center. These lines are by default placed on the 4 semi-axes of
    the galaxy. The lines are actually wedges with increasing width as
    a function of radius. This helps keep roughly constant S/N in the
    bins, allowing the profile to extend far into the outskirts of a
    galaxy. The user may increase the number of wedgest to extract
    more stucture from the galaxy, however at some point the wedges
    will begin to cross each other. AutoProf will warn the user when
    this happens, but will carry on anyway.

    Parameters
    -----------------
    ap_radialprofiles_nwedges : int, default 4
      number of radial wedges to sample. Recommended choosing a power
      of 2.

    ap_radialprofiles_width : float, default 15
      User set width of radial sampling wedges in degrees.

    ap_radialprofiles_pa : float, default 0
      user set position angle at which to measure radial wedges
      relative to the global position angle, in degrees.

    ap_radialprofiles_expwidth : bool, default False
      Tell AutoProf to use exponentially increasing widths for radial
      samples. In this case *ap_radialprofiles_width* corresponds to
      the final width of the radial sampling.

    ap_radialprofiles_variable_pa : bool, default False
      Tell AutoProf to rotate radial sampling wedges with the position
      angle profile of the galaxy.

    Notes
    ----------
    :References:
    - 'prof header' (optional)
    - 'prof units' (optional)
    - 'prof data' (optional)
    - 'mask' (optional)
    - 'background'
    - 'center'
    - 'init pa' (optional)

    Returns
    -------
    IMG : ndarray
      Unaltered galaxy image

    results : dict
      No results provided as this method writes its own profile

      .. code-block:: python

        {'prof header': , # Previously extracted SB profile, with extra columns appended for radial profiles (list)
         'prof units': , # Previously extracted SB profile, with extra units appended for radial profiles (dict)
         'prof data': # Previously extracted SB profile, with extra columns appended for radial profiles (dict)

        }

    """

    mask = results["mask"] if "mask" in results else None
    nwedges = (options["ap_radialprofiles_nwedges"]
               if "ap_radialprofiles_nwedges" in options else 4)
    wedgeangles = np.linspace(0, 2 * np.pi * (1 - 1.0 / nwedges), nwedges)

    zeropoint = options["ap_zeropoint"] if "ap_zeropoint" in options else 22.5

    if "prof data" in results:
        R = np.array(results["prof data"]["R"]) / options["ap_pixscale"]
    else:
        startR = (options["ap_sampleinitR"] if "ap_sampleinitR" in options else
                  min(1.0, results["psf fwhm"] / 2))
        endR = (options["ap_sampleendR"] if "ap_sampleendR" in options else
                min(max(IMG.shape) / np.sqrt(2), 3 * results["init R"]))
        R = np.logspace(
            np.log10(startR),
            np.log10(endR),
            int(
                np.log10(endR / startR) /
                np.log10(1 + (options["ap_samplegeometricscale"] if
                              "ap_samplegeometricscale" in options else 0.1))),
        )
    if ("ap_radialprofiles_variable_pa" in options
            and options["ap_radialprofiles_variable_pa"]):
        pa = np.array(results["prof data"]["pa"]) * np.pi / 180
    else:
        pa = np.ones(len(R)) * (
            (options["ap_radialprofiles_pa"] * np.pi /
             180) if "ap_radialprofiles_pa" in options else results["init pa"])
    dat = IMG - results["background"]

    maxwedgewidth = (options["ap_radialprofiles_width"]
                     if "ap_radialprofiles_width" in options else 15.0)
    maxwedgewidth *= np.pi / 180
    if ("ap_radialprofiles_expwidth" in options
            and options["ap_radialprofiles_expwidth"]):
        wedgewidth = maxwedgewidth * np.exp(R / R[-1] - 1)
    else:
        wedgewidth = np.ones(len(R)) * maxwedgewidth

    if wedgewidth[-1] * nwedges > 2 * np.pi:
        logging.warning(
            "%s: Radial sampling wedges are overlapping! %i wedges with a maximum width of %.3f rad"
            % (nwedges, wedgewidth[-1]))

    sb = list([] for i in wedgeangles)
    sbE = list([] for i in wedgeangles)
    avgmedflux = np.inf

    for i in range(len(R)):
        isobandwidth = R[i] * (options["ap_isoband_width"]
                               if "ap_isoband_width" in options else 0.025)
        if (avgmedflux > (results["background noise"] *
                          (options["ap_isoband_start"] if "ap_isoband_start"
                           in options else 2)) or isobandwidth < 0.5):
            isovals = list(
                _iso_extract(
                    dat,
                    R[i],
                    {
                        "ellip": 0,
                        "pa": 0
                    },
                    results["center"],
                    more=True,
                    minN=int(5 * 2 * np.pi / wedgewidth[i]),
                    mask=mask,
                ))
        else:
            isovals = list(
                _iso_between(
                    dat,
                    R[i] - isobandwidth,
                    R[i] + isobandwidth,
                    {
                        "ellip": 0,
                        "pa": 0
                    },
                    results["center"],
                    more=True,
                    mask=mask,
                ))
        isovals[1] -= pa[i]
        avgmedflux = []

        for sa_i in range(len(wedgeangles)):
            aselect = np.abs(Angle_TwoAngles_cos(
                wedgeangles[sa_i], isovals[1])) < (wedgewidth[i] / 2)
            if np.sum(aselect) == 0:
                sb[sa_i].append(99.999)
                sbE[sa_i].append(99.999)
                continue
            medflux = _average(
                isovals[0][aselect],
                options["ap_isoaverage_method"]
                if "ap_isoaverage_method" in options else "median",
            )
            avgmedflux.append(medflux)
            scatflux = _scatter(
                isovals[0][aselect],
                options["ap_isoaverage_method"]
                if "ap_isoaverage_method" in options else "median",
            )
            sb[sa_i].append(
                flux_to_sb(medflux, options["ap_pixscale"], zeropoint
                           ) if medflux > 0 else 99.999)
            sbE[sa_i].append((2.5 * scatflux /
                              (np.sqrt(np.sum(aselect)) * medflux *
                               np.log(10))) if medflux > 0 else 99.999)
        avgmedflux = np.mean(avgmedflux)

    if "prof header" in results:
        newprofheader = results["prof header"]
        newprofunits = results["prof units"]
        newprofdata = results["prof data"]
    else:
        newprofheader = ["R"]
        newprofunits = {"R": "arcsec"}
        newprofdata = {"R": R * options["ap_pixscale"]}

    for sa_i in range(len(wedgeangles)):
        p1, p2 = (
            "SB_rad[%.1f]" % (wedgeangles[sa_i] * 180 / np.pi),
            "SB_rad_e[%.1f]" % (wedgeangles[sa_i] * 180 / np.pi),
        )
        newprofheader.append(p1)
        newprofheader.append(p2)
        newprofunits[p1] = "mag*arcsec^-2"
        newprofunits[p2] = "mag*arcsec^-2"
        newprofdata[p1] = sb[sa_i]
        newprofdata[p2] = sbE[sa_i]

    if "ap_doplot" in options and options["ap_doplot"]:
        Plot_Radial_Profiles(dat, R, sb, sbE, pa, nwedges, wedgeangles,
                             wedgewidth, results, options)

    return IMG, {
        "prof header": newprofheader,
        "prof units": newprofunits,
        "prof data": newprofdata,
    }