def Plot_Galaxy_Image(IMG, results, options): """ Plots an LSB image of the object without anything else drawn above it. Useful for inspecting images for spurious features """ if 'center' in results: center = results['center'] elif 'ap_set_center' in options: center = options['ap_set_center'] elif 'ap_guess_center' in options: center = options['ap_guess_center'] else: center = {'x': IMG.shape[1]/2, 'y': IMG.shape[0]/2} if 'prof data' in results: edge = 1.2*results['prof data']['R'][-1]/options['pixscale'] elif 'init R' in results: edge = 3*results['init R'] elif 'fit R' in results: edge = 2*results['fit R'] else: edge = max(IMG.shape)/2 edge = min([edge, abs(center['x'] - IMG.shape[1]), center['x'], abs(center['y'] - IMG.shape[0]), center['y']]) ranges = [[max(0,int(center['x']-edge)), min(IMG.shape[1],int(center['x']+edge))], [max(0,int(center['y']-edge)), min(IMG.shape[0],int(center['y']+edge))]] LSBImage(IMG[ranges[1][0]:ranges[1][1],ranges[0][0]:ranges[0][1]] - results['background'], results['background noise']) if not ('ap_nologo' in options and options['ap_nologo']): AddLogo(plt.gcf()) plt.savefig('%sclean_image_%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 Plot_PSF_Stars( IMG, stars_x, stars_y, stars_fwhm, psf, results, options, flagstars=None ): LSBImage(IMG - results["background"], results["background noise"]) AddScale(plt.gca(), IMG.shape[0]*options['ap_pixscale']) for i in range(len(stars_fwhm)): plt.gca().add_patch( Ellipse( (stars_x[i], stars_y[i]), 20 * psf, 20 * psf, 0, fill=False, linewidth=1.5, color=autocolours["red1"] if not flagstars is None and flagstars[i] else autocolours["blue1"], ) ) plt.tight_layout() if not ("ap_nologo" in options and options["ap_nologo"]): AddLogo(plt.gcf()) plt.savefig( os.path.join( options["ap_plotpath"] if "ap_plotpath" in options else "", "PSF_Stars_%s.jpg" % options["ap_name"], ), dpi=options["ap_plotdpi"] if "ap_plotdpi" in options else 300, ) plt.close()
def Photutils_Fit(IMG, results, options): """ Function to run the photutils automated isophote analysis on an image. """ dat = IMG - results['background'] 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 = { 'fit R': isolist.sma[1:], 'fit ellip': isolist.eps[1:], 'fit ellip_err': isolist.ellip_err[1:], 'fit pa': isolist.pa[1:], 'fit pa_err': isolist.pa_err[1:], 'auxfile fitlimit': 'fit limit semi-major axis: %.2f pix' % isolist.sma[-1] } if 'ap_doplot' in options and options['ap_doplot']: ranges = [[ max(0, int(results['center']['y'] - res['fit R'][-1] * 1.2)), min(dat.shape[1], int(results['center']['y'] + res['fit R'][-1] * 1.2)) ], [ max(0, int(results['center']['x'] - res['fit R'][-1] * 1.2)), min(dat.shape[0], int(results['center']['x'] + res['fit R'][-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(res['fit R'])): plt.gca().add_patch( Ellipse( (int(res['fit R'][-1] * 1.2), int(res['fit R'][-1] * 1.2)), 2 * res['fit R'][i], 2 * res['fit R'][i] * (1. - res['fit ellip'][i]), res['fit pa'][i] * 180 / np.pi, fill=False, linewidth=0.5, color='r')) if not ('ap_nologo' in options and options['ap_nologo']): AddLogo(plt.gcf()) plt.savefig( '%sfit_ellipse_%s.jpg' % (options['ap_plotpath'] if 'ap_plotpath' in options else '', options['ap_name']), dpi=300) plt.close() return IMG, res
def Plot_Isophote_Init_Ellipse(dat, circ_ellipse_radii, ellip, phase, results, options): ranges = [ [ max(0, int(results["center"]["x"] - circ_ellipse_radii[-1] * 1.5)), min( dat.shape[1], int(results["center"]["x"] + circ_ellipse_radii[-1] * 1.5) ), ], [ max(0, int(results["center"]["y"] - circ_ellipse_radii[-1] * 1.5)), min( dat.shape[0], int(results["center"]["y"] + circ_ellipse_radii[-1] * 1.5) ), ], ] LSBImage( dat[ranges[1][0] : ranges[1][1], ranges[0][0] : ranges[0][1]], results["background noise"], ) AddScale(plt.gca(), (ranges[0][1] - ranges[0][0])*options['ap_pixscale']) plt.gca().add_patch( Ellipse( ( results["center"]["x"] - ranges[0][0], results["center"]["y"] - ranges[1][0], ), 2 * circ_ellipse_radii[-1], 2 * circ_ellipse_radii[-1] * (1.0 - ellip), phase * 180 / np.pi, fill=False, linewidth=1, color=autocolours["blue1"], ) ) plt.plot( [results["center"]["x"] - ranges[0][0]], [results["center"]["y"] - ranges[1][0]], marker="x", markersize=3, color=autocolours["red1"], ) if not ("ap_nologo" in options and options["ap_nologo"]): AddLogo(plt.gcf()) plt.savefig( os.path.join( options["ap_plotpath"] if "ap_plotpath" in options else "", "initialize_ellipse_%s.jpg" % options["ap_name"], ), dpi=options["ap_plotdpi"] if "ap_plotdpi" in options else 300, ) plt.close()
def Mask_Segmentation_Map(IMG, results, options): if 'ap_mask_file' not in options or options['ap_mask_file'] is None: mask = np.zeros(IMG.shape, dtype=bool) else: mask = Read_Image(options['ap_mask_file'], options) if 'center' in results: if mask[int(results['center']['y']), int(results['center']['x'])] > 1.1: mask[mask == mask[int(results['center']['y']), int(results['center']['x'])]] = 0 elif 'ap_set_center' in options: if mask[int(options['ap_set_center']['y']), int(options['ap_set_center']['x'])] > 1.1: mask[mask == mask[int(options['ap_set_center']['y']), int(options['ap_set_center']['x'])]] = 0 elif 'ap_guess_center' in options: if mask[int(options['ap_guess_center']['y']), int(options['ap_guess_center']['x'])] > 1.1: mask[mask == mask[int(options['ap_guess_center']['y']), int(options['ap_guess_center']['x'])]] = 0 elif mask[int(IMG.shape[0] / 2), int(IMG.shape[1] / 2)] > 1.1: mask[mask == mask[int(IMG.shape[0] / 2), int(IMG.shape[1] / 2)]] = 0 # Plot star mask for diagnostic purposes if 'ap_doplot' in options and options['ap_doplot']: bkgrnd = results[ 'background'] if 'background' in results else np.median(IMG) noise = results[ 'background noise'] if 'background noise' in results else iqr( IMG, rng=[16, 84]) / 2 LSBImage(IMG - bkgrnd, noise) showmask = np.copy(mask) showmask[showmask > 1] = 1 showmask[showmask < 1] = np.nan plt.imshow(showmask, origin='lower', cmap='Reds_r', alpha=0.5) plt.tight_layout() if not ('ap_nologo' in options and options['ap_nologo']): AddLogo(plt.gcf()) plt.savefig( '%smask_%s.jpg' % (options['ap_plotpath'] if 'ap_plotpath' in options else '', options['ap_name'])) plt.close() return IMG, {'mask': mask.astype(bool)}
def Plot_Radial_Profiles( dat, R, sb, sbE, pa, nwedges, wedgeangles, wedgewidth, results, options ): zeropoint = options["ap_zeropoint"] if "ap_zeropoint" in options else 22.5 ranges = [ [ max(0, int(results["center"]["x"] - 1.5 * R[-1] - 2)), min(dat.shape[1], int(results["center"]["x"] + 1.5 * R[-1] + 2)), ], [ max(0, int(results["center"]["y"] - 1.5 * R[-1] - 2)), min(dat.shape[0], int(results["center"]["y"] + 1.5 * R[-1] + 2)), ], ] cmap = cm.get_cmap("hsv") colorind = (np.linspace(0, 1 - 1 / nwedges, nwedges) + 0.1) % 1.0 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( os.path.join( options["ap_plotpath"] if "ap_plotpath" in options else "", "radial_profiles_%s.jpg" % 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"], ) AddScale(plt.gca(), (ranges[0][1] - ranges[0][0])*options['ap_pixscale']) cx, cy = ( results["center"]["x"] - ranges[0][0], results["center"]["y"] - ranges[1][0], ) for sa_i in range(len(wedgeangles)): if np.all(pa == pa[0]): plt.gca().add_patch( Wedge( (cx, cy), R[-1], (wedgeangles[sa_i] + pa[0] - wedgewidth[-1] / 2) * 180 / np.pi, (wedgeangles[sa_i] + pa[0] + wedgewidth[-1] / 2) * 180 / np.pi, facecolor=cmap(colorind[sa_i]), linewidth=0, alpha=0.3, ) ) else: 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( os.path.join( options["ap_plotpath"] if "ap_plotpath" in options else "", "radial_profiles_wedges_%s.jpg" % options["ap_name"], ), dpi=options["ap_plotdpi"] if "ap_plotdpi" in options else 300, ) plt.close()
def Plot_Isophote_Fit(dat, sample_radii, parameters, results, options): for i in range(len(parameters)): if not "m" in parameters[i]: parameters[i]["m"] = None Rlim = sample_radii[-1] * ( 1.0 if parameters[-1]["m"] is None else np.exp( sum( np.abs(parameters[-1]["Am"][m]) for m in range(len(parameters[-1]["m"])) ) ) ) ranges = [ [ max(0, int(results["center"]["x"] - Rlim * 1.2)), min(dat.shape[1], int(results["center"]["x"] + Rlim * 1.2)), ], [ max(0, int(results["center"]["y"] - Rlim * 1.2)), min(dat.shape[0], int(results["center"]["y"] + Rlim * 1.2)), ], ] LSBImage( dat[ranges[1][0] : ranges[1][1], ranges[0][0] : ranges[0][1]], results["background noise"], ) AddScale(plt.gca(), (ranges[0][1] - ranges[0][0])*options['ap_pixscale']) for i in range(len(sample_radii)): N = max(15, int(0.9 * 2 * np.pi * sample_radii[i])) theta = np.linspace(0, 2 * np.pi * (1.0 - 1.0 / N), N) theta = np.arctan((1.0 - parameters[i]["ellip"]) * np.tan(theta)) + np.pi * ( np.cos(theta) < 0 ) R = sample_radii[i] * ( 1.0 if parameters[i]["m"] is None else np.exp( sum( parameters[i]["Am"][m] * np.cos(parameters[i]["m"][m] * (theta + parameters[i]["Phim"][m])) for m in range(len(parameters[i]["m"])) ) ) ) X, Y = parametric_SuperEllipse( theta, parameters[i]["ellip"], 2 if parameters[i]["C"] is None else parameters[i]["C"], ) X, Y = Rotate_Cartesian(parameters[i]["pa"], X, Y) X, Y = ( R * X + results["center"]["x"] - ranges[0][0], R * Y + results["center"]["y"] - ranges[1][0], ) plt.plot( list(X) + [X[0]], list(Y) + [Y[0]], linewidth=((i + 1) / len(sample_radii)) ** 2, color=autocolours["red1"], ) if not ("ap_nologo" in options and options["ap_nologo"]): AddLogo(plt.gcf()) plt.savefig( os.path.join( options["ap_plotpath"] if "ap_plotpath" in options else "", "fit_ellipse_%s.jpg" % options["ap_name"], ), dpi=options["ap_plotdpi"] if "ap_plotdpi" in options else 300, ) plt.close()
def _Plot_Isophotes(dat, R, parameters, results, options): for i in range(len(parameters)): if not "m" in parameters[i]: parameters[i]["m"] = None Rlim = R[-1] * ( 1.0 if parameters[-1]["m"] is None else np.exp( sum( np.abs(parameters[-1]["Am"][m]) for m in range(len(parameters[-1]["m"])) ) ) ) ranges = [ [ max(0, int(results["center"]["x"] - Rlim * 1.2)), min(dat.shape[1], int(results["center"]["x"] + Rlim * 1.2)), ], [ max(0, int(results["center"]["y"] - Rlim * 1.2)), min(dat.shape[0], int(results["center"]["y"] + Rlim * 1.2)), ], ] LSBImage( dat[ranges[1][0] : ranges[1][1], ranges[0][0] : ranges[0][1]], results["background noise"], ) AddScale(plt.gca(), (ranges[0][1] - ranges[0][0])*options['ap_pixscale']) fitlim = results["fit R"][-1] if "fit R" in results else np.inf for i in range(len(R)): N = max(15, int(0.9 * 2 * np.pi * R[i])) theta = np.linspace(0, 2 * np.pi * (1.0 - 1.0 / N), N) theta = np.arctan((1.0 - parameters[i]["ellip"]) * np.tan(theta)) + np.pi * ( np.cos(theta) < 0 ) RR = R[i] * ( np.ones(N) if parameters[i]["m"] is None else np.exp( sum( parameters[i]["Am"][m] * np.cos(parameters[i]["m"][m] * (theta + parameters[i]["Phim"][m])) for m in range(len(parameters[i]["m"])) ) ) ) X = RR * np.cos(theta) Y = RR * (1 - parameters[i]["ellip"]) * np.sin(theta) X, Y = ( X * np.cos(parameters[i]["pa"]) - Y * np.sin(parameters[i]["pa"]), X * np.sin(parameters[i]["pa"]) + Y * np.cos(parameters[i]["pa"]), ) X += results["center"]["x"] - ranges[0][0] Y += results["center"]["y"] - ranges[1][0] plt.plot( list(X) + [X[0]], list(Y) + [Y[0]], linewidth=((i + 1) / len(R)) ** 2, color=autocolours["blue1"] if (i % 4 == 0) else autocolours["red1"], linestyle="-" if R[i] < fitlim else "--", ) if not ("ap_nologo" in options and options["ap_nologo"]): AddLogo(plt.gcf()) plt.savefig( os.path.join( options["ap_plotpath"] if "ap_plotpath" in options else "", "photometry_ellipse_%s.jpg" % options["ap_name"], ), dpi=options["ap_plotdpi"] if "ap_plotdpi" in options else 300, ) plt.close()
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 Isophote_Fit_FFT_mean(IMG, results, options): """Fit elliptical isophotes to a galaxy image using FFT coefficients and regularization. Same as the standard isophote fitting routine, except uses less robust mean/std measures. This is only intended for low S/N data where pixels have low integer counts. Parameters ----------------- ap_scale : float, default 0.2 growth scale when fitting isophotes, not the same as *ap_sample---scale*. ap_fit_limit : float, default 2 noise level out to which to extend the fit in units of pixel background noise level. Default is 2, smaller values will end fitting further out in the galaxy image. ap_regularize_scale : float, default 1 scale factor to apply to regularization coupling factor between isophotes. Default of 1, larger values make smoother fits, smaller values give more chaotic fits. Notes ---------- :References: - 'background' - 'background noise' - 'center' - 'psf fwhm' - 'init ellip' - 'init pa' Returns ------- IMG : ndarray Unaltered galaxy image results : dict .. code-block:: python {'fit ellip': , # array of ellipticity values (ndarray) 'fit pa': , # array of PA values (ndarray) 'fit R': , # array of semi-major axis values (ndarray) 'fit ellip_err': , # optional, array of ellipticity error values (ndarray) 'fit pa_err': , # optional, array of PA error values (ndarray) 'auxfile fitlimit': # optional, auxfile message (string) } """ if "ap_scale" in options: scale = options["ap_scale"] else: scale = 0.2 # subtract background from image during processing dat = IMG - results["background"] mask = results["mask"] if "mask" in results else None if not np.any(mask): mask = None # Determine sampling radii ###################################################################### shrink = 0 while shrink < 5: sample_radii = [3 * results["psf fwhm"] / 2] while sample_radii[-1] < (max(IMG.shape) / 2): isovals = _iso_extract( dat, sample_radii[-1], { "ellip": results["init ellip"], "pa": results["init pa"] }, results["center"], more=False, mask=mask, ) if (np.mean(isovals) < (options["ap_fit_limit"] if "ap_fit_limit" in options else 1) * results["background noise"]): break sample_radii.append(sample_radii[-1] * (1.0 + scale / (1.0 + shrink))) if len(sample_radii) < 15: shrink += 1 else: break if shrink >= 5: raise Exception( "Unable to initialize ellipse fit, check diagnostic plots. Possible missed center." ) ellip = np.ones(len(sample_radii)) * results["init ellip"] pa = np.ones(len(sample_radii)) * results["init pa"] logging.debug("%s: sample radii: %s" % (options["ap_name"], str(sample_radii))) # Fit isophotes ###################################################################### perturb_scale = np.array([0.03, 0.06]) regularize_scale = (options["ap_regularize_scale"] if "ap_regularize_scale" in options else 1.0) N_perturb = 5 count = 0 count_nochange = 0 use_center = copy(results["center"]) I = np.array(range(len(sample_radii))) while count < 300 and count_nochange < (3 * len(sample_radii)): # Periodically include logging message if count % 10 == 0: logging.debug("%s: count: %i" % (options["ap_name"], count)) count += 1 np.random.shuffle(I) for i in I: perturbations = [] perturbations.append({"ellip": copy(ellip), "pa": copy(pa)}) perturbations[-1]["loss"] = _FFT_mean_loss( dat, sample_radii, perturbations[-1]["ellip"], perturbations[-1]["pa"], i, use_center, results["background noise"], mask=mask, reg_scale=regularize_scale if count > 4 else 0, name=options["ap_name"], ) for n in range(N_perturb): perturbations.append({"ellip": copy(ellip), "pa": copy(pa)}) if count % 3 in [0, 1]: perturbations[-1]["ellip"][i] = _x_to_eps( _inv_x_to_eps(perturbations[-1]["ellip"][i]) + np.random.normal(loc=0, scale=perturb_scale[0])) if count % 3 in [1, 2]: perturbations[-1]["pa"][i] = ( perturbations[-1]["pa"][i] + np.random.normal( loc=0, scale=perturb_scale[1])) % np.pi perturbations[-1]["loss"] = _FFT_mean_loss( dat, sample_radii, perturbations[-1]["ellip"], perturbations[-1]["pa"], i, use_center, results["background noise"], mask=mask, reg_scale=regularize_scale if count > 4 else 0, name=options["ap_name"], ) best = np.argmin(list(p["loss"] for p in perturbations)) if best > 0: ellip = copy(perturbations[best]["ellip"]) pa = copy(perturbations[best]["pa"]) count_nochange = 0 else: count_nochange += 1 logging.info("%s: Completed isohpote fit in %i itterations" % (options["ap_name"], count)) # detect collapsed center ###################################################################### for i in range(5): if (_inv_x_to_eps(ellip[i]) - _inv_x_to_eps(ellip[i + 1])) > 0.5: ellip[:i + 1] = ellip[i + 1] pa[:i + 1] = pa[i + 1] # Smooth ellip and pa profile ###################################################################### smooth_ellip = copy(ellip) smooth_pa = copy(pa) ellip[:3] = min(ellip[:3]) smooth_ellip = _ellip_smooth(sample_radii, smooth_ellip, 5) smooth_pa = _pa_smooth(sample_radii, smooth_pa, 5) if "ap_doplot" in options and options["ap_doplot"]: ranges = [ [ max(0, int(use_center["x"] - sample_radii[-1] * 1.2)), min(dat.shape[1], int(use_center["x"] + sample_radii[-1] * 1.2)), ], [ max(0, int(use_center["y"] - sample_radii[-1] * 1.2)), min(dat.shape[0], int(use_center["y"] + sample_radii[-1] * 1.2)), ], ] 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', norm = ImageNormalize(stretch=LogStretch())) for i in range(len(sample_radii)): plt.gca().add_patch( Ellipse( (use_center["x"] - ranges[0][0], use_center["y"] - ranges[1][0]), 2 * sample_radii[i], 2 * sample_radii[i] * (1.0 - ellip[i]), pa[i] * 180 / np.pi, fill=False, linewidth=((i + 1) / len(sample_radii))**2, color="r", )) if not ("ap_nologo" in options and options["ap_nologo"]): AddLogo(plt.gcf()) plt.savefig( "%sfit_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() plt.scatter(sample_radii, ellip, color="r", label="ellip") plt.scatter(sample_radii, pa / np.pi, color="b", label="pa/$np.pi$") show_ellip = _ellip_smooth(sample_radii, ellip, deg=5) show_pa = _pa_smooth(sample_radii, pa, deg=5) plt.plot( sample_radii, show_ellip, color="orange", linewidth=2, linestyle="--", label="smooth ellip", ) plt.plot( sample_radii, show_pa / np.pi, color="purple", linewidth=2, linestyle="--", label="smooth pa/$np.pi$", ) # plt.xscale('log') plt.legend() if not ("ap_nologo" in options and options["ap_nologo"]): AddLogo(plt.gcf()) plt.savefig( "%sphaseprofile_%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() # Compute errors ###################################################################### ellip_err = np.zeros(len(ellip)) ellip_err[:2] = np.sqrt(np.sum((ellip[:4] - smooth_ellip[:4])**2) / 4) ellip_err[-1] = np.sqrt(np.sum((ellip[-4:] - smooth_ellip[-4:])**2) / 4) pa_err = np.zeros(len(pa)) pa_err[:2] = np.sqrt(np.sum((pa[:4] - smooth_pa[:4])**2) / 4) pa_err[-1] = np.sqrt(np.sum((pa[-4:] - smooth_pa[-4:])**2) / 4) for i in range(2, len(pa) - 1): ellip_err[i] = np.sqrt( np.sum((ellip[i - 2:i + 2] - smooth_ellip[i - 2:i + 2])**2) / 4) pa_err[i] = np.sqrt( np.sum((pa[i - 2:i + 2] - smooth_pa[i - 2:i + 2])**2) / 4) res = { "fit ellip": ellip, "fit pa": pa, "fit R": sample_radii, "fit ellip_err": ellip_err, "fit pa_err": pa_err, "auxfile fitlimit": "fit limit semi-major axis: %.2f pix" % sample_radii[-1], } return IMG, res
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 Isophote_Initialize(IMG, results, options): """ Determine the global pa and ellipticity for a galaxy. First grow circular isophotes until reaching near the noise floor, then evaluate the phase of the second FFT coefficients and determine the average direction. Then fit an ellipticity for one of the outer isophotes. """ ###################################################################### # Initial attempt to find size of galaxy in image # based on when isophotes SB values start to get # close to the background noise level circ_ellipse_radii = [results['psf fwhm']] allphase = [] dat = IMG - results['background'] while circ_ellipse_radii[-1] < (len(IMG) / 2): circ_ellipse_radii.append(circ_ellipse_radii[-1] * (1 + 0.2)) isovals = _iso_extract(dat, circ_ellipse_radii[-1], 0., 0., results['center'], more=True, sigmaclip=True, sclip_nsigma=3, interp_mask=True) coefs = fft(isovals[0]) allphase.append(coefs[2]) # Stop when at 3 time background noise if np.quantile(isovals[0], 0.8) < (3 * results['background noise'] ) and len(circ_ellipse_radii) > 4: break logging.info('%s: init scale: %f pix' % (options['ap_name'], circ_ellipse_radii[-1])) # Find global position angle. phase = (-Angle_Median(np.angle(allphase[-5:])) / 2) % np.pi # Find global ellipticity test_ellip = np.linspace(0.05, 0.95, 15) test_f2 = [] for e in test_ellip: test_f2.append( sum( list( _fitEllip_loss(e, dat, circ_ellipse_radii[-2] * m, phase, results['center'], results['background noise']) for m in np.linspace(0.8, 1.2, 5)))) ellip = test_ellip[np.argmin(test_f2)] res = minimize(lambda e, d, r, p, c, n: sum( list( _fitEllip_loss(_x_to_eps(e[0]), d, r * m, p, c, n) for m in np.linspace(0.8, 1.2, 5))), x0=_inv_x_to_eps(ellip), args=(dat, circ_ellipse_radii[-2], phase, results['center'], results['background noise']), method='Nelder-Mead', options={ 'initial_simplex': [[_inv_x_to_eps(ellip) - 1 / 15], [_inv_x_to_eps(ellip) + 1 / 15]] }) if res.success: logging.debug( '%s: using optimal ellipticity %.3f over grid ellipticity %.3f' % (options['ap_name'], _x_to_eps(res.x[0]), ellip)) ellip = _x_to_eps(res.x[0]) # Compute the error on the parameters ###################################################################### RR = np.linspace(circ_ellipse_radii[-2] - results['psf fwhm'], circ_ellipse_radii[-2] + results['psf fwhm'], 10) errallphase = [] for rr in RR: isovals = _iso_extract(dat, rr, 0., 0., results['center'], more=True, sigmaclip=True, sclip_nsigma=3, interp_mask=True) coefs = fft(isovals[0]) errallphase.append(coefs[2]) sample_pas = (-np.angle(1j * np.array(errallphase) / np.mean(errallphase)) / 2) % np.pi pa_err = iqr(sample_pas, rng=[16, 84]) / 2 res_multi = map( lambda rrp: minimize(lambda e, d, r, p, c, n: _fitEllip_loss( _x_to_eps(e[0]), d, r, p, c, n), x0=_inv_x_to_eps(ellip), args=(dat, rrp[0], rrp[1], results['center'], results['background noise']), method='Nelder-Mead', options={ 'initial_simplex': [[ _inv_x_to_eps(ellip) - 1 / 15 ], [_inv_x_to_eps(ellip) + 1 / 15]] }), zip(RR, sample_pas)) ellip_err = iqr(list(_x_to_eps(rm.x[0]) for rm in res_multi), rng=[16, 84]) / 2 circ_ellipse_radii = np.array(circ_ellipse_radii) if 'ap_doplot' in options and options['ap_doplot']: ranges = [ [ max(0, int(results['center']['x'] - circ_ellipse_radii[-1] * 1.5)), min(dat.shape[1], int(results['center']['x'] + circ_ellipse_radii[-1] * 1.5)) ], [ max(0, int(results['center']['y'] - circ_ellipse_radii[-1] * 1.5)), min(dat.shape[0], int(results['center']['y'] + circ_ellipse_radii[-1] * 1.5)) ] ] 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())) plt.gca().add_patch( Ellipse((results['center']['x'] - ranges[0][0], results['center']['y'] - ranges[1][0]), 2 * circ_ellipse_radii[-1], 2 * circ_ellipse_radii[-1] * (1. - ellip), phase * 180 / np.pi, fill=False, linewidth=1, color='y')) plt.plot([results['center']['x'] - ranges[0][0]], [results['center']['y'] - ranges[1][0]], marker='x', markersize=3, color='r') if not ('ap_nologo' in options and options['ap_nologo']): AddLogo(plt.gcf()) plt.savefig( '%sinitialize_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() fig, ax = plt.subplots(2, 1, figsize=(6, 6)) plt.subplots_adjust(hspace=0.01, wspace=0.01) ax[0].plot(circ_ellipse_radii[:-1], ((-np.angle(allphase) / 2) % np.pi) * 180 / np.pi, color='k') ax[0].axhline(phase * 180 / np.pi, color='r') ax[0].axhline((phase + pa_err) * 180 / np.pi, color='r', linestyle='--') ax[0].axhline((phase - pa_err) * 180 / np.pi, color='r', linestyle='--') #ax[0].axvline(circ_ellipse_radii[-2], color = 'orange', linestyle = '--') ax[0].set_xlabel('Radius [pix]', fontsize=16) ax[0].set_ylabel('FFT$_{1}$ phase [deg]', fontsize=16) ax[0].tick_params(labelsize=12) ax[1].plot(test_ellip, test_f2, color='k') ax[1].axvline(ellip, color='r') ax[1].axvline(ellip + ellip_err, color='r', linestyle='--') ax[1].axvline(ellip - ellip_err, color='r', linestyle='--') ax[1].set_xlabel('Ellipticity [1 - b/a]', fontsize=16) ax[1].set_ylabel('Loss [FFT$_{2}$/med(flux)]', fontsize=16) ax[1].tick_params(labelsize=14) plt.tight_layout() if not ('ap_nologo' in options and options['ap_nologo']): AddLogo(plt.gcf()) plt.savefig( '%sinitialize_ellipse_optimize_%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() auxmessage = 'global ellipticity: %.3f +- %.3f, pa: %.3f +- %.3f deg, size: %f pix' % ( ellip, ellip_err, PA_shift_convention(phase) * 180 / np.pi, pa_err * 180 / np.pi, circ_ellipse_radii[-2]) return IMG, { 'init ellip': ellip, 'init ellip_err': ellip_err, 'init pa': phase, 'init pa_err': pa_err, 'init R': circ_ellipse_radii[-2], 'auxfile initialize': auxmessage }
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 Isophote_Fit_Forced(IMG, results, options): """ Take isophotal fit from a given profile. """ with open(options['ap_forcing_profile'], 'r') as f: raw = f.readlines() for i, l in enumerate(raw): if l[0] != '#': readfrom = i break header = list(h.strip() for h in raw[readfrom].split(',')) force = dict((h, []) for h in header) for l in raw[readfrom + 2:]: for d, h in zip(l.split(','), header): force[h].append(float(d.strip())) force['pa'] = PA_shift_convention(np.array(force['pa']), deg=True) if 'ap_doplot' in options and options['ap_doplot']: dat = IMG - results['background'] ranges = [ [ max( 0, int(results['center']['y'] - (np.array(force['R'])[-1] / options['ap_pixscale']) * 1.2)), min( dat.shape[1], int(results['center']['y'] + (np.array(force['R'])[-1] / options['ap_pixscale']) * 1.2)) ], [ max( 0, int(results['center']['x'] - (np.array(force['R'])[-1] / options['ap_pixscale']) * 1.2)), min( dat.shape[0], int(results['center']['x'] + (np.array(force['R'])[-1] / options['ap_pixscale']) * 1.2)) ] ] LSBImage(dat[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]], results['background noise']) # plt.imshow(np.clip(dat[ranges[0][0]: ranges[0][1], ranges[1][0]: ranges[1][1]], # a_min = 0,a_max = None), origin = 'lower', cmap = 'Greys_r', norm = ImageNormalize(stretch=LogStretch())) for i in range(0, len(np.array(force['R'])), 2): plt.gca().add_patch( Ellipse( (results['center']['x'] - ranges[0][0], results['center']['y'] - ranges[1][0]), 2 * (np.array(force['R'])[i] / options['ap_pixscale']), 2 * (np.array(force['R'])[i] / options['ap_pixscale']) * (1. - force['ellip'][i]), force['pa'][i], fill=False, linewidth=0.5, color='r')) if not ('ap_nologo' in options and options['ap_nologo']): AddLogo(plt.gcf()) plt.savefig( '%sforcedfit_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() res = { 'fit ellip': np.array(force['ellip']), 'fit pa': np.array(force['pa']) * np.pi / 180, 'fit R': list(np.array(force['R']) / options['ap_pixscale']) } if 'ellip_e' in force and 'pa_e' in force: res['fit ellip_err'] = np.array(force['ellip_e']) res['fit pa_err'] = np.array(force['pa_e']) * np.pi / 180 return IMG, res
def Isophote_Initialize_mean(IMG, results, options): """Fit global elliptical isophote to a galaxy image using FFT coefficients. Same as the default isophote initialization routine, except uses mean/std measures for low S/N applications. Parameters ----------------- ap_fit_limit : float, default 2 noise level out to which to extend the fit in units of pixel background noise level. Default is 2, smaller values will end fitting further out in the galaxy image. Notes ---------- :References: - 'background' - 'background noise' - 'psf fwhm' - 'center' Returns ------- IMG : ndarray Unaltered galaxy image results : dict .. code-block:: python {'init ellip': , # Ellipticity of the global fit (float) 'init pa': ,# Position angle of the global fit (float) 'init R': ,# Semi-major axis length of global fit (float) 'auxfile initialize': # optional, message for aux file to record the global ellipticity and postition angle (string) } """ ###################################################################### # Initial attempt to find size of galaxy in image # based on when isophotes SB values start to get # close to the background noise level circ_ellipse_radii = [results["psf fwhm"]] allphase = [] dat = IMG - results["background"] while circ_ellipse_radii[-1] < (len(IMG) / 2): circ_ellipse_radii.append(circ_ellipse_radii[-1] * (1 + 0.2)) isovals = _iso_extract( dat, circ_ellipse_radii[-1], { "ellip": 0.0, "pa": 0.0 }, results["center"], more=True, ) coefs = fft(isovals[0]) allphase.append(coefs[2]) # Stop when at 3 times background noise if (np.mean(isovals[0]) < (3 * results["background noise"]) and len(circ_ellipse_radii) > 4): break logging.info("%s: init scale: %f pix" % (options["ap_name"], circ_ellipse_radii[-1])) # Find global position angle. phase = (-Angle_Median(np.angle(allphase[-5:])) / 2) % np.pi # (-np.angle(np.mean(allphase[-5:]))/2) % np.pi # Find global ellipticity test_ellip = np.linspace(0.05, 0.95, 15) test_f2 = [] for e in test_ellip: test_f2.append( sum( list( _fitEllip_mean_loss( e, dat, circ_ellipse_radii[-2] * m, phase, results["center"], results["background noise"], ) for m in np.linspace(0.8, 1.2, 5)))) ellip = test_ellip[np.argmin(test_f2)] res = minimize( lambda e, d, r, p, c, n: sum( list( _fitEllip_mean_loss(_x_to_eps(e[0]), d, r * m, p, c, n) for m in np.linspace(0.8, 1.2, 5))), x0=_inv_x_to_eps(ellip), args=( dat, circ_ellipse_radii[-2], phase, results["center"], results["background noise"], ), method="Nelder-Mead", options={ "initial_simplex": [ [_inv_x_to_eps(ellip) - 1 / 15], [_inv_x_to_eps(ellip) + 1 / 15], ] }, ) if res.success: logging.debug( "%s: using optimal ellipticity %.3f over grid ellipticity %.3f" % (options["ap_name"], _x_to_eps(res.x[0]), ellip)) ellip = _x_to_eps(res.x[0]) # Compute the error on the parameters ###################################################################### RR = np.linspace( circ_ellipse_radii[-2] - results["psf fwhm"], circ_ellipse_radii[-2] + results["psf fwhm"], 10, ) errallphase = [] for rr in RR: isovals = _iso_extract(dat, rr, { "ellip": 0.0, "pa": 0.0 }, results["center"], more=True) coefs = fft(isovals[0]) errallphase.append(coefs[2]) sample_pas = (-np.angle(1j * np.array(errallphase) / np.mean(errallphase)) / 2) % np.pi pa_err = np.std(sample_pas) res_multi = map( lambda rrp: minimize( lambda e, d, r, p, c, n: _fitEllip_mean_loss( _x_to_eps(e[0]), d, r, p, c, n), x0=_inv_x_to_eps(ellip), args=(dat, rrp[0], rrp[1], results["center"], results[ "background noise"]), method="Nelder-Mead", options={ "initial_simplex": [ [_inv_x_to_eps(ellip) - 1 / 15], [_inv_x_to_eps(ellip) + 1 / 15], ] }, ), zip(RR, sample_pas), ) ellip_err = np.std(list(_x_to_eps(rm.x[0]) for rm in res_multi)) circ_ellipse_radii = np.array(circ_ellipse_radii) if "ap_doplot" in options and options["ap_doplot"]: ranges = [ [ max(0, int(results["center"]["x"] - circ_ellipse_radii[-1] * 1.5)), min( dat.shape[1], int(results["center"]["x"] + circ_ellipse_radii[-1] * 1.5), ), ], [ max(0, int(results["center"]["y"] - circ_ellipse_radii[-1] * 1.5)), min( dat.shape[0], int(results["center"]["y"] + circ_ellipse_radii[-1] * 1.5), ), ], ] 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())) plt.gca().add_patch( Ellipse( ( results["center"]["x"] - ranges[0][0], results["center"]["y"] - ranges[1][0], ), 2 * circ_ellipse_radii[-1], 2 * circ_ellipse_radii[-1] * (1.0 - ellip), phase * 180 / np.pi, fill=False, linewidth=1, color="y", )) plt.plot( [results["center"]["x"] - ranges[0][0]], [results["center"]["y"] - ranges[1][0]], marker="x", markersize=3, color="r", ) plt.tight_layout() if not ("ap_nologo" in options and options["ap_nologo"]): AddLogo(plt.gcf()) plt.savefig( "%sinitialize_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() fig, ax = plt.subplots(2, 1, figsize=(6, 6)) ax[0].plot( circ_ellipse_radii[:-1], ((-np.angle(allphase) / 2) % np.pi) * 180 / np.pi, color="k", ) ax[0].axhline(phase * 180 / np.pi, color="r") ax[0].axhline((phase + pa_err) * 180 / np.pi, color="r", linestyle="--") ax[0].axhline((phase - pa_err) * 180 / np.pi, color="r", linestyle="--") # ax[0].axvline(circ_ellipse_radii[-2], color = 'orange', linestyle = '--') ax[0].set_xlabel("Radius [pix]") ax[0].set_ylabel("FFT$_{1}$ phase [deg]") ax[1].plot(test_ellip, test_f2, color="k") ax[1].axvline(ellip, color="r") ax[1].axvline(ellip + ellip_err, color="r", linestyle="--") ax[1].axvline(ellip - ellip_err, color="r", linestyle="--") ax[1].set_xlabel("Ellipticity [1 - b/a]") ax[1].set_ylabel("Loss [FFT$_{2}$/med(flux)]") plt.tight_layout() if not ("ap_nologo" in options and options["ap_nologo"]): AddLogo(plt.gcf()) plt.savefig( "%sinitialize_ellipse_optimize_%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() auxmessage = ( "global ellipticity: %.3f +- %.3f, pa: %.3f +- %.3f deg, size: %f pix" % ( ellip, ellip_err, PA_shift_convention(phase) * 180 / np.pi, pa_err * 180 / np.pi, circ_ellipse_radii[-2], )) return IMG, { "init ellip": ellip, "init ellip_err": ellip_err, "init pa": phase, "init pa_err": pa_err, "init R": circ_ellipse_radii[-2], "auxfile initialize": auxmessage, }
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 Plot_Galaxy_Image(IMG, results, options): """Generate a plain image of the galaxy Plots an LSB image of the object without anything else drawn above it. Useful for inspecting images for spurious features. This step can be run at any point in the pipeline. It will take advantage of whatever information has been determined so far. So if it is the first pipeline step, it has little to work from and will simply plot the whole image, if it is run after the isophote initialization step then the plotted image will be cropped to focus on the galaxy. 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. Notes -------------- :References: - 'background' - 'background noise' - 'center' (optional) - 'init R' (optional) Returns ------- IMG : ndarray Unaltered galaxy image results : dict .. code-block:: python {} """ if "center" in results: center = results["center"] elif "ap_set_center" in options: center = options["ap_set_center"] elif "ap_guess_center" in options: center = options["ap_guess_center"] else: center = {"x": IMG.shape[1] / 2, "y": IMG.shape[0] / 2} if "prof data" in results: edge = 1.2 * results["prof data"]["R"][-1] / options["ap_pixscale"] elif "init R" in results: edge = 3 * results["init R"] elif "fit R" in results: edge = 2 * results["fit R"] else: edge = max(IMG.shape) / 2 edge = min( [ edge, abs(center["x"] - IMG.shape[1]), center["x"], abs(center["y"] - IMG.shape[0]), center["y"], ] ) ranges = [ [max(0, int(center["x"] - edge)), min(IMG.shape[1], int(center["x"] + edge))], [max(0, int(center["y"] - edge)), min(IMG.shape[0], int(center["y"] + edge))], ] LSBImage( IMG[ranges[1][0] : ranges[1][1], ranges[0][0] : ranges[0][1]] - results["background"], results["background noise"], ) if not ("ap_nologo" in options and options["ap_nologo"]): AddLogo(plt.gcf()) plt.savefig( "%sclean_image_%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 Mask_Segmentation_Map(IMG, results, options): """Reads the results from other masking routines into AutoProf. Creates a mask from a supplied segmentation map. Such maps typically number each source with an integer. In such a case, AutoProf will check to see if the object center lands on one of these segments, if so it will zero out that source-id before converting the segmentation map into a mask. If the supplied image is just a 0, 1 mask then AutoProf will take it as is. Parameters ----------------- ap_mask_file : string, default None path to fits file which is a mask for the image. Must have the same dimensions as the main image. See Also -------- ap_savemask : bool, default False indicates if the mask should be saved after fitting Notes ---------- :References: - 'background' (optional) - 'background noise' (optional) - 'center' (optional) - 'mask' (optional) Returns ------- IMG : ndarray Unaltered galaxy image results : dict .. code-block:: python {'mask': # 2d mask image with boolean datatype (ndarray) } """ if "ap_mask_file" not in options or options["ap_mask_file"] is None: mask = np.zeros(IMG.shape, dtype=bool) else: mask = Read_Image(options["ap_mask_file"], options) if "center" in results: if mask[int(results["center"]["y"]), int(results["center"]["x"])] > 1.1: mask[mask == mask[int(results["center"]["y"]), int(results["center"]["x"])]] = 0 elif "ap_set_center" in options: if (mask[int(options["ap_set_center"]["y"]), int(options["ap_set_center"]["x"])] > 1.1): mask[mask == mask[int(options["ap_set_center"]["y"]), int(options["ap_set_center"]["x"]), ]] = 0 elif "ap_guess_center" in options: if (mask[int(options["ap_guess_center"]["y"]), int(options["ap_guess_center"]["x"]), ] > 1.1): mask[mask == mask[int(options["ap_guess_center"]["y"]), int(options["ap_guess_center"]["x"]), ]] = 0 elif mask[int(IMG.shape[0] / 2), int(IMG.shape[1] / 2)] > 1.1: mask[mask == mask[int(IMG.shape[0] / 2), int(IMG.shape[1] / 2)]] = 0 if "mask" in results: mask = np.logical_or(mask, results["mask"]) # Plot star mask for diagnostic purposes if "ap_doplot" in options and options["ap_doplot"]: bkgrnd = results[ "background"] if "background" in results else np.median(IMG) noise = (results["background noise"] if "background noise" in results else iqr(IMG, rng=[16, 84]) / 2) LSBImage(IMG - bkgrnd, noise) showmask = np.copy(mask) showmask[showmask > 1] = 1 showmask[showmask < 1] = np.nan plt.imshow(showmask, origin="lower", cmap="Reds_r", alpha=0.5) plt.tight_layout() if not ("ap_nologo" in options and options["ap_nologo"]): AddLogo(plt.gcf()) plt.savefig( "%smask_%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, {"mask": mask.astype(bool)}
def Star_Mask(IMG, results, options): """Masking routine which identifies stars and masks a region around them. Using an edge detecting convolutional filter, sources are identified that are of similar scale as the PSF. These sources are masked with a region roughly 2 times the FWHM of the source. See Also -------- ap_savemask : bool, default False indicates if the mask should be saved after fitting starfind :func:`autoprofutils.SharedFunctions.StarFind` Notes ---------- :References: - 'background' - 'background noise' - 'psf fwhm' - 'center' - 'mask' (optional) Returns ------- IMG : ndarray Unaltered galaxy image results : dict .. code-block:: python {'mask': # 2d mask image with boolean datatype (ndarray) } """ fwhm = results["psf fwhm"] use_center = results["center"] # Find scale of bounding box for galaxy. Stars will only be found within this box smaj = results["fit R"][-1] if "fit R" in results else max(IMG.shape) xbox = int(1.5 * smaj) ybox = int(1.5 * smaj) xbounds = [ max(0, int(use_center["x"] - xbox)), min(int(use_center["x"] + xbox), IMG.shape[1]), ] ybounds = [ max(0, int(use_center["y"] - ybox)), min(int(use_center["y"] + ybox), IMG.shape[0]), ] # Run photutils wrapper for IRAF star finder dat = IMG - results["background"] all_stars = StarFind( dat[ybounds[0]:ybounds[1], xbounds[0]:xbounds[1]], fwhm, results["background noise"], detect_threshold=10, minsep=3, reject_size=3, ) mask = np.zeros(IMG.shape, dtype=bool) # Mask star pixels and area around proportionate to their total flux XX, YY = np.meshgrid(range(IMG.shape[0]), range(IMG.shape[1]), indexing="ij") for x, y, f, d, p in zip( all_stars["x"], all_stars["y"], all_stars["fwhm"], all_stars["deformity"], all_stars["peak"], ): if (np.sqrt((x - (xbounds[1] - xbounds[0]) / 2)**2 + (y - (ybounds[1] - ybounds[0]) / 2)**2) < 10 * results["psf fwhm"]): continue # compute distance of every pixel to the identified star R = np.sqrt((YY - (x + xbounds[0]))**2 + (XX - (y + ybounds[0]))**2) mask[R < (max(np.log10(p / results["background noise"]), 2) * f)] = True if "mask" in results: mask = np.logical_or(mask, results["mask"]) # Plot star mask for diagnostic purposes if "ap_doplot" in options and options["ap_doplot"]: LSBImage( dat[ybounds[0]:ybounds[1], xbounds[0]:xbounds[1]], results["background noise"], ) dat = mask.astype(float)[ybounds[0]:ybounds[1], xbounds[0]:xbounds[1]] dat[dat == 0] = np.nan plt.imshow(dat, origin="lower", cmap="Reds_r", alpha=0.7) plt.tight_layout() plt.savefig( "%smask_%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, {"mask": mask}
def Plot_Axial_Profiles(dat, R, sb, sbE, pa, results, options): zeropoint = options["ap_zeropoint"] if "ap_zeropoint" in options else 22.5 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 = 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, ) 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( os.path.join( options["ap_plotpath"] if "ap_plotpath" in options else "", "axial_profile_q%i_%s.jpg" % (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(dat.shape[1], int(results["center"]["x"] + outto + 2)), ], [ max(0, int(results["center"]["y"] - outto - 2)), min(dat.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"], ) AddScale(plt.gca(), (ranges[0][1] - ranges[0][0])*options['ap_pixscale']) 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) 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( os.path.join( options["ap_plotpath"] if "ap_plotpath" in options else "", "axial_profile_lines_%s.jpg" % options["ap_name"], ), dpi=options["ap_plotdpi"] if "ap_plotdpi" in options else 300, ) plt.close()
def Isophote_Fit_FFT_mean(IMG, results, options): """ Fit isophotes by minimizing the amplitude of the second FFT coefficient, relative to the local median flux. Included is a regularization term which penalizes isophotes for having large differences between parameters of adjacent isophotes. """ if 'ap_scale' in options: scale = options['ap_scale'] else: scale = 0.2 # subtract background from image during processing dat = IMG - results['background'] mask = results['mask'] if 'mask' in results else None if not np.any(mask): mask = None # Determine sampling radii ###################################################################### shrink = 0 while shrink < 5: sample_radii = [3 * results['psf fwhm'] / 2] while sample_radii[-1] < (max(IMG.shape) / 2): isovals = _iso_extract(dat, sample_radii[-1], results['init ellip'], results['init pa'], results['center'], more=False, mask=mask) if np.mean(isovals) < (options['ap_fit_limit'] if 'ap_fit_limit' in options else 1) * results['background noise']: break sample_radii.append(sample_radii[-1] * (1. + scale / (1. + shrink))) if len(sample_radii) < 15: shrink += 1 else: break if shrink >= 5: raise Exception( 'Unable to initialize ellipse fit, check diagnostic plots. Possible missed center.' ) ellip = np.ones(len(sample_radii)) * results['init ellip'] pa = np.ones(len(sample_radii)) * results['init pa'] logging.debug('%s: sample radii: %s' % (options['ap_name'], str(sample_radii))) # Fit isophotes ###################################################################### perturb_scale = np.array([0.03, 0.06]) regularize_scale = options[ 'ap_regularize_scale'] if 'ap_regularize_scale' in options else 1. N_perturb = 5 count = 0 count_nochange = 0 use_center = copy(results['center']) I = np.array(range(len(sample_radii))) while count < 300 and count_nochange < (3 * len(sample_radii)): # Periodically include logging message if count % 10 == 0: logging.debug('%s: count: %i' % (options['ap_name'], count)) count += 1 np.random.shuffle(I) for i in I: perturbations = [] perturbations.append({'ellip': copy(ellip), 'pa': copy(pa)}) perturbations[-1]['loss'] = _FFT_mean_loss( dat, sample_radii, perturbations[-1]['ellip'], perturbations[-1]['pa'], i, use_center, results['background noise'], mask=mask, reg_scale=regularize_scale if count > 4 else 0, name=options['ap_name']) for n in range(N_perturb): perturbations.append({'ellip': copy(ellip), 'pa': copy(pa)}) if count % 3 in [0, 1]: perturbations[-1]['ellip'][i] = _x_to_eps( _inv_x_to_eps(perturbations[-1]['ellip'][i]) + np.random.normal(loc=0, scale=perturb_scale[0])) if count % 3 in [1, 2]: perturbations[-1]['pa'][i] = ( perturbations[-1]['pa'][i] + np.random.normal( loc=0, scale=perturb_scale[1])) % np.pi perturbations[-1]['loss'] = _FFT_mean_loss( dat, sample_radii, perturbations[-1]['ellip'], perturbations[-1]['pa'], i, use_center, results['background noise'], mask=mask, reg_scale=regularize_scale if count > 4 else 0, name=options['ap_name']) best = np.argmin(list(p['loss'] for p in perturbations)) if best > 0: ellip = copy(perturbations[best]['ellip']) pa = copy(perturbations[best]['pa']) count_nochange = 0 else: count_nochange += 1 logging.info('%s: Completed isohpote fit in %i itterations' % (options['ap_name'], count)) # detect collapsed center ###################################################################### for i in range(5): if (_inv_x_to_eps(ellip[i]) - _inv_x_to_eps(ellip[i + 1])) > 0.5: ellip[:i + 1] = ellip[i + 1] pa[:i + 1] = pa[i + 1] # Smooth ellip and pa profile ###################################################################### smooth_ellip = copy(ellip) smooth_pa = copy(pa) ellip[:3] = min(ellip[:3]) smooth_ellip = _ellip_smooth(sample_radii, smooth_ellip, 5) smooth_pa = _pa_smooth(sample_radii, smooth_pa, 5) if 'ap_doplot' in options and options['ap_doplot']: ranges = [[ max(0, int(use_center['x'] - sample_radii[-1] * 1.2)), min(dat.shape[1], int(use_center['x'] + sample_radii[-1] * 1.2)) ], [ max(0, int(use_center['y'] - sample_radii[-1] * 1.2)), min(dat.shape[0], int(use_center['y'] + sample_radii[-1] * 1.2)) ]] 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', norm = ImageNormalize(stretch=LogStretch())) for i in range(len(sample_radii)): plt.gca().add_patch( Ellipse((use_center['x'] - ranges[0][0], use_center['y'] - ranges[1][0]), 2 * sample_radii[i], 2 * sample_radii[i] * (1. - ellip[i]), pa[i] * 180 / np.pi, fill=False, linewidth=((i + 1) / len(sample_radii))**2, color='r')) if not ('ap_nologo' in options and options['ap_nologo']): AddLogo(plt.gcf()) plt.savefig( '%sfit_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() plt.scatter(sample_radii, ellip, color='r', label='ellip') plt.scatter(sample_radii, pa / np.pi, color='b', label='pa/$np.pi$') show_ellip = _ellip_smooth(sample_radii, ellip, deg=5) show_pa = _pa_smooth(sample_radii, pa, deg=5) plt.plot(sample_radii, show_ellip, color='orange', linewidth=2, linestyle='--', label='smooth ellip') plt.plot(sample_radii, show_pa / np.pi, color='purple', linewidth=2, linestyle='--', label='smooth pa/$np.pi$') #plt.xscale('log') plt.legend() if not ('ap_nologo' in options and options['ap_nologo']): AddLogo(plt.gcf()) plt.savefig( '%sphaseprofile_%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() # Compute errors ###################################################################### ellip_err = np.zeros(len(ellip)) ellip_err[:2] = np.sqrt(np.sum((ellip[:4] - smooth_ellip[:4])**2) / 4) ellip_err[-1] = np.sqrt(np.sum((ellip[-4:] - smooth_ellip[-4:])**2) / 4) pa_err = np.zeros(len(pa)) pa_err[:2] = np.sqrt(np.sum((pa[:4] - smooth_pa[:4])**2) / 4) pa_err[-1] = np.sqrt(np.sum((pa[-4:] - smooth_pa[-4:])**2) / 4) for i in range(2, len(pa) - 1): ellip_err[i] = np.sqrt( np.sum((ellip[i - 2:i + 2] - smooth_ellip[i - 2:i + 2])**2) / 4) pa_err[i] = np.sqrt( np.sum((pa[i - 2:i + 2] - smooth_pa[i - 2:i + 2])**2) / 4) res = { 'fit ellip': ellip, 'fit pa': pa, 'fit R': sample_radii, 'fit ellip_err': ellip_err, 'fit pa_err': pa_err, 'auxfile fitlimit': 'fit limit semi-major axis: %.2f pix' % sample_radii[-1] } return IMG, res
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 EllipseModel_General(IMG, results, options): zeropoint = options['ap_zeropoint'] if 'ap_zeropoint' in options else 22.5 CHOOSE = np.array(results['prof data']['SB_e']) < 0.5 R = np.array(results['prof data']['R'])[CHOOSE] / options['ap_pixscale'] sb = UnivariateSpline(np.log10(R), np.array(results['prof data']['SB'])[CHOOSE], ext=3, s=0) pa = UnivariateSpline(np.log10(R), np.array(results['prof data']['pa'])[CHOOSE] * np.pi / 180, ext=3, s=0) q = UnivariateSpline(np.log10(R), 1 - np.array(results['prof data']['ellip'])[CHOOSE], ext=3, s=0) ranges = [[ max(0, int(results['center']['x'] - R[-1] - 2)), min(IMG.shape[1], int(results['center']['x'] + R[-1] + 2)) ], [ max(0, int(results['center']['y'] - R[-1] - 2)), min(IMG.shape[0], int(results['center']['y'] + R[-1] + 2)) ]] XX, YY = np.meshgrid( np.arange(ranges[0][1] - ranges[0][0], dtype=float), np.arange(ranges[1][1] - ranges[1][0], dtype=np.float32)) XX -= results['center']['x'] - float(ranges[0][0]) YY -= results['center']['y'] - float(ranges[1][0]) MM = np.zeros(XX.shape, dtype=np.float32) Prox = np.zeros(XX.shape, dtype=np.float32) + np.inf WINDOW = [[0, XX.shape[0]], [0, XX.shape[1]]] for r in reversed( np.logspace( np.log10(R[0] / 2), np.log10(R[-1]), int( len(R) * 2 * (option['ap_ellipsemodel_resolution'] if 'ap_ellipsemodel_resolution' in options else 1)))): if r < (R[-1] / 2): WINDOW = [[ max( 0, int(results['center']['y'] - float(ranges[1][0]) - r * 1.5)), min( XX.shape[0], int(results['center']['y'] - float(ranges[1][0]) + r * 1.5)) ], [ max( 0, int(results['center']['x'] - float(ranges[0][0]) - r * 1.5)), min( XX.shape[1], int(results['center']['x'] - float(ranges[0][0]) + r * 1.5)) ]] RR = np.sqrt((XX[WINDOW[0][0]:WINDOW[0][1],WINDOW[1][0]:WINDOW[1][1]]*np.cos(-pa(np.log10(r))) - YY[WINDOW[0][0]:WINDOW[0][1],WINDOW[1][0]:WINDOW[1][1]]*np.sin(-pa(np.log10(r))))**2 + \ ((XX[WINDOW[0][0]:WINDOW[0][1],WINDOW[1][0]:WINDOW[1][1]]*np.sin(-pa(np.log10(r))) + YY[WINDOW[0][0]:WINDOW[0][1],WINDOW[1][0]:WINDOW[1][1]]*np.cos(-pa(np.log10(r))))/np.clip(q(np.log10(r)),a_min = 0.03,a_max = 1))**2) D = np.abs(RR - r) CLOSE = D < Prox[WINDOW[0][0]:WINDOW[0][1], WINDOW[1][0]:WINDOW[1][1]] if np.any(CLOSE): MM[WINDOW[0][0]:WINDOW[0][1], WINDOW[1][0]:WINDOW[1][1]][CLOSE] = sb(np.log10(RR[CLOSE])) Prox[WINDOW[0][0]:WINDOW[0][1], WINDOW[1][0]:WINDOW[1][1]][CLOSE] = D[CLOSE] MM = 10**(-(MM - zeropoint - 5 * np.log10(options['ap_pixscale'])) / 2.5) RR = np.sqrt((XX * np.cos(-pa(np.log10(R[-1]))) - YY * np.sin(-pa(np.log10(R[-1]))))**2 + ((XX * np.sin(-pa(np.log10(R[-1]))) + YY * np.cos(-pa(np.log10(R[-1])))) / np.clip(q(np.log10(R[-1])), a_min=0.03, a_max=1))**2) MM[RR > R[-1]] = 0 Model = np.zeros(IMG.shape, dtype=np.float32) Model[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]] = MM header = fits.Header() hdul = fits.HDUList([fits.PrimaryHDU(header=header), fits.ImageHDU(Model)]) hdul.writeto('%s%s_genmodel.fits' % (options['ap_plotpath'] if 'ap_plotpath' in options else '', options['ap_name']), overwrite=True) if 'ap_doplot' in options and options['ap_doplot']: ranges = [[ max(0, int(results['center']['x'] - R[-1] * 1.2)), min(IMG.shape[1], int(results['center']['x'] + R[-1] * 1.2)) ], [ max(0, int(results['center']['y'] - R[-1] * 1.2)), min(IMG.shape[0], int(results['center']['y'] + R[-1] * 1.2)) ]] plt.figure(figsize=(7, 7)) autocmap.set_under('k', alpha=0) showmodel = Model[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]].copy() showmodel[showmodel > 0] += np.max(showmodel) / (10**3.5) - np.min( showmodel[showmodel > 0]) plt.imshow(showmodel, origin='lower', cmap=autocmap, norm=ImageNormalize(stretch=LogStretch(), clip=False)) plt.axis('off') plt.subplots_adjust(left=0.03, right=0.97, top=0.97, bottom=0.05) if not ('ap_nologo' in options and options['ap_nologo']): AddLogo(plt.gcf()) plt.savefig( '%sellipsemodel_gen_%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() residual = IMG[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]] - results[ 'background'] - Model[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]] plt.figure(figsize=(7, 7)) plt.imshow(residual, origin='lower', cmap='PuBu', vmin=np.quantile(residual, 0.0001), vmax=0) plt.imshow(np.clip(residual, a_min=0, a_max=np.quantile(residual, 0.9999)), origin='lower', cmap=autocmap, norm=ImageNormalize(stretch=LogStretch(), clip=False), interpolation='none', clim=[1e-5, None]) plt.axis('off') plt.subplots_adjust(left=0.03, right=0.97, top=0.97, bottom=0.05) if not ('ap_nologo' in options and options['ap_nologo']): AddLogo(plt.gcf()) plt.savefig( '%sellipseresidual_gen_%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() if 'ap_paperplots' in options and options['ap_paperplots']: plt.figure(figsize=(7, 7)) LSBImage( IMG[ranges[1][0]:ranges[1][1], ranges[0][0]:ranges[0][1]] - results['background'], results['background noise']) plt.axis('off') plt.subplots_adjust(left=0.03, right=0.97, top=0.97, bottom=0.05) plt.savefig( '%sclearimage_%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, {'ellipse model': Model}