def display_bins_generators(xBin, yBin, velBin, x, y, angle=None, **kwargs): """ Displays a Voronoi binned map starting from the original coordinates of the pixels and the coordinates of the *generators* (not the centroids!) of the Voronoi tessellation, as provided in output e.g. by my voronoi_2d_binning routine. NB: When possible, insted of this routine, one should use the more general display_bins routine which uses the binNumber of every spaxel insted of the Voronoi generators. :param xBin: coordinates of the *generators* of the Voronoi tessellation :param yBin: :param velBin: :param x: coordinates of the original spaxels :param y: :param angle: :param kwargs: :return: image """ warnings.warn('When possible, usage of the routine display_bins is preferred to display_bins_generators') if not (xBin.size == yBin.size == velBin.size): raise ValueError('The vectors (XBIN, YBIN, VEL) must have the same size') if x.size != y.size: raise ValueError('The vectors (X, Y) must have the same size') if x.size < xBin.size: raise ValueError('The vectors (X, Y) cannot be smaller than (XBIN, YBIN)') # Perform a Voronoi tessellation starting from the coordinates # of the generators and the coordinates of the original pixels # binNum = np.argmin((x[:, np.newaxis] - xBin)**2 + (y[:, np.newaxis] - yBin)**2, axis=1) f = display_pixels(x, y, velBin[binNum], angle=angle, **kwargs) return f
def sn_plot(galname, sndata): fig = plt.figure(figsize=(7, 5)) fig.add_subplot(1, 1, 1) ax1 = display_pixels(sndata[0, :], sndata[1, :], sndata[2, :], colorbar=1, label='S/N') ax1.axes.set_xlabel(r'SPAXEL') ax1.axes.set_ylabel(r'SPAXEL') fig.savefig(galname + '_sn.pdf', facecolor='None')
def dual_plot(grid, SX, SY, SVEL, GX, GY, GVEL, stellar_pa, halpha_pa): fig, ax = plt.subplots(1,2, figsize=(10,3)) # display_pixels doesn't like obj-orientated matplotlib plt.sca(ax[0]) display_pixels(SX, SY, SVEL, colorbar=True) ax[0].set_title('STAR-PA: '+str(stellar_pa)) # Plotting 0 and 90 deg lines. rad = np.sqrt(np.max(grid[0]**2 + grid[1]**2)) ang = [0, -np.pi] ax[0].plot(rad*np.cos(ang), rad*np.sin(ang), linestyle='dashed', color='gray', linewidth=3, alpha=0.2) ax[1].plot(rad*np.cos(ang), rad*np.sin(ang), linestyle='dashed', color='gray', linewidth=3, alpha=0.2) ang = [-np.pi/2, -np.pi*3/2] ax[0].plot(rad*np.cos(ang), rad*np.sin(ang), linestyle='dashed', color='gray', linewidth=3, alpha=0.2) ax[1].plot(rad*np.cos(ang), rad*np.sin(ang), linestyle='dashed', color='gray', linewidth=3, alpha=0.2) # Plotting pa lines. if stellar_pa != np.nan: rad = np.sqrt(np.max(grid[0]**2 + grid[1]**2)) ang = [0,np.pi] + np.radians(stellar_pa) ax[0].plot(rad*np.cos(-ang), rad*np.sin(-ang), 'k--', linewidth=3) # Zero-velocity line ax[0].plot(-rad*np.sin(-ang), rad*np.cos(-ang), color="limegreen", linewidth=3) # Major axis PA # limits and labels. ax[0].set_xlim([np.min(grid[0]), np.max(grid[0])]) ax[0].set_ylim([np.min(grid[1]), np.max(grid[1])]) ax[0].set_xlabel('arcsec') ax[0].set_ylabel('arcsec') plt.sca(ax[1]) display_pixels(GX, GY, GVEL, colorbar=True) ax[1].set_title('GAS-PA: '+str(halpha_pa)) # Plotting pa lines. if halpha_pa != np.nan: rad = np.sqrt(np.max(grid[0]**2 + grid[1]**2)) ang = [0,np.pi] + np.radians(halpha_pa) ax[1].plot(rad*np.cos(-ang), rad*np.sin(-ang), 'k--', linewidth=3) # Zero-velocity line ax[1].plot(-rad*np.sin(-ang), rad*np.cos(-ang), color="limegreen", linewidth=3) # Major axis PA # limits and labels. ax[1].set_xlim([np.min(grid[0]), np.max(grid[0])]) ax[1].set_ylim([np.min(grid[1]), np.max(grid[1])]) ax[1].set_xlabel('arcsec') ax[1].set_ylabel('arcsec') return
def display_bins(x, y, bin_num, vel_bin, **kwargs): assert bin_num.dtype.kind == 'i', "bin_num must be integer" assert x.size == y.size == bin_num.size, "The vectors (x, y, bin_num) must have the same size" assert np.unique( bin_num ).size == vel_bin.size, "vel_bin size does not match number of bins" img = display_pixels(x, y, vel_bin[bin_num], **kwargs) return img
def display_bins(x, y, binNum, velBin): if not (x.size == y.size == binNum.size): raise ValueError("The vectors (x, y, binNum) must have the same size") if np.uniq(binNum).size != velBin.size: raise ValueError("velBin size does not match number of bins") img = display_pixels(x, y, velBin[binNum]) return img
def display_bins_generators(xBin, yBin, velBin, x, y, angle=None, **kwargs): """ Displays a Voronoi binned map starting from the original coordinates of the pixels and the coordinates of the *generators* (not the centroids!) of the Voronoi tessellation, as provided in output e.g. by my voronoi_2d_binning routine. NB: When possible, instead of this routine, one should use the more general display_bins routine which uses the binNumber of every spaxel instead of the Voronoi generators. :param xBin: coordinates of the *generators* of the Voronoi tessellation :param yBin: :param velBin: :param x: coordinates of the original spaxels :param y: :param angle: :param kwargs: :return: image """ warnings.warn( 'When possible, usage of the routine display_bins is preferred to display_bins_generators' ) assert xBin.size == yBin.size == velBin.size, 'The vectors (XBIN, YBIN, VEL) must have the same size' assert x.size == y.size, 'The vectors (X, Y) must have the same size' assert x.size >= xBin.size, 'The vectors (X, Y) cannot be smaller than (XBIN, YBIN)' # Perform a Voronoi tessellation starting from the coordinates # of the generators and the coordinates of the original pixels # if x.size < 1e4: binNum = np.argmin((x[:, None] - xBin)**2 + (y[:, None] - yBin)**2, axis=1) else: # use for loop to reduce memory usage binNum = np.zeros(x.size, dtype=int) for j, (xj, yj) in enumerate(zip(x, y)): binNum[j] = np.argmin((xj - xBin)**2 + (yj - yBin)**2) f = display_pixels(x, y, velBin[binNum], angle=angle, **kwargs) return f
def display_bins_generators(xBin, yBin, velBin, x, y, angle=None, **kwargs): """ Displays a Voronoi binned map starting from the original coordinates of the pixels and the coordinates of the *generators* (not the centroids!) of the Voronoi tessellation, as provided in output e.g. by my voronoi_2d_binning routine. NB: When possible, insted of this routine, one should use the more general display_bins routine which uses the binNumber of every spaxel insted of the Voronoi generators. :param xBin: coordinates of the *generators* of the Voronoi tessellation :param yBin: :param velBin: :param x: coordinates of the original spaxels :param y: :param angle: :param kwargs: :return: image """ warnings.warn( 'When possible, usage of the routine display_bins is preferred to display_bins_generators' ) if not (xBin.size == yBin.size == velBin.size): raise ValueError( 'The vectors (XBIN, YBIN, VEL) must have the same size') if x.size != y.size: raise ValueError('The vectors (X, Y) must have the same size') if x.size < xBin.size: raise ValueError( 'The vectors (X, Y) cannot be smaller than (XBIN, YBIN)') # Perform a Voronoi tessellation starting from the coordinates # of the generators and the coordinates of the original pixels # binNum = np.argmin( (x[:, np.newaxis] - xBin)**2 + (y[:, np.newaxis] - yBin)**2, axis=1) f = display_pixels(x, y, velBin[binNum], angle=angle, **kwargs) return f
def lambdaR_e_calc(xpix, ypix, flux, vel_pix, disp_pix, eff_rad, ellip, phot_ang, sig_psf=0., n=2., sigma_e=False, plot=False, vmin=None, vmax=None, dmin=None, dmax=None): ''' Given 2D kinematic data, this routine calculates a value for the luminosity-weighted stellar angular momentum parameter lambdaR within one effective radius (see Equation (2) from Graham et al. 2018 = G18). Plotting requires the routine display_pixels available from Michele Cappellari's webpage: http://purl.org/cappellari/software The required quanitites are: * xpix: Array of pixel x-coordinates, spatial scale should be arcsec. * ypix: Array of pixel y-coordinates. * flux: Array of pixel flux. * vel_pix: Array of pixel velocity (should be corrected to the systemic velocity). * disp_pix: Array of pixel velocity dispersion (must be corrected for the instrumental dispersion). * eff_rad: Circular effective radius in arcsec. * ellip: Ellipticity of the half-light ellipse = 1 - axis ratio. * phot_ang: Photometric major axis East of North (E = 90 degrees = 9 o'clock on the sky). Optional: * sigma_e: If True, will calculate the effective velocity dispersion within the half-light ellipse (see Equation (3) from G18). ################################################################################################# Optional PSF Correction lambdaR_e is affected by smearing of the velocity field due to the finite PSF. In Graham et al. 2018, we presented an analytic correction to account for this effect (see Subsection 3.8 and Appendix C). The correction should be applied for regular rotators where the semi major axis is larger than the dispersion of the PSF (sig_PSF) and the Sersic index falls within the range 0.5 < n < 6.5. * sig_psf: Dispersion of the PSF (= FWHM/2.355, assumed to be a Gaussian). Default = 0 (i.e. no correction) * n: Sersic index. Default = 2 ################################################################################################# Plotting commands: * plot: If true, the routine will plot maps of the velocity, velocity dispersion and flux indicating which pixels were included in the calculation. Default = False * vmin: Minimum velocity for plotting. * vmax: Maximum velocity for plotting. * dmin: Minimum velocity dispersion for plotting. * dmax: Maximum velocity dispersion for plotting. ################################################################################################# Returns: * lambdaR: Returns lambdaR_e within the half-light ellipse. * error: Returns the error calculated using the equations given in Figure C4 of G18. * frac: Returns the fraction of pixels within the half-light ellipse with disp_pix = 0 (See Subsection 3.6 and Appendix B of G18). * sigma_e: If sigma_e option is True, then sigma_e is returned. ''' xpix, ypix, flux, vel_pix, disp_pix = np.ravel(xpix), np.ravel(ypix), np.ravel(flux), np.ravel(vel_pix), np.ravel(disp_pix) # Rotate kinematic data to lie along the major axis theta = np.radians(90-phot_ang) xpix_rot = xpix * np.cos(theta) - ypix * np.sin(theta) ypix_rot = xpix * np.sin(theta) + ypix * np.cos(theta) # Select pixels contained wth half-light ellipse w = (xpix_rot ** 2 * (1-ellip) + ypix_rot ** 2 / (1-ellip) < eff_rad ** 2) # Calculate fraction of half-light ellipse covered by pixels try: frac = (vel_pix.size - sum(np.isnan(vel_pix)))/sum(w) except ZeroDivisionError: frac = 1 if frac < 0.85: print('Warning: data covers less than 85% of the half-light ellipse') r = np.sqrt(xpix ** 2 + ypix ** 2) # radius vector # Calculate lambdaR_e, Equation 2, G18 try: num = flux[w] * r[w] * abs(vel_pix[w]) # numerator of lambdaR denom = flux[w] * r[w] * np.sqrt(vel_pix[w] ** 2 + disp_pix[w] ** 2) # denominator of lambdaR except IndexError: print('xpix:', xpix.shape, 'ypix:', ypix.shape, 'flux:', flux.shape, 'vel_pix:', vel_pix.shape, 'disp_pix:', disp_pix.shape) print('Check that input sizes match: all quantities should have sizes equal to the number of pixels') try: lambdaR = np.around(sum(num)/sum(denom), 3) except ZeroDivisionError: print('Denominator == 0') lambdaR = -999. # Measure fraction of pixels with sigma=0 try: w1 = disp_pix[w] == 0 frac = round(sum(w1) / len(w1), 3) except ZeroDivisionError: frac = 0 # Calculate beam correction semi_maj_axis = eff_rad/np.sqrt(1-ellip) # Semi-major axis sig_psf_re_ratio = sig_psf/semi_maj_axis # Ratio between sig_PSF and semi-major axis if sig_psf_re_ratio > 1: print('Semi-major axis is smaller than sig_PSF: not correcting') if (n < 0.5) | (n > 6.5): print('Sersic index is outside the range 0.5 < n < 6.5: not correcting') # Equation 5, G18 lambdaR_true = np.around(lambdaR * (1 + (n - 2) * (0.26 * sig_psf_re_ratio)) * (1 + (sig_psf_re_ratio / 0.47) ** 1.76) ** 0.84, 3) if lambdaR_true > 1: print('Warning: corrected value of lambdaR_e is greater than 1') error = np.around([-0.08*n*sig_psf_re_ratio, 0.03*sig_psf_re_ratio], 3) # Figure C4, G18 if lambdaR_true + error[1] < lambdaR: error[1] = lambdaR - lambdaR_true lambdaR = "{0:.4f}".format(lambdaR_true) if sigma_e: try: num = flux[w] * (vel_pix[w] ** 2 + disp_pix[w] ** 2) # numerator of sigma_e denom = flux[w] # denominator of sigma_e except IndexError: print('xpix:', xpix.shape, 'ypix:', ypix.shape, 'flux:', flux.shape, 'vel_pix:', vel_pix.shape, 'disp_pix:', disp_pix.shape) print('Check that input sizes match: all quantities should have sizes equal to the number of pixels') try: sigma_e = "{0:.1f}".format(np.sqrt(sum(num)/sum(denom))) except ZeroDivisionError: print('Denominator == 0') sigma_e = -999 if plot: # Plot velocity, velocity dispersion and flux plt.figure(figsize=(15, 6)) plt.subplot(1, 3, 1) ax = plt.gca() if not vmax: vmax = max(vel_pix) if not vmin: vmin = min(vel_pix) display_pixels(xpix, ypix, vel_pix, vmin=vmin, vmax=vmax, alpha=0.2) im = display_pixels(xpix[w], ypix[w], vel_pix[w], vmin=vmin, vmax=vmax) ellipse1 = patches.Ellipse(xy=(0, 0), width=2 * eff_rad * np.sqrt(1 - ellip), fill=False, height=2 * eff_rad / np.sqrt(1 - ellip), angle=phot_ang, color='red', linewidth=3) ax.add_patch(ellipse1) ellipse2 = patches.Ellipse(xy=(0, 0), width=2 * sig_psf, fill=False, height=2 * sig_psf, angle=0, color='grey', linewidth=3) ax.add_patch(ellipse2) ax.set_xlim(-1.1 * eff_rad / np.sqrt(1 - ellip), 1.1 * eff_rad / np.sqrt(1 - ellip)) ax.set_ylim(-1.1 * eff_rad / np.sqrt(1 - ellip), 1.1 * eff_rad / np.sqrt(1 - ellip)) cbar = plt.colorbar(im[0], ax=ax, fraction=0.046, pad=0.04) cbar.ax.set_ylabel('km/s', rotation=270, fontsize=18) ax.set_title('Velocity') plt.subplot(1, 3, 2) ax = plt.gca() if not dmax: dmax = max(disp_pix) if not dmin: dmin = min(disp_pix) display_pixels(xpix, ypix, disp_pix, vmin=dmin, vmax=dmax, alpha=0.2) im = display_pixels(xpix[w], ypix[w], disp_pix[w], vmin=dmin, vmax=dmax) ellipse1 = patches.Ellipse(xy=(0, 0), width=2 * eff_rad * np.sqrt(1 - ellip), fill=False, height=2 * eff_rad / np.sqrt(1 - ellip), angle=phot_ang, color='red', linewidth=3) ax.add_patch(ellipse1) ellipse2 = patches.Ellipse(xy=(0, 0), width=2 * sig_psf, fill=False, height=2 * sig_psf, angle=0, color='grey', linewidth=3) ax.add_patch(ellipse2) ax.set_xlim(-1.1 * eff_rad / np.sqrt(1 - ellip), 1.1 * eff_rad / np.sqrt(1 - ellip)) ax.set_ylim(-1.1 * eff_rad / np.sqrt(1 - ellip), 1.1 * eff_rad / np.sqrt(1 - ellip)) cbar = plt.colorbar(im[0], ax=ax, fraction=0.046, pad=0.04) cbar.ax.set_ylabel('km/s', rotation=270, fontsize=18) ax.set_title('Velocity Dispersion') plt.subplot(1, 3, 3) ax = plt.gca() display_pixels(xpix, ypix, flux, alpha=0.2) im = display_pixels(xpix[w], ypix[w], flux[w]) ellipse1 = patches.Ellipse(xy=(0, 0), width=2 * eff_rad * np.sqrt(1 - ellip), fill=False, height=2 * eff_rad / np.sqrt(1 - ellip), angle=phot_ang, color='red', linewidth=3) ax.add_patch(ellipse1) ellipse2 = patches.Ellipse(xy=(0, 0), width=2 * sig_psf, fill=False, height=2 * sig_psf, angle=0, color='grey', linewidth=3) ax.add_patch(ellipse2) ax.set_xlim(-1.1 * eff_rad / np.sqrt(1 - ellip), 1.1 * eff_rad / np.sqrt(1 - ellip)) ax.set_ylim(-1.1 * eff_rad / np.sqrt(1 - ellip), 1.1 * eff_rad / np.sqrt(1 - ellip)) plt.colorbar(im[0], ax=ax, fraction=0.046, pad=0.04) plt.title('Flux') plt.suptitle('$\lambda_{R_e}$ = ' + str(lambdaR), fontsize=24) plt.tight_layout(h_pad=1.0) plt.show() if sigma_e: return lambdaR, error, frac, sigma_e else: return lambdaR, error, frac