Beispiel #1
0
    def mc_errors(self, nsim=200):
        """ Calculate the errors using MC simulations"""
        errs = np.zeros((nsim, len(self.error)))
        for i in range(nsim):
            y = self.bestfit + np.random.normal(0, self.noise, len(
                self.galaxy))

            noise = np.ones_like(self.galaxy) * self.noise
            sim = ppxf(self.bestfit_unbroad,
                       y,
                       noise,
                       velscale, [0, self.sol[1]],
                       goodpixels=self.goodpixels,
                       plot=False,
                       moments=4,
                       degree=-1,
                       mdegree=-1,
                       vsyst=self.vsyst,
                       lam=self.lam,
                       quiet=True,
                       bias=0.)
            errs[i] = sim.sol
        median = np.ma.median(errs, axis=0)
        error = 1.4826 * np.ma.median(np.ma.abs(errs - median), axis=0)
        # Here I am using always the maximum error between the simulated
        # and the values given by pPXF.
        self.error = np.maximum(error, self.error)
        return
Beispiel #2
0
    def run(self):
        c = 299792.458  # speed of light in km/s
        meps = np.finfo('float64').eps

        dv = self.dv
        velscale = self.velscale
        templates = self.templates
        lam_gal = self.lam_gal
        mask = self.mask
        wave_mask = self.wave_mask

        use_mask = mask[wave_mask]
        flux = ((self.flux)[wave_mask])
        noise = ((self.ivar**(-0.5))[wave_mask])

        nonzero_finite_bool = use_mask & (noise > 0) & (np.isfinite(noise))
        igoodpixels = np.zeros(len(lam_gal)).astype(int)
        igoodpixels[self.goodpixels] = 1
        maskpixels = (igoodpixels & nonzero_finite_bool) > 0

        noise[(noise <= 0) | ~np.isfinite(noise)] = meps

        galaxy = flux / np.median(
            flux[maskpixels])  # Normalize spectrum to avoid numerical issues
        noise = noise / np.median(flux[maskpixels])
        medsn = np.median(galaxy[maskpixels] / noise[maskpixels])

        # 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 = [0, 200.]  # (km/s), starting guess for [V, sigma]

        adegree = 0
        mdegree = 0
        pp = ppxf(templates,
                  galaxy,
                  noise,
                  velscale,
                  start,
                  mask=maskpixels,
                  plot=False,
                  quiet=True,
                  moments=2,
                  degree=adegree,
                  mdegree=mdegree,
                  vsyst=dv,
                  clean=False,
                  lam=lam_gal)
        return pp, medsn
Beispiel #3
0
	def ppxf_fit(self, spectrum, noise, verbose=False):
		"""Run pPXF on one spaxel. Note: must run prepstellarfit() first!

		Arguments:
			spectrum, noise (1D array): Observed spectrum and variance array
			verbose (bool): If 'True', make diagnostic plots

		Returns:
			params (float 1D array): Best fit velocity, velocity dispersion, velocity error, velocity dispersion error
		"""

		# Make sure spectrum and noise arrays are the right length
		if spectrum.size != self.wvl_cropped.size:
			raise ValueError('Size of spectrum array %s does not match size of wvl array %s' % (spectrum.size, self.wvl_cropped.size))
		if noise.size != self.wvl_cropped.size:
			raise ValueError('Size of noise array %s does not match size of wvl array %s' % (noise.size, self.wvl_cropped.size))

		# Prep the observed spectrum
		galspec = ndimage.gaussian_filter1d(spectrum, self.sigma)
		galaxy, logLam1, velscale = util.log_rebin(self.lamRange1, galspec)
		galaxy = galaxy/np.median(galaxy)

		# Shift the template to fit the starting wavelength of the galaxy spectrum
		c = 299792.458
		dv = (self.logLam2[0] - logLam1[0])*c  # km/s

		goodPixels = util.determine_goodpixels(logLam1, self.lamRange2, 0)

		# Here the actual fit starts. The best fit is plotted on the screen
		start = [0., 200.]  # (km/s), starting guess for [V, sigma]

		if verbose: plot=True
		else: plot=False

		pp = ppxf(self.templates, galaxy, np.sqrt(noise), velscale, start,
				  goodpixels=goodPixels, plot=plot, moments=2,
				  degree=6, vsyst=dv, clean=False, quiet=True)
		if verbose:
			plt.show()

			print("Formal errors:")
			print("     dV    dsigma")
			print("".join("%8.2g" % f for f in pp.error*np.sqrt(pp.chi2)))

			print('Best-fitting redshift z:', (self.z + 1)*(1 + pp.sol[0]/c) - 1)

		return np.asarray([pp.sol[0], pp.sol[1], pp.error[0]*np.sqrt(pp.chi2), pp.error[1]*np.sqrt(pp.chi2), pp.chi2]), np.exp(logLam1), pp.bestfit, galaxy, noise
Beispiel #4
0
    def mc_errors(self, nsim=200):
        """ Calculate the errors using MC simulations"""
        errs = np.zeros((nsim, len(self.error)))
        for i in range(nsim):
            y = self.bestfit + np.random.normal(0, self.noise,
                                                len(self.galaxy))

            noise = np.ones_like(self.galaxy) * self.noise
            sim = ppxf(self.bestfit_unbroad, y, noise, velscale,
                       [0, self.sol[1]],
                       goodpixels=self.goodpixels, plot=False, moments=4,
                       degree=-1, mdegree=-1,
                       vsyst=self.vsyst, lam=self.lam, quiet=True, bias=0.)
            errs[i] = sim.sol
        median = np.ma.median(errs, axis=0)
        error = 1.4826 * np.ma.median(np.ma.abs(errs - median), axis=0)
        # Here I am using always the maximum error between the simulated
        # and the values given by pPXF.
        self.error = np.maximum(error, self.error)
        return
Beispiel #5
0
def spectra_distance(star, template, mask, degree):

    # Make ppxf fit without any velocity shift and with no sigma broadening.
    # Only the polynomials are fitted.
    velscale = 1.  # Arbitrary number: we work in pixels units
    start = [0, 1.]  # Negligible sigma: OK with >2016 Fourier pPXF code
    noise = np.ones_like(star)
    pp = ppxf(template,
              star,
              noise,
              velscale,
              start,
              moments=2,
              degree=degree,
              linear=True,
              mask=mask)
    resid = star - pp.bestfit
    dist = np.percentile(np.abs(resid[mask]), 95.45) / np.mean(
        star[mask]) * 50  # 2sigma/2 in %

    return dist, pp
def fit_single_spectrum(lam_gal,
                        flux_gal,
                        templates,
                        velscale,
                        start,
                        velscale_ratio=1,
                        noise=None,
                        goodpixels=None,
                        dv=0,
                        add_poly_deg=4,
                        smooth=False,
                        smooth_sigma_pix=None,
                        clean=False,
                        plot=False):
    """
    Fit a single spectrum with PPXF
    :param lam_gal:
    :param flux_gal:
    :param templates:
    :param velscale:
    :param start:
    :param velscale_ratio:
    :param noise:
    :param goodpixels:
    :param dv:
    :param add_poly_deg:
    :return:
    """

    # Smooth the spectrum to the resolution of the EMILES templates if requested
    # The smoothing length should be provided by the user
    if smooth:
        flux_gal = ppxf_util.gaussian_filter1d(flux_gal, smooth_sigma_pix)

    # Log rebin the spectrum to the given velocity scale
    flux_rebin_gal, log_lam_gal, velscale = ppxf_util.log_rebin(
        [lam_gal[0], lam_gal[-1]], flux_gal, velscale=velscale)

    # Generate a default goodpixels vector
    if goodpixels is None:
        goodpixels = np.arange(len(flux_rebin_gal))

    # Normalize the spectrum to avoid rounding error
    norm = np.nanmedian(flux_rebin_gal)
    flux_rebin_gal /= np.nanmedian(flux_rebin_gal)

    # Generate a noise spectrum if noise is None. Use 15% of the flux
    if noise is None:
        noise = 0.15 * np.abs(flux_rebin_gal)
        noise[np.isnan(noise) | (noise == 0)] = 1.0

    # Remove NaNs and infs from goodpixels and change them to 0 in the spectrum
    nonfinite_pix = ~np.isfinite(flux_rebin_gal)
    flux_rebin_gal[nonfinite_pix] = 0.0

    for i in range(len(nonfinite_pix)):
        if nonfinite_pix[i]:
            test = np.where(goodpixels == i)[0]
            if len(test) > 0:
                goodpixels = np.delete(goodpixels, test[0])

    # Require at least 50% of the original pixels to do a fit
    if np.float(len(goodpixels)) / np.float(len(log_lam_gal)) > 0.25:

        # Run ppxf
        pp = ppxf.ppxf(templates,
                       flux_rebin_gal,
                       noise,
                       velscale,
                       start,
                       plot=False,
                       moments=2,
                       degree=add_poly_deg,
                       vsyst=dv,
                       goodpixels=goodpixels,
                       velscale_ratio=velscale_ratio,
                       clean=clean)

        vel = pp.sol[0]
        disp = pp.sol[1]
        chi2 = pp.chi2
        temp_weights = pp.weights
        gal = pp.galaxy
        bestfit = pp.bestfit
        stellar = pp.bestfit - pp.apoly
        apoly = pp.apoly
        residual = pp.galaxy - pp.bestfit

        if plot:

            pp.plot()
            fig = plt.gcf()

            return vel, disp, chi2, temp_weights, gal, bestfit, stellar, apoly, residual, norm, fig

        else:

            return vel, disp, chi2, temp_weights, gal, bestfit, stellar, apoly, residual, norm

    else:

        return 'Not enough pixels to fit.'
Beispiel #7
0
Datei: ppxf.py Projekt: FRBs/FRB
def fit_spectrum(spec,
                 zgal,
                 specresolution,
                 tie_balmer=False,
                 miles_dir=None,
                 rebin=True,
                 limit_doublets=False,
                 degree_add=None,
                 degree_mult=5,
                 **kwargs):
    """This is a wrapper for pPXF to fit stellar population models as well as
    emission lines to galaxy spectra.  Although pPXF allows otherwise, the
    emission lines are kinematically fixed to one another as are the stellar
    models, and the stars and gas independently vary with one another.

    Please see the pPXF documentation for more details on the vast array of
    parameters and options afforded by the software.

    The pPXF software may be downloaded at
    http://www-astro.physics.ox.ac.uk/~mxc/software/

    Parameters
    ----------
    spec : XSpectrum1D
        Spectrum to be fitted
    zgal : float
        Redshift of galaxy
    specresolution : float
        Spectral resolution (R) of the data
    tie_balmer : bool, optional
        Assume intrinsic Balmer decrement.  See documentation in ppxf_util.py,
        as this has implications for the derived reddening.
    limit_doublets : bool, optional
        Limit the ratios of [OII] and [SII] lines to ranges allowed by atomic
        physics.  See documentation in ppxf_util.py, as this has implications
        for getting the true flux values from those reported.
    degree_add : int, optional
        Degree of the additive Legendre polynomial used to modify the template
        continuum in the fit.
    degree_mult : int,optional
    miles_dir: str, optional
      Location of MILES models

    Returns
    -------
    ppfit : ppxf
        Object returned by pPXF; attributes are data pertaining to the fit
    miles : miles
        Contains information about the stellar templates used in the fit.
        See the documentation in miles_util.py for full details
    weights : 1d numpy vector
        Weights of the *stellar* template components.  Equivalent to the
        first N elements of ppfit.weights where N is the number of stellar
        templates used in the fit.
    """

    if spec is None:
        print('Galaxy has no spectrum associated')
        return None

    ### Spectra must be rebinned to a log scale (in wavelength).
    ### pPXF provides a routine to do this, but the input spectra
    ### must be sampled uniformly linear. linetools to the rescue!
    if rebin:
        meddiff = np.median(spec.wavelength.value[1:] -
                            spec.wavelength.value[0:-1])
        newwave = np.arange(spec.wavelength.value[0],
                            spec.wavelength.value[-1], meddiff)
        spec = spec.rebin(newwave * units.AA, do_sig=True, grow_bad_sig=True)

    # get data and transform wavelength to rest frame for template fitting
    wave = spec.wavelength.to(units.Angstrom).value
    flux = spec.flux.value
    flux = flux * (1. + zgal)
    noise = spec.sig.value
    noise = noise * (1. + zgal)
    wave = wave / (1. + zgal)
    # transform to air wavelengths for MILES templates and get approx. FWHM
    wave *= np.median(util.vac_to_air(wave) / wave)

    # use only wavelength range covered by templates
    mask = (wave > 3540) & (wave < 7409)
    #mask = (wave > 1682) & (wave < 10000.)
    maskidx = np.where(mask)[0]
    # also deal with declared good regions of the spectrum
    if 'goodpixels' in kwargs:
        goodpix = kwargs['goodpixels']
        pixmask = np.in1d(maskidx, goodpix)
        newgoodpix = np.where(pixmask)[0]
        kwargs['goodpixels'] = newgoodpix

    wave = wave[mask]
    flux = flux[mask]
    noise = noise[mask]

    # Nonnegative noise values are not allowed
    noise[noise <= 0] = np.max(noise)

    # pPXF requires the spectra to be log rebinned, so do it
    flux, logLam, velscale = util.log_rebin(np.array([wave[0], wave[-1]]),
                                            flux)
    noise, junk1, junk2 = util.log_rebin(np.array([wave[0], wave[-1]]), noise)

    ### The following lines unnecessary for DEIMOS/Hecto spectra due to their
    ### units but rescaling may be necessary for some
    # galaxy = flux / np.median(flux)  # Normalize spectrum to avoid numerical issues
    # print 'Scale flux by', round(np.median(flux),2)
    galaxy = flux  # use the native units

    # pPXF wants the spectral resolution in units of wavelength
    FWHM_gal = wave / specresolution

    ### Set up stellar templates
    #miles_dir = resource_filename('ppxf', '/miles_models/')
    #miles_dir = resource_filename('ppxf', '/emiles_padova_chabrier/')
    if miles_dir is None:
        miles_dir = resource_filename('ppxf', '/miles_padova_chabrier/')
    #path4libcall = miles_dir + 'Mun1.30*.fits'
    #path4libcall = miles_dir + 'Ech1.30*.fits'
    path4libcall = miles_dir + 'Mch1.30*.fits'
    miles = lib.miles(path4libcall, velscale, FWHM_gal, wave_gal=wave)

    ### Stuff for regularization dimensions
    reg_dim = miles.templates.shape[1:]
    stars_templates = miles.templates.reshape(miles.templates.shape[0], -1)

    # See the pPXF documentation for the keyword REGUL
    regul_err = 0.01  # Desired regularization error

    ### Now the emission lines!  Only include lines in fit region.
    if 'goodpixels' in kwargs:
        gal_lam = wave[newgoodpix]
        # Also, log rebinning the spectrum change which pixels are 'goodpixels'
        newnewgoodpix = np.searchsorted(np.exp(logLam), gal_lam, side='left')
        uqnewnewgoodpix = np.unique(newnewgoodpix)
        if uqnewnewgoodpix[-1] == len(wave):
            uqnewnewgoodpix = uqnewnewgoodpix[:-1]
        kwargs['goodpixels'] = uqnewnewgoodpix

    else:
        gal_lam = wave

    def FWHM_func(wave):  # passed to generate emission line templates
        return wave / specresolution

    gas_templates, gas_names, line_wave = util.emission_lines_mask(
        miles.log_lam_temp,
        gal_lam,
        FWHM_func,
        tie_balmer=tie_balmer,
        limit_doublets=limit_doublets)

    # How many gas components do we have?
    balmerlines = [ll for ll in gas_names if ll[0] == 'H']
    numbalm = len(balmerlines)
    numforbid = len(gas_names) - numbalm

    # Stack all templates
    templates = np.column_stack([stars_templates, gas_templates])

    # other needed quantities
    dv = c * (miles.log_lam_temp[0] - logLam[0])
    ### use the following line if not transforming to z=0 first
    # vel = c * np.log(1 + zgal)  # eq.(8) of Cappellari (2017)
    vel = 0.  # We already transformed to the restframe!
    start = [vel, 25.]  # (km/s), starting guess for [V, sigma]

    ### Set up combination of templates
    n_temps = stars_templates.shape[1]
    n_balmer = 1 if tie_balmer else numbalm  # Number of Balmer lines included in the fit
    n_forbidden = numforbid  # Number of other lines included in the fit

    # Assign component=0 to the stellar templates, component=1 to the Balmer
    # emission lines templates, and component=2 to the forbidden lines.
    # component = [0]*n_temps + [1]*n_balmer + [2]*n_forbidden
    component = [0] * n_temps + [1] * (n_balmer + n_forbidden
                                       )  # tie the gas lines together
    gas_component = np.array(
        component) > 0  # gas_component=True for gas templates

    # Fit (V, sig, h3, h4) moments=4 for the stars
    # and (V, sig) moments=2 for the two gas kinematic components
    if len(gas_names) > 0:
        moments = [2, 2]  # fix the gas kinematic components to one another
        start = [[vel, 50.],
                 start]  # Adopt different gas/stars starting values
    else:
        moments = [2]  # only stars to be fit
        start = [vel, 50.]

        # If the Balmer lines are tied one should allow for gas reddening.
    # The gas_reddening can be different from the stellar one, if both are fitted.
    gas_reddening = 0 if tie_balmer else None

    if degree_add is None:
        degree_add = -1
    t = time.time()
    ppfit = ppxf.ppxf(templates,
                      galaxy,
                      noise,
                      velscale,
                      start,
                      plot=False,
                      moments=moments,
                      degree=degree_add,
                      vsyst=dv,
                      lam=np.exp(logLam),
                      clean=False,
                      regul=1. / regul_err,
                      reg_dim=reg_dim,
                      component=component,
                      gas_component=gas_component,
                      gas_names=gas_names,
                      gas_reddening=gas_reddening,
                      mdegree=degree_mult,
                      **kwargs)

    print('Desired Delta Chi^2: %.4g' % np.sqrt(2 * galaxy.size))
    print('Current Delta Chi^2: %.4g' % ((ppfit.chi2 - 1) * galaxy.size))
    print('Elapsed time in PPXF: %.2f s' % (time.time() - t))

    weights = ppfit.weights[
        ~gas_component]  # Exclude weights of the gas templates
    weights = weights.reshape(reg_dim)  # Normalized

    return ppfit, miles, weights
