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, {}
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 }
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 }
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}
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, {}
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, {}
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, {}
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, }