def ppxf_kinematics_example_sauron(): # Read a galaxy spectrum and define the wavelength range # dir = 'spectra/' file = dir + 'NGC4550_SAURON.fits' hdu = pyfits.open(file) gal_lin = hdu[0].data h1 = hdu[0].header lamRange1 = h1['CRVAL1'] + np.array([0.,h1['CDELT1']*(h1['NAXIS1']-1)]) FWHM_gal = 4.2 # SAURON has an instrumental resolution FWHM of 4.2A. # If the galaxy is at a significant redshift (z > 0.03), one would need to apply # a large velocity shift in PPXF to match the template to the galaxy spectrum. # This would require a large initial value for the velocity (V > 1e4 km/s) # in the input parameter START = [V,sig]. This can cause PPXF to stop! # The solution consists of bringing the galaxy spectrum roughly to the # rest-frame wavelength, before calling PPXF. In practice there is no # need to modify the spectrum before the usual LOG_REBIN, given that a # red shift corresponds to a linear shift of the log-rebinned spectrum. # One just needs to compute the wavelength range in the rest-frame # and adjust the instrumental resolution of the galaxy observations. # This is done with the following three commented lines: # # z = 1.23 # Initial estimate of the galaxy redshift # lamRange1 = lamRange1/(1+z) # Compute approximate restframe wavelength range # FWHM_gal = FWHM_gal/(1+z) # Adjust resolution in Angstrom galaxy, logLam1, velscale = util.log_rebin(lamRange1, gal_lin) galaxy = galaxy/np.median(galaxy) # Normalize spectrum to avoid numerical issues noise = galaxy*0 + 0.0049 # Assume constant noise per pixel here # Read the list of filenames from the Single Stellar Population library # by Vazdekis (1999, ApJ, 513, 224). A subset of the library is included # for this example with permission. See http://purl.org/cappellari/software # for suggestions of more up-to-date stellar libraries. # vazdekis = glob.glob(dir + 'Rbi1.30z*.fits') vazdekis.sort() FWHM_tem = 1.8 # Vazdekis spectra have a resolution FWHM of 1.8A. # Extract the wavelength range and logarithmically rebin one spectrum # to the same velocity scale of the SAURON galaxy spectrum, to determine # the size needed for the array which will contain the template spectra. # hdu = pyfits.open(vazdekis[0]) ssp = hdu[0].data h2 = hdu[0].header lamRange2 = h2['CRVAL1'] + np.array([0.,h2['CDELT1']*(h2['NAXIS1']-1)]) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates = np.empty((sspNew.size,len(vazdekis))) # Convolve the whole Vazdekis library of spectral templates # with the quadratic difference between the SAURON and the # Vazdekis instrumental resolution. Logarithmically rebin # and store each template as a column in the array TEMPLATES. # Quadratic sigma difference in pixels Vazdekis --> SAURON # The formula below is rigorously valid if the shapes of the # instrumental spectral profiles are well approximated by Gaussians. # FWHM_dif = np.sqrt(FWHM_gal**2 - FWHM_tem**2) sigma = FWHM_dif/2.355/h2['CDELT1'] # Sigma difference in pixels for j in range(len(vazdekis)): hdu = pyfits.open(vazdekis[j]) ssp = hdu[0].data ssp = ndimage.gaussian_filter1d(ssp,sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates[:,j] = sspNew/np.median(sspNew) # Normalizes templates # The galaxy and the template spectra do not have the same starting wavelength. # For this reason an extra velocity shift DV has to be applied to the template # to fit the galaxy spectrum. We remove this artificial shift by using the # keyword VSYST in the call to PPXF below, so that all velocities are # measured with respect to DV. This assume the redshift is negligible. # In the case of a high-redshift galaxy one should de-redshift its # wavelength to the rest frame before using the line below (see above). # c = 299792.458 dv = (logLam2[0]-logLam1[0])*c # km/s vel = 450. # Initial estimate of the galaxy velocity in km/s z = np.exp(vel/c) - 1 # Relation between velocity and redshift in pPXF goodPixels = util.determine_goodpixels(logLam1, lamRange2, z) # Here the actual fit starts. The best fit is plotted on the screen. # Gas emission lines are excluded from the pPXF fit using the GOODPIXELS keyword. # start = [vel, 180.] # (km/s), starting guess for [V,sigma] t = clock() pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=True, moments=4, degree=4, vsyst=dv) print("Formal errors:") print(" dV dsigma dh3 dh4") print("".join("%8.2g" % f for f in pp.error*np.sqrt(pp.chi2))) print('Elapsed time in PPXF: %.2f s' % (clock() - t))
def ppxf_population_example_sdss(): # Read SDSS DR8 galaxy spectrum taken from here http://www.sdss3.org/dr8/ # The spectrum is *already* log rebinned by the SDSS DR8 # pipeline and log_rebin should not be used in this case. # file = 'spectra/NGC3522_SDSS.fits' hdu = pyfits.open(file) t = hdu[1].data z = float(hdu[1].header["Z"]) # SDSS redshift estimate # Only use the wavelength range in common between galaxy and stellar library. # mask = (t.field('wavelength') > 3540) & (t.field('wavelength') < 7409) galaxy = t[mask].field('flux')/np.median(t[mask].field('flux')) # Normalize spectrum to avoid numerical issues wave = t[mask].field('wavelength') # The noise level is chosen to give Chi^2/DOF=1 without regularization (REGUL=0) # noise = galaxy*0 + 0.01528 # Assume constant noise per pixel here # The velocity step was already chosen by the SDSS pipeline # and we convert it below to km/s # c = 299792.458 # speed of light in km/s velscale = np.log(wave[1]/wave[0])*c FWHM_gal = 2.76 # SDSS has an instrumental resolution FWHM of 2.76A. templates, lamRange_temp = setup_spectral_library(velscale, FWHM_gal) # The galaxy and the template spectra do not have the same starting wavelength. # For this reason an extra velocity shift DV has to be applied to the template # to fit the galaxy spectrum. We remove this artificial shift by using the # keyword VSYST in the call to PPXF below, so that all velocities are # measured with respect to DV. This assume the redshift is negligible. # In the case of a high-redshift galaxy one should de-redshift its # wavelength to the rest frame before using the line below as described # in PPXF_KINEMATICS_EXAMPLE_SAURON. # c = 299792.458 dv = (np.log(lamRange_temp[0])-np.log(wave[0]))*c # km/s vel = c*z # Initial estimate of the galaxy velocity in km/s goodpixels = util.determine_goodpixels(np.log(wave),lamRange_temp,vel) # Here the actual fit starts. The best fit is plotted on the screen. # # IMPORTANT: Ideally one would like not to use any polynomial in the fit # as the continuum shape contains important information on the population. # Unfortunately this is often not feasible, due to small calibration # uncertainties in the spectral shape. To avoid affecting the line strength of # the spectral features, we exclude additive polynomials (DEGREE=-1) and only use # multiplicative ones (MDEGREE=10). This is only recommended for population, not # for kinematic extraction, where additive polynomials are always recommended. # start = [vel, 180.] # (km/s), starting guess for [V,sigma] # See the pPXF documentation for the keyword REGUL, # for an explanation of the following two lines # templates /= np.median(templates) # Normalizes templates by a scalar regul_err = 0.004 # Desired regularization error t = clock() plt.clf() plt.subplot(211) pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodpixels, plot=True, moments=4, degree=-1, vsyst=dv, clean=False, mdegree=10, regul=1./regul_err) # When the two numbers below are the same, the solution is the smoothest # consistent with the observed spectrum. # print('Desired Delta Chi^2: %.4g' % np.sqrt(2*goodpixels.size)) print('Current Delta Chi^2: %.4g' % ((pp.chi2 - 1)*goodpixels.size)) print('Elapsed time in PPXF: %.2f s' % (clock() - t)) plt.subplot(212) s = templates.shape print(s) weights = pp.weights.reshape(s[1],s[2])/pp.weights.sum() plt.imshow(np.rot90(weights), interpolation='nearest', cmap='gist_heat', aspect='auto', extent=(np.log10(1.0), np.log10(17.7828), -1.9, 0.45)) plt.colorbar() plt.title("Mass Fraction") plt.xlabel("log$_{10}$ Age (Gyr)") plt.ylabel("[M/H]") plt.tight_layout() plt.show() vazdekis = glob.glob('miles_models/Mun1.30*.fits') '''
def ppxf_kinematics_example_sauron(): # Read a galaxy spectrum and define the wavelength range # file = 'spectra/NGC4550_SAURON.fits' hdu = fits.open(file) gal_lin = hdu[0].data h1 = hdu[0].header lamRange1 = h1['CRVAL1'] + np.array( [0., h1['CDELT1'] * (h1['NAXIS1'] - 1)]) FWHM_gal = 4.2 # SAURON has an instrumental resolution FWHM of 4.2A. # If the galaxy is at a significant redshift (z > 0.03), one would need to apply # a large velocity shift in PPXF to match the template to the galaxy spectrum. # This would require a large initial value for the velocity (V > 1e4 km/s) # in the input parameter START = [V,sig]. This can cause PPXF to stop! # The solution consists of bringing the galaxy spectrum roughly to the # rest-frame wavelength, before calling PPXF. In practice there is no # need to modify the spectrum before the usual LOG_REBIN, given that a # red shift corresponds to a linear shift of the log-rebinned spectrum. # One just needs to compute the wavelength range in the rest-frame # and adjust the instrumental resolution of the galaxy observations. # This is done with the following three commented lines: # # z = 1.23 # Initial estimate of the galaxy redshift # lamRange1 = lamRange1/(1+z) # Compute approximate restframe wavelength range # FWHM_gal = FWHM_gal/(1+z) # Adjust resolution in Angstrom galaxy, logLam1, velscale = util.log_rebin(lamRange1, gal_lin) galaxy = galaxy / np.median( galaxy) # Normalize spectrum to avoid numerical issues noise = galaxy * 0 + 0.0047 # Assume constant noise per pixel here # Read the list of filenames from the Single Stellar Population library # by Vazdekis (2010, MNRAS, 404, 1639) http://miles.iac.es/. A subset # of the library is included for this example with permission vazdekis = glob.glob('miles_models/Mun1.30Z*.fits') FWHM_tem = 2.51 # Vazdekis+10 spectra have a constant resolution FWHM of 2.51A. # Extract the wavelength range and logarithmically rebin one spectrum # to the same velocity scale of the SAURON galaxy spectrum, to determine # the size needed for the array which will contain the template spectra. # hdu = fits.open(vazdekis[0]) ssp = hdu[0].data h2 = hdu[0].header lamRange2 = h2['CRVAL1'] + np.array( [0., h2['CDELT1'] * (h2['NAXIS1'] - 1)]) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates = np.empty((sspNew.size, len(vazdekis))) # Convolve the whole Vazdekis library of spectral templates # with the quadratic difference between the SAURON and the # Vazdekis instrumental resolution. Logarithmically rebin # and store each template as a column in the array TEMPLATES. # Quadratic sigma difference in pixels Vazdekis --> SAURON # The formula below is rigorously valid if the shapes of the # instrumental spectral profiles are well approximated by Gaussians. # FWHM_dif = np.sqrt(FWHM_gal**2 - FWHM_tem**2) sigma = FWHM_dif / 2.355 / h2['CDELT1'] # Sigma difference in pixels for j, file in enumerate(vazdekis): hdu = fits.open(file) ssp = hdu[0].data ssp = ndimage.gaussian_filter1d(ssp, sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates # The galaxy and the template spectra do not have the same starting wavelength. # For this reason an extra velocity shift DV has to be applied to the template # to fit the galaxy spectrum. We remove this artificial shift by using the # keyword VSYST in the call to PPXF below, so that all velocities are # measured with respect to DV. This assume the redshift is negligible. # In the case of a high-redshift galaxy one should de-redshift its # wavelength to the rest frame before using the line below (see above). # c = 299792.458 dv = (logLam2[0] - logLam1[0]) * c # km/s vel = 450. # Initial estimate of the galaxy velocity in km/s z = np.exp(vel / c) - 1 # Relation between velocity and redshift in pPXF goodPixels = util.determine_goodpixels(logLam1, lamRange2, z) # Here the actual fit starts. The best fit is plotted on the screen. # Gas emission lines are excluded from the pPXF fit using the GOODPIXELS keyword. # start = [vel, 180., 0, 0] # (km/s), starting guess for [V,sigma] t = clock() pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=True, moments=4, degree=4, vsyst=dv) print("Formal errors:") print(" dV dsigma dh3 dh4") print("".join("%8.2g" % f for f in pp.error * np.sqrt(pp.chi2))) print('Elapsed time in PPXF: %.2f s' % (clock() - t))
def ppxf_nifs_kinematics(newgal=None, resid=None, centers=None, coords=[0, 0]): file_dir = path.dirname(path.realpath(__file__)) # path of this procedure # Read a galaxy spectrum and define the wavelength range file = file_dir + '/spectra/pgc12557_combined.fits' # '/spectra/NGC4550_SAURON.fits' # my current file location hdu = fits.open(file) gal_lin = hdu[1].data # gal_lin = hdu[0].data h1 = hdu[ 1].header # I need to use 1st extension header (0=general, 1=science, 2=variance, 3=data quality flags) lamRange1 = h1['CRVAL3'] + np.array( [0., h1['CD3_3'] * (h1['NAXIS3'] - 1)]) # [ 19971.86914062 24319.31070422] print(lamRange1, 'l1') # print(gal_lin[0][20]) all 0s # print((gal_lin[300][35])) NOT all 0s! # len(gal_lin) = 2040, len(gal_lin[0]) = 69, len(gal_lin[0][1]) = 71 # 2040 --> NAXIS 3, 69 --> NAXIS2, 71 --> NAXIS1 # There's a len 2040 spectrum at each gal_lin[:,x,y] --> gal_lin[:, x, y] is an array len(2040) starting at # lamRange1[0] and finishing at lamRange1[1], with each pixel in between separated by h1['CD3_3']. # CUT SPECTRUM TO WAVELENGTH RANGE 2.26 - 2.42 low_lim = 22600. up_lim = 24200. cut1 = int( (low_lim - lamRange1[0]) / h1['CD3_3'] ) # num pixels between pix 1 & pix corresponding to 2.26 microns cut2 = int( (up_lim - lamRange1[0]) / h1['CD3_3'] ) # num pixels between pix 1 & pix corresponding to 2.42 microns # print(cut1, cut2, 'cuts') # 1232.62354281, 1983.04191009 --> int(cut1) = 1232, int(cut2) = 1983 gal_lin = gal_lin[cut1: cut2] # cut gal_lin spectrum to new wavelength range start = h1['CRVAL3'] + h1['CD3_3'] * cut1 stop = h1['CRVAL3'] + h1['CD3_3'] * cut2 lamRange1 = [start, stop ] # redefine lamRange1 to correspond to new wavelength range print(lamRange1, 'l1, cut') # len(gal_lin) is now NOT 2040 but 1983 - 1233 = 750 FWHM_gal = 4.2 # SAURON has an instrumental resolution FWHM of 4.2A. # BUCKET: do I need this? If so what for? # If the galaxy is at significant redshift, one should bring the galaxy # spectrum roughly to the rest-frame wavelength, before calling pPXF # (See Sec2.4 of Cappellari 2017). In practice there is no # need to modify the spectrum in any way, given that a red shift # corresponds to a linear shift of the log-rebinned spectrum. # One just needs to compute the wavelength range in the rest-frame # and adjust the instrumental resolution of the galaxy observations. # This is done with the following three commented lines: # # z = 1.23 # Initial estimate of the galaxy redshift # lamRange1 = lamRange1/(1+z) # Compute approximate restframe wavelength range # FWHM_gal = FWHM_gal/(1+z) # Adjust resolution in Angstrom # There's a len 2040 spectrum at each gal_lin[:,x,y] x = coords[0] y = coords[1] # if newgal is None: galaxy, logLam1, velscale = util.log_rebin( lamRange1, gal_lin[:, x, y]) # no input velscale --> fcn returns it print(len(galaxy), 'len gal') # 750 now because of cut to gal_lin! print(np.median(galaxy)) galaxy = galaxy / np.median( galaxy) # Normalize spectrum to avoid numerical issues # else: # galaxy, logLam1, velscale = util.log_rebin(lamRange1, newgal) # newgal is the spectrum at coords x, y # basically bootstrap the noise! # Do one fit with flat noise, then save the best fit spectrum and the residuals. # Then, iterate ~200 times. For these iterations, set bias = 0.0. Each iteration, for each pixel, use the spectrum # value as the center of a gaussian and use the residuals at that pixel value as the width of the gaussian. Draw # from the resultant distribution to make the new noise. For each iteration, save the output V, sigma, h3, h4, and # print each spectrum so that we can see it evolving (it should look more like a real spectrum, rather than smooth # curve without lines) noise = np.full_like(galaxy, 0.0047) # Assume constant noise per pixel here # MEANTIME: why is output velocity close to systemic insted of close to 0, if I'm dealing with redshift already? # SET bias = 0 # print('shape', noise.shape) # 751, # print(galaxy.shape) # 751, # Read the list of filenames from the Single Stellar Population library # by Vazdekis (2010, MNRAS, 404, 1639) http://miles.iac.es/. A subset # of the library is included for this example with permission vazdekis = glob.glob( file_dir + '/veltemps/*.fits') # '/miles_models/Mun1.30Z*.fits') # my new # BUCKET: what are FWHM of spectra in the veltemps library?? FWHM_tem = 2.51 # Vazdekis+10 spectra have a constant resolution FWHM of 2.51A. # BUCKET: what spectral sampling compared to galaxy do we want for templates?? # velscale_ratio = 2 # 1.23 # 2 # adopts 2x higher spectral sampling for templates than for galaxy # PROBLEM!! If velscale_ratio is not integer, we get issue later because we slice a list with velscale_ratio # so need to change velscale? But it's set by util.log_rebin originally! Only need this if oversampling the # templates, which I'm not doing # Extract the wavelength range and logarithmically rebin one spectrum # to a velocity scale 2x smaller than the SAURON galaxy spectrum, to determine # the size needed for the array which will contain the template spectra. # print(vazdekis[0], 'template name') hdu = fits.open( vazdekis[0] ) # do for just one template to determine size needed for array containing all templates ssp = hdu[ 1].data # was hdu[0], but that's generic header rather than science header h2 = hdu[1].header # was hdu[0] lamRange2 = h2['CRVAL1'] + np.array([0., h2['CD1_1'] * (h2['NAXIS1'] - 1) ]) # BUCKET want NAXIS - 1? print(lamRange2, 'l2') # [ 20628.29 24291.89] # print((lamRange2[1] - lamRange2[0])/h2['CD1_1'], 'num of steps in lam2') # 1720. # print(len(ssp), 'len ssp') # 1721 sspNew, logLam2, velscale_temp = util.log_rebin( lamRange2, ssp, velscale=velscale) # /velscale_ratio) # print(len(sspNew), 'len sspnew') # 622 hmmmm NEED THIS TO BE >= (len(galaxy)=750) # FIXED, now 1791 templates = np.empty((sspNew.size, len(vazdekis))) # Convolve the whole Vazdekis library of spectral templates # with the quadratic difference between the SAURON and the # Vazdekis instrumental resolution. Logarithmically rebin # and store each template as a column in the array TEMPLATES. # Quadratic sigma difference in pixels Vazdekis --> SAURON # The formula below is rigorously valid if the shapes of the # instrumental spectral profiles are well approximated by Gaussians. # FWHM_dif = np.sqrt(FWHM_gal**2 - FWHM_tem**2) # sigma = FWHM_dif/2.355/h2['CDELT1'] # Sigma difference in pixels sigma = FWHM_dif / 2.355 / h2['CD1_1'] # Sigma difference in pixels for j, file in enumerate( vazdekis ): # now for each template file; so why do the thing above with just 1?? hdu = fits.open(file) # ssp = hdu[0].data ssp = hdu[1].data ssp = ndimage.gaussian_filter1d(ssp, sigma) # ndimage.gaussian_filter takes input array (ssp) and filters it. Sigma = standard deviation of Gaussian kernel # used to filter the array # note: discrete convolution is defined (for any 2 arrays a, v): (a*v)[n] == sum m(-inf to inf) of a[m]*v[n-m] # where a is the original curve and v is the gaussian # print(len(ssp)) # 1721 --> currently by default being resampled to be 116, want it to be resampled to be 71 sspNew, logLam2, velscale_temp = util.log_rebin( lamRange2, ssp, velscale=velscale) # /velscale_ratio) # print(velscale_temp, 'vt') # 78.82967746 # print(velscale, 'v') # 78.82967746 # print(len(sspNew)) # need this to be >= len(galaxy) # now 1791 templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates # print(len(templates[0])) # len(templates)=29, len(templates[0] = 19) # The galaxy and the template spectra do not have the same starting wavelength. # For this reason an extra velocity shift DV has to be applied to the template # to fit the galaxy spectrum. We remove this artificial shift by using the # keyword VSYST in the call to PPXF below, so that all velocities are # measured with respect to DV. This assume the redshift is negligible. # In the case of a high-redshift galaxy one should de-redshift its # wavelength to the rest frame before using the line below (see above). # c = 299792.458 dv = (logLam2[0] - logLam1[0]) * c # km/s ''' if velscale_ratio > 1: dv = (np.mean(logLam2[:velscale_ratio]) - logLam1[0])*c # km/s else: dv = (logLam2[0] - logLam1[0])*c # km/s ''' z = 0.016561 # z = 0.0015 # Initial redshift estimate of the galaxy goodPixels = util.determine_goodpixels(logLam1, lamRange2, z) # Here the actual fit starts. The best fit is plotted on the screen. # Gas emission lines are excluded from the pPXF fit using the GOODPIXELS keyword. # vel = c * np.log(1 + z) # eq.(8) of Cappellari (2017) start = [vel, 200.] # [vel, 200.] # (km/s), starting guess for [V, sigma] t = clock() # print(galaxy.shape[0]) # = len(galaxy) = 750 # print(goodPixels) # print(max(noise[:, x, y]), min(noise[:, x, y]), np.median(noise[:, x, y]), np.mean(noise[:, x, y])) # 0.00106575 -2.77079e-05 4.62628e-05 5.89877e-05 if newgal is None: pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=True, moments=4, degree=4, vsyst=dv, velscale_ratio=1, bias=0.) # velscale_ratio=velscale_ratio) stuff = pp.bestfit, pp.galaxy, pp.sol, goodPixels else: pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=True, moments=4, degree=4, vsyst=dv, velscale_ratio=1, bias=0.) # velscale_ratio=velscale_ratio) pp_new = ppxf(templates, newgal, noise, velscale, start, goodpixels=goodPixels, plot=False, moments=4, degree=4, vsyst=dv, velscale_ratio=1, bias=0.) # velscale_ratio=velscale_ratio) # pp.plot() # Plot best fit and gas lines x_ax = np.arange(galaxy.size) plt.plot(x_ax, pp.galaxy, 'k') plt.plot(x_ax, newgal, 'b') stuff = pp_new.bestfit, pp_new.galaxy, pp_new.sol print("Formal errors:") print(" dV dsigma dh3 dh4") print("".join("%8.2g" % f for f in pp.error * np.sqrt(pp.chi2))) print('Elapsed time in pPXF: %.2f s' % (clock() - t)) # If the galaxy is at significant redshift z and the wavelength has been # de-redshifted with the three lines "z = 1.23..." near the beginning of # this procedure, the best-fitting redshift is now given by the following # commented line (equation 2 of Cappellari et al. 2009, ApJ, 704, L34; # http://adsabs.harvard.edu/abs/2009ApJ...704L..34C) # # print('Best-fitting redshift z:', (z + 1)*(1 + pp.sol[0]/c) - 1) return stuff # output_spectrum, output_noise # for use in noise_in
def ppxf_nifs_kinematics(): file_dir = path.dirname(path.realpath(__file__)) # path of this procedure # Read a galaxy spectrum and define the wavelength range # file = file_dir + '/spectra/pgc12557_combined.fits' # '/spectra/NGC4550_SAURON.fits' # my current file location hdu = fits.open(file) gal_lin = hdu[1].data # gal_lin = hdu[0].data # h1 = hdu[0].header h1 = hdu[ 1].header # I need to use 1st extension header (0=general, 1=science, 2=variance, 3=data quality flags) ''' print(h1) BITPIX = -32 / array data type NAXIS = 3 / number of array dimensions NAXIS1 = 71 NAXIS2 = 69 NAXIS3 = 2040 PCOUNT = 0 / number of parameters GCOUNT = 1 / number of groups EXTNAME = 'SCI ' / Extension name EXTVER = 1 / Extension version INHERIT = F / Inherits global header ORIGIN = 'NOAO-IRAF FITS Image Kernel July 2003' / FITS file originator OBJECT = 'PGC12557' / Name of the object observed DISPAXIS= 3 / Dispersion axis PIXSCALE= 0.05 / Pixel scale in arcsec/pixel CTYPE1 = 'LINEAR ' / coordinate type for the dispersion axis CTYPE2 = 'LINEAR ' / coordinate type for the spatial axis DC-FLAG = 0 / CD1_1 = 0.05 / CD2_2 = 0.05 / DCLOG1 = 'Transform' WCSDIM = 3 / CRVAL1 = 2.987 CRPIX1 = 68. CRPIX2 = 2. WAT1_001= 'wtype=linear axtype=xi' / WAT2_001= 'wtype=linear axtype=eta' / CTYPE3 = 'LINEAR ' / WAT3_001= 'wtype=linear axtype=wave' / CRVAL3 = 19971.869140625 / CD3_3 = 2.1321439743 / AIRMASS = 1.374 CRPIX3 = 1. LTM1_1 = 1. LTM2_2 = 1. LTM3_3 = 1. WAT0_001= 'system=world' END ''' ''' # NOTE: Copied from util.log_rebin(): lamRange: two elements vector containing the central wavelength of the first and last pixels in the spectrum, which is assumed to have constant wavelength scale! E.g. from the values in the standard FITS keywords: LAMRANGE = CRVAL1 + [0, CDELT1*(NAXIS1 - 1)]. It must be LAMRANGE[0] < LAMRANGE[1]. ''' # lamRange1 = h1['CRVAL1'] + np.array([0., h1['CDELT1']*(h1['NAXIS1'] - 1)]) # original # lamRange1 = h1['CRVAL3'] + np.array([0., h1['CD3_3']*(h1['CRPIX3'])]) # [ 19971.86914062 19974.0012846 ] # if I use CRPIX3, probably don't want CRPIX3 - 1 because CRPIX3 = 1., and need lamRange1[0] < lamRange1[1] lamRange1 = h1['CRVAL3'] + np.array( [0., h1['CD3_3'] * (h1['NAXIS3'] - 1)]) # [ 19971.86914062 24319.31070422] print(lamRange1, 'l1') # print(gal_lin[0][20]) all 0s # print((gal_lin[300][35])) NOT all 0s! # print(len(lamRange1)) # len(lamRange1) = 2, # len(gal_lin) = 2040, len(gal_lin[0]) = 69, len(gal_lin[0][1]) = 71 # 2040 --> NAXIS 3, 69 --> NAXIS2, 71 --> NAXIS1 # HMM: want gal_lin to be spectrum itself, which should just be 1d array # There's a len 2040 spectrum at each gal_lin[:,x,y] # CUT SPECTRUM TO WAVELENGTH RANGE 2.26 - 2.42 # SO: gal_lin is an array len(2040) starting at lamRange1[0] and finishing at lamRange1[1], with each pixel in # between separated by h1['CD3_3']. So find [22600 - lamRange1[0]] / CD3_3, and that should give the number of # pixels between pixel 1 and the pixel corresponding roughly to 2.26 microns. Then find [24200 - lamRange1[1]] / # CD3_3, which should give the number of pixels between pixel 1 and the pixel corresponding to 2.42 microns. start = 22600. stop = 24200. cut1 = (start - lamRange1[0]) / h1['CD3_3'] cut2 = (stop - lamRange1[0]) / h1['CD3_3'] # print(cut1, cut2, 'cuts') # 1232.62354281, 1983.04191009 gal_lin = gal_lin[int(cut1):int(cut2)] # [1233:1983] # start1 = h1['CRVAL3'] + h1['CD3_3'] * int(cut1) # stop1 = h1['CRVAL3'] + h1['CD3_3'] * int(cut2) # print(start1, stop1, 'me') lamRange1 = [ h1['CRVAL3'] + h1['CD3_3'] * int(cut1), h1['CRVAL3'] + h1['CD3_3'] * int(cut2) ] print(lamRange1, 'l1, cut') # len(gal_lin) is now NOT 2040 but 1983 - 1233 = 750 FWHM_gal = 4.2 # SAURON has an instrumental resolution FWHM of 4.2A. # BUCKET: do I need this? If so what for? # If the galaxy is at significant redshift, one should bring the galaxy # spectrum roughly to the rest-frame wavelength, before calling pPXF # (See Sec2.4 of Cappellari 2017). In practice there is no # need to modify the spectrum in any way, given that a red shift # corresponds to a linear shift of the log-rebinned spectrum. # One just needs to compute the wavelength range in the rest-frame # and adjust the instrumental resolution of the galaxy observations. # This is done with the following three commented lines: # # z = 1.23 # Initial estimate of the galaxy redshift # lamRange1 = lamRange1/(1+z) # Compute approximate restframe wavelength range # FWHM_gal = FWHM_gal/(1+z) # Adjust resolution in Angstrom # There's a len 2040 spectrum at each gal_lin[:,x,y] x = 33 y = 35 galaxy, logLam1, velscale = util.log_rebin( lamRange1, gal_lin[:, x, y]) # no input velscale --> function returns print(len(galaxy), 'len gal') # 750 now because of cut to gal_lin! galaxy = galaxy / np.median( galaxy) # Normalize spectrum to avoid numerical issues # print(galaxy) # BUCKET: constant noise/pix good assumption for me? If so what value do I use? TRY our noise! Maybe trust more?! # basically bootstrap the noise! # Do one fit with flat noise, then save the best fit spectrum and the residuals. # Then, iterate ~200 times. For these iterations, set bias = 0.0. Each iteration, for each pixel, use the spectrum # value as the center of a gaussian and use the residuals at that pixel value as the width of the gaussian. Draw # from the resultant distribution to make the new noise. For each iteration, save the output V, sigma, h3, h4, and # print each spectrum so that we can see it evolving (it should look more like a real spectrum, rather than smooth # curve without lines) noise = np.full_like(galaxy, 0.0047) # Assume constant noise per pixel here # MEANTIME: why is output velocity close to systemic insted of close to 0, if I'm dealing with redshift already? # SET bias = 0 # # print(noise.shape) # 751, # print(galaxy.shape) # 751, # Read the list of filenames from the Single Stellar Population library # by Vazdekis (2010, MNRAS, 404, 1639) http://miles.iac.es/. A subset # of the library is included for this example with permission vazdekis = glob.glob( file_dir + '/veltemps/*.fits') # '/miles_models/Mun1.30Z*.fits') # my new # BUCKET: what are FWHM of spectra in the veltemps library?? FWHM_tem = 2.51 # Vazdekis+10 spectra have a constant resolution FWHM of 2.51A. # BUCKET: what spectral sampling compared to galaxy do we want for templates?? # velscale_ratio = 2 # 1.23 # 2 # adopts 2x higher spectral sampling for templates than for galaxy # PROBLEM!! If velscale_ratio is not integer, we get issue later because we slice a list with velscale_ratio # so need to change velscale? But it's set by util.log_rebin originally! Only need this if oversampling the # templates, which I'm not doing # Extract the wavelength range and logarithmically rebin one spectrum # to a velocity scale 2x smaller than the SAURON galaxy spectrum, to determine # the size needed for the array which will contain the template spectra. # print(vazdekis[0], 'template name') hdu = fits.open( vazdekis[0] ) # do for just one template to determine size needed for array containing all templates ssp = hdu[ 1].data # was hdu[0], but that's generic header rather than science header h2 = hdu[1].header # was hdu[0] ''' for line in h2: print(line, h2[line]) XTENSION IMAGE BITPIX -32 NAXIS 1 NAXIS1 1721 PCOUNT 0 GCOUNT 1 EXTNAME SCI EXTVER 1 INHERIT False ORIGIN NOAO-IRAF FITS Image Kernel July 2003 OBJECT BD-01 3097 DATE 2015-02-15T13:32:06 IRAF-TLM 2015-02-15T13:32:30 NFPAD 2015-02-14T14:40:41 GEM-TLM 2015-02-14T14:40:41 DISPAXIS 1 PIXSCALE 0.043 NSCUTSEC [1:2040,1145:1213] NSCUTSPC 13 FIXPIX Feb 14 8:44 Bad pixel file is tmpdq20234_650 CTYPE1 LINEAR CD1_1 2.13 CTYPE2 LINEAR CD2_2 0.04570113 DCLOG1 Transform DC-FLAG 0 WCSDIM 3 CRVAL1 20628.29 CRPIX1 1.0 CRPIX2 -28.0 WAXMAP01 1 0 0 29 0 0 WAT1_001 wtype=linear axtype=wave WAT2_001 wtype=linear axtype=eta CTYPE3 LINEAR WAT3_001 wtype=linear axtype=xi CRVAL3 1.751 CD3_3 0.103 LTV2 -29.0 LTM1_1 1.0 LTM2_2 1.0 LTM3_3 1.0 WAT0_001 system=image EXPTIME 25.0 IMCMB001 xatfbrsnN20070508S0187.fits[SCI] IMCMB002 xatfbrsnN20070508S0190.fits[SCI] IMCMB003 xatfbrsnN20070508S0191.fits[SCI] IMCMB004 xatfbrsnN20070508S0194.fits[SCI] NCOMBINE 4 GAINORIG 1.0 RONORIG 0.0 GAIN 2.666667 RDNOISE 0.0 ''' # lamRange2 = h2['CRVAL1'] + np.array([0., h2['CDELT1']*(h2['NAXIS1'] - 1)]) # original lamRange2 = h2['CRVAL1'] + np.array([0., h2['CD1_1'] * (h2['NAXIS1'] - 1) ]) # BUCKET want NAXIS - 1? # lamRange2 = h2['CRVAL1'] + np.array([0., h2['CD1_1']*(h2['CRPIX1'])]) # or use CRPIX1?? print(lamRange2, 'l2') # [ 20628.29 24291.89] # print((lamRange2[1] - lamRange2[0])/h2['CD1_1'], 'num of steps in lam2') # 1720. # print(len(ssp), 'len ssp') # 1721 sspNew, logLam2, velscale_temp = util.log_rebin( lamRange2, ssp, velscale=velscale) # /velscale_ratio) # print(len(sspNew), 'len sspnew') # 622 hmmmm NEED THIS TO BE >= (len(galaxy)=750) # FIXED, now 1791 templates = np.empty((sspNew.size, len(vazdekis))) # Convolve the whole Vazdekis library of spectral templates # with the quadratic difference between the SAURON and the # Vazdekis instrumental resolution. Logarithmically rebin # and store each template as a column in the array TEMPLATES. # Quadratic sigma difference in pixels Vazdekis --> SAURON # The formula below is rigorously valid if the shapes of the # instrumental spectral profiles are well approximated by Gaussians. # FWHM_dif = np.sqrt(FWHM_gal**2 - FWHM_tem**2) # sigma = FWHM_dif/2.355/h2['CDELT1'] # Sigma difference in pixels sigma = FWHM_dif / 2.355 / h2['CD1_1'] # Sigma difference in pixels for j, file in enumerate( vazdekis ): # now for each template file; so why do the thing above with just 1?? hdu = fits.open(file) # ssp = hdu[0].data ssp = hdu[1].data ssp = ndimage.gaussian_filter1d(ssp, sigma) # ndimage.gaussian_filter takes input array (ssp) and filters it. Sigma = standard deviation of Gaussian kernel # used to filter the array # note: discrete convolution is defined (for any 2 arrays a, v): (a*v)[n] == sum m(-inf to inf) of a[m]*v[n-m] # where a is the original curve and v is the gaussian # print(len(ssp)) # 1721 --> currently by default being resampled to be 116, want it to be resampled to be 71 sspNew, logLam2, velscale_temp = util.log_rebin( lamRange2, ssp, velscale=velscale) # /velscale_ratio) # print(velscale_temp, 'vt') # 78.82967746 # print(velscale, 'v') # 78.82967746 # print(len(sspNew)) # need this to be >= len(galaxy) # now 1791 templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates # print(len(templates[0])) # len(templates)=29, len(templates[0] = 19) # The galaxy and the template spectra do not have the same starting wavelength. # For this reason an extra velocity shift DV has to be applied to the template # to fit the galaxy spectrum. We remove this artificial shift by using the # keyword VSYST in the call to PPXF below, so that all velocities are # measured with respect to DV. This assume the redshift is negligible. # In the case of a high-redshift galaxy one should de-redshift its # wavelength to the rest frame before using the line below (see above). # c = 299792.458 dv = (logLam2[0] - logLam1[0]) * c # km/s ''' if velscale_ratio > 1: dv = (np.mean(logLam2[:velscale_ratio]) - logLam1[0])*c # km/s else: dv = (logLam2[0] - logLam1[0])*c # km/s ''' z = 0.016561 # z = 0.0015 # Initial redshift estimate of the galaxy goodPixels = util.determine_goodpixels(logLam1, lamRange2, z) # Here the actual fit starts. The best fit is plotted on the screen. # Gas emission lines are excluded from the pPXF fit using the GOODPIXELS keyword. # vel = c * np.log(1 + z) # eq.(8) of Cappellari (2017) start = [vel, 200.] # [vel, 200.] # (km/s), starting guess for [V, sigma] t = clock() # print(galaxy.shape[0]) # = len(galaxy) = 750 # print(goodPixels) # print(max(noise[:, x, y]), min(noise[:, x, y]), np.median(noise[:, x, y]), np.mean(noise[:, x, y])) # 0.00106575 -2.77079e-05 4.62628e-05 5.89877e-05 pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=True, moments=4, degree=4, vsyst=dv, velscale_ratio=1) # velscale_ratio=velscale_ratio) print("Formal errors:") print(" dV dsigma dh3 dh4") print("".join("%8.2g" % f for f in pp.error * np.sqrt(pp.chi2))) print('Elapsed time in pPXF: %.2f s' % (clock() - t)) # If the galaxy is at significant redshift z and the wavelength has been # de-redshifted with the three lines "z = 1.23..." near the beginning of # this procedure, the best-fitting redshift is now given by the following # commented line (equation 2 of Cappellari et al. 2009, ApJ, 704, L34; # http://adsabs.harvard.edu/abs/2009ApJ...704L..34C) # # print('Best-fitting redshift z:', (z + 1)*(1 + pp.sol[0]/c) - 1) return pp.bestfit, pp.galaxy, pp.sol # output_spectrum, output_noise # for use in noise_in
def ppxf_kinematics_example_sdss(): # Read SDSS DR12 galaxy spectrum taken from here http://dr12.sdss3.org/ # The spectrum is *already* log rebinned by the SDSS DR12 # pipeline and log_rebin should not be used in this case. file = 'spectra/NGC4636_SDSS_DR12.fits' hdu = pyfits.open(file) t = hdu['COADD'].data z = 0.003129 # SDSS redshift estimate # Only use the wavelength range in common between galaxy and stellar library. mask = (t['loglam'] > np.log10(3540)) & (t['loglam'] < np.log10(7409)) flux = t['flux'][mask] galaxy = flux/np.median(flux) # Normalize spectrum to avoid numerical issues loglam_gal = t['loglam'][mask] lam_gal = 10**loglam_gal noise = galaxy*0 + 0.0166 # Assume constant noise per pixel here c = 299792.458 # speed of light in km/s frac = lam_gal[1]/lam_gal[0] # Constant lambda fraction per pixel dlam_gal = (frac - 1)*lam_gal # Size of every pixel in Angstrom wdisp = t['wdisp'][mask] # Intrinsic dispersion of every pixel, in pixels units fwhm_gal = 2.355*wdisp*dlam_gal # Resolution FWHM of every pixel, in Angstroms velscale = np.log(frac)*c # Constant velocity scale in km/s per pixel # If the galaxy is at a significant redshift (z > 0.03), one would need to apply # a large velocity shift in PPXF to match the template to the galaxy spectrum. # This would require a large initial value for the velocity (V > 1e4 km/s) # in the input parameter START = [V,sig]. This can cause PPXF to stop! # The solution consists of bringing the galaxy spectrum roughly to the # rest-frame wavelength, before calling PPXF. In practice there is no # need to modify the spectrum in any way, given that a red shift # corresponds to a linear shift of the log-rebinned spectrum. # One just needs to compute the wavelength range in the rest-frame # and adjust the instrumental resolution of the galaxy observations. # This is done with the following three commented lines: # # lam_gal = lam_gal/(1+z) # Compute approximate restframe wavelength # fwhm_gal = fwhm_gal/(1+z) # Adjust resolution in Angstrom # Read the list of filenames from the Single Stellar Population library # by Vazdekis (2010, MNRAS, 404, 1639) http://miles.iac.es/. A subset # of the library is included for this example with permission vazdekis = glob.glob('miles_models/Mun1.30Z*.fits') vazdekis.sort() fwhm_tem = 2.51 # Vazdekis+10 spectra have a constant resolution FWHM of 2.51A. # Extract the wavelength range and logarithmically rebin one spectrum # to the same velocity scale of the SDSS galaxy spectrum, to determine # the size needed for the array which will contain the template spectra. # hdu = pyfits.open(vazdekis[0]) ssp = hdu[0].data h2 = hdu[0].header lam_temp = h2['CRVAL1'] + h2['CDELT1']*np.arange(h2['NAXIS1']) lamRange_temp = [np.min(lam_temp), np.max(lam_temp)] sspNew, logLam2, velscale = util.log_rebin(lamRange_temp, ssp, velscale=velscale) templates = np.empty((sspNew.size, len(vazdekis))) # Interpolates the galaxy spectral resolution at the location of every pixel # of the templates. Outside the range of the galaxy spectrum the resolution # will be extrapolated, but this is irrelevant as those pixels cannot be # used in the fit anyway. fwhm_gal = np.interp(lam_temp, lam_gal, fwhm_gal) # Convolve the whole Vazdekis library of spectral templates # with the quadratic difference between the SDSS and the # Vazdekis instrumental resolution. Logarithmically rebin # and store each template as a column in the array TEMPLATES. # Quadratic sigma difference in pixels Vazdekis --> SDSS # The formula below is rigorously valid if the shapes of the # instrumental spectral profiles are well approximated by Gaussians. # # In the line below, the fwhm_dif is set to zero when fwhm_gal < fwhm_tem. # In principle it should never happen and a higher resolution template should be used. # fwhm_dif = np.sqrt((fwhm_gal**2 - fwhm_tem**2).clip(0)) sigma = fwhm_dif/2.355/h2['CDELT1'] # Sigma difference in pixels for j, fname in enumerate(vazdekis): hdu = pyfits.open(fname) ssp = hdu[0].data ssp = util.gaussian_filter1d(ssp, sigma) # perform convolution with variable sigma sspNew, logLam2, velscale = util.log_rebin(lamRange_temp, ssp, velscale=velscale) templates[:, j] = sspNew/np.median(sspNew) # Normalizes templates # The galaxy and the template spectra do not have the same starting wavelength. # For this reason an extra velocity shift DV has to be applied to the template # to fit the galaxy spectrum. We remove this artificial shift by using the # keyword VSYST in the call to PPXF below, so that all velocities are # measured with respect to DV. This assume the redshift is negligible. # In the case of a high-redshift galaxy one should de-redshift its # wavelength to the rest frame before using the line below (see above). # c = 299792.458 dv = np.log(lam_temp[0]/lam_gal[0])*c # km/s goodpixels = util.determine_goodpixels(np.log(lam_gal), lamRange_temp, z) # Here the actual fit starts. The best fit is plotted on the screen. # Gas emission lines are excluded from the pPXF fit using the GOODPIXELS keyword. # vel = c*np.log(1 + z) # Initial estimate of the galaxy velocity in km/s start = [vel, 200.] # (km/s), starting guess for [V,sigma] t = clock() pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodpixels, plot=True, moments=4, degree=12, vsyst=dv, clean=False) print("Formal errors:") print(" dV dsigma dh3 dh4") print("".join("%8.2g" % f for f in pp.error*np.sqrt(pp.chi2))) print('Elapsed time in PPXF: %.2f s' % (clock() - t))
def ppxf_population_example_sdss(): # Read SDSS DR8 galaxy spectrum taken from here http://www.sdss3.org/dr8/ # The spectrum is *already* log rebinned by the SDSS DR8 # pipeline and log_rebin should not be used in this case. # file = 'spectra/NGC3522_SDSS_DR8.fits' hdu = fits.open(file) t = hdu[1].data z = float(hdu[1].header["Z"]) # SDSS redshift estimate # Only use the wavelength range in common between galaxy and stellar library. # mask = (t['wavelength'] > 3540) & (t['wavelength'] < 7409) flux = t['flux'][mask] galaxy = flux / np.median( flux) # Normalize spectrum to avoid numerical issues wave = t['wavelength'][mask] # The noise level is chosen to give Chi^2/DOF=1 without regularization (REGUL=0) # noise = galaxy * 0 + 0.01528 # Assume constant noise per pixel here # The velocity step was already chosen by the SDSS pipeline # and we convert it below to km/s # c = 299792.458 # speed of light in km/s velscale = np.log(wave[1] / wave[0]) * c FWHM_gal = 2.76 # SDSS has an instrumental resolution FWHM of 2.76A. templates, lamRange_temp, logAge_grid, metal_grid = \ setup_spectral_library(velscale, FWHM_gal) # The galaxy and the template spectra do not have the same starting wavelength. # For this reason an extra velocity shift DV has to be applied to the template # to fit the galaxy spectrum. We remove this artificial shift by using the # keyword VSYST in the call to PPXF below, so that all velocities are # measured with respect to DV. This assume the redshift is negligible. # In the case of a high-redshift galaxy one should de-redshift its # wavelength to the rest frame before using the line below as described # in PPXF_KINEMATICS_EXAMPLE_SAURON. # c = 299792.458 dv = c * np.log(lamRange_temp[0] / wave[0]) # km/s goodpixels = util.determine_goodpixels(np.log(wave), lamRange_temp, z) # Here the actual fit starts. The best fit is plotted on the screen. # # IMPORTANT: Ideally one would like not to use any polynomial in the fit # as the continuum shape contains important information on the population. # Unfortunately this is often not feasible, due to small calibration # uncertainties in the spectral shape. To avoid affecting the line strength of # the spectral features, we exclude additive polynomials (DEGREE=-1) and only use # multiplicative ones (MDEGREE=10). This is only recommended for population, not # for kinematic extraction, where additive polynomials are always recommended. # vel = c * np.log(1 + z) # Initial estimate of the galaxy velocity in km/s start = [vel, 180., 0, 0] # (km/s), starting guess for [V,sigma] # See the pPXF documentation for the keyword REGUL, # for an explanation of the following two lines # templates /= np.median(templates) # Normalizes templates by a scalar regul_err = 0.004 # Desired regularization error t = clock() plt.clf() plt.subplot(211) pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodpixels, plot=True, moments=4, degree=-1, vsyst=dv, clean=False, mdegree=10, regul=1. / regul_err) # When the two numbers below are the same, the solution is the smoothest # consistent with the observed spectrum. # print('Desired Delta Chi^2: %.4g' % np.sqrt(2 * goodpixels.size)) print('Current Delta Chi^2: %.4g' % ((pp.chi2 - 1) * goodpixels.size)) print('Elapsed time in PPXF: %.2f s' % (clock() - t)) print('Mass-weighted <logAge> [Gyr]: %.3g' % (np.sum(pp.weights * logAge_grid.ravel()) / np.sum(pp.weights))) print('Mass-weighted <[M/H]>: %.3g' % (np.sum(pp.weights * metal_grid.ravel()) / np.sum(pp.weights))) plt.subplot(212) s = templates.shape weights = pp.weights.reshape(s[1:]) / pp.weights.sum() plt.imshow(np.rot90(weights), interpolation='nearest', cmap='gist_heat', aspect='auto', origin='upper', extent=[np.log10(1), np.log10(17.7828), -1.9, 0.45]) plt.colorbar() plt.title("Mass Fraction") plt.xlabel("log$_{10}$ Age (Gyr)") plt.ylabel("[M/H]") plt.tight_layout() plt.show()
lam, full, full_data, logAge_grid, metal_grid, templates = \ load_bc03_library(x, velscale*(0.9999), FWHM_gal=1, version='S') else: lam, full, full_data, logAge_grid, metal_grid, templates = \ load_bc03_library(x, velscale*(0.9999), FWHM_gal=0.1, version='S') lamRange_temp = [wave[0],wave[-1]] #normalize templates by median scalar norm=np.median(templates) templates /= norm full /= norm dv = c*np.log(lamRange_temp[0]/wave[0]) # km/s goodpixels = util.determine_goodpixels(log_wave, lamRange_temp, z) start = [c*np.log(1 + z), 0.01]#3*velscale] fixed = [True, True]#, False, False] fixed = [False, False] print start pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodpixels, plot=False, moments=2, degree=-1, vsyst=dv, clean=False, regul=1, mdegree=0, reddening=0.1,lam=wave, fixed=fixed ) # noise *= np.sqrt(pp.chi2) # pp = ppxf(templates, galaxy, noise, velscale, start,
def ppxf_kinematics_example_sdss(): # Read SDSS DR8 galaxy spectrum taken from here http://www.sdss3.org/dr8/ # The spectrum is *already* log rebinned by the SDSS DR8 # pipeline and log_rebin should not be used in this case. # file = 'spectra/NGC3522_SDSS.fits' hdu = pyfits.open(file) t = hdu[1].data z = float(hdu[1].header["Z"]) # SDSS redshift estimate # Only use the wavelength range in common between galaxy and stellar library. # mask = (t.field('wavelength') > 3540) & (t.field('wavelength') < 7409) galaxy = t[mask].field('flux')/np.median(t[mask].field('flux')) # Normalize spectrum to avoid numerical issues wave = t[mask].field('wavelength') noise = galaxy*0 + 0.0156 # Assume constant noise per pixel here # The velocity step was already chosen by the SDSS pipeline # and we convert it below to km/s # c = 299792.458 # speed of light in km/s velscale = np.log(wave[1]/wave[0])*c FWHM_gal = 2.76 # SDSS has an instrumental resolution FWHM of 2.76A. # If the galaxy is at a significant redshift (z > 0.03), one would need to apply # a large velocity shift in PPXF to match the template to the galaxy spectrum. # This would require a large initial value for the velocity (V > 1e4 km/s) # in the input parameter START = [V,sig]. This can cause PPXF to stop! # The solution consists of bringing the galaxy spectrum roughly to the # rest-frame wavelength, before calling PPXF. In practice there is no # need to modify the spectrum in any way, given that a red shift # corresponds to a linear shift of the log-rebinned spectrum. # One just needs to compute the wavelength range in the rest-frame # and adjust the instrumental resolution of the galaxy observations. # This is done with the following three commented lines: # # z = 1.23 # Initial estimate of the galaxy redshift # wave = wave/(1+z) # Compute approximate restframe wavelength # FWHM_gal = FWHM_gal/(1+z) # Adjust resolution in Angstrom # Read the list of filenames from the Single Stellar Population library # by Vazdekis (2010, MNRAS, 404, 1639) http://miles.iac.es/. A subset # of the library is included for this example with permission # vazdekis = glob.glob('miles_models/Mun1.30Z*.fits') FWHM_tem = 2.51 # Vazdekis+10 spectra have a resolution FWHM of 2.51A. # Extract the wavelength range and logarithmically rebin one spectrum # to the same velocity scale of the SDSS galaxy spectrum, to determine # the size needed for the array which will contain the template spectra. # hdu = pyfits.open(vazdekis[0]) ssp = hdu[0].data h2 = hdu[0].header lamRange2 = h2['CRVAL1'] + np.array([0.,h2['CDELT1']*(h2['NAXIS1']-1)]) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates = np.empty((sspNew.size,len(vazdekis))) # Convolve the whole Vazdekis library of spectral templates # with the quadratic difference between the SDSS and the # Vazdekis instrumental resolution. Logarithmically rebin # and store each template as a column in the array TEMPLATES. # Quadratic sigma difference in pixels Vazdekis --> SDSS # The formula below is rigorously valid if the shapes of the # instrumental spectral profiles are well approximated by Gaussians. # FWHM_dif = np.sqrt(FWHM_gal**2 - FWHM_tem**2) sigma = FWHM_dif/2.355/h2['CDELT1'] # Sigma difference in pixels for j in range(len(vazdekis)): hdu = pyfits.open(vazdekis[j]) ssp = hdu[0].data ssp = ndimage.gaussian_filter1d(ssp,sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates[:,j] = sspNew/np.median(sspNew) # Normalizes templates # The galaxy and the template spectra do not have the same starting wavelength. # For this reason an extra velocity shift DV has to be applied to the template # to fit the galaxy spectrum. We remove this artificial shift by using the # keyword VSYST in the call to PPXF below, so that all velocities are # measured with respect to DV. This assume the redshift is negligible. # In the case of a high-redshift galaxy one should de-redshift its # wavelength to the rest frame before using the line below (see above). # c = 299792.458 dv = (logLam2[0]-np.log(wave[0]))*c # km/s vel = c*z # Initial estimate of the galaxy velocity in km/s goodpixels = util.determine_goodpixels(np.log(wave),lamRange2,vel) # Here the actual fit starts. The best fit is plotted on the screen. # Gas emission lines are excluded from the pPXF fit using the GOODPIXELS keyword. # start = [vel, 180.] # (km/s), starting guess for [V,sigma] t = clock() pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodpixels, plot=True, moments=4, degree=10, vsyst=dv, clean=False) print("Formal errors:") print(" dV dsigma dh3 dh4") print("".join("%8.2g" % f for f in pp.error*np.sqrt(pp.chi2))) print('Elapsed time in PPXF: %.2f s' % (clock() - t))
def ppxfit(ncompfit, brot, bfract, mom): velscale = 110. file = 'NGC0528-V500.rscube.fits' hdu = pyfits.open(file) gal_lin = hdu[0].data h1 = hdu[0].header medfl = np.loadtxt("medgalpy.txt") x = medfl[:, 0] y = medfl[:, 1] sig = medfl[:, 2] noise = medfl[:, 3] bins = np.loadtxt("voronoi_2d_binning_output.txt", skiprows=1) x = bins[:, 0] y = bins[:, 1] binnum = bins[:, 2] binco = np.loadtxt("bins.txt") xbin = binco[:, 0] ybin = binco[:, 1] file = 'galaxybinspy.fits' # spectra arranged horizontally hdu = pyfits.open(file) gal_bin = hdu[0].data gs = gal_bin.shape nbins = gs[0] xcut = 0.0 ycut = 0.0 delta = h1['CDELT3'] lamRange1 = h1['CRVAL3'] + np.array( [xcut * delta, delta * ((h1['NAXIS3'] - 1) - ycut)]) FWHM_gal = 6.0 # CALIFA has an instrumental resolution FWHM of 6A. galaxyz, logLam1, velscale = util.log_rebin(lamRange1, gal_bin[0, :], velscale=velscale) galaxy = np.empty((galaxyz.size, nbins)) noise = np.empty((galaxyz.size, nbins)) for j in range(nbins): galaxy[:, j], logLam1, velscale = util.log_rebin(lamRange1, gal_bin[j, :], velscale=velscale) galaxy[:, j] = galaxy[:, j] / np.median( galaxy[:, j]) # Normalize spectrum to avoid numerical issues noise[:, j] = galaxy[:, j] * 0 + 0.0049 # Assume constant noise per pixel here #dir='/home/ppxmt3/astro/MILES/' dir = 'galspec/' miles = glob.glob(dir + 'Mun*.fits') miles.sort() FWHM_tem = 2.5 # Miles spectra have a resolution FWHM of 1.8A. age = np.empty(len(miles)) met = np.empty(len(miles)) #age=np.chararray(len(miles),itemsize=7) #met=np.chararray(len(miles),itemsize=5) for j in range(len(miles)): ast = miles[j][22:29] mst = miles[j][17:21] pm = miles[j][16:17] if pm == 'p': pmn = '+' elif pm == 'm': pmn = '-' mstpm = (pmn, mst) #met[j,:]=miles[j][16:19] age[j] = float(ast) met[j] = float("".join(mstpm)) #age2,inda=np.unique(age,return_inverse=True) #met2,ind=np.unique(met,return_inverse=True) #c=1 #for i in range(len(age2)/2): #indout=np.where(age==age2[c])[0] ##print(indout) #miles=np.delete(miles,indout) #age=np.delete(age,indout) #c=c+2 # Extract the wavelength range and logarithmically rebin one spectrum # to the same velocity scale of the CALIFA galaxy spectrum, to determine # the size needed for the array which will contain the template spectra. hdu = pyfits.open(miles[0]) ssp = hdu[0].data h2 = hdu[0].header lamRange2 = h2['CRVAL1'] + np.array( [0., h2['CDELT1'] * (h2['NAXIS1'] - 1)]) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) # Convolve the whole miles library of spectral templates # with the quadratic difference between the CALIFA and the # miles instrumental resolution. Logarithmically rebin # and store each template as a column in the array TEMPLATES. # Quadratic sigma difference in pixels miles --> CALIFA # The formula below is rigorously valid if the shapes of the # instrumental spectral profiles are well approximated by Gaussians. # FWHM_dif = np.sqrt(FWHM_gal**2 - FWHM_tem**2) sigma = FWHM_dif / 2.355 / h2['CDELT1'] # Sigma difference in pixels #========================================================================================================================== #One component fit - saves veloctiy values in 'NGC528_onecompkin.txt' to be used as initial estimates for two component fit if ncompfit == 1: templates = np.empty((sspNew.size, len(miles))) for j in range(len(miles)): hdu = pyfits.open(miles[j]) ssp = hdu[0].data ssp = ndimage.gaussian_filter1d(ssp, sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates c = 299792.458 dv = (logLam2[0] - logLam1[0]) * c # km/s vel = 4750. # Initial estimate of the galaxy velocity in km/s z = np.exp( vel / c) - 1 # Relation between velocity and redshift in pPXF goodPixels = util.determine_goodpixels(logLam1, lamRange2, z) start = np.zeros(2) output = np.zeros((nbins, 5)) output[:, 0] = xbin[:] output[:, 1] = ybin[:] start[:] = [vel, 3. * velscale] # (km/s), starting guess for [V,sigma] for j in range(nbins): print('On ', j + 1, ' out of ', nbins) print(start) pp = ppxf(templates, galaxy[:, j], noise[:, j], velscale, start, goodpixels=goodPixels, degree=4, vsyst=dv, plot=True, moments=mom) kinem = np.loadtxt("ppxfout.txt") if mom == 2: output[j, 2] = kinem[0] #vel output[j, 3] = kinem[1] #sigma output[j, 4] = kinem[2] #chisq if mom == 4: output[j, 2] = kinem[0] #vel output[j, 3] = kinem[1] #sigma output[j, 4] = kinem[4] #chisq np.savetxt('NGC528_onecompkinm2nch.txt', output, fmt="%10.3g") #========================================================================================================================= #Two component fit elif ncompfit == 2: # To determine flux fraction of bulge. Set bfract to 0 to disable # 'bulgediskblock.fits' is created by running GALFIT to get a galfit.01 file of best fit parameters, then using # '>galfit -o3 galfit.01' to get cube of galaxy image, bulge image and disk image if bfract == 1: file = 'bulgediskblock.fits' hdu = pyfits.open(file) galb = hdu[1].data bulge = hdu[2].data disk = hdu[3].data # Bin bulge and disk images into same binning as datacube nbins = xbin.shape[0] avbulge = np.zeros(nbins) avdisk = np.zeros(nbins) avtot = np.zeros(nbins) binflux = np.zeros(nbins) x = x.astype(int) y = y.astype(int) for j in range(nbins): b = np.where(binnum == j)[0] valbin = b.size if valbin == 1: avbulge[j] = bulge[y[b], x[b]] avdisk[j] = disk[y[b], x[b]] avtot[j] = galb[x[b], y[b]] else: avbulge[j] = np.median(bulge[y[b], x[b]]) avdisk[j] = np.median(disk[y[b], x[b]]) avtot[j] = np.median(galb[x[b], y[b]]) bulge_fraction = avbulge / (avbulge + avdisk) hdu = pyfits.PrimaryHDU(bulge_fraction) hdu.writeto('bulge_fraction.fits', clobber=True) #==================================================================================== templates = np.empty((sspNew.size, 2 * len(miles))) ssparr = np.empty((ssp.size, len(miles))) for j in range(len(miles)): hdu = pyfits.open(miles[j]) ssparr[:, j] = hdu[0].data ssp = hdu[0].data ssp = ndimage.gaussian_filter1d(ssp, sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates for j in range(len(miles), 2 * len(miles)): hdu = pyfits.open(miles[j - len(miles)]) ssp = hdu[0].data ssp = ndimage.gaussian_filter1d(ssp, sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates component = np.zeros((2 * len(miles)), dtype=np.int) component[0:len(miles)] = 0 component[len(miles):] = 1 c = 299792.458 dv = (logLam2[0] - logLam1[0]) * c # km/s vel = 4750. # Initial estimate of the galaxy velocity in km/s z = np.exp( vel / c) - 1 # Relation between velocity and redshift in pPXF goodPixels = util.determine_goodpixels(logLam1, lamRange2, z) kin = np.loadtxt('NGC528_onecompkinnch.txt') xbin = kin[:, 0] ybin = kin[:, 1] vpxf = kin[:, 2] spxf = kin[:, 3] occh = kin[:, 4] velbulge = vel sigdisk = 50. file = 'bulge_fraction.fits' # Read out bulge fraction for each bin hdu = pyfits.open(file) bulge_fraction = hdu[0].data bvel = np.zeros(nbins) bsig = np.zeros(nbins) bh3 = np.zeros(nbins) bh4 = np.zeros(nbins) dvel = np.zeros(nbins) dsig = np.zeros(nbins) dh3 = np.zeros(nbins) dh4 = np.zeros(nbins) bwt = np.zeros(nbins) dwt = np.zeros(nbins) output = np.zeros((nbins, 10 + (2 * (mom - 2)))) popoutput = np.zeros((nbins, 6)) output[:, 0] = xbin[:] output[:, 1] = ybin[:] popoutput[:, 0] = xbin[:] popoutput[:, 1] = ybin[:] bmet = np.zeros(nbins) bage = np.zeros(nbins) dage = np.zeros(nbins) dmet = np.zeros(nbins) count = 0 for j in range(2, nbins - 1): print('Bin number:', j + 1, 'out of', nbins) print('Bulge fraction:', bulge_fraction[j]) if spxf[j] > 350: spxf[j] = 350. if abs(vpxf[j] - 4750.) > 300: vpxf[j] = 4750. #start = np.array([[vpxf[j], spxf[j]],[vpxf[j],sigdisk]]) # (km/s), starting guess for [V,sigma] start = np.array([[velbulge, spxf[j]], [vpxf[j], sigdisk] ]) # (km/s), starting guess for [V,sigma] print('Starting velocity estimates:', start[0, 0], start[0, 1], start[1, 0], start[1, 1]) print('Xbin:', xbin[j], 'Ybin:', ybin[j]) t = clock() pp = ppxf(templates, galaxy[:, j], noise[:, j], velscale, start, bulge_fraction=bulge_fraction[j], goodpixels=goodPixels, moments=[mom, mom], degree=4, vsyst=dv, component=component, brot=1, plot=True) #brot=0 for nonrotating, brot=1 for rotating #Kinematics kinem = np.loadtxt("ppxfout.txt") wts = np.loadtxt("ppxfoutwt.txt") if mom == 2: output[j, 2] = kinem[0, 0] #bvel output[j, 3] = kinem[0, 1] #bsig output[j, 4] = kinem[1, 0] #dvel output[j, 5] = kinem[1, 1] #dsig output[j, 6] = wts[0] #bulge weight output[j, 7] = wts[1] #disk weight output[j, 8] = wts[2] #chisqn output[j, 9] = wts[3] #chisq if mom == 4: output[j, 2] = kinem[0, 0] #bvel output[j, 3] = kinem[0, 1] #bsig output[j, 4] = kinem[0, 2] #bh3 output[j, 5] = kinem[0, 3] #bh4 output[j, 6] = kinem[1, 0] #dvel output[j, 7] = kinem[1, 1] #dsig output[j, 8] = kinem[1, 2] #dh3 output[j, 9] = kinem[1, 3] #dh4 output[j, 10] = wts[0] #bulge weight output[j, 11] = wts[1] #disk weight output[j, 12] = wts[2] #chisqn output[j, 13] = wts[3] #chisq print(wts[0], wts[1]) print('Chisq difference from one comp fit (pos = improved)', occh[j] - wts[2]) if occh[j] > wts[2]: count = count + 1 bwt[j] = wts[0] dwt[j] = wts[1] #Populations #wtsb=np.loadtxt("ppxfoutwtsb.txt") #wtsd=np.loadtxt("ppxfoutwtsd.txt") #shwb=wtsb.shape #shwd=wtsd.shape #if len(shwb) > 1: #bulgewt=np.array(wtsb[0,:]) #bulgewt=bulgewt/bulgewt.sum() #bulgewtin=np.array(wtsb[1,:],dtype=int) #else: #bulgewt=1. #bulgewtin=np.int(wtsb[1]) #if len(shwd) > 1: #diskwt=np.array(wtsd[0,:]) #diskwt=diskwt/diskwt.sum() #diskwtin=np.array(wtsd[1,:],dtype=int) #else: #diskwt=1. #diskwtin=np.int(wtsd[1]) #bage[j]=np.dot(bulgewt,age[bulgewtin]) #bmet[j]=np.dot(bulgewt,met[bulgewtin]) #dage[j]=np.dot(diskwt,age[diskwtin]) #dmet[j]=np.dot(diskwt,met[diskwtin]) #popoutput[j,2]=bage[j] #popoutput[j,3]=bmet[j] #popoutput[j,4]=dage[j] #popoutput[j,5]=dmet[j] #Plots #ssparr=templates #print(gal_bin.shape) #gal_bin=galaxy #print(gal_bin.shape) #bulgespec=ssparr[:,bulgewtin].dot(bulgewt) #diskspec=ssparr[:,diskwtin].dot(diskwt) #diskspec=diskspec/np.median(diskspec) #bulgespec=bulgespec/np.median(bulgespec) #plt.xlabel("Wavelength") #plt.ylabel("Counts") #plt.plot(5*(gal_bin[goodPixels,j]/np.median(gal_bin[goodPixels,j])), 'k') #plt.plot(3*(bulgespec[goodPixels]), 'r') #plt.plot(2*(diskspec[goodPixels]), 'b') #plt.plot(5*(bulgespec[goodPixels]+diskspec[goodPixels])/np.median(bulgespec[goodPixels]+diskspec[goodPixels]), 'g') #plt.savefig('outfit') np.savetxt('NGC528conskinnchcheckbrot.txt', output, fmt="%10.3g")
def ppxf_kinematics(bin_sci, ppxf_file, ppxf_bestfit, template_fits, template_resolution, lam_range=[4173., 5404.], vel_init=1500., sig_init=100., bias=0., plot=False, quiet=True, badPixels=[], clean=False): """ Follow the pPXF usage example by Michile Cappellari INPUT: DIR_SCI_COMB (comb_fits_sci_{S/N}/bin_sci_{S/N}.fits), TEMPLATE_* (spectra/Mun1.30z*.fits) OUTPUT: PPXF_FILE (ppxf_output_sn30.txt), OUT_BESTFIT_FITS (ppxf_fits/ppxf_bestfit_sn30.fits) """ if os.path.exists(ppxf_file): print('File {} already exists'.format(ppxf_file)) return # Read a galaxy spectrum and define the wavelength range assert len(glob.glob(bin_sci.format('*'))) > 0, 'Binned spectra {} not found'.format(glob.glob(bin_sci.format('*'))) # hdu = fits.open(in_file[0]) # gal_lin = hdu[0].data # h1 = hdu[0].header with fits.open(bin_sci.format(0)) as gal_hdu: gal_lin = gal_hdu[0].data gal_hdr = gal_hdu[0].header # lamRange1 = h1['CRVAL1'] + np.array([0.,h1['CDELT1']*(h1['NAXIS1']-1)]) # FWHM_gal = 4.2 # SAURON has an instrumental resolution FWHM of 4.2A. # lamRange1 is now variable lam_range (because I was lazy and didnt put headers into the binned spectra) FWHM_gal = 2.3 # GMOS IFU has an instrumental resolution FWHM of 2.3 A # If the galaxy is at a significant redshift (z > 0.03), one would need to apply a large velocity shift in PPXF to # match the template to the galaxy spectrum.This would require a large initial value for the velocity (V > 1e4 km/s) # in the input parameter START = [V,sig]. This can cause PPXF to stop! The solution consists of bringing the galaxy # spectrum roughly to the rest-frame wavelength, before calling PPXF. In practice there is no need to modify the # spectrum before the usual LOG_REBIN, given that a red shift corresponds to a linear shift of the log-rebinned # spectrum. One just needs to compute the wavelength range in the rest-frame and adjust the instrumental resolution # of the galaxy observations. This is done with the following three commented lines: # # z = 1.23 # Initial estimate of the galaxy redshift # lamRange1 = lamRange1/(1+z) # Compute approximate restframe wavelength range # FWHM_gal = FWHM_gal/(1+z) # Adjust resolution in Angstrom # galaxy, logLam1, velscale = util.log_rebin(lamRange1, gal_lin) # galaxy = galaxy/np.median(galaxy) # Normalize spectrum to avoid numerical issues # noise = galaxy*0 + 0.0049 # Assume constant noise per pixel here galaxy, logLam1, velscale = util.log_rebin(lam_range, gal_lin) galaxy = galaxy / np.median(galaxy) # Normalize spectrum to avoid numerical issues # Read the list of filenames from the Single Stellar Population library by Vazdekis (1999, ApJ, 513, 224). A subset # of the library is included for this example with permission. See http://purl.org/cappellari/software # for suggestions of more up-to-date stellar libraries. # vazdekis = glob.glob(dir + 'Rbi1.30z*.fits') # vazdekis.sort() # FWHM_tem = 1.8 # Vazdekis spectra have a resolution FWHM of 1.8A. template_spectra = glob.glob(template_fits) assert len(template_spectra) > 0, 'Template spectra not found: {}'.format(template_fits) FWHM_tem = template_resolution # Extract the wavelength range and logarithmically rebin one spectrum to the same velocity scale of the SAURON # galaxy spectrum, to determine the size needed for the array which will contain the template spectra. # hdu = fits.open(vazdekis[0]) # ssp = hdu[0].data # h2 = hdu[0].header # lamRange2 = h2['CRVAL1'] + np.array([0.,h2['CDELT1']*(h2['NAXIS1']-1)]) # sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) # templates = np.empty((sspNew.size,len(vazdekis))) with fits.open(template_spectra[0]) as temp_hdu: ssp = temp_hdu[0].data h2 = temp_hdu[0].header lamRange2 = h2['CRVAL1'] + np.array([0., h2['CDELT1'] * (h2['NAXIS1'] - 1)]) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates = np.empty((sspNew.size, len(template_spectra))) # Convolve the whole Vazdekis library of spectral templates with the quadratic difference between the SAURON and the # Vazdekis instrumental resolution. Logarithmically rebin and store each template as a column in the array TEMPLATES # Quadratic sigma difference in pixels Vazdekis --> SAURON # The formula below is rigorously valid if the shapes of the instrumental spectral profiles are well approximated # by Gaussians. # FROM GWENS IDL SCRIPT ---------------------------------------------------------- # Example: BC03 spectra have a resolution of 3A at 5100A, this corresponds to sigma ~ 3.0/5100*3e5/2.35 = 75 km/s. # The GMOS IFU has an instrumental resolution of 2.3 A, this corresponds to sigma ~ 2.3/5100*3e5/2.35 = 56.8 km/s. # The quadratic difference is sigma = sqrt(56.8^2 - 75^2) = undefined # (the above reasoning can be applied if the shape of the instrumental spectral profiles can be well approximated # by a Gaussian). # For the lower resolution models, we must degrade the DATA to fit the models. # Thus: The quadratic difference is sigma = sqrt(75^2 - 56.8^2) = 49.0 km/s # --------------------------------------------------------------------------------- # FWHM_dif = np.sqrt(FWHM_gal ** 2 - template_resolution ** 2) FWHM_dif = np.sqrt(FWHM_tem ** 2 - FWHM_gal ** 2) sigma = FWHM_dif / 2.355 / h2['CDELT1'] # SIGMA DIFFERENCE IN PIXELS, 1.078435697220085 # Logarithmically rebin the whole Mun library of spectra, and store each template as a column in the array TEMPLATES # for j in range(len(vazdekis)): # hdu = fits.open(vazdekis[j]) # ssp = hdu[0].data # ssp = ndimage.gaussian_filter1d(ssp, sigma) # sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) # templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates for j in range(len(template_spectra)): with fits.open(template_spectra[j]) as temp_hdu_j: ssp_j = temp_hdu_j[0].data ssp_j = ndimage.gaussian_filter1d(ssp_j, sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp_j, velscale=velscale) templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates # The galaxy and the template spectra do not have the same starting wavelength. For this reason an extra velocity # shift DV has to be applied to the template to fit the galaxy spectrum. We remove this artificial shift by using # the keyword VSYST in the call to PPXF below, so that all velocities are measured with respect to DV. This assume # the redshift is negligible.In the case of a high-redshift galaxy one should de-redshift its wavelength to the # rest frame before using the line below (see above). vel_list = [] sig_list = [] dV_list = [] dsigma_list = [] h3_list = [] h4_list = [] dh3_list = [] dh4_list = [] for j in range(len(glob.glob(bin_sci.format('*')))): b_gal = fits.getdata(bin_sci.format(j), 0) b_gal = ndimage.gaussian_filter1d(b_gal, sigma) galaxy, logLam1, velscale = util.log_rebin(lam_range, b_gal, velscale=velscale) noise = galaxy * 0 + 1 # Assume constant noise per pixel here c = 299792.458 dv = (logLam2[0] - logLam1[0]) * c # km/s # vel = 1500. # Initial estimate of the galaxy velocity in km/s z = np.exp(vel_init / c) - 1 # Relation between velocity and redshift in pPXF goodPixels = util.determine_goodpixels(logLam1, lamRange2, z) if len(badPixels) > 0: indices = [] for idx, pix in enumerate(goodPixels): if pix in badPixels: indices.append(idx) goodPixels = np.delete(goodPixels, indices) # Here the actual fit starts. The best fit is plotted on the screen. # Gas emission lines are excluded from the pPXF fit using the GOODPIXELS keyword. start = [vel_init, sig_init] # (km/s), starting guess for [V,sigma] t = clock() pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodPixels, plot=plot, moments=4, degree=4, vsyst=dv, bias=bias, quiet=False, clean=clean) if not quiet: print("Formal errors:") print(" dV dsigma dh3 dh4") print("".join("%8.2g" % f for f in pp.error * np.sqrt(pp.chi2))) # If the galaxy is at significant redshift z and the wavelength has been de-redshifted with the three lines # "z = 1.23..." near the beginning of this procedure, the best-fitting redshift is now given by the following # commented line (equation 2 of Cappellari et al. 2009, ApJ, 704, L34; # http://adsabs.harvard.edu/abs/2009ApJ...704L..34C) # print, 'Best-fitting redshift z:', (z + 1)*(1 + sol[0]/c) - 1 # Gwen obtains the velocity and sigma information from the SOL parameter # moments = 4 so sol = [vel, sig, h3, h4] vel_list.append(pp.sol[0]) sig_list.append(pp.sol[1]) dV_list.append((pp.error * np.sqrt(pp.chi2))[0]) dsigma_list.append((pp.error * np.sqrt(pp.chi2))[1]) h3_list.append(pp.sol[2]) h4_list.append(pp.sol[3]) dh3_list.append((pp.error * np.sqrt(pp.chi2))[2]) dh4_list.append((pp.error * np.sqrt(pp.chi2))[3]) hdu_best = fits.PrimaryHDU() hdu_best.data = pp.bestfit hdu_best.header['CD1_1'] = gal_hdr['CD1_1'] hdu_best.header['CDELT1'] = gal_hdr['CD1_1'] hdu_best.header['CRPIX1'] = gal_hdr['CRPIX1'] hdu_best.header['CRVAL1'] = gal_hdr['CRVAL1'] hdu_best.header['NAXIS1'] = pp.bestfit.size hdu_best.header['CTYPE1'] = 'LINEAR' # corresponds to sampling of values above hdu_best.header['DC-FLAG'] = '1' # 0 = linear, 1= log-linear sampling hdu_best.writeto(ppxf_bestfit.format(j), clobber=True) print('Elapsed time in PPXF: %.2f s' % (clock() - t)) np.savetxt(ppxf_file, np.column_stack([vel_list, sig_list, h3_list, h4_list, dV_list, dsigma_list, dh3_list, dh4_list]), fmt=b'%10.6f %10.6f %10.6f %10.6f %10.6f %10.6f %10.6f %10.6f', header='velocity sigma h3 h4 dV dsigma dh3 dh4') return vel_list
lam, full, full_data, logAge_grid, metal_grid, templates = \ load_bc03_library(x, velscale*(0.9999), FWHM_gal=1, version='S') else: lam, full, full_data, logAge_grid, metal_grid, templates = \ load_bc03_library(x, velscale*(0.9999), FWHM_gal=0.1, version='S') lamRange_temp = [wave[0], wave[-1]] #normalize templates by median scalar norm = np.median(templates) templates /= norm full /= norm dv = c * np.log(lamRange_temp[0] / wave[0]) # km/s goodpixels = util.determine_goodpixels(log_wave, lamRange_temp, z) start = [c * np.log(1 + z), 0.01] #3*velscale] fixed = [True, True] #, False, False] fixed = [False, False] print start pp = ppxf(templates, galaxy, noise, velscale, start, goodpixels=goodpixels, plot=False, moments=2, degree=-1,
def ppxfit(ncompfit, brot, bfract, mom): velscale = 110.0 file = "NGC0528-V500.rscube.fits" hdu = pyfits.open(file) gal_lin = hdu[0].data h1 = hdu[0].header medfl = np.loadtxt("medgalpy.txt") x = medfl[:, 0] y = medfl[:, 1] sig = medfl[:, 2] noise = medfl[:, 3] bins = np.loadtxt("voronoi_2d_binning_output.txt", skiprows=1) x = bins[:, 0] y = bins[:, 1] binnum = bins[:, 2] binco = np.loadtxt("bins.txt") xbin = binco[:, 0] ybin = binco[:, 1] file = "galaxybinspy.fits" # spectra arranged horizontally hdu = pyfits.open(file) gal_bin = hdu[0].data gs = gal_bin.shape nbins = gs[0] xcut = 0.0 ycut = 0.0 delta = h1["CDELT3"] lamRange1 = h1["CRVAL3"] + np.array([xcut * delta, delta * ((h1["NAXIS3"] - 1) - ycut)]) FWHM_gal = 6.0 # CALIFA has an instrumental resolution FWHM of 6A. galaxyz, logLam1, velscale = util.log_rebin(lamRange1, gal_bin[0, :], velscale=velscale) galaxy = np.empty((galaxyz.size, nbins)) noise = np.empty((galaxyz.size, nbins)) for j in range(nbins): galaxy[:, j], logLam1, velscale = util.log_rebin(lamRange1, gal_bin[j, :], velscale=velscale) galaxy[:, j] = galaxy[:, j] / np.median(galaxy[:, j]) # Normalize spectrum to avoid numerical issues noise[:, j] = galaxy[:, j] * 0 + 0.0049 # Assume constant noise per pixel here # dir='/home/ppxmt3/astro/MILES/' dir = "galspec/" miles = glob.glob(dir + "Mun*.fits") miles.sort() FWHM_tem = 2.5 # Miles spectra have a resolution FWHM of 1.8A. age = np.empty(len(miles)) met = np.empty(len(miles)) # age=np.chararray(len(miles),itemsize=7) # met=np.chararray(len(miles),itemsize=5) for j in range(len(miles)): ast = miles[j][22:29] mst = miles[j][17:21] pm = miles[j][16:17] if pm == "p": pmn = "+" elif pm == "m": pmn = "-" mstpm = (pmn, mst) # met[j,:]=miles[j][16:19] age[j] = float(ast) met[j] = float("".join(mstpm)) # age2,inda=np.unique(age,return_inverse=True) # met2,ind=np.unique(met,return_inverse=True) # c=1 # for i in range(len(age2)/2): # indout=np.where(age==age2[c])[0] ##print(indout) # miles=np.delete(miles,indout) # age=np.delete(age,indout) # c=c+2 # Extract the wavelength range and logarithmically rebin one spectrum # to the same velocity scale of the CALIFA galaxy spectrum, to determine # the size needed for the array which will contain the template spectra. hdu = pyfits.open(miles[0]) ssp = hdu[0].data h2 = hdu[0].header lamRange2 = h2["CRVAL1"] + np.array([0.0, h2["CDELT1"] * (h2["NAXIS1"] - 1)]) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) # Convolve the whole miles library of spectral templates # with the quadratic difference between the CALIFA and the # miles instrumental resolution. Logarithmically rebin # and store each template as a column in the array TEMPLATES. # Quadratic sigma difference in pixels miles --> CALIFA # The formula below is rigorously valid if the shapes of the # instrumental spectral profiles are well approximated by Gaussians. # FWHM_dif = np.sqrt(FWHM_gal ** 2 - FWHM_tem ** 2) sigma = FWHM_dif / 2.355 / h2["CDELT1"] # Sigma difference in pixels # ========================================================================================================================== # One component fit - saves veloctiy values in 'NGC528_onecompkin.txt' to be used as initial estimates for two component fit if ncompfit == 1: templates = np.empty((sspNew.size, len(miles))) for j in range(len(miles)): hdu = pyfits.open(miles[j]) ssp = hdu[0].data ssp = ndimage.gaussian_filter1d(ssp, sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates c = 299792.458 dv = (logLam2[0] - logLam1[0]) * c # km/s vel = 4750.0 # Initial estimate of the galaxy velocity in km/s z = np.exp(vel / c) - 1 # Relation between velocity and redshift in pPXF goodPixels = util.determine_goodpixels(logLam1, lamRange2, z) start = np.zeros(2) output = np.zeros((nbins, 5)) output[:, 0] = xbin[:] output[:, 1] = ybin[:] start[:] = [vel, 3.0 * velscale] # (km/s), starting guess for [V,sigma] for j in range(nbins): print("On ", j + 1, " out of ", nbins) print(start) pp = ppxf( templates, galaxy[:, j], noise[:, j], velscale, start, goodpixels=goodPixels, degree=4, vsyst=dv, plot=True, moments=mom, ) kinem = np.loadtxt("ppxfout.txt") if mom == 2: output[j, 2] = kinem[0] # vel output[j, 3] = kinem[1] # sigma output[j, 4] = kinem[2] # chisq if mom == 4: output[j, 2] = kinem[0] # vel output[j, 3] = kinem[1] # sigma output[j, 4] = kinem[4] # chisq np.savetxt("NGC528_onecompkinm2nch.txt", output, fmt="%10.3g") # ========================================================================================================================= # Two component fit elif ncompfit == 2: # To determine flux fraction of bulge. Set bfract to 0 to disable # 'bulgediskblock.fits' is created by running GALFIT to get a galfit.01 file of best fit parameters, then using # '>galfit -o3 galfit.01' to get cube of galaxy image, bulge image and disk image if bfract == 1: file = "bulgediskblock.fits" hdu = pyfits.open(file) galb = hdu[1].data bulge = hdu[2].data disk = hdu[3].data # Bin bulge and disk images into same binning as datacube nbins = xbin.shape[0] avbulge = np.zeros(nbins) avdisk = np.zeros(nbins) avtot = np.zeros(nbins) binflux = np.zeros(nbins) x = x.astype(int) y = y.astype(int) for j in range(nbins): b = np.where(binnum == j)[0] valbin = b.size if valbin == 1: avbulge[j] = bulge[y[b], x[b]] avdisk[j] = disk[y[b], x[b]] avtot[j] = galb[x[b], y[b]] else: avbulge[j] = np.median(bulge[y[b], x[b]]) avdisk[j] = np.median(disk[y[b], x[b]]) avtot[j] = np.median(galb[x[b], y[b]]) bulge_fraction = avbulge / (avbulge + avdisk) hdu = pyfits.PrimaryHDU(bulge_fraction) hdu.writeto("bulge_fraction.fits", clobber=True) # ==================================================================================== templates = np.empty((sspNew.size, 2 * len(miles))) ssparr = np.empty((ssp.size, len(miles))) for j in range(len(miles)): hdu = pyfits.open(miles[j]) ssparr[:, j] = hdu[0].data ssp = hdu[0].data ssp = ndimage.gaussian_filter1d(ssp, sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates for j in range(len(miles), 2 * len(miles)): hdu = pyfits.open(miles[j - len(miles)]) ssp = hdu[0].data ssp = ndimage.gaussian_filter1d(ssp, sigma) sspNew, logLam2, velscale = util.log_rebin(lamRange2, ssp, velscale=velscale) templates[:, j] = sspNew / np.median(sspNew) # Normalizes templates component = np.zeros((2 * len(miles)), dtype=np.int) component[0 : len(miles)] = 0 component[len(miles) :] = 1 c = 299792.458 dv = (logLam2[0] - logLam1[0]) * c # km/s vel = 4750.0 # Initial estimate of the galaxy velocity in km/s z = np.exp(vel / c) - 1 # Relation between velocity and redshift in pPXF goodPixels = util.determine_goodpixels(logLam1, lamRange2, z) kin = np.loadtxt("NGC528_onecompkinnch.txt") xbin = kin[:, 0] ybin = kin[:, 1] vpxf = kin[:, 2] spxf = kin[:, 3] occh = kin[:, 4] velbulge = vel sigdisk = 50.0 file = "bulge_fraction.fits" # Read out bulge fraction for each bin hdu = pyfits.open(file) bulge_fraction = hdu[0].data bvel = np.zeros(nbins) bsig = np.zeros(nbins) bh3 = np.zeros(nbins) bh4 = np.zeros(nbins) dvel = np.zeros(nbins) dsig = np.zeros(nbins) dh3 = np.zeros(nbins) dh4 = np.zeros(nbins) bwt = np.zeros(nbins) dwt = np.zeros(nbins) output = np.zeros((nbins, 10 + (2 * (mom - 2)))) popoutput = np.zeros((nbins, 6)) output[:, 0] = xbin[:] output[:, 1] = ybin[:] popoutput[:, 0] = xbin[:] popoutput[:, 1] = ybin[:] bmet = np.zeros(nbins) bage = np.zeros(nbins) dage = np.zeros(nbins) dmet = np.zeros(nbins) count = 0 for j in range(2, nbins - 1): print("Bin number:", j + 1, "out of", nbins) print("Bulge fraction:", bulge_fraction[j]) if spxf[j] > 350: spxf[j] = 350.0 if abs(vpxf[j] - 4750.0) > 300: vpxf[j] = 4750.0 # start = np.array([[vpxf[j], spxf[j]],[vpxf[j],sigdisk]]) # (km/s), starting guess for [V,sigma] start = np.array([[velbulge, spxf[j]], [vpxf[j], sigdisk]]) # (km/s), starting guess for [V,sigma] print("Starting velocity estimates:", start[0, 0], start[0, 1], start[1, 0], start[1, 1]) print("Xbin:", xbin[j], "Ybin:", ybin[j]) t = clock() pp = ppxf( templates, galaxy[:, j], noise[:, j], velscale, start, bulge_fraction=bulge_fraction[j], goodpixels=goodPixels, moments=[mom, mom], degree=4, vsyst=dv, component=component, brot=1, plot=True, ) # brot=0 for nonrotating, brot=1 for rotating # Kinematics kinem = np.loadtxt("ppxfout.txt") wts = np.loadtxt("ppxfoutwt.txt") if mom == 2: output[j, 2] = kinem[0, 0] # bvel output[j, 3] = kinem[0, 1] # bsig output[j, 4] = kinem[1, 0] # dvel output[j, 5] = kinem[1, 1] # dsig output[j, 6] = wts[0] # bulge weight output[j, 7] = wts[1] # disk weight output[j, 8] = wts[2] # chisqn output[j, 9] = wts[3] # chisq if mom == 4: output[j, 2] = kinem[0, 0] # bvel output[j, 3] = kinem[0, 1] # bsig output[j, 4] = kinem[0, 2] # bh3 output[j, 5] = kinem[0, 3] # bh4 output[j, 6] = kinem[1, 0] # dvel output[j, 7] = kinem[1, 1] # dsig output[j, 8] = kinem[1, 2] # dh3 output[j, 9] = kinem[1, 3] # dh4 output[j, 10] = wts[0] # bulge weight output[j, 11] = wts[1] # disk weight output[j, 12] = wts[2] # chisqn output[j, 13] = wts[3] # chisq print(wts[0], wts[1]) print("Chisq difference from one comp fit (pos = improved)", occh[j] - wts[2]) if occh[j] > wts[2]: count = count + 1 bwt[j] = wts[0] dwt[j] = wts[1] # Populations # wtsb=np.loadtxt("ppxfoutwtsb.txt") # wtsd=np.loadtxt("ppxfoutwtsd.txt") # shwb=wtsb.shape # shwd=wtsd.shape # if len(shwb) > 1: # bulgewt=np.array(wtsb[0,:]) # bulgewt=bulgewt/bulgewt.sum() # bulgewtin=np.array(wtsb[1,:],dtype=int) # else: # bulgewt=1. # bulgewtin=np.int(wtsb[1]) # if len(shwd) > 1: # diskwt=np.array(wtsd[0,:]) # diskwt=diskwt/diskwt.sum() # diskwtin=np.array(wtsd[1,:],dtype=int) # else: # diskwt=1. # diskwtin=np.int(wtsd[1]) # bage[j]=np.dot(bulgewt,age[bulgewtin]) # bmet[j]=np.dot(bulgewt,met[bulgewtin]) # dage[j]=np.dot(diskwt,age[diskwtin]) # dmet[j]=np.dot(diskwt,met[diskwtin]) # popoutput[j,2]=bage[j] # popoutput[j,3]=bmet[j] # popoutput[j,4]=dage[j] # popoutput[j,5]=dmet[j] # Plots # ssparr=templates # print(gal_bin.shape) # gal_bin=galaxy # print(gal_bin.shape) # bulgespec=ssparr[:,bulgewtin].dot(bulgewt) # diskspec=ssparr[:,diskwtin].dot(diskwt) # diskspec=diskspec/np.median(diskspec) # bulgespec=bulgespec/np.median(bulgespec) # plt.xlabel("Wavelength") # plt.ylabel("Counts") # plt.plot(5*(gal_bin[goodPixels,j]/np.median(gal_bin[goodPixels,j])), 'k') # plt.plot(3*(bulgespec[goodPixels]), 'r') # plt.plot(2*(diskspec[goodPixels]), 'b') # plt.plot(5*(bulgespec[goodPixels]+diskspec[goodPixels])/np.median(bulgespec[goodPixels]+diskspec[goodPixels]), 'g') # plt.savefig('outfit') np.savetxt("NGC528conskinnchcheckbrot.txt", output, fmt="%10.3g")