def ppxf_example_kinematics_sauron():

    ppxf_dir = path.dirname(path.realpath(ppxf_package.__file__))

    # Read a galaxy spectrum and define the wavelength range
    #

    cube_id = 468
    cube_file = "data/cubes/cube_" + str(cube_id) + ".fits"
    hdu = fits.open(cube_file)
    gal_lin = hdu[0].data
    h1 = hdu[0].header

    cube_x_data = np.load("results/cube_" + str(int(cube_id)) + "/cube_" +
                          str(int(cube_id)) + "_cbd_x.npy")
    cube_y_data = np.load("results/cube_" + str(int(cube_id)) + "/cube_" +
                          str(int(cube_id)) + "_cbs_y.npy")

    mask = (cube_x_data > 5000) & (cube_x_data < 6000)
    cube_y_data = cube_y_data[mask]

    lamRange1 = h1['CRVAL1'] + np.array(
        [0., np.abs(h1['CD1_1']) * (h1['NAXIS1'] - 1)])
    FWHM_gal = 2.51  # SAURON has an instrumental resolution FWHM of 4.2A.

    # 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

    galaxy, logLam1, velscale = util.log_rebin(lamRange1, cube_y_data)

    galaxy = galaxy / np.median(
        galaxy)  # Normalize spectrum to avoid numerical issues
    noise = np.full_like(galaxy,
                         0.0047)  # Assume constant noise per pixel here

    print(galaxy)
    # 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(ppxf_dir + '/miles_models/Mun1.30Z*.fits')
    FWHM_tem = 2.51  # Vazdekis+10 spectra have a constant resolution FWHM of 2.51A.
    velscale_ratio = 2  # adopts 2x higher spectral sampling for templates than for galaxy

    # 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.
    #
    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_temp = util.log_rebin(lamRange2,
                                                    ssp,
                                                    velscale=velscale /
                                                    velscale_ratio)
    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_temp = util.log_rebin(lamRange2,
                                                        ssp,
                                                        velscale=velscale /
                                                        velscale_ratio)
        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
    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.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.]  # (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,
              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))
def apply_pPXF(spectra_file,
               fwhm_instrument,
               redshift,
               noise,
               vel_ratio=1,
               template_dir=None,
               fwhm_templates=None,
               templates=None,
               logLam2=None):
    """
    Function to fit galaxy spectra using pPXF.

    If provided with the template directory and resolution, this function will run the degrade_templates()
    function in order to match the resolution of the observation and the templates. Else, if the degraded
    templates are already provided, this function will just use the templates provided to fit the observed 
    galaxy spectra. 

    :param spectra_file: A string describing the path to the SimSpin spectra file.
    :param fwhm_instrument: A float describing the resolution of the observed spectra.
    :param redshift: A float describing the redshift, z, of the observed galaxy.
    :param noise: A float describing the level of noise within the observed spectra.
    :param vel_ratio: A float describing the sampling rate of the templates relative
        to the observed spectra. Default value is 1.
    
    If wishing to degrade templates to the appropriate resolution, 

    :param template_dir: A string describing the path to the directory in which the template files are 
        located. Default is None.
    :param fwhm_templates: A float describing the resolution of the template spectra. Default 
        is None.

    If you have already degraded the templates to the appropriate resolution for a previous 
    fit, you can provide these variables directly to the function to avoid recalculating the
    comupationally expensive degradation and rebinning:

    :param templates: A matrix containing the rebinned and degraded templates. Default is None.
    :param logLam2: An array describing the wavelength labels of the templates. Default is None.

    """
    hdu = fits.open(spectra_file)
    dim = hdu[0].data.shape
    mid = np.array([round(dim[1] / 2), round(dim[2] / 2)])

    gal_lin = hdu[0].data  # pulling in the spectra for each pixel
    h1 = hdu[0].header
    lamRange1 = h1['CRVAL3'] + (np.array([-h1['CRPIX3'], h1['CRPIX3']]) *
                                h1['CDELT3'])  # wavelength range
    FWHM_gal = fwhm_instrument  # SAMI has an instrumental resolution FWHM of 2.65A.

    z = redshift  # 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[:, mid[0],
                                                                  mid[1]])

    if not templates or not logLam2:
        assert isinstance(template_dir, str) and isinstance(
            fwhm_templates, float
        ), 'Please provide the path to the template directory and template resolution'
        templates, logLam2 = degrade_templates(template_dir,
                                               fwhm_templates,
                                               FWHM_observation=FWHM_gal,
                                               velscale_obs=velscale,
                                               vel_ratio=vel_ratio)

    velocity = np.empty([dim[1], dim[2]])
    dispersion = np.empty([dim[1], dim[2]])

    for x in range(0, dim[1]):
        for y in range(0, dim[2]):

            if all(np.isnan(gal_lin[:, x, y])):
                velocity[x, y] = None
                dispersion[x, y] = None

            else:
                galaxy, logLam1, velscale = util.log_rebin(
                    lamRange1, gal_lin[:, x, y])
                galaxy = galaxy / np.median(
                    galaxy)  # Normalize spectrum to avoid numerical issues
                noise = np.full_like(
                    galaxy, noise)  # Assume constant noise per pixel here

                if velscale_ratio > 1:
                    dv = (np.mean(logLam2[:velscale_ratio]) -
                          logLam1[0]) * c  # km/s
                else:
                    dv = (logLam2[0] - logLam1[0]) * c  # km/s

                start = [100, 200.]  # (km/s), starting guess for [V, sigma]

                pp = ppxf.ppxf(templates,
                               galaxy,
                               noise,
                               velscale,
                               start,
                               plot=False,
                               moments=2,
                               quiet=True,
                               degree=4,
                               vsyst=dv,
                               velscale_ratio=vel_ratio)

                velocity[x, y] = pp.sol[0]
                dispersion[x, y] = pp.sol[1]

    return velocity, dispersion
def ppxf_example_sky_and_symmetric_losvd():

    ppxf_dir = path.dirname(path.realpath(ppxf_package.__file__))

    # Solar metallicity, Age=12.59 Gyr
    hdu = fits.open(
        ppxf_dir +
        '/miles_models/Mun1.30Zp0.00T12.5893_iPp0.00_baseFe_linear_FWHM_2.51.fits'
    )
    ssp = hdu[0].data
    h = hdu[0].header

    lamRange = h['CRVAL1'] + np.array([0., h['CDELT1'] * (h['NAXIS1'] - 1)])
    velscale = 70.  # km/s
    star, logLam, velscale = util.log_rebin(lamRange, ssp, velscale=velscale)
    star /= np.mean(star)

    # Adopted input parameters =================================================

    vel = 200. / velscale  # Velocity of 1st spectrum in pixels (2nd has -vel)
    sigma = 300. / velscale  # Dispersion of both spectra in pixels
    h3 = 0.1  # h3 of 1st spectrum (2nd has -h3)
    h4 = 0.1
    sn = 40.
    moments = 4
    deg = 4
    vshift = 10  # Adopted systemic velocity in pixels
    vsyst = vshift * velscale  # Adopted systemic velocity in km/s

    # Generate input Sky =======================================================
    # For illustration, the sky is modelled as two Gaussian emission lines

    n = star.size
    x = np.arange(n)
    sky1 = np.exp(-0.5 * (x - 1000)**2 / 100)
    sky2 = np.exp(-0.5 * (x - 2000)**2 / 100)

    # Generate input LOSVD =====================================================

    dx = int(abs(vel) + 5 * sigma)
    v = np.linspace(-dx, dx, 2 * dx + 1)
    w = (v - vel) / sigma
    w2 = w**2
    gauss = np.exp(-0.5 * w2)
    gauss /= np.sum(gauss)
    h3poly = w * (2 * w2 - 3) / np.sqrt(3)
    h4poly = (w2 * (4 * w2 - 12) + 3) / np.sqrt(24)
    losvd = gauss * (1 + h3 * h3poly + h4 * h4poly)

    # Generate first synthetic spectrum ========================================
    # The template is convolved with the LOSVD

    x = np.linspace(-1, 1, n)
    galaxy1 = signal.fftconvolve(star, losvd, mode="same")
    galaxy1 = np.roll(galaxy1, vshift)  # Mimic nonzero systemic velocity
    galaxy1 *= legendre.legval(
        x, np.append(1,
                     np.random.uniform(-0.1, 0.1,
                                       deg - 1)))  # Multiplicative polynomials
    galaxy1 += legendre.legval(x,
                               np.random.uniform(-0.1, 0.1,
                                                 deg))  # Additive polynomials
    galaxy1 += sky1 + 2 * sky2  # Add two sky lines
    galaxy1 = np.random.normal(galaxy1, 1 / sn)  # Add noise

    # Generate symmetric synthetic spectrum ====================================
    # The same template is convolved with a reversed LOSVD
    # and different polynomials and sky lines are included

    galaxy2 = signal.fftconvolve(star, np.flip(losvd, 0), mode="same")
    galaxy2 = np.roll(galaxy2, vshift)  # Mimic nonzero systemic velocity
    galaxy2 *= legendre.legval(
        x, np.append(1,
                     np.random.uniform(-0.1, 0.1,
                                       deg - 1)))  # Multiplicative polynomials
    galaxy2 += legendre.legval(x,
                               np.random.uniform(-0.1, 0.1,
                                                 deg))  # Additive polynomials
    galaxy2 += 2 * sky1 + sky2  # Add two sky lines
    galaxy2 = np.random.normal(galaxy2, 1 / sn)  # Add noise

    # Load spectral templates ==================================================

    vazdekis = glob.glob(ppxf_dir + '/miles_models/Mun1.30Z*.fits')
    templates = np.empty((n, len(vazdekis)))
    for j, file in enumerate(vazdekis):
        hdu = fits.open(file)
        ssp = hdu[0].data
        sspNew, logLam2, velscale = util.log_rebin(lamRange,
                                                   ssp,
                                                   velscale=velscale)
        templates[:, j] = sspNew / np.median(sspNew)  # Normalize templates

    # Do the fit ===============================================================

    # Input both galaxy spectra simultaneously to pPXF
    galaxy = np.column_stack([galaxy1, galaxy2])

    # Use two sky templates for each galaxy spectrum
    sky = np.column_stack([sky1, sky2])

    # Randomized starting guess
    vel0 = vel + np.random.uniform(-1, 1)
    sigma0 = sigma * np.random.uniform(0.8, 1.2)
    start = np.array([vel0, sigma0]) * velscale  # Convert to km/s
    goodpixels = np.arange(50, n - 50)

    print(
        "\nThe input values are: Vel=%0.0f, sigma=%0.0f, h3=%0.1f, h4=%0.1f\n"
        % (vel * velscale, sigma * velscale, h3, h4))

    t = clock()

    pp = ppxf(templates,
              galaxy,
              np.full_like(galaxy, 1 / sn),
              velscale,
              start,
              goodpixels=goodpixels,
              plot=1,
              moments=moments,
              vsyst=vsyst,
              mdegree=deg,
              degree=deg,
              sky=sky)

    print('Elapsed time in pPXF: %.2f s' % (clock() - t))
    plt.pause(1)
Beispiel #11
0
def ppxf_example_population_gas_sdss(tie_balmer, limit_doublets):

    ppxf_dir = path.dirname(path.realpath(ppxf_package.__file__))

    cube_id = 468

    # reading cube_data
    cube_file = "data/cubes/cube_" + str(cube_id) + ".fits"
    hdu = fits.open(cube_file)
    t = hdu[0].data

    # using our redshift estimate from lmfit
    cube_result_file = ("results/cube_" + str(cube_id) + "/cube_" +
                        str(cube_id) + "_lmfit.txt")
    cube_result_file = open(cube_result_file)

    line_count = 0
    for crf_line in cube_result_file:
        if (line_count == 20):
            curr_line = crf_line.split()
            z = float(curr_line[1])
        line_count += 1

    cube_x_data = np.load("results/cube_" + str(int(cube_id)) + "/cube_" +
                          str(int(cube_id)) + "_cbd_x.npy")
    cube_y_data = np.load("results/cube_" + str(int(cube_id)) + "/cube_" +
                          str(int(cube_id)) + "_cbs_y.npy")

    # Only use the wavelength range in common between galaxy and stellar library.
    #
    mask = (cube_x_data > 3540) & (cube_x_data < 7409)
    flux = cube_y_data[mask]
    galaxy = flux / np.median(
        flux)  # Normalize spectrum to avoid numerical issues
    wave = cube_x_data[mask]

    # The SDSS wavelengths are in vacuum, while the MILES ones are in air.
    # For a rigorous treatment, the SDSS vacuum wavelengths should be
    # converted into air wavelengths and the spectra should be resampled.
    # To avoid resampling, given that the wavelength dependence of the
    # correction is very weak, I approximate it with a constant factor.
    #
    wave *= np.median(util.vac_to_air(wave) / wave)

    # The noise level is chosen to give Chi^2/DOF=1 without regularization (REGUL=0).
    # A constant noise is not a bad approximation in the fitted wavelength
    # range and reduces the noise in the fit.
    #
    noise = np.full_like(galaxy,
                         0.01635)  # 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 = c * np.log(wave[1] / wave[0])  # eq.(8) of Cappellari (2017)
    FWHM_gal = 2.76  # SDSS has an approximate instrumental resolution FWHM of 2.76A.

    #------------------- Setup templates -----------------------

    pathname = ppxf_dir + '/miles_models/Mun1.30*.fits'
    miles = lib.miles(pathname, velscale, FWHM_gal)

    # The stellar templates are reshaped below into a 2-dim array with each
    # spectrum as a column, however we save the original array dimensions,
    # which are needed to specify the regularization dimensions
    #
    reg_dim = miles.templates.shape[1:]
    stars_templates = miles.templates.reshape(miles.templates.shape[0], -1)

    # See the pPXF documentation for the keyword REGUL,
    regul_err = 0.013  # Desired regularization error

    # Construct a set of Gaussian emission line templates.
    # Estimate the wavelength fitted range in the rest frame.
    #
    lam_range_gal = np.array([np.min(wave), np.max(wave)]) / (1 + z)
    gas_templates, gas_names, line_wave = util.emission_lines(
        miles.log_lam_temp,
        lam_range_gal,
        FWHM_gal,
        tie_balmer=tie_balmer,
        limit_doublets=limit_doublets)

    # Combines the stellar and gaseous templates into a single array.
    # During the PPXF fit they will be assigned a different kinematic
    # COMPONENT value
    #
    templates = np.column_stack([stars_templates, gas_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 as described
    # in PPXF_EXAMPLE_KINEMATICS_SAURON and Sec.2.4 of Cappellari (2017)
    #
    c = 299792.458
    dv = c * (miles.log_lam_temp[0] - np.log(wave[0])
              )  # eq.(8) of Cappellari (2017)
    vel = c * np.log(1 + z)  # eq.(8) of Cappellari (2017)
    start = [vel, 180.]  # (km/s), starting guess for [V, sigma]

    n_temps = stars_templates.shape[1]
    n_forbidden = np.sum(["[" in a
                          for a in gas_names])  # forbidden lines contain "[*]"
    n_balmer = len(gas_names) - n_forbidden

    # Assign component=0 to the stellar templates, component=1 to the Balmer
    # gas emission lines templates and component=2 to the forbidden lines.
    component = [0] * n_temps + [1] * n_balmer + [2] * n_forbidden
    gas_component = np.array(
        component) > 0  # gas_component=True for gas templates

    # Fit (V, sig, h3, h4) moments=4 for the stars
    # and (V, sig) moments=2 for the two gas kinematic components
    moments = [4, 2, 2]

    # Adopt the same starting value for the stars and the two gas components
    start = [start, start, start]

    # If the Balmer lines are tied one should allow for gas reddeining.
    # The gas_reddening can be different from the stellar one, if both are fitted.
    gas_reddening = 0 if tie_balmer else None

    # Here the actual fit starts.
    #
    # 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.
    #
    t = clock()
    pp = ppxf(templates,
              galaxy,
              noise,
              velscale,
              start,
              plot=False,
              moments=moments,
              degree=-1,
              mdegree=10,
              vsyst=dv,
              lam=wave,
              clean=False,
              regul=1. / regul_err,
              reg_dim=reg_dim,
              component=component,
              gas_component=gas_component,
              gas_names=gas_names,
              gas_reddening=gas_reddening)

    # When the two Delta Chi^2 below are the same, the solution
    # is the smoothest consistent with the observed spectrum.
    #
    print('Desired Delta Chi^2: %.4g' % np.sqrt(2 * galaxy.size))
    print('Current Delta Chi^2: %.4g' % ((pp.chi2 - 1) * galaxy.size))
    print('Elapsed time in PPXF: %.2f s' % (clock() - t))

    weights = pp.weights[
        ~gas_component]  # Exclude weights of the gas templates
    weights = weights.reshape(reg_dim) / weights.sum()  # Normalized

    miles.mean_age_metal(weights)
    miles.mass_to_light(weights, band="r")

    # Plot fit results for stars and gas.
    plt.clf()
    plt.subplot(211)
    pp.plot()

    # Plot stellar population mass fraction distribution
    plt.subplot(212)
    miles.plot(weights)
    plt.tight_layout()
    #plt.pause(1)
    plt.show()
Beispiel #12
0
    #Randomize numpy seeds. Otherwise all processes will give exact same values. 
    #This seed depends on exact system time
    np.random.seed(seed=microsecond) 
    normal = np.random.normal(size=noise.shape) * noise
    galaxy = gal + normal

goodPixels = np.arange(len(galaxy))

em_lam = np.array([4363, 5007, 4861, 6548, 6562, 6584, 6717, 6731])

goodPixels = [x for x in range(len(lam)) if ~((lam[x] < (em_lam + 10)) & (lam[x] > (em_lam - 10))).all()]
goodPixels = np.array(goodPixels)
degrees = [10,10]
degrees = minimize_leg(galname, templates, galaxy, noise, velscale, start, 
                      goodPixels, dv, velscale_ratio, iterations, RunMC, 
                     find_min = True)

ppxf_obj = ppxf.ppxf(templates, galaxy, noise, velscale, start, 
                     goodpixels=goodPixels, plot=False, moments=4,
                     degree=degrees[0],vsyst=dv, 
                     velscale_ratio = velscale_ratio, 
                     mdegree=degrees[1], clean=False)

redshift = ppxf_obj.sol[0]
vel_disp = ppxf_obj.sol[1]
vel_disp_err = ppxf_obj.error[1]*np.sqrt(ppxf_obj.chi2)

print("Minimization took %s minutes to complete"
      %int(round((time.time() - stime) / 60.)))

ppxf_obj.plot()
Beispiel #13
0
    def cal_veldis(self,
                   temp_spec=None,
                   lib_path=None,
                   temp_array=None,
                   informat='text',
                   temp_num=None,
                   sig_ins=None,
                   rand_temp=False,
                   fwhm_temp=None,
                   doplot=True,
                   verbose=True,
                   moments=4,
                   plot=True,
                   degree=None,
                   mask_reg=None,
                   quiet=False,
                   show_weight=False,
                   clean=False):
        """
        This function calculates velocity dispersion using 'ppxf'
        method.
        """
        """First Setup some parameters """
        if temp_spec is None:
            self.temp_spec = self.gen_rebinned_temp(lib_path=lib_path,
                                                    temp_array=temp_array,
                                                    informat=informat,
                                                    temp_num=temp_num,
                                                    sig_ins=sig_ins,
                                                    rand_temp=rand_temp,
                                                    fwhm_temp=fwhm_temp,
                                                    doplot=doplot,
                                                    verbose=verbose)
        else:
            self.temp_spec = temp_spec

        if mask_reg is not None:
            self.mask_region = self.masking(pixel_range=mask_reg)

        if degree is None:
            deg = np.arange(4, 6)
        else:
            deg = np.arange(degree[0], degree[1])
        """Setup the containers to store velocity dispersion and error 
           values """
        vel_dis = np.zeros(len(deg))
        error = np.zeros(len(deg))
        best_fit = []
        """good pixels are the pixels which have been used in the fit"""
        good_pixels = []
        """Do the velocity dispersion calculation """
        for i, d in enumerate(deg):
            print('\ndegree : %d' % d)
            if mask_reg is None:
                pp = ppxf(self.temp_spec,
                          self.flux_rebinned,
                          self.noise_rebinned,
                          self.v,
                          self.start,
                          moments=moments,
                          plot=plot,
                          vsyst=self.vsyst,
                          degree=d,
                          quiet=quiet,
                          clean=clean,
                          lam=np.exp(self.wav_rebinned))
            else:
                pp = ppxf(self.temp_spec,
                          self.flux_rebinned,
                          self.noise_rebinned,
                          self.v,
                          self.start,
                          moments=moments,
                          plot=plot,
                          vsyst=self.vsyst,
                          degree=d,
                          mask=self.mask_region,
                          quiet=quiet,
                          lam=np.exp(self.wav_rebinned),
                          clean=clean)

            vel_dis[i] = pp.sol[1]
            error[i] = pp.error[1]
            best_fit.append(pp.bestfit)
            good_pixels.append(pp.goodpixels)
            if plot:
                plt.figure()

            if show_weight:
                [print('%d, %f'%(i,w)) for i,w in enumerate(pp.weights)\
                                                               if w>10]
        self.vel_dis = vel_dis
        self.error = error
        self.deg = deg
        self.best_fit = best_fit
        self.goodpixels = good_pixels
    def stellarfit(self, plot=True):
        """ Fit stellar continuum of integrated spectra, return stellar kinematics,
			and subtract continuum/absorption features.

			Args:
				plot (bool): if 'True', make plots

			Returns:
				kinematics (array): [vel, veldisp, vel_err, veldisp_err, wvlarray, fitspectrum, obsspectrum, obsspectrum_err]
		"""

        print('Preparing templates for stellar kinematics fit...')

        # Define path to pPXF directory
        ppxf_dir = path.dirname(path.realpath(ppxf_package.__file__))
        print(ppxf_dir)

        # Define spectrum
        spectrum = self.totalspec[self.goodwvl]
        noise = self.totalvar[self.goodwvl]
        wvl = self.wvl_zcorr[self.goodwvl]

        print(spectrum.shape, noise.shape, wvl.shape)

        # Define wavelength range
        lamRange1 = [wvl[0], wvl[-1]]
        fwhm_gal = 2.4 / (1 + self.z)  # KCWI instrumental FWHM of ~2.4A

        # Rebin spectrum into log scale to get initial velocity scale
        galaxy, logLam1, velscale = util.log_rebin(lamRange1, spectrum)

        # Read the list of filenames from template library
        vazdekis = glob.glob(
            ppxf_dir +
            '/miles_models/Mun1.30*.fits')  # From E-MILES SSP library
        #vazdekis = glob.glob(ppxf_dir + '/miles_stellar/s*.fits') # From MILES stellar library
        fwhm_tem = 2.51  # Vazdekis+10 spectra have a constant resolution FWHM of 2.51A.

        # Open template spectrum in order to get the size of the template array
        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_temp = util.log_rebin(lamRange2,
                                                        ssp,
                                                        velscale=velscale)
        templates = np.empty((sspNew.size, len(vazdekis)))

        # Convolve observed spectrum with quadratic difference between observed and template resolution.
        # (This is valid if shapes of instrumental spectral profiles are well approximated by Gaussians.)
        fwhm_dif = np.sqrt(np.abs(fwhm_gal**2 - fwhm_tem**2))
        sigma = fwhm_dif / 2.355 / h2['CDELT1']  # Sigma difference in pixels
        galspec = ndimage.gaussian_filter1d(spectrum, sigma)

        # Now logarithmically rebin this new observed spectrum
        galaxy, logLam1, velscale = util.log_rebin(lamRange1,
                                                   galspec,
                                                   velscale=velscale)

        # TEST: log-rebin the error spectrum too?
        noise, _, _ = util.log_rebin(lamRange1, noise, velscale=velscale)

        # Open and normalize all the templates
        for j, file in enumerate(vazdekis):
            hdu = fits.open(file)
            ssp = hdu[0].data
            sspNew, logLam2, velscale_temp = util.log_rebin(lamRange2,
                                                            ssp,
                                                            velscale=velscale)
            templates[:,
                      j] = sspNew / np.median(sspNew)  # Normalizes templates

        # Prep the observed spectrum
        galaxy = galaxy / np.median(galaxy)

        print('Doing stellar kinematics fit...')
        print(galaxy.shape, noise.shape)

        # Shift the template to fit the starting wavelength of the galaxy spectrum
        c = 299792.458
        dv = (logLam2[0] - logLam1[0]) * c  # km/s

        goodPixels = util.determine_goodpixels(logLam1, lamRange2, 0)

        # Here the actual fit starts. The best fit is plotted on the screen
        start = [0., 200.]  # (km/s), starting guess for [V, sigma]

        pp = ppxf(templates,
                  galaxy,
                  np.sqrt(noise),
                  velscale,
                  start,
                  goodpixels=goodPixels,
                  plot=plot,
                  moments=2,
                  degree=6,
                  vsyst=dv,
                  clean=False,
                  quiet=True)
        if plot:
            plt.show()

        print('Chi2:', pp.chi2)
        print('Best-fitting redshift z:',
              (self.z + 1) * (1 + pp.sol[0] / c) - 1)
        print('Final solution:', pp.sol)
        print("Errors:", pp.error * np.sqrt(pp.chi2))

        if plot:
            plt.figure(figsize=(9, 3))
            lines = np.array([
                3726.03, 3728.82, 3970.08, 4101.76, 4340.47, 4363.21, 4861.33,
                4958.92, 5006.84, 6300.30, 6548.03, 6583.41, 6562.80, 6716.47,
                6730.85
            ])
            for line in lines:
                if line < np.exp(logLam1)[-1]:
                    plt.axvspan(line - 10.,
                                line + 10.,
                                color='gray',
                                alpha=0.25)
            plt.fill_between(np.exp(logLam1),
                             galaxy - np.sqrt(noise),
                             galaxy + np.sqrt(noise),
                             color='r',
                             alpha=0.8)
            plt.plot(np.exp(logLam1), pp.bestfit, 'k-')
            plt.xlabel(r'$\lambda (\AA)$', fontsize=14)
            plt.ylabel(r'Normalized flux', fontsize=14)
            plt.ylim(-0.05, 2.0)
            plt.savefig('figures/' + self.galaxyname + '/' + 'intspec.png',
                        bbox_inches='tight')
            plt.show()

        # Subtract stellar contribution from spectrum
        print('Normalizing data by best-fit stellar template...')
        self.spectrum_norm = galaxy - pp.bestfit * np.median(galaxy)
        self.kinematics_wvl = np.exp(logLam1)
        np.save('output/' + self.galaxyname + '/intspec_norm',
                self.spectrum_norm)
        np.save('output/' + self.galaxyname + '/intspec_wvl',
                self.kinematics_wvl)

        # Plot image for testing
        if plot:

            # Plot example spectrum
            plt.figure(figsize=(9, 3))
            lines = np.array([
                3726.03, 3728.82, 3970.08, 4101.76, 4340.47, 4363.21, 4861.33,
                4958.92, 5006.84, 6300.30, 6548.03, 6583.41, 6562.80, 6716.47,
                6730.85
            ])
            for line in lines:
                if line < np.exp(logLam1)[-1]:
                    plt.axvspan(line - 10.,
                                line + 10.,
                                color='gray',
                                alpha=0.25)
            plt.fill_between(np.exp(logLam1),
                             self.spectrum_norm - np.sqrt(noise),
                             self.spectrum_norm + np.sqrt(noise),
                             color='r',
                             alpha=0.8)
            plt.plot(np.exp(logLam1), self.spectrum_norm, 'k-')
            plt.xlabel(r'$\lambda (\AA)$', fontsize=14)
            plt.ylabel(r'Normalized flux', fontsize=14)
            plt.xlim(3500, 5100)

            plt.savefig('figures/' + self.galaxyname + '/' +
                        'intspec_norm.png',
                        bbox_inches='tight')
            plt.show()

        return np.asarray([
            pp.sol[0], pp.sol[1], pp.error[0] * np.sqrt(pp.chi2),
            pp.error[1] * np.sqrt(pp.chi2)
        ]), np.exp(logLam1), pp.bestfit, galaxy, noise
Beispiel #15
0
def ppxf_single(object_id,
                z_init,
                lambda_spec,
                galaxy_lin,
                error_lin,
                cars_model,
                emsub_specfile=None,
                plotfile=None,
                outfile=None,
                outfile_spec=None,
                mpoly=None,
                apoly=None,
                reflines=None):

    # Speed of light
    c = 299792.458

    #h_spec = spec_hdu[0].header
    #Ang_air = h_spec['CRVAL3'] + np.arange(0,h_spec['CDELT3']*(h_spec['NAXIS3']),h_spec['CDELT3'])

    #s = 10**4/Ang_air
    #n = 1 + 0.00008336624212083 + 0.02408926869968 / (130.1065924522 - s**2) + 0.000159740894897 / (38.92568793293 - s**2)
    #lambda_spec = Ang_air*n

    #wave = h_spec['CRVAL3'] + np.arange(0,h_spec['CDELT3']*(h_spec['NAXIS3']),h_spec['CDELT3'])

    #lambda_spec = vactoair(wave)
    #lambda_spec = h_spec['CRVAL3'] + np.arange(0,h_spec['CDELT3']*(h_spec['NAXIS3']),h_spec['CDELT3'])
    # Crop to finite
    use = (np.isfinite(galaxy_lin) & (galaxy_lin > 0.0))

    # Making a "use" vector
    use_indices = np.arange(galaxy_lin.shape[0])[use]
    galaxy_lin = (galaxy_lin[use_indices.min():(use_indices.max() + 1)])[2:-3]
    error_lin = (error_lin[use_indices.min():(use_indices.max() + 1)])[2:-3]
    lambda_spec = (lambda_spec[use_indices.min():(use_indices.max() +
                                                  1)])[2:-3]

    lamRange_gal = np.array([np.min(lambda_spec), np.max(lambda_spec)])

    # New resolution estimate = 0.9 \AA (sigma), converting from sigma to FWHM
    FWHM_gal = 2.355 * (np.max(lambda_spec) -
                        np.min(lambda_spec)) / len(lambda_spec)
    print('FWHM', FWHM_gal)
    lamRange_gal = lamRange_gal / (
        1 + float(z_init))  # Compute approximate restframe wavelength range
    FWHM_gal = FWHM_gal / (1 + float(z_init))  # Adjust resolution in Angstrom

    sigma_gal = FWHM_gal / (2.3548 * 4500.0) * c  # at ~4500 \AA

    # log rebinning for the fits
    galaxy, logLam_gal, velscale = util.log_rebin(np.around(lamRange_gal,
                                                            decimals=3),
                                                  galaxy_lin,
                                                  flux=True)

    noise, logLam_noise, velscale = util.log_rebin(np.around(lamRange_gal,decimals=3), error_lin, \
                                              velscale=velscale, flux=True)

    # correcting for infinite or zero noise
    noise[np.logical_or((noise == 0.0), np.isnan(noise))] = 1.0
    galaxy[np.logical_or((galaxy < 0.0), np.isnan(galaxy))] = 0.0

    if galaxy.shape != noise.shape:
        galaxy = galaxy[:-1]
        logLam_gal = logLam_gal[:-1]

    # Define lamRange_temp and logLam_temp

    #lamRange_temp, logLam_temp = setup_spectral_library_conroy(velscale[0], FWHM_gal)

    # Construct a set of Gaussian emission line templates.
    # Estimate the wavelength fitted range in the rest frame.
    #

    gas_templates, line_names, line_wave = util.emission_lines(
        logLam_gal, lamRange_gal, FWHM_gal)

    goodpixels = np.arange(galaxy.shape[0])
    wave = np.exp(logLam_gal)

    # crop very red end (only affects a very small subsample)
    # goodpixels = goodpixels[wave <= 5300]

    # exclude lines at the edges (where things can go very very wrong :))
    include_lines = np.where((line_wave > (wave.min() + 10.0))
                             & (line_wave < (wave.max() - 10.0)))
    if line_wave[include_lines[0]].shape[0] < line_wave.shape[0]:
        line_wave = line_wave[include_lines[0]]
        line_names = line_names[include_lines[0]]
        gas_templates = gas_templates[:, include_lines[0]]

    #reg_dim = stars_templates.shape[1:]
    reg_dim = gas_templates.shape[1:]
    templates = gas_templates
    #np.hstack([gas_templates, gas_templates])

    dv = 0  #(logLam_temp[0]-logLam_gal[0])*c # km/s

    vel = 0  #np.log(z_init + 1)*c # Initial estimate of the galaxy velocity in km/s
    #z = np.exp(vel/c) - 1   # Relation between velocity and redshift in pPXF

    # 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.
    #
    t = clock()

    nNLines = gas_templates.shape[1]
    component = [0] * nNLines
    start_gas = [vel, 100.]
    start = start_gas  # adopt the same starting value for both gas (BLs and NLs) and stars
    moments = [
        2
    ]  # fit (V,sig,h3,h4) for the stars and (V,sig) for the gas' broad and narrow components

    fixed = None

    ## Additive polynomial degree

    if apoly is None:
        degree = int(
            np.ceil((lamRange_gal[1] - lamRange_gal[0]) /
                    (100.0 * (1. + float(z_init)))))
    else:
        degree = int(apoly)

    if mpoly is None: mdegree = 3
    else: mdegree = int(mpoly)

    # Trying: sigmas must be kinematically decoupled

    # #Trying: velocities must be kinematically decoupled
    #A_ineq = [[0,0,2,0,-1,0]]
    #b_ineq = [0]

    bounds_gas = [[-1000, 1000], [0, 1000]]
    bounds = bounds_gas

    pp = ppxf.ppxf(templates,
                   galaxy,
                   noise,
                   velscale,
                   start,
                   fixed=fixed,
                   plot=False,
                   moments=moments,
                   mdegree=mdegree,
                   degree=degree,
                   vsyst=dv,
                   reg_dim=reg_dim,
                   goodpixels=goodpixels,
                   bounds=bounds)
    #component=component,

    # redshift_to_newtonian:
    # return (v-astropy.constants.c.to('km/s').value*redshift)/(1.0+redshift)

    # "v" from above is actually the converted velocity which is
    # (np.exp(v_out/c) - 1)*c

    v_gas = pp.sol[0]
    ev_gas = pp.error[0]

    conv_vel_gas = (np.exp(pp.sol[0] / c) - 1) * c

    vel_gas = (1 + z_init) * (conv_vel_gas - c * z_init)

    sigma_gas = pp.sol[1]  #first # is template, second # is the moment
    esigma_gas = pp.error[1]  #*np.sqrt(pp.chi2)

    zfit_gas = (z_init + 1) * (1 + pp.sol[0] / c) - 1

    zfit_stars = z_init

    ezfit_gas = (z_init + 1) * pp.error[0] * np.sqrt(pp.chi2) / c

    if plotfile is not None:
        #
        ### All of the rest of this plots and outputs the results of the fit
        ### Feel free to comment anything out at will
        #

        maskedgalaxy = np.copy(galaxy)
        lowSN = np.where(noise > (0.9 * np.max(noise)))
        maskedgalaxy[lowSN] = np.nan

        wave = np.exp(logLam_gal) * (1. + z_init) / (1. + zfit_stars)

        fig = plt.figure(figsize=(12, 7))
        ax1 = fig.add_subplot(211)

        # plotting smoothed spectrum
        smoothing_fact = 3

        ax1.plot(wave,
                 convolve(maskedgalaxy, Box1DKernel(smoothing_fact)),
                 color='Gray',
                 linewidth=0.5)
        ax1.plot(wave[goodpixels],
                 convolve(maskedgalaxy[goodpixels],
                          Box1DKernel(smoothing_fact)),
                 'k',
                 linewidth=1.)

        label = "Best fit template from high res Conroy SSPs + emission lines at z={0:.3f}".format(
            zfit_stars)

        # overplot stellar templates alone
        ax1.plot(wave, pp.bestfit, 'r', linewidth=1.0, alpha=0.75, label=label)

        ax1.set_ylabel('Flux')
        ax1.legend(loc='upper right', fontsize=10)
        ax1.set_title(object_id)

        xmin, xmax = ax1.get_xlim()

        ax2 = fig.add_subplot(413, sharex=ax1, sharey=ax1)

        # plotting emission lines if included in the fit

        gas = pp.matrix[:, -nNLines:].dot(pp.weights[-nNLines:])

        ax2.plot(wave, gas, 'b', linewidth=2,\
                 label = '$\sigma_{gas}$'+'={0:.0f}$\pm${1:.0f} km/s'.format(sigma_gas, esigma_gas)+', $V_{gas}$'+'={0:.0f}$\pm${1:.0f} km/s'.format(v_gas, ev_gas)) # overplot emission lines alone
        cars_model = (cars_model[use_indices.min():(use_indices.max() +
                                                    1)])[2:-3]
        ax2.plot(np.array(lambda_spec)/(1+z_init), cars_model, color='orange', linewidth=1,\
            label = 'CARS Model')

        #(lambda_spec[use_indices.min():(use_indices.max()+1)])[2:-3]
        stars = pp.bestfit - gas

        #if (ymax > 3.0*np.median(stars)): ymax = 3.0*np.median(stars)
        #if (ymin < -0.5): ymin = -0.5

        ax2.set_ylabel('Best Fits')

        ax2.legend(loc='upper left', fontsize=10)

        # Plotting the residuals
        ax3 = fig.add_subplot(817, sharex=ax1)
        ax3.plot(wave[goodpixels],
                 (convolve(maskedgalaxy, Box1DKernel(smoothing_fact)) -
                  pp.bestfit)[goodpixels],
                 'k',
                 label='Fit Residuals')
        #ax3.set_yticks([-0.5,0,0.5])
        ax3.set_ylabel('Residuals')

        ax4 = fig.add_subplot(818, sharex=ax1)

        ax4.plot(wave, noise, 'k', label='Flux Error')

        ax4.set_ylabel('Noise')
        ax4.set_xlabel('Rest Frame Wavelength [$\AA$]')
        #ax4.set_yticks(np.arange(0,0.5,0.1))
        '''if reflines is not None:
            for i,w,label in zip(range(len(reflines)),reflines['wave'],reflines['label']):
                if ((w > xmin) and (w < xmax)):
#                     ax1.text(w,ymin+(ymax-ymin)*(0.03+0.08*(i % 2)),'$\mathrm{'+label+'}$',fontsize=10,\
#                              horizontalalignment='center',\
#                              bbox=dict(boxstyle='round', facecolor='white', alpha=0.5))
                    print(label.decode("utf-8"))
                    ax1.text(w,ymin+(ymax-ymin)*(0.03+0.08*(i % 2)),'$\mathrm{'+label.decode("utf-8")+'}$',fontsize=10,\
                             horizontalalignment='center',\
                             bbox=dict(boxstyle='round', facecolor='white', alpha=0.5))
                    ax1.plot([w,w],[ymin,ymax],':k',alpha=0.5)'''

        fig.subplots_adjust(hspace=0.05)
        plt.setp([a.get_xticklabels() for a in fig.axes[:-1]], visible=False)

        #print('Saving figure to {0}'.format(plotfile))

        ax1.set_xlim([6450, 6750])

        ax2.set_xlim([6450, 6750])

        #ymin, ymax = ax1.get_ylim([])

        plt.savefig(plotfile, dpi=150)
        plt.close()
    return v_gas, vel_gas, sigma_gas, pp.chi2
    #    print('# id z_stars ez_stars sigma_stars esigma_stars z_gas ez_gas sigma_gas esigma_gas SN_median SN_rf_4000 SN_obs_8030 chi2dof')
    print('{0:d} {1:.6f} {2:.6f} {3:.1f} {4:.1f} {5:.6f} {6:.6f} {7:.1f} {8:.1f} {9:.1f} {10:.1f} {11:.1f} {12:.2f}\n'.format(\
                      object_id,zfit_stars,ezfit_stars, sigma_stars,esigma_stars,\
                      zfit_gas, ezfit_gas, sigma_gas, esigma_gas, SN_median, SN_rf_4000, SN_obs_8030, pp.chi2))

    ## This prints the fit parameters to an open file object called outfile
    if outfile is not None:
        outfile.write('{0:d} {1:d} {2:.6f} {3:.6f} {4:.1f} {5:.1f} {6:.6f} {7:.6f} {8:.1f} {9:.1f} {10:.1f} {11:.1f} {12:.1f} {13:.2f}\n'.format(\
                      object_id, row2D, zfit_stars, ezfit_stars, sigma_stars,esigma_stars,\
                      zfit_gas, ezfit_gas, sigma_gas, esigma_gas, SN_median, SN_rf_4000, SN_obs_8030, pp.chi2))

    ## This outputs the spectrum and best-fit model to an open file object called outfile_spec
    if outfile_spec is not None:
        outfile_spec.write(
            '# l f ef f_stars f_gas f_model_tot used_in_fit add_poly mult_poly\n'
        )
        for i in np.arange(wave.shape[0]):
            isgood = 0
            if goodpixels[goodpixels == i].shape[0] == 1: isgood = 1
            outfile_spec.write(
                '{0:0.4f} {1:0.4f} {2:0.4f} {3:0.4f} {4:0.4f} {5:0.4f} {6} {7:0.4f} {8:0.4f}\n'
                .format(wave[i], galaxy[i], noise[i], stars[i], gas[i],
                        pp.bestfit[i], isgood, add_polynomial[i],
                        mult_polynomial[i]))

#     ## This outputs the best-fit emission-subtracted spectrum to a fits file
#     ## but I've commented it out since you are unlikely to need this!
#     if emsub_specfile is not None:
#         wave = np.exp(logLam_gal)*(1.+z_init)
#         if include_gas:
#             emsub = galaxy - gas
#         else:
#             emsub = galaxy

#         col1 = fits.Column(name='wavelength', format='E', array=wave)
#         col2 = fits.Column(name='flux',format='E',array=galaxy)
#         col3 = fits.Column(name='error',format='E',array=noise)
#         col4 = fits.Column(name='flux_emsub',format='E',array=emsub)

#         cols = fits.ColDefs([col1,col2,col3,col4])

#         tbhdu = fits.BinTableHDU.from_columns(cols)

#         # delete old file if it exists
#         if os.path.isfile(emsub_specfile): os.remove(emsub_specfile)
#         tbhdu.writeto(emsub_specfile)

# return zfit_stars, ezfit_stars, sigma_stars, esigma_stars, zfit_gas, ezfit_gas, \
#     sigma_gas, esigma_gas, SN_median, SN_rf_4000, SN_obs_8030, pp.chi2
    return zfit_stars, sigma_stars, sigma_blr, wave, pp.bestfit
Beispiel #16
0
    def rv_fit(self, guesses, niter=10000, line_sigma=3,\
    n_CPU=-1, line_significants=5, RV_guess_var=0.):
        """
        The module runs the radial velocity fit using `ppxf`_ and the
        :ref:`Monte Carlo`.

        Args:
            guesses : :func:`numpy.array`
                The initial guesses for the the radial velocity fit guesses in
                the form [RV,sepctral_dispersion]

        Kwargs:
            niter : :obj:`int` (optional, default: 10000)
                number of iterations to bootstrap the spectrum

            line_sigma: :obj:`int` (optional, default: 3):
                sigma for the RV clipping for the individual lines

            n_CPU : :obj:`float` (optional, default: -1)
                Setting the number of CPUs used for the parallelization. If set
                to -1 all available system resources are used. Maximum number
                of CPUs is the number of spectral lines the fit is performed
                to.

            line_significants: :obj:`int` (optional, default: 5)
                The sigma-level for the spectral line to be above the continuum
                in order to be considered *valid*

            RV_guess_var : :obj:`float` (optional, default: 0)
                The maximum variation the RV guess will be varied using a
                uniform distribution.

        """

        self.logger.info('Starting the RV fitting')
        self.logger.info('Settings: niter=' + str(niter)\
        + '; sigma RV for excluding lines: ' + str(line_sigma))

        start_time = time.time()

        if self.loglevel == "DEBUG":
            n_CPU = 1
        if n_CPU == -1:
            n_CPU = cpu_count()
        self.logger.info('Max number of cores: '\
        + str(n_CPU))

        ### transfer the spectrum to log-space
        log_spec_f, logspec_lambda, velscale_spec\
        = ppxf_util.log_rebin([self.spec_lambda[0],\
        self.spec_lambda[-1]], self.spec_f)

        log_template_f, log_template_lambda, velscale_template\
        = ppxf_util.log_rebin([self.spec_lambda[0],\
        self.spec_lambda[-1]], self.template_f)

        log_spec_err, log_spec_err_lambda, velscale_spec_err\
        = ppxf_util.log_rebin([self.spec_lambda[0],\
        self.spec_lambda[-1]], self.spec_err)

        log_spec_err[~np.isfinite(log_spec_err)] = 1.
        # This happens if AO was used and there is a gap in the spec

        exponent = int(np.log10(np.nanmedian(log_spec_f)) - 4.)
        # Obtain larger flux numbers to make it numerically stable

        factor = float(10**(exponent * (-1)))

        log_spec_f = log_spec_f * factor
        log_template_f = log_template_f * factor
        log_spec_err = log_spec_err * factor

        l_fit = self.cat.loc[:, 'l_fit'].values.astype(np.float64)
        l_lab = self.cat.loc[:, 'l_lab'].values.astype(np.float64)
        line_name = self.cat.index

        fwhm_g, fwhm_l, fwhm_v\
        = voigt_FWHM(self.cat.loc[:, 'sg_fit'].values.astype(np.float64),\
        self.cat.loc[:, 'sl_fit'].values.astype(np.float64))

        used = np.where(self.cat.loc[:, 'used'] == 'f')
        l_fit = np.delete(l_fit, used)
        l_lab = np.delete(l_lab, used)
        line_name = np.delete(line_name, used)
        fwhm_g = np.delete(fwhm_g, used)
        fwhm_l = np.delete(fwhm_l, used)
        fwhm_v = np.delete(fwhm_v, used)

        v = np.zeros_like(l_lab)
        ev = np.zeros_like(l_lab)

        for i, line in enumerate(l_lab):

            self.logger.info('Started line ' + line_name[i])
            if self.cat.loc[line_name[i], 'significance'] > line_significants:
                mask = np.zeros(len(log_template_f), dtype=bool)
                ind_min = np.argmin(np.abs(log_template_lambda\
                - np.log(line - 0.5 * fwhm_v[i]))) - 1

                ind_max = np.argmin(np.abs(log_template_lambda\
                - np.log(line + 0.5 * fwhm_v[i]))) + 1

                ind_center = np.argmin(np.abs(log_template_lambda\
                - np.log(line)))
                mask[ind_min:ind_max + 1] = True

                log_spec_f[~np.isfinite(log_spec_f)] = 0.

                pp_outliers_init = ppxf.ppxf(log_template_f, log_spec_f,\
                log_spec_err, velscale_spec, guesses,\
                mask=mask, degree=-1, clean=False, quiet=True,\
                plot=False, fixed=[0, 1])

                self.logger.info('RV guess variation line ' + line_name[i]\
                + ': ' + str('{:4.2f}'.format(RV_guess_var)) + 'km/s')

                v[i], ev[i] = ppxf_MC(log_template_f, log_spec_f,\
                log_spec_err, velscale_spec, guesses, nrand=0.5 * niter,\
                goodpixels=pp_outliers_init.goodpixels, degree=-1,\
                moments=2, RV_guess_var=RV_guess_var, n_CPU=n_CPU)

                self.logger.info('Finished line ' + line_name[i]\
                + ': RV=(' + str('{:4.2f}'.format(v[i])) + '+-'\
                + str('{:4.3f}'.format(ev[i])) + ')km/s')

            else:
                self.logger.info(line_name[i]\
                + ': Line not significant [level ='\
                + str(line_significants) + ']')
                ev[i] = np.nan
                v[i] = np.nan

            self.cat.loc[line_name[i], 'RV'] = v[i]
            self.cat.loc[line_name[i], 'eRV'] = ev[i]

        remaining_lines = line_clipping(self, v, line_significants,\
        sigma=line_sigma)

        if remaining_lines.mask.all():
            self.logger.error('NO USABLE LINE FOUND WITH SET PARAMETER !!')
        else:
            self.cat.loc[line_name[~remaining_lines.mask], 'used'] = 'x'

            l_fit = l_fit[~remaining_lines.mask]
            fwhm_v = fwhm_v[~remaining_lines.mask]

            rv_var_lines = MAD(self.cat.loc[line_name[~remaining_lines.mask],\
            'RV'])
            RV_guess_var_min = RV_guess_var
            if rv_var_lines > RV_guess_var:
                RV_guess_var = rv_var_lines

            self.logger.info('RV guess variation: min = '\
            + str(RV_guess_var_min) + 'km/s; used = '\
            + str('{:4.2f}'.format(RV_guess_var)) + 'km/s')

            mask = np.zeros(len(self.spec_lambda), dtype=bool)

            for i, line in enumerate(l_fit):
                ind_min = np.argmin(np.abs(logspec_lambda\
                - np.log(line - 0.5 * fwhm_v[i]))) - 1
                ind_max = np.argmin(np.abs(logspec_lambda\
                - np.log(line + 0.5 * fwhm_v[i]))) + 1
                ind_center = np.argmin(np.abs(logspec_lambda - np.log(line)))
                mask[ind_min:ind_max + 1] = True

            # pp_final_plot = plt.figure(str(self.spec_id)\
            # + '_ppxf_fit_final', figsize=(10, 3))
            if len(l_lab) > 1:
                pp_final_init = ppxf.ppxf(log_template_f, log_spec_f,\
                log_spec_err, velscale_spec, guesses, degree=-1, clean=False,\
                mask=mask, quiet=True, fixed=[0, 1])

            if len(l_lab) == 1:
                pp_final_init = pp_outliers_init
            # pp_final_init.plot()
            # # plt.tight_layout()
            # plt.savefig(self.spec_id + '_ppxf_fit_final.png', dpi=600)
            # plt.close()

            self.logger.info('Started final RV fit')
            self.rv, self.erv = ppxf_MC(log_template_f, log_spec_f,\
            log_spec_err, velscale_spec, guesses,\
            nrand=niter, goodpixels=pp_final_init.goodpixels, degree=-1,\
            moments=2, spec_id=self.spec_id, RV_guess_var=RV_guess_var,\
            n_CPU=n_CPU)

            elapsed_time = time.time() - start_time
            self.logger.info('Used lines for RV fit: '\
            + ', '.join(line_name[~remaining_lines.mask]))

            self.logger.info('Finished RV fitting: RV=('\
            + str('{:4.2f}'.format(self.rv)) + '+-'\
            + str('{:4.3f}'.format(self.erv)) + ')km/s based on '\
            + str(len(l_fit)) + '/' + str(len(line_name)) + ' lines')

            self.logger.info('Elapsed time: '\
            + time.strftime("%H:%M:%S", time.gmtime(elapsed_time)))
Beispiel #17
0
def ppxf_L_tot(int_spec, header, redshift, vel, dist_mod, dM_err=[0.1, 0.1]):
    """Take in collapsed galaxy spectra, with vel and distance modulus, to calculate L_bol based on various filter bands. \
        Also returns Vega mangitudes for g,r and Johnson V bands, along with sigma (LOSVD).

    Parameters
    ----------
    int_spec : float, array
        Collapsed spectra of the galaxy.
    header : Fits header object
        galaxy's Fits file header, containing information for the function.
    redshift : float
        Redshift of the galaxy, from recession velocity
    vel : float
        recession velocity of the galaxy, in km/s
    dist_mod : float
        Distance modulus for use in the calculation of Lbol and magnitudes
    dM_err : list, optional
        Error in distance modulus, by default [0.1,0.1]

    Returns
    -------
    dict
        lum_bol_g, lum_bol_g_u, lum_bol_g_l, mag_g, mag_r, mag_v, fit_sigma
    """

    # Read a galaxy spectrum and define the wavelength range

    lamRange1 = header['CRVAL3'] + np.array([
        0., header['CD3_3'] * (header['NAXIS3'] - 1)
    ])  #IMPORTANTE: EL RANGO DE LAMBDAS ESTA EN ESCALA LOGARITMICA
    #Transformamos los pixeles en lambdas:
    #lam=np.linspace(lamRange1[0],lamRange[1],len(gal_lin[0,:]))
    FWHM_gal = 2.81  # 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, int_spec)
    cond = np.exp(logLam1) <= 6900
    # Getting the apparent magnitude of the galaxy in the g, r and V bands
    mag_g, Flux_g = library(np.exp(logLam1[cond]),
                            galaxy[cond] * 1.0e-20,
                            filt="SDSS",
                            band="g")
    mag_r, Flux_r = library(np.exp(logLam1[cond]),
                            galaxy[cond] * 1.0e-20,
                            filt="SDSS",
                            band="r")
    mag_v, Flux_v = library(np.exp(logLam1[cond]),
                            galaxy[cond] * 1.0e-20,
                            filt="GROUND_JOHNSON",
                            band="V")

    # Converting to absolute magnitude
    M_g = mag_g - dist_mod  #
    M_g_u = mag_g - (dist_mod + dM_err[0])  # upper distance M_g
    M_g_l = mag_g - (dist_mod - dM_err[1])  # lower distance M_g

    # M_r = mag_r - dist_mod
    # M_r_u = mag_r - (dist_mod + dM_err[0]) # upper distance M_g
    # M_r_l = mag_r - (dist_mod - dM_err[1])

    # M_v = mag_v - dist_mod
    # M_v_u = mag_v - (dist_mod + dM_err[0]) # upper distance M_g
    # M_v_l = mag_v - (dist_mod - dM_err[1])

    galaxy = galaxy / np.median(
        galaxy)  # Normalize spectrum to avoid numerical issues
    noise = np.full_like(galaxy,
                         redshift)  # 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(
        'emiles/Ekb1.30*')  # PUT HERE THE DIRECTORY TO EMILES_STARS
    FWHM_tem = 2.51  # Vazdekis+10 spectra have a constant resolution FWHM of 2.51A.
    velscale_ratio = 2  # adopts 2x higher spectral sampling for templates than for galaxy
    miles_path = path.expanduser("emiles/Ekb1.30Z*.fits")
    miles = lib.miles(miles_path, velscale, FWHM_tem)
    stars_templates = miles.templates.reshape(miles.templates.shape[0], -1)
    reg_dim = miles.templates.shape[1:]
    # 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.
    #
    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_temp = util.log_rebin(lamRange2,
                                                    ssp,
                                                    velscale=velscale /
                                                    velscale_ratio)
    templates = np.empty(
        (sspNew.size, len(vazdekis)))  # PUT HERE THE DIRECTORY TO MILES_STARS

    # 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.
    #
    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_temp = util.log_rebin(lamRange2,
                                                    ssp,
                                                    velscale=velscale /
                                                    velscale_ratio)
    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, vazd in enumerate(vazdekis):
        hdu = fits.open(vazd)
        ssp = hdu[0].data
        ssp = ndimage.gaussian_filter1d(ssp, sigma)
        sspNew, logLam2, velscale_temp = util.log_rebin(lamRange2,
                                                        ssp,
                                                        velscale=velscale /
                                                        velscale_ratio)
        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  # in km/s
    #c = 299792458.0 # speed of light
    dv = (logLam2[0] - logLam1[0]) * c  # km/s
    z = np.exp(vel / c) - 1  # Relation between velocity and redshift in pPXF

    cond = np.exp(logLam1) <= 7810  #6900
    logLam1 = logLam1[cond]
    galaxy = galaxy[cond]
    noise = noise[cond]
    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, 200.]  # (km/s), starting guess for [V, sigma]
    t = clock()

    galaxy[np.where(np.isfinite(galaxy) == False)] = 0.0
    noise[np.where(np.isfinite(noise) == False)] = 0.0
    templates[np.where(np.isfinite(templates) == False)] = 0.0

    pp = ppxf(templates,
              galaxy,
              noise * 0.0 + 1.0,
              velscale,
              start,
              goodpixels=goodPixels,
              plot=True,
              moments=4,
              mdegree=15,
              vsyst=dv,
              velscale_ratio=velscale_ratio)

    weights = pp.weights
    normalized_weights = weights / np.sum(weights)

    # Use miles_utils to get metallicity
    weights = pp.weights
    weights = weights.reshape(reg_dim) / weights.sum()  # Normalized
    miles.mean_age_metal(weights)

    fit_sigma = pp.sol

    optimal_template = np.zeros(templates.shape[0])
    for j in range(0, templates.shape[1]):
        optimal_template = optimal_template + templates[:,
                                                        j] * normalized_weights[
                                                            j]

    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))

    # Pass the optimal template to get the bolometric correction for the g-band
    BC_g = transmission(np.exp(logLam2), optimal_template, band="g")
    # BC_r = transmission(np.exp(logLam2), optimal_template, band="r")
    # BC_v = transmission(np.exp(logLam2), optimal_template, band="V")

    # Obtaining the bolometric correction of the Sun
    BC_sun_g, M_sun_g = library(0, 0, filt="SDSS", band="g", get_sun='Y')
    # BC_sun_r, M_sun_r = library(0,0,filt="SDSS", band="r",get_sun='Y')
    # BC_sun_v, M_sun_v = library(0,0,filt="GROUND_JOHNSON", band="V",get_sun='Y')

    # Getting the bolometric luminosity (in solar luminosity) for the g-band
    lum_bol_g = 10.0**(-0.4 * (M_g - M_sun_g)) * 10.0**(-0.4 *
                                                        (BC_g - BC_sun_g))
    lum_bol_g_u = 10.0**(-0.4 * (M_g_u - M_sun_g)) * 10.0**(
        -0.4 * (BC_g - BC_sun_g))  # upper dM L
    lum_bol_g_l = 10.0**(-0.4 * (M_g_l - M_sun_g)) * 10.0**(
        -0.4 * (BC_g - BC_sun_g))  # lower dM L

    # lum_bol_r = 10.0**(-0.4*(M_r-M_sun_r)) * 10.0**(-0.4*(BC_r-BC_sun_r))
    # lum_bol_r_u = 10.0**(-0.4*(M_r_u-M_sun_r)) * 10.0**(-0.4*(BC_r-BC_sun_r)) # upper dM L
    # lum_bol_r_l = 10.0**(-0.4*(M_r_l-M_sun_r)) * 10.0**(-0.4*(BC_r-BC_sun_r)) # lower dM L

    # lum_bol_v = 10.0**(-0.4*(M_v-M_sun_v)) * 10.0**(-0.4*(BC_v-BC_sun_v))
    # lum_bol_v_u = 10.0**(-0.4*(M_v_u-M_sun_v)) * 10.0**(-0.4*(BC_v-BC_sun_v)) # upper dM L
    # lum_bol_v_l = 10.0**(-0.4*(M_v_l-M_sun_v)) * 10.0**(-0.4*(BC_v-BC_sun_v)) # lower dM L

    #lum_bol_r, [lum_bol_r_u, lum_bol_r_l], lum_bol_v, [lum_bol_v_u, lum_bol_v_l],
    result_dict = {
        "Lbol": lum_bol_g,
        "Lbol_err_up": lum_bol_g_u,
        "Lbol_err_lo": lum_bol_g_l,
        "mag_g": mag_g,
        "mag_r": mag_r,
        "mag_v": mag_v,
        "sigma": fit_sigma[1]
    }

    return result_dict
Beispiel #18
0
def ppxf_example_simulation():

    ppxf_dir = path.dirname(path.realpath(ppxf_package.__file__))

    hdu = fits.open(
        ppxf_dir +
        '/miles_models/Mun1.30Zp0.00T12.5893_iPp0.00_baseFe_linear_FWHM_2.51.fits'
    )  # Solar metallicitly, Age=12.59 Gyr
    ssp = hdu[0].data
    h = hdu[0].header

    lamRange = h['CRVAL1'] + np.array([0., h['CDELT1'] * (h['NAXIS1'] - 1)])
    c = 299792.458  # speed of light in km/s
    velscale = c * h['CDELT1'] / max(
        lamRange)  # Do not degrade original velocity sampling
    star, logLam, velscale = util.log_rebin(lamRange, ssp, velscale=velscale)

    # The finite sampling of the observed spectrum is modeled in detail:
    # the galaxy spectrum is obtained by oversampling the actual observed spectrum
    # to a high resolution. This represent the true spectrum, which is later resampled
    # to lower resolution to simulate the observations on the CCD. Similarly, the
    # convolution with a well-sampled LOSVD is done on the high-resolution spectrum,
    # and later resampled to the observed resolution before fitting with PPXF.

    factor = 10  # Oversampling integer factor for an accurate convolution
    starNew = ndimage.interpolation.zoom(
        star, factor,
        order=3)  # This is the underlying spectrum, known at high resolution
    star = rebin(
        starNew, factor
    )  # Make sure that the observed spectrum is the integral over the pixels

    h3 = 0.1  # Adopted G-H parameters of the LOSVD
    h4 = 0.1
    sn = 30.  # Adopted S/N of the Monte Carlo simulation
    m = 300  # Number of realizations of the simulation
    moments = 4
    velV = np.random.rand(m)  # velocity in *pixels* [=V(km/s)/velScale]
    sigmaV = np.linspace(
        0.5, 4, m)  # Range of sigma in *pixels* [=sigma(km/s)/velScale]

    result = np.zeros((m, moments))  # This will store the results
    t = clock()

    for j, (vel, sigma) in enumerate(zip(velV, sigmaV)):

        dx = int(
            abs(vel) +
            5 * sigma)  # Sample the Gaussian and GH at least to vel+5*sigma
        x = np.linspace(
            -dx, dx, 2 * dx * factor +
            1)  # Evaluate the Gaussian using steps of 1/factor pixels.
        w = (x - vel) / sigma
        w2 = w**2
        gauss = np.exp(-0.5 * w2)
        gauss /= np.sum(gauss)  # Normalized total(gauss)=1
        h3poly = w * (2. * w2 - 3.) / np.sqrt(3.)  # H3(y)
        h4poly = (w2 * (4. * w2 - 12.) + 3.) / np.sqrt(24.)  # H4(y)
        losvd = gauss * (1. + h3 * h3poly + h4 * h4poly)

        galaxy = signal.fftconvolve(
            starNew, losvd, mode="same")  # Convolve the oversampled spectrum
        galaxy = rebin(
            galaxy, factor)  # Integrate spectrum into original spectral pixels
        noise = galaxy / sn  # 1sigma error spectrum
        galaxy = np.random.normal(galaxy,
                                  noise)  # Add noise to the galaxy spectrum
        start = np.array([
            vel + np.random.uniform(-1, 1),
            sigma * np.random.uniform(0.8, 1.2)
        ]) * velscale  # Convert to km/s

        pp = ppxf(star,
                  galaxy,
                  noise,
                  velscale,
                  start,
                  goodpixels=np.arange(dx, galaxy.size - dx),
                  plot=False,
                  moments=moments,
                  bias=0.2)
        result[j, :] = pp.sol

    print('Calculation time: %.2f s' % (clock() - t))

    plt.clf()
    plt.subplot(221)
    plt.plot(sigmaV * velscale, result[:, 0] - velV * velscale, '+k')
    plt.axhline(0, color='r')
    plt.axvline(velscale, linestyle='dashed')
    plt.axvline(2 * velscale, linestyle='dashed')
    plt.ylim(-20, 20)
    plt.xlabel(r'$\sigma_{\rm in}\ (km\ s^{-1})$')
    plt.ylabel(r'$V - V_{\rm in}\ (km\ s^{-1})$')
    plt.text(2.05 * velscale, -15, r'2$\times$velscale')

    plt.subplot(222)
    plt.plot(sigmaV * velscale, result[:, 1] - sigmaV * velscale, '+k')
    plt.axhline(0, color='r')
    plt.axvline(velscale, linestyle='dashed')
    plt.axvline(2 * velscale, linestyle='dashed')
    plt.ylim(-20, 20)
    plt.xlabel(r'$\sigma_{in}\ (km\ s^{-1})$')
    plt.ylabel(r'$\sigma - \sigma_{\rm in}\ (km\ s^{-1})$')
    plt.text(2.05 * velscale, -15, r'2$\times$velscale')

    plt.subplot(223)
    plt.plot(sigmaV * velscale, result[:, 2], '+k')
    plt.axhline(h3, color='r')
    plt.axhline(0, linestyle='dotted', color='limegreen')
    plt.axvline(velscale, linestyle='dashed')
    plt.axvline(2 * velscale, linestyle='dashed')
    plt.ylim(-0.2 + h3, 0.2 + h3)
    plt.xlabel(r'$\sigma_{\rm in}\ (km\ s^{-1})$')
    plt.ylabel('$h_3$')
    plt.text(2.05 * velscale, h3 - 0.15, r'2$\times$velscale')

    plt.subplot(224)
    plt.plot(sigmaV * velscale, result[:, 3], '+k')
    plt.axhline(h4, color='r')
    plt.axhline(0, linestyle='dotted', color='limegreen')
    plt.axvline(velscale, linestyle='dashed')
    plt.axvline(2 * velscale, linestyle='dashed')
    plt.ylim(-0.2 + h4, 0.2 + h4)
    plt.xlabel(r'$\sigma_{\rm in}\ (km\ s^{-1})$')
    plt.ylabel('$h_4$')
    plt.text(2.05 * velscale, h4 - 0.15, r'2$\times$velscale')

    plt.tight_layout()
    plt.pause(1)
Beispiel #19
0
def ppxf_example_kinematics_sdss():

    ppxf_dir = path.dirname(path.realpath(ppxf_package.__file__))

    # 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 = ppxf_dir + '/spectra/NGC4636_SDSS_DR12.fits'
    hdu = fits.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 = np.full_like(galaxy,
                         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  # Velocity scale in km/s per pixel (eq.8 of Cappellari 2017)

    # If the galaxy is at significant redshift, one should bring the galaxy
    # spectrum roughly to the rest-frame wavelength, before calling pPXF
    # (See Sec.2.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:
    #
    # 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(ppxf_dir + '/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 SDSS 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
    lam_temp = h2['CRVAL1'] + h2['CDELT1'] * np.arange(h2['NAXIS1'])
    lamRange_temp = [np.min(lam_temp), np.max(lam_temp)]
    sspNew = util.log_rebin(lamRange_temp, ssp, velscale=velscale)[0]
    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 = fits.open(fname)
        ssp = hdu[0].data
        ssp = util.gaussian_filter1d(
            ssp, sigma)  # perform convolution with variable sigma
        sspNew = util.log_rebin(lamRange_temp, ssp, velscale=velscale)[0]
        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)  # eq.(8) of Cappellari (2017)
    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,
              lam=lam_gal)

    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))
Beispiel #20
0
    def apply_ppxf(self,
                   galnoise,
                   FWHM_gal,
                   FWHM_tem,
                   z,
                   moments,
                   plot=False,
                   directory_plots=''):
        """This is the method that uses the PPxF package and applies it to the trimmed
        data. It saves the results in the class variable 'results_ppxf'. It also has the
        possibility of saving the plots that it produces.

        Parameters
        ----------
        galnoise : float
            Noise in the galaxy's data.
        FWHM_gal : float
            Full width-half maximum of the galaxy.
        FWHM_tem : float
            Full width-half maximum of the templates.
        z : float
            The galaxy's redshift.
        moments : int
            The number of moments that should be used with PPxF.
        plot : boolean, optional
            Boolean to determine if the plots should be saved.
        directory_plots :  String
            String that represents the absolute path where the plots are 
            to be saved at.

        """

        # make sure that the list of results is empty
        self.results_ppxf = []

        temp_header, ssp = self.templates[self.templates_names[0]]

        CRVAL1h3 = temp_header["CRVAL1"]
        CRPIX1h3 = temp_header["CRPIX1"]
        CDELT1h3 = temp_header["CDELT1"]
        NAXIS1h3 = temp_header["NAXIS1"]

        # calculate the wavelenght range from the templates
        lamRange2 = (CRVAL1h3 - CRPIX1h3 * CDELT1h3) + np.array(
            [0., (CDELT1h3 * NAXIS1h3 - 1.0)])

        # speed of light
        c = 299792.458

        # Initial estimate of the galaxy's velocity in km/s
        vel = c * z

        # I NEED TO UNDERSTAND WHAT THE FOLLOWING TWO LINES MEAN
        goodPixels = np.arange(
            1150,
            1820)  #(1150,1820) #[1150,1820] or perhaps an np.arange(1150,1820)

        start = [vel, 120.]

        # The current pixel being worked on
        number_of_pixel = 1

        ### Start nested loop
        for i in np.arange(self.cube.trimmed_data.shape[1]):

            for j in np.arange(self.cube.trimmed_data.shape[2]):

                # the spectrum corresponding to these pixel coordinates
                spec = self.cube.trimmed_data[:, i, j]

                galaxy, logLam1, velscale = util.log_rebin(
                    self.cube.wavelength_range, spec)

                galaxy = galaxy / np.median(galaxy)  # Normalizing the spectrum
                noise = galaxy * 0 + galnoise  # Assuming constant noise per pixel here

                sspNew, logLam2, velscale = util.log_rebin(lamRange2,
                                                           ssp,
                                                           velscale=velscale)

                templates = np.zeros((sspNew.shape[0], self.ntemplates))

                FWHM_dif = np.sqrt((FWHM_gal**2 - FWHM_tem**2))

                sigma = FWHM_dif / 2.355 / CDELT1h3

                #list_of_templates_names = list(templates_dictionary.keys())

                k = 0

                while k < self.ntemplates:
                    h3, ssp = self.templates[self.templates_names[k]]
                    ssp = util.gaussian_filter1d(
                        ssp, sigma)  # perform convolution with variable
                    sspNew = util.log_rebin(lamRange2, ssp,
                                            velscale=velscale)[0]
                    templates[:, k] = sspNew / np.median(sspNew)
                    k += 1

                dv = (logLam2[0] - logLam1[0]) * c  # km/s

                pp = ppxf(templates,
                          galaxy,
                          noise,
                          velscale,
                          start,
                          goodpixels=goodPixels,
                          plot=False,
                          moments=moments,
                          degree=4,
                          mdegree=0,
                          vsyst=dv)

                self.results_ppxf.append(pp)

                if plot:
                    plt.rcParams['figure.figsize'] = (32, 20)
                    myplt = pp.plot()
                    plt.savefig(directory_plots + str(number_of_pixel) +
                                ".pdf")
                    plt.clf()
                    number_of_pixel += 1
Beispiel #21
0
    def fit(self, wavelength, data, mask=None, initial_velocity=0.0, initial_sigma=150.0, fwhm_gal=2, fwhm_model=1.8,
            noise=0.05, plot_fit=False, quiet=False, deg=4, moments=4, **kwargs):
        """
        Performs the pPXF fit.
        
        Parameters
        ----------
        wavelength : numpy.ndarray
            Wavelength coordinates of the data.
        data : numpy.ndarray
            Input spectrum flux vector.
        mask : list
            List of masked regions, as pairs of wavelength coordinates.
        initial_velocity : float
            Initial guess for radial velocity.
        initial_sigma : float
            Initial guess for the velocity dispersion.
        fwhm_gal : float
            Full width at half maximum of a resolution element in the observed spectrum in units of pixels.
        fwhm_model : float
            The same as the above for the models.
        noise : float or numpy.ndarray
            If float it as assumed as the signal to noise ratio, and will be horizontally applied to the whole
            spectrum. If it is an array, it will be interpreted as individual noise values for each pixel.
        plot_fit : bool
            Plots the resulting fit.
        quiet : bool
            Prints information on the fit.
        deg : int
            Degree of polynomial function to be fit in addition to the stellar population spectrum.
        moments : int
            Number of moments in the Gauss-Hermite polynomial. A simple Gaussian would be 2.
        kwargs
            Additional keyword arguments passed directly to ppxf.

        Returns
        -------
        pp
            pPXF output object.

        See Also
        --------
        ppxf, ppxf_util
        """

        self.mask = mask

        fw = (wavelength >= self.fitting_window[0]) & (wavelength < self.fitting_window[1])

        lam_range1 = wavelength[fw][[0, -1]]
        gal_lin = copy.deepcopy(data[fw])

        self.obs_flux = gal_lin

        galaxy, log_lam1, velscale = ppxf_util.log_rebin(lam_range1, gal_lin)

        # Here we use the goodpixels as the fitting window
        gp = np.arange(len(log_lam1))
        lam1 = np.exp(log_lam1)
        self.obs_wavelength = lam1

        if self.mask is not None:
            if len(self.mask) == 1:
                gp = gp[(lam1 < self.mask[0][0]) | (lam1 > self.mask[0][1])]
            else:
                m = np.array([(lam1 < i[0]) | (lam1 > i[1]) for i in self.mask])
                gp = gp[np.sum(m, 0) == m.shape[0]]

        self.good_pixels = gp

        lam_range2 = self.base_wavelength[[0, -1]]
        ssp = self.base[0]

        ssp_new, log_lam2, velscale = ppxf_util.log_rebin(lam_range2, ssp, velscale=velscale)
        templates = np.empty((ssp_new.size, len(self.base)))

        fwhm_dif = np.sqrt(fwhm_gal ** 2 - fwhm_model ** 2)
        # Sigma difference in pixels
        sigma = fwhm_dif / 2.355 / self.base_delta

        for j in range(len(self.base)):
            ssp = self.base[j]
            ssp = gaussian_filter(ssp, sigma)
            ssp_new, log_lam2, velscale = ppxf_util.log_rebin(lam_range2, ssp, velscale=velscale)
            # Normalizes templates
            templates[:, j] = ssp_new / np.median(ssp_new)

        c = constants.c.value * 1.e-3
        dv = (log_lam2[0] - log_lam1[0]) * c  # km/s
        # z = np.exp(vel/c) - 1

        # Here the actual fit starts.
        start = [initial_velocity, initial_sigma]  # (km/s), starting guess for [V,sigma]

        # Assumes uniform noise accross the spectrum
        def make_noise(galaxy, noise):
            noise = galaxy * noise
            noise_mask = (~np.isfinite(noise)) | (noise <= 0.0)
            mean_noise = np.mean(noise[~noise_mask])
            noise[noise_mask] = mean_noise
            return noise
        if isinstance(noise, float):
            noise = make_noise(galaxy, noise)
        elif isinstance(noise, np.ndarray):
            noise, log_lam1, velscale = ppxf_util.log_rebin(lam_range1, copy.deepcopy(noise)[fw])
        self.noise = noise

        self.normalization_factor = np.nanmean(galaxy)
        galaxy = copy.deepcopy(ma.getdata(galaxy / self.normalization_factor))
        noise = copy.deepcopy(ma.getdata(np.abs(noise / self.normalization_factor)))

        assert np.all((noise > 0) & np.isfinite(noise)), 'Invalid values encountered in noise spectrum.'
        if len(gp.shape) > 1:
            gp = gp[0]
        pp = ppxf.ppxf(templates, galaxy, noise, velscale, start, goodpixels=gp, moments=moments, degree=deg, vsyst=dv,
                       quiet=quiet, **kwargs)

        self.solution = pp

        if plot_fit:
            self.plot_fit()

        return pp
Beispiel #22
0
def ppxf_wifis(spectrumfl, z_guess=0.004, sigma_guess=170.):

    ppxf_dir = path.dirname(path.realpath(ppxf_package.__file__))

    # Read a galaxy spectrum and define the wavelength range
    #
    hdu = fits.open(spectrumfl)
    gal_lin = hdu[0].data
    wl_lin = hdu[1].data
    noise_lin = hdu[2].data

    #wh_fit = np.where((wl_lin >= 11600) & (wl_lin <= 13000))[0]
    wh_fit = np.where((wl_lin >= 11600) & (wl_lin <= 13000))[0]
    gal_lin = gal_lin[wh_fit]
    wl_lin = wl_lin[wh_fit]
    noise_lin = noise_lin[wh_fit]

    #lamRange1 = h1['CRVAL1'] + np.array([0., h1['CDELT1']*(h1['NAXIS1'] - 1)])
    lamRange1 = [wl_lin[0], wl_lin[-1]]
    #FWHM_gal = 6.06  # SAURON has an instrumental resolution FWHM of 4.2A.
    #FWHM_gal = 5.08  # SAURON has an instrumental resolution FWHM of 4.2A.
    FWHM_gal = 4.68  # SAURON has an instrumental resolution FWHM of 4.2A.

    # 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

    galaxy, logLam1, velscale = util.log_rebin(lamRange1, gal_lin)
    noise, logLam1, velscale = util.log_rebin(lamRange1, noise_lin)
    galaxy = galaxy / np.median(
        galaxy)  # Normalize spectrum to avoid numerical issues
    noise = noise / np.median(noise)
    #noise = np.full_like(galaxy, 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('/home/elliot/mcmcgemini/spec/vcj_ssp/*t05.0*Zp0.0*.s100')
    vazdekis = glob.glob('/home/elliot/mcmcgemini/spec/vcj_ssp/*.s100')

    FWHM_tem = 1.63  # Vazdekis+10 spectra have a constant resolution FWHM of 2.51A.
    #FWHM_tem = 3  # Vazdekis+10 spectra have a constant resolution FWHM of 2.51A.
    velscale_ratio = 3  # adopts 2x higher spectral sampling for templates than for galaxy

    # 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.
    #
    hdu = np.loadtxt(vazdekis[0])
    ssp = hdu[:, 74]

    modelwl = hdu[:, 0]
    wh_wifis = np.where((modelwl >= 8000) & (modelwl <= 13500))[0]
    modelwl_wifis = modelwl[wh_wifis]
    wl_rebin = np.linspace(modelwl_wifis[0],
                           modelwl_wifis[-1],
                           num=len(modelwl_wifis))
    ssp_rebin = np.interp(wl_rebin, modelwl_wifis, ssp[wh_wifis])

    lamRange2 = [wl_rebin[0], wl_rebin[-1]]
    sspNew, logLam2, velscale_temp = util.log_rebin(lamRange2,
                                                    ssp_rebin,
                                                    velscale=velscale /
                                                    velscale_ratio)

    #h2 = hdu[0].header
    #lamRange2 = h2['CRVAL1'] + np.array([0., h2['CDELT1']*(h2['NAXIS1'] - 1)])
    fullage = np.array([1.0, 3.0, 5.0, 7.0, 9.0, 11.0, 13.5])
    #fullZ = np.array([-1.5, -1.0, -0.5, 0.0, 0.2])
    fullZ = np.array([-0.5, 0.0, 0.2])
    #templates = np.empty((sspNew.size, len(vazdekis)))
    templates = np.empty((sspNew.size, len(fullage), len(fullZ)))

    conroydict = {}
    for fl in vazdekis:
        flspl = fl.split('/')[-1]
        mnamespl = flspl.split('_')
        age = float(mnamespl[3][1:])
        Zsign = mnamespl[4][1]
        Zval = float(mnamespl[4][2:5])
        if Zsign == "m":
            Zval = -1.0 * Zval
        conroydict[(age, Zval)] = fl

    # 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 / (wl_rebin[1] - wl_rebin[0]
                                )  # Sigma difference in pixels

    for j in range(len(fullage)):
        for k in range(len(fullZ)):
            #for j, file in enumerate(vazdekis):
            x = pd.read_csv(conroydict[(fullage[j], fullZ[k])],
                            delim_whitespace=True,
                            header=None)
            hdu = np.array(x)
            #hdu = np.loadtxt(conroydict[(fullage[j],fullZ[j])])
            ssp = hdu[:, 73]

            ssp_rebin = np.interp(wl_rebin, modelwl_wifis, ssp[wh_wifis])

            ssp = ndimage.gaussian_filter1d(ssp_rebin, sigma)
            sspNew, logLam2, velscale_temp = util.log_rebin(lamRange2,
                                                            ssp,
                                                            velscale=velscale /
                                                            velscale_ratio)
            templates[:, j,
                      k] = 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.mean(logLam2[:velscale_ratio]) - logLam1[0]) * c  # km/s

    z = z_guess  # 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, sigma_guess]  # (km/s), starting guess for [V, sigma]
    t = clock()

    pp = ppxf(templates,
              galaxy,
              noise,
              velscale,
              start,
              goodpixels=goodPixels,
              plot=True,
              moments=2,
              degree=4,
              vsyst=dv,
              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
Beispiel #23
0
def run_ppxf(fields, w1, w2, targetSN, tempfile, logdir, redo=False,
             ncomp=2, **kwargs):
    """ New function to run pPXF. """
    global velscale
    stars = pf.getdata(tempfile, 0)
    emission = pf.getdata(tempfile, 1)
    logLam_temp = wavelength_array(tempfile, axis=1, extension=0)
    ngas = len(emission)
    nstars = len(stars)
    templates = np.column_stack((stars.T, emission.T))
    ##########################################################################
    # Set components
    if ncomp == 1:
        components = np.zeros(nstars + ngas)
        kwargs["component"] = components
    elif ncomp == 2:
        components = np.hstack((np.zeros(nstars), np.ones(ngas))).astype(int)
        kwargs["component"] = components
    ##########################################################################
    for f in fields:
        print "Working on Field {0}".format(f[-1])
        os.chdir(os.path.join(data_dir, "combined_{0}".format(f)))
        outdir = os.path.join(os.getcwd(), logdir)
        if not os.path.exists(outdir):
            os.mkdir(outdir)
        fits = "binned_sn{0}_res2.95.fits".format(targetSN)
        data = pf.getdata(fits, 0)
        w = wavelength_array(fits, axis=1, extension=0)
        bins = wavelength_array(fits, axis=2, extension=0)
        ######################################################################
        # Slice array before fitting
        idx = np.where(np.logical_and(w >= w1, w <= w2))[0]
        data = data[:,idx]
        w = w[idx]
        ######################################################################
        for i,bin in enumerate(bins):
            output = os.path.join(outdir, "{1}_bin{2:04d}.pkl".format(targetSN,
                                                                      f, bin))
            outroot = output.replace(".pkl", "")
            if os.path.exists(output) and not redo:
                continue
            print "PPXF run {0}/{1}".format(i+1, len(bins))
            spec = data[i,:]
            signal, noise, sn = snr(spec)
            galaxy, logLam, vtemp = util.log_rebin([w[0], w[-1]],             \
                                                      spec, velscale=velscale)
            dv = (logLam_temp[0]-logLam[0])*c
            lam = np.exp(logLam)
            name = "{0}_bin{1:04d}".format(f, bin)
            noise =  np.ones_like(galaxy) * noise
            kwargs["lam"] = lam
            ###################################################################
            # Masking bad pixels
            skylines = np.array([5577,5889, 6300, 6863])
            goodpixels = np.arange(len(lam))
            for line in skylines:
                sky = np.argwhere((lam < line - 10) | (lam > line + 10)).ravel()
                goodpixels = np.intersect1d(goodpixels, sky)
            kwargs["goodpixels"] = goodpixels
            ###################################################################
            kwargs["vsyst"] = dv
            pp = ppxf(templates, galaxy, noise, velscale, **kwargs)
            title = "Field {0} Bin {1}".format(f[-1], bin)
            pp.name = name
            # Adding other things to the pp object
            pp.has_emission = True
            pp.dv = dv
            pp.w = np.exp(logLam)
            pp.velscale = velscale
            pp.ngas = ngas
            pp.ntemplates = nstars
            pp.templates = 0
            pp.id = id
            pp.name = name
            pp.title = title
            ppsave(pp, outroot=outroot)
            ppf = ppload(outroot)
            ppf = pPXF(ppf, velscale)
            ppf.plot("{1}/{0}.png".format(name, outdir))
    return
Beispiel #24
0
def minimize_leg(galname, templates, galaxy, noise, velscale, start, goodPixels,
                 dv, velscale_ratio, iterations, useMC, find_min=True):

#Minimizes ppxf uncertainty using legendre polynomials
#find_min=True will force the program to find the minimum again
    value_range = range(5,23)

    if useMC == False: #so that you don't double up on the multiprocessing and kill the server
        #iterations
        feeds = []
        for ad in value_range:
            for md in value_range:
                feeds.append([(ad, md), i, iterations, templates, galaxy, 
                noise, velscale, start, goodPixels, dv, velscale_ratio])
        P = mp.Pool(iterations)
        outputs = P.map(min_worker, feeds)

        outputs = np.array(outputs)
        degrees = outputs[:,0]
        errors = outputs[:,1]
        deg = degrees[np.argmin(errors)]
        

        print(' ')
        print('Deg is:')
        print(deg)
        print(' ')
        
        return deg
        

    
    else: #serial version
        
        stime = time.time()
        
        deg_used = []
        errors   = []

        if find_min or np.any(np.isnan(degrees)):
        #Sample parameter space within $value_range to find minimum uncertainty
            minimum = 1e10
            degrees = [0,0] #ad, md
            for ad in value_range: #degree of additive legendre polynomial
                for md in value_range: #degree of multiplicative legendre polynomial
                    print('degrees of %i additive, %i multiplicative'%(ad, md))
                    #print((ad, md))
                    
                    ppxf_obj = ppxf.ppxf(templates, galaxy, noise, velscale, 
                                         start, goodpixels=goodPixels, 
                                         plot=False, moments=4, degree=ad,
                                         vsyst=dv, 
                                         velscale_ratio = velscale_ratio, 
                                         mdegree=md, clean=False)
                    error = ppxf_obj.error[1]*np.sqrt(ppxf_obj.chi2)
                    
                    deg_used.append((ad,md))
                    errors.append(error)
                    
                    #error = ppxf_obj.chi2
                    print('Uncertainty is ' + str(round(float(error), 2)) + ' km/s')
                    if error < minimum:
                        minimum = error
                        degrees[0] = ad
                        degrees[1] = md
                
        print('')
        print('Degree of Linear Legendre Polynomial is: ' + str(degrees[0]))
        print('')
        print('Degree of Multiplicative Legendre Polynomial is: ' + str(degrees[1]))
        print('')
        print('Took %s seconds to complete'%int(round(time.time() - stime)))
        print('')


    return degrees
Beispiel #25
0
def ppxf_example_two_components():

    ppxf_dir = path.dirname(path.realpath(ppxf_package.__file__))

    hdu = fits.open(
        ppxf_dir +
        '/miles_models/Mun1.30Zp0.00T12.5893_iPp0.00_baseFe_linear_FWHM_2.51.fits'
    )  # Solar metallicitly, Age=12.59 Gyr
    gal_lin = hdu[0].data
    h1 = hdu[0].header
    lamRange1 = h1['CRVAL1'] + np.array(
        [0., h1['CDELT1'] * (h1['NAXIS1'] - 1)])
    c = 299792.458  # speed of light in km/s
    velscale = c * h1['CDELT1'] / max(
        lamRange1)  # Do not degrade original velocity sampling
    model1, logLam1, velscale = util.log_rebin(lamRange1,
                                               gal_lin,
                                               velscale=velscale)
    model1 /= np.median(model1)

    hdu = fits.open(
        ppxf_dir +
        '/miles_models/Mun1.30Zp0.00T01.0000_iPp0.00_baseFe_linear_FWHM_2.51.fits'
    )  # Solar metallicitly, Age=1.00 Gyr
    gal_lin = hdu[0].data
    model2, logLam1, velscale = util.log_rebin(lamRange1,
                                               gal_lin,
                                               velscale=velscale)
    model2 /= np.median(model2)

    model = np.column_stack([model1, model2])
    galaxy = np.empty_like(model)

    # These are the input values in spectral pixels
    # for the (V,sigma) of the two kinematic components
    #
    vel = np.array([0., 300.]) / velscale
    sigma = np.array([200., 100.]) / velscale

    # The synthetic galaxy model consists of the sum of two
    # SSP spectra with age of 1Gyr and 13Gyr respectively
    # with different velocity and dispersion
    #
    for j in range(len(vel)):
        dx = int(abs(vel[j]) +
                 4. * sigma[j])  # Sample the Gaussian at least to vel+4*sigma
        v = np.linspace(-dx, dx, 2 * dx + 1)
        losvd = np.exp(-0.5 * ((v - vel[j]) / sigma[j])**2)  # Gaussian LOSVD
        losvd /= np.sum(losvd)  # normalize LOSVD
        galaxy[:, j] = signal.fftconvolve(model[:, j], losvd, mode="same")
        galaxy[:, j] /= np.median(model[:, j])
    galaxy = np.sum(galaxy, axis=1)
    sn = 100.
    noise = galaxy / sn
    galaxy = np.random.normal(galaxy, noise)  # add noise to galaxy

    # Adopts two templates per kinematic component
    #
    templates = np.column_stack([model1, model2, model1, model2])

    # With multiple stellar kinematic components a good starting velocity is essential.
    # Starting too far from the solution pPXF may *not* converge to the global minimum.
    # Giving the same starting point for both components should be generally avoided and
    # and is used below just to illustrate how one can swap the components using constr_kinem.
    # In general one should explore a grid of starting velocities as illustrated
    # e.g. in Sec.3.3 of Mitzkus et al. (2017 https://ui.adsabs.harvard.edu/abs/2017MNRAS.464.4789M)
    start = [[100, 100], [100, 100]]
    goodPixels = np.arange(20, 6000)

    t = clock()
    plt.clf()

    print("\n++++++++++++++++++++++++++++++++++++++++++++++\n"
          "     No constraints on the kinematics\n"
          "----------------------------------------------")

    plt.subplot(211)
    plt.title("Two components pPXF fit")

    pp = ppxf(templates,
              galaxy,
              noise,
              velscale,
              start,
              goodpixels=goodPixels,
              plot=True,
              degree=4,
              moments=[2, 2],
              component=[0, 0, 1, 1])

    print("\n++++++++++++++++++++++++++++++++++++++++++++++\n"
          "Force sigma(component=0) >= sigma(component=1)\n"
          "----------------------------------------------")

    # Note the swapping of the two components in the solution
    # with respect to the previous uncostrained fit

    A_ineq = [[0, -1, 0, 1]]  # -sigma0 + sigma1 <= 0
    b_ineq = [0]
    constr_kinem = {"A_ineq": A_ineq, "b_ineq": b_ineq}

    pp = ppxf(templates,
              galaxy,
              noise,
              velscale,
              start,
              goodpixels=goodPixels,
              degree=4,
              moments=[2, 2],
              component=[0, 0, 1, 1],
              constr_kinem=constr_kinem)

    print("\n++++++++++++++++++++++++++++++++++++++++++++++\n"
          "         Single component pPXF fit\n"
          "----------------------------------------------")

    plt.subplot(212)
    plt.title("Single component pPXF fit")

    start = start[0]
    pp = ppxf(templates,
              galaxy,
              noise,
              velscale,
              start,
              goodpixels=goodPixels,
              plot=True,
              degree=4,
              moments=2)

    print("==============================================")
    print("Total elapsed time %.2f s" % (clock() - t))

    plt.tight_layout()
    plt.pause(1)
Beispiel #26
0
def ppxf_fit_spec(galaxy,
                  noise,
                  templates,
                  fit_range,
                  lamRange2,
                  logLam1,
                  logLam2,
                  velscale,
                  start,
                  sky=None,
                  plot=True,
                  moments=4,
                  degree=-1,
                  mdegree=4,
                  goodpixels=None):
    """Use pPXF to fit kinematics to a spectrum. This function masks the templates so they're the appropriate lengths (a bit longer than the galaxy spectrum) then
    runs pPXF


    inputs:

    galaxy- a full length galaxy spectrum (i.e 4112 pixels for SWIFT)
    noise- noise spectrum, same length as galaxy
    templates- array of templates
    lamRange2- wavelength range of the templates, pre masking
    logLam1- log rebinned wavelength array for galaxy
    logLam2- log rebinned wavelength array for templates
    fit_range- range over which you want to fit the galaxy
    start- starting guess for V and Sigma (or h3, h4, etc if you want higher moments)
    sky- optionally fit a sky spectra at the same time. Set to None if you're not using
  


    outputs:

    pp- the ppxf class
    sky- the input sky spectrum, but masked. If sky=None, then this is None
    galaxy- the input galaxy spectrum, but masked
    noise- the input noise spectrum, but masked



    """

    #We need to pad the templates for ~100 angstroms each side of the galaxy spectrum.

    lower_lam, upper_lam = np.array(fit_range)

    #print(upper_lam)

    #print(lamRange2)

    pad = 300

    #make the template mask
    tmask = np.where((logLam2 >= np.log((lower_lam - pad)))
                     & (logLam2 <= np.log((upper_lam + pad))))[0]

    #mask the  wavelength range and the templates
    logLam2 = logLam2[tmask]
    templates = templates[tmask, :]

    #make the mask for the galaxy spectrum
    #mask=np.where((logLam1 >= np.log(lower_lam)) & (logLam1 <= np.log(upper_lam)))[0]

    #mask the galaxy, sky and variance, plus the wavelengh ranges
    """
    logLam1=logLam1[mask]


    galaxy=galaxy[mask]



    if SKY_FLAG:

        sky=sky[mask, :]

      
    noise=noise[mask]
    """

    #################################################################################

    # 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

    #print("logam2[0] is {}, loglam1[0] is {}, logLam1/(1+z) is {}".format(logLam2[0], logLam1[0],logLam1[0]/(1+z)))
    dv = (logLam2[0] - logLam1[0]) * c  # km/s

    vel, sigma = start

    z_ppxf = np.exp(
        vel / c) - 1  # Relation between velocity and redshift in pPXF

    if goodpixels is None:
        goodpixels = util.determine_goodpixels(logLam1, lamRange2, z_ppxf)

    print("#########################################################")
    print("Velocity shift DV is {}".format(dv))
    print("The templates are shape {}".format(np.shape(templates)))
    print("The galaxy is shape {}".format(np.shape(galaxy)))

    print("#########################################################")

    # 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.
    #

    t = clock()

    if sky is not None:
        pp = ppxf(templates,
                  galaxy,
                  noise,
                  velscale,
                  start,
                  goodpixels=goodpixels,
                  plot=plot,
                  moments=moments,
                  degree=degree,
                  mdegree=mdegree,
                  vsyst=dv,
                  sky=sky)

    else:
        pp = ppxf(templates,
                  galaxy,
                  noise,
                  velscale,
                  start,
                  goodpixels=goodpixels,
                  plot=plot,
                  moments=moments,
                  degree=degree,
                  mdegree=mdegree,
                  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))

    return (pp, sky, galaxy, noise)
Beispiel #27
0
def ppxf_example_two_components():

    ppxf_dir = path.dirname(path.realpath(ppxf_package.__file__))

    hdu = fits.open(
        ppxf_dir +
        '/miles_models/Mun1.30Zp0.00T12.5893_iPp0.00_baseFe_linear_FWHM_2.51.fits'
    )  # Solar metallicitly, Age=12.59 Gyr
    gal_lin = hdu[0].data
    h1 = hdu[0].header
    lamRange1 = h1['CRVAL1'] + np.array(
        [0., h1['CDELT1'] * (h1['NAXIS1'] - 1)])
    c = 299792.458  # speed of light in km/s
    velscale = c * h1['CDELT1'] / max(
        lamRange1)  # Do not degrade original velocity sampling
    model1, logLam1, velscale = util.log_rebin(lamRange1,
                                               gal_lin,
                                               velscale=velscale)
    model1 /= np.median(model1)

    hdu = fits.open(
        ppxf_dir +
        '/miles_models/Mun1.30Zp0.00T01.0000_iPp0.00_baseFe_linear_FWHM_2.51.fits'
    )  # Solar metallicitly, Age=1.00 Gyr
    gal_lin = hdu[0].data
    model2, logLam1, velscale = util.log_rebin(lamRange1,
                                               gal_lin,
                                               velscale=velscale)
    model2 /= np.median(model2)

    model = np.column_stack([model1, model2])
    galaxy = np.empty_like(model)

    # These are the input values in spectral pixels
    # for the (V,sigma) of the two kinematic components
    #
    vel = np.array([0., 300.]) / velscale
    sigma = np.array([200., 100.]) / velscale

    # The synthetic galaxy model consists of the sum of two
    # SSP spectra with age of 1Gyr and 13Gyr respectively
    # with different velocity and dispersion
    #
    for j in range(len(vel)):
        dx = int(abs(vel[j]) +
                 4. * sigma[j])  # Sample the Gaussian at least to vel+4*sigma
        v = np.linspace(-dx, dx, 2 * dx + 1)
        losvd = np.exp(-0.5 * ((v - vel[j]) / sigma[j])**2)  # Gaussian LOSVD
        losvd /= np.sum(losvd)  # normaize LOSVD
        galaxy[:, j] = signal.fftconvolve(model[:, j], losvd, mode="same")
        galaxy[:, j] /= np.median(model[:, j])
    galaxy = np.sum(galaxy, axis=1)
    sn = 100.
    noise = galaxy / sn
    galaxy = np.random.normal(galaxy, noise)  # add noise to galaxy

    # Adopts two templates per kinematic component
    #
    templates = np.column_stack([model1, model2, model1, model2])

    # Start both kinematic components from the same guess.
    # With multiple stellar kinematic components
    # a good starting guess is essential
    #
    start = [np.mean(vel) * velscale, np.mean(sigma) * velscale]
    start = [start, start]
    goodPixels = np.arange(20, 6000)

    t = clock()

    plt.clf()
    plt.subplot(211)
    plt.title("Two components pPXF fit")
    print("+++++++++++++++++++++++++++++++++++++++++++++")

    pp = ppxf(templates,
              galaxy,
              noise,
              velscale,
              start,
              goodpixels=goodPixels,
              plot=True,
              degree=4,
              moments=[2, 2],
              component=[0, 0, 1, 1])

    plt.subplot(212)
    plt.title("Single component pPXF fit")
    print("---------------------------------------------")

    start = start[0]
    pp = ppxf(templates,
              galaxy,
              noise,
              velscale,
              start,
              goodpixels=goodPixels,
              plot=True,
              degree=4,
              moments=2)

    print("=============================================")
    print("Total elapsed time %.2f s" % (clock() - t))

    plt.tight_layout()
    plt.pause(1)
Beispiel #28
0
def kinematics_sdss(cube_id, y_data_var, fit_range):
    file_loc = "ppxf_results" + "/cube_" + str(int(cube_id))
    if not os.path.exists(file_loc):
        os.mkdir(file_loc)

    # reading cube_data
    cube_file = (
        "/Volumes/Jacky_Cao/University/level4/project/cubes_better/cube_" +
        str(cube_id) + ".fits")
    hdu = fits.open(cube_file)
    t = hdu[1].data

    spectra = cube_reader.spectrum_creator(cube_file)

    # using our redshift estimate from lmfit
    cube_result_file = ("cube_results/cube_" + str(cube_id) + "/cube_" +
                        str(cube_id) + "_lmfit.txt")
    cube_result_file = open(cube_result_file)

    line_count = 0
    for crf_line in cube_result_file:
        if (line_count == 20):
            curr_line = crf_line.split()
            z = float(curr_line[1])
        line_count += 1

    cube_x_data = np.load("cube_results/cube_" + str(int(cube_id)) + "/cube_" +
                          str(int(cube_id)) + "_cbd_x.npy")
    if (np.sum(y_data_var) == 0):
        cube_y_data = np.load("cube_results/cube_" + str(int(cube_id)) +
                              "/cube_" + str(int(cube_id)) + "_cbs_y.npy")
    else:
        cube_y_data = y_data_var

    cube_x_original = cube_x_data
    cube_y_original = cube_y_data

    # masking the data to ignore initial 'noise' / non-features
    initial_mask = (cube_x_data > 3540 * (1 + z))
    cube_x_data = cube_x_original[initial_mask]
    cube_y_data = cube_y_original[initial_mask]

    # calculating the signal to noise
    sn_region = np.array([4000, 4080]) * (1 + z)
    sn_region_mask = ((cube_x_data > sn_region[0]) &
                      (cube_x_data < sn_region[1]))

    cube_y_sn_region = cube_y_data[sn_region_mask]
    cy_sn_mean = np.mean(cube_y_sn_region)
    cy_sn_std = np.std(cube_y_sn_region)
    cy_sn = cy_sn_mean / cy_sn_std

    #print("s/n:")
    #print(cy_sn, cy_sn_mean, cy_sn_std)

    # cube noise
    cube_noise_data = cube_noise()
    spectrum_noise = cube_noise_data['spectrum_noise']
    spec_noise = spectrum_noise[initial_mask]

    # will need this for when we are considering specific ranges
    if (isinstance(fit_range, str)):
        pass
    else:
        rtc = fit_range * (1 + z
                           )  # create a new mask and mask our x and y data
        rtc_mask = ((cube_x_data > rtc[0]) & (cube_x_data < rtc[1]))

        cube_x_data = cube_x_data[rtc_mask]
        cube_y_data = cube_y_data[rtc_mask]

        spec_noise = spec_noise[rtc_mask]

    lamRange = np.array([np.min(cube_x_data), np.max(cube_x_data)])
    specNew, logLam, velscale = log_rebin(lamRange, cube_y_data)
    lam = np.exp(logLam)

    loglam = np.log10(lam)
    # Only use the wavelength range in common between galaxy and stellar library.
    mask = (loglam > np.log10(3540)) & (loglam < np.log10(9464))
    flux = specNew[mask]

    galaxy = flux / np.median(
        flux)  # Normalize spectrum to avoid numerical issues
    loglam_gal = loglam[mask]
    lam_gal = 10**loglam_gal

    # galaxy spectrum not scaled
    galaxy_ns = flux

    segmentation_data = hdu[2].data
    seg_loc_rows, seg_loc_cols = np.where(segmentation_data == cube_id)
    signal_pixels = len(seg_loc_rows)

    spec_noise = spec_noise[mask]

    noise = (spec_noise * np.sqrt(signal_pixels)) / np.median(flux)

    # sky noise
    sky_noise = cube_reader.sky_noise("data/skyvariance_csub.fits")
    skyNew, skyLogLam, skyVelScale = log_rebin(lamRange,
                                               sky_noise[initial_mask])
    skyNew = skyNew[mask]

    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

    data_shape = np.shape(galaxy)
    wdisp = np.full(data_shape, 1,
                    dtype=float)  # Intrinsic dispersion of every pixel

    fwhm_gal = 2.51 * 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 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:
    #
    #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
    #template_set = glob.glob('miles_models/Mun1.30Z*.fits')
    #template_set = glob.glob('jacoby_models/jhc0*.fits')
    #fwhm_tem = 4.5 # instrumental resolution in Ångstroms.

    # NOAO Coudé templates
    template_set = glob.glob("noao_templates/*.fits")
    fwhm_tem = 1.35

    # 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 = fits.open(template_set[0])
    #ssp = hdu[0].data
    #h2 = hdu[0].header
    #lam_temp = h2['CRVAL1'] + h2['CDELT1']*np.arange(h2['NAXIS1'])

    hdu = fits.open(template_set[0])
    noao_data = hdu[1].data[0]
    ssp = noao_data[1]
    lam_temp = noao_data[0]

    lamRange_temp = [np.min(lam_temp), np.max(lam_temp)]
    sspNew = util.log_rebin(lamRange_temp, ssp, velscale=velscale)[0]
    templates = np.empty((sspNew.size, len(template_set)))

    # 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

    spacing = lam_temp[1] - lam_temp[0]
    sigma = fwhm_dif / 2.355 / spacing  # Sigma difference in pixels

    for j, fname in enumerate(template_set):
        hdu = fits.open(fname)
        #ssp = hdu[0].data

        noao_data = hdu[1].data[0]
        ssp = noao_data[1]

        ssp = util.gaussian_filter1d(
            ssp, sigma)  # perform convolution with variable sigma
        sspNew = util.log_rebin(lamRange_temp, ssp, velscale=velscale)[0]
        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

    #lam_gal_alt = lam_gal * (1+z)
    #lamRange_temp = [np.min(lam_temp), np.max(lam_temp)]
    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)  # eq.(8) of Cappellari (2017)
    start = [vel, 200.]  # (km/s), starting guess for [V, sigma]
    t = process_time()

    f = io.StringIO()
    with redirect_stdout(f):
        pp = ppxf(templates,
                  galaxy,
                  noise,
                  velscale,
                  start,
                  sky=skyNew,
                  goodpixels=goodpixels,
                  plot=True,
                  moments=4,
                  degree=12,
                  vsyst=dv,
                  clean=False,
                  lam=lam_gal)

    ppxf_variables = pp.sol
    ppxf_errors = pp.error

    red_chi2 = pp.chi2
    best_fit = pp.bestfit

    x_data = cube_x_data[mask]
    y_data = cube_y_data[mask]

    print(ppxf_variables)
    #plt.show()

    if ((np.sum(y_data_var) == 0) and isinstance(fit_range, str)):
        np.save(file_loc + "/cube_" + str(int(cube_id)) + "_lamgal", lam_gal)
        np.save(file_loc + "/cube_" + str(int(cube_id)) + "_flux", flux)

        np.save(file_loc + "/cube_" + str(int(cube_id)) + "_x", x_data)
        np.save(file_loc + "/cube_" + str(int(cube_id)) + "_y", y_data)

        np.save(file_loc + "/cube_" + str(int(cube_id)) + "_noise", noise)

        # if best fit i.e. perturbation is 0, save everything

        kinematics_file = open(
            file_loc + "/cube_" + str(int(cube_id)) + "_kinematics.txt", 'w')

        np.save(file_loc + "/cube_" + str(int(cube_id)) + "_model", best_fit)

        print("Rough reduced chi-squared from ppxf: " + str(pp.chi2))

        data_to_file = f.getvalue()

        kinematics_file.write(data_to_file)
        kinematics_file.write("")

        kinematics_file.write("Formal errors: \n")
        kinematics_file.write("     dV    dsigma   dh3      dh4 \n")
        kinematics_file.write("".join("%8.2g" % f
                                      for f in pp.error * np.sqrt(pp.chi2)) +
                              "\n")

        kinematics_file.write('Elapsed time in PPXF: %.2f s' %
                              (process_time() - t) + "\n")

        plt.tight_layout()
        graph_loc = "ppxf_results" + "/cube_" + str(int(cube_id))
        if not os.path.exists(graph_loc):
            os.mkdir(graph_loc)

        kinematics_graph = (graph_loc + "/cube_" + str(int(cube_id)) +
                            "_kinematics.pdf")
        plt.savefig(kinematics_graph)
        #plt.show()
        plt.close("all")
    if not isinstance(fit_range, str):
        # saving graphs if not original range
        fit_range = fit_range * (1 + z)
        fitting_plotter(cube_id, fit_range, x_data, y_data, best_fit, noise)

    # 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):
    #
    #print, 'Best-fitting redshift z:', (z + 1)*(1 + sol[0]/c) - 1

    return {
        'reduced_chi2': red_chi2,
        'noise': noise,
        'variables': ppxf_variables,
        'y_data': galaxy,
        'x_data': lam_gal,
        'redshift': z,
        'y_data_original': cube_y_original,
        'non_scaled_y': galaxy_ns,
        'model_data': best_fit,
        'noise_original': spec_noise,
        'errors': ppxf_errors
    }
Beispiel #29
0
def run_ppxf(group,
             specs,
             redo=False,
             ncomp=2,
             logdir="ppxf",
             window=50,
             w1=None,
             w2=None,
             **kwargs):
    """ New function to run pPXF. """
    global velscale
    tempfile = os.path.join(
        home, "MILES/templates/"
        "templates_w3540.5_7409.6_res4.7.fits")
    stars = pf.getdata(tempfile, 0)
    emission = pf.getdata(tempfile, 1)
    logLam_temp = wavelength_array(tempfile, axis=1, extension=0)
    ngas = len(emission)
    nstars = len(stars)
    templates = np.column_stack((stars.T, emission.T))
    ##########################################################################
    # Set components
    if ncomp == 1:
        components = np.zeros(nstars + ngas)
        kwargs["component"] = components
    elif ncomp == 2:
        components = np.hstack((np.zeros(nstars), np.ones(ngas))).astype(int)
        kwargs["component"] = components
    ##########################################################################
    for spec in specs:
        os.chdir(os.path.join(data_dir, group))
        outdir = os.path.join(os.getcwd(), logdir)
        if not os.path.exists(outdir):
            os.mkdir(outdir)
        data = pf.getdata(spec)
        w = wavelength_array(spec)
        if w1 == None:
            w1 = w[0]
        if w2 == None:
            w2 = w[-1]
        idx = np.where((w >= w1) & (w <= w2))[0]
        data = data[idx]
        w = w[idx]
        #######################################################################
        # Preparing output
        output = os.path.join(outdir, spec.replace(".fits", ".pkl"))
        outroot = output.replace(".pkl", "")
        if os.path.exists(output) and not redo:
            continue
        print group, spec
        signal, noise, sn = snr(data)
        galaxy, logLam, vtemp = util.log_rebin([w[0], w[-1]],
                                               data,
                                               velscale=velscale)
        lam = np.exp(logLam)
        ###################################################################
        # Trimming spectra
        idx = np.where(np.exp(logLam) > np.exp(logLam_temp[0]))[0]
        didx = int(1500. / velscale)  # Space in the beginning of the spec
        idx = idx[didx:]
        galaxy = galaxy[idx]
        lam = lam[idx]
        logLam = logLam[idx]
        #######################################################################
        # Masking bad pixels
        goodpixels = np.arange(len(lam), dtype=float)
        gaps = set_badpixels(group)
        for gap in gaps:
            idx = np.where((lam > gap[0] - window / 2.)
                           & (lam < gap[1] + window / 2.))[0]
            goodpixels[idx] = np.nan
        goodpixels = goodpixels[~np.isnan(goodpixels)].astype(int)
        kwargs["goodpixels"] = goodpixels
        #######################################################################
        kwargs["lam"] = lam
        dv = (logLam_temp[0] - logLam[0]) * c
        kwargs["vsyst"] = dv
        noise = np.ones_like(galaxy) * noise
        # First fitting to obtain realistic noise
        pp0 = ppxf(templates, galaxy, noise, velscale, **kwargs)
        pp0.has_emission = True
        pp0.dv = dv
        pp0.w = np.exp(logLam)
        pp0.velscale = velscale
        pp0.ngas = ngas
        pp0.ntemplates = nstars
        pp0.templates = 0
        pp0.name = spec
        pp0.title = ""
        pp0 = pPXF(pp0, velscale)
        pp0.calc_sn()
        res = (pp0.galaxy - pp0.bestfit)
        noise = rolling_std(res, window, center=True)
        noise[:window / 2] = noise[window + 1]
        noise[-window / 2 + 1:] = noise[-window / 2]
        # Second fitting using results from first interaction
        pp = ppxf(templates, galaxy, noise, velscale, **kwargs)
        title = "Group {} , Spectrum {}".format(
            group.upper(),
            spec.replace(".fits", "").replace("spec", ""))
        # Adding other things to the pp object
        pp.has_emission = True
        pp.dv = dv
        pp.w = np.exp(logLam)
        pp.velscale = velscale
        pp.ngas = ngas
        pp.ntemplates = nstars
        pp.templates = 0
        pp.id = id
        pp.name = spec
        pp.title = title
        ppsave(pp, outroot=outroot)
        ppf = ppload(outroot)
        ppf = pPXF(ppf, velscale)
        ppf.plot("{1}/{0}.png".format(pp.name.replace(".fits", ""), outdir))
    return