def test_model_indices(): # Setup bm = SpectralIndicesBitMask() absdb = AbsorptionIndexDB.from_key('EXTINDX') bhddb = BandheadIndexDB.from_key('BHBASIC') # Grab the model spectra tpl = TemplateLibrary('M11MILES', spectral_step=1e-4, log=True, hardcopy=False) flux = numpy.ma.MaskedArray(tpl['FLUX'].data, mask=tpl.bitmask.flagged(tpl['MASK'].data, flag=['NO_DATA', 'WAVE_INVALID', 'FLUX_INVALID', 'SPECRES_NOFLUX'])) # Try to measure only absorption-line indices indices = SpectralIndices.measure_indices(absdb, None, tpl['WAVE'].data, flux[:2,:], bitmask=bm) # Try to measure only bandhead indices indices = SpectralIndices.measure_indices(None, bhddb, tpl['WAVE'].data, flux[:2,:], bitmask=bm) # Measure both indices = SpectralIndices.measure_indices(absdb, bhddb, tpl['WAVE'].data, flux[:2,:], bitmask=bm) # Test the output indx = numpy.ma.MaskedArray(indices['INDX'], mask=bm.flagged(indices['MASK'])) indx_bf = numpy.ma.MaskedArray(indices['INDX_BF'], mask=bm.flagged(indices['MASK'])) assert indx.shape == (2,46), 'Incorrect output shape' assert numpy.sum(indx.mask[0,:]) == 12, 'Incorrect number of masked indices' assert numpy.allclose(indx[1,:].compressed(), numpy.array([-2.67817275e-02, 1.94113040e-02, 2.92495672e-01, 1.91132126e+00, 1.86182351e+00, 1.13929718e+00, 2.45269081e+00, 2.24190385e+00, 3.55293194e+00, 5.25612762e+00, 2.09506842e-02, 5.47343625e-02, 2.29779119e-01, 1.92039303e+00, 1.95663049e+00, 9.81723064e-01, 7.44058546e-01, 5.46484409e-01, 1.56520293e+00, 8.93716574e-03, 1.82158534e-02, 2.46748795e+00, 1.03916389e+00, 2.27668045e+00, 2.68943564e+00, 6.77715967e+00, 1.31272416e+00, 9.12401084e-03, 3.52657124e-03, 1.92826953e-03, -1.00946682e-02, 2.01732938e-02, 1.17072128e+00, 1.12525642e+00]), rtol=0.0, atol=1e-4), 'Index values are different' assert numpy.std(indx.compressed() - indx_bf.compressed()) < 0.01, \ 'Index definitions are too different'
def measure_spec(fsps_flux): if fsps_flux.shape[0] == len(fsps_wave): fsps_flux = fsps_flux.reshape(1, -1) else: pass nspec = fsps_flux.shape[0] # Provide the guess redshift and guess velocity dispersion guess_redshift = np.full(nspec, 0.0001, dtype=float) guess_dispersion = np.full(nspec, 80.0, dtype=float) # Perform the fits and construct the models model_wave, model_flux, model_mask, model_par = ppxf.fit( tpl["WAVE"].data.copy(), tpl["FLUX"].data.copy(), fsps_wave, fsps_flux, np.sqrt(fsps_flux), guess_redshift, guess_dispersion, iteration_mode="none", velscale_ratio=1, degree=8, mdegree=-1, moments=2, quiet=True) em_model_wave, em_model_flux, em_model_base, em_model_mask, em_model_fit_par, em_model_eml_par = elric.fit( fsps_wave, fsps_flux, emission_lines=emlines, ivar=np.abs(1 / (fsps_flux + 0.0000001)), sres=np.ones_like(fsps_flux), continuum=model_flux, guess_redshift=model_par["KIN"][:, 0] / c, guess_dispersion=model_par["KIN"][:, 1], base_order=1, quiet=True) indx_measurements = SpectralIndices.measure_indices( absdb=abs_db, bhddb=band_db, wave=fsps_wave, flux=fsps_flux, ivar=np.abs(1 / (fsps_flux + 0.0000001)), mask=None, redshift=model_par["KIN"][:, 0] / c, bitmask=None) plt.close("all") plt.cla() plt.clf() return em_model_eml_par, indx_measurements
# simplifications stellar_continuum = StellarContinuumModel('GAU-MILESHC', binned_spectra, guess_vel=vel, guess_sig=100., analysis_path=analysis_path) emission_line_moments = EmissionLineMoments('EMOMM', binned_spectra, stellar_continuum=stellar_continuum, redshift=nsa_redshift, analysis_path=analysis_path) emission_line_model = EmissionLineModel('EFITM', binned_spectra, stellar_continuum=stellar_continuum, redshift=nsa_redshift, dispersion=100.0, analysis_path=analysis_path) spectral_indices = SpectralIndices('INDXEN', binned_spectra, redshift=nsa_redshift, stellar_continuum=stellar_continuum, emission_line_model=emission_line_model, analysis_path=analysis_path) construct_maps_file(drpf, rdxqa=rdxqa, binned_spectra=binned_spectra, stellar_continuum=stellar_continuum, emission_line_moments=emission_line_moments, emission_line_model=emission_line_model, spectral_indices=spectral_indices, nsa_redshift=nsa_redshift, analysis_path=analysis_path) construct_cube_file(drpf, binned_spectra=binned_spectra, stellar_continuum=stellar_continuum, emission_line_model=emission_line_model, analysis_path=analysis_path) print('Elapsed time: {0} seconds'.format(time.perf_counter() - t))
def measure_spec(fluxes, errors=None, ivar=None, sres=None): """ This function takes an array of spectra of shape (# of spectra, # of wavelengths) and returns the pre-defined emission line and absorption features in each spectra in two recorded arrays of shape (# of spectra,). The recorded arrays can be accessed to give the quantities returned by the MaNGA DAP, e.g. ["EW"], ["EWERR"], ["INDX"]. The user can change the emission and absorption quantities returned by specifying different emission line and absorption feature databases. INPUTS :fluxes: Fluxes at each manga wavelength, M for any number of spectra, X. Shape (X, M). OUTPUTS :em_model_eml_par: Recorded array containing the emisison line parameter measurements from the defined user database. Shape (X, ). :indx_measurement: Recorded array containing the absorption feature parameter measurements from the defined user database. Shape (X, ). """ if ivar is None: ivar = 1/(0.1*fluxes)**2 if errors is None: errors = 0.1*fluxes if sres is None: sres = np.ones_like(fluxes) tpl = TemplateLibrary("MILESHC", match_to_drp_resolution=False, velscale_ratio=1, spectral_step=1e-4, log=True, directory_path=".", processed_file="mileshc.fits", clobber=True) # Instantiate the object that does the fitting contbm = StellarContinuumModelBitMask() ppxf = PPXFFit(contbm) # Define the absorption and bandhead feature databases define_abs_db = SpectralFeatureDBDef(key='USERABS', file_path='extindxsnitch.par') abs_db = AbsorptionIndexDB(u"USERABS", indxdb_list=define_abs_db) band_db = BandheadIndexDB(u"BHBASIC") global indx_names indx_names = np.hstack([abs_db.data["name"], band_db.data["name"]]) # Define the emission line feature database specm = EmissionLineModelBitMask() elric = Elric(specm) global emlines define_em_db = SpectralFeatureDBDef(key='USEREM', file_path='elpsnitch.par') emlines = EmissionLineDB(u"USEREM", emldb_list=define_em_db) # Check to see if a single spectra has been input. If the single spectra is shape (# of wavelengths,) then reshape # to give (1, #number of wavelengths) if fluxes.shape[0] == len(manga_wave): fluxes = fluxes.reshape(1,-1) else: pass nspec = fluxes.shape[0] # Provide the guess redshift and guess velocity dispersion guess_redshift = np.full(nspec, 0.0001, dtype=float) guess_dispersion = np.full(nspec, 77.0, dtype=float) # Perform the fits to the continuum, emission lines and absorption features model_wave, model_flux, model_mask, model_par = ppxf.fit(tpl["WAVE"].data.copy(), tpl["FLUX"].data.copy(), manga_wave, fluxes, errors, guess_redshift, guess_dispersion, iteration_mode="none", velscale_ratio=1, degree=8, mdegree=-1, moments=2, quiet=True) em_model_wave, em_model_flux, em_model_base, em_model_mask, em_model_fit_par, em_model_eml_par = elric.fit(manga_wave, fluxes, emission_lines=emlines, ivar=ivar, sres=sres, continuum=model_flux, guess_redshift = model_par["KIN"][:,0]/c, guess_dispersion=model_par["KIN"][:,1], base_order=1, quiet=True) indx_measurements = SpectralIndices.measure_indices(absdb=abs_db, bhddb=band_db, wave=manga_wave, flux=fluxes-em_model_flux, ivar=ivar, mask=None, redshift=model_par["KIN"][:,0]/c, bitmask=None) # Close all plots generated by the MaNGA DAP pipeline plt.close("all") plt.cla() plt.clf() return em_model_eml_par, indx_measurements
def main(): t = time.perf_counter() #------------------------------------------------------------------- # Read spectra to fit. The following reads a single MaNGA spectrum. # This is where you should read in your own spectrum to fit. # Plate-IFU to use plt = 7815 ifu = 3702 # Spaxel coordinates x = 25 #30 y = 25 #37 # Where to find the relevant datacube. This example accesses the test data # that can be downloaded by executing the script here: # https://github.com/sdss/mangadap/blob/master/download_test_data.py directory_path = defaults.dap_source_dir() / 'data' / 'remote' # Read a spectrum wave, flux, ivar, sres = get_spectra(plt, ifu, x, y, directory_path=directory_path) # In general, the DAP fitting functions expect data to be in 2D # arrays with shape (N-spectra,N-wave). So if you only have one # spectrum, you need to expand the dimensions: flux = flux.reshape(1,-1) ivar = ivar.reshape(1,-1) ferr = numpy.ma.power(ivar, -0.5) sres = sres.reshape(1,-1) # The majority (if not all) of the DAP methods expect that your # spectra are binned logarithmically in wavelength (primarily # because this is what pPXF expects). You can either have the DAP # function determine this value (commented line below) or set it # directly. The value is used to resample the template spectra to # match the sampling of the spectra to fit (up to some integer; see # velscale_ratio). # spectral_step = spectral_coordinate_step(wave, log=True) spectral_step = 1e-4 # Hereafter, the methods expect a wavelength vector, a flux array # with the spectra to fit, an ferr array with the 1-sigma errors in # the flux, and sres with the wavelength-dependent spectral # resolution, R = lambda / Dlambda #------------------------------------------------------------------- #------------------------------------------------------------------- # The DAP needs a reasonable guess of the redshift of the spectrum # (within +/- 2000 km/s). In this example, I'm pulling the redshift # from the DRPall file. There must be one redshift estimate per # spectrum to fit. Here that means it's a single element array # This example accesses the test data # that can be downloaded by executing the script here: # https://github.com/sdss/mangadap/blob/master/download_test_data.py drpall_file = directory_path / f'drpall-{drp_test_version}.fits' z = numpy.array([get_redshift(plt, ifu, drpall_file)]) print('Redshift: {0}'.format(z[0])) # The DAP also requires an initial guess for the velocity # dispersion. A guess of 100 km/s is usually robust, but this may # depend on your spectral resolution. dispersion = numpy.array([100.]) #------------------------------------------------------------------- #------------------------------------------------------------------- # The following sets the keyword for the template spectra to use # during the fit. You can specify different template sets to use # during the stellar-continuum (stellar kinematics) fit and the # emission-line modeling. # Templates used in the stellar continuum fits sc_tpl_key = 'MILESHC' # Templates used in the emission-line modeling el_tpl_key = 'MASTARSSP' # You also need to specify the sampling for the template spectra. # The templates must be sampled with the same pixel step as the # spectra to be fit, up to an integer factor. The critical thing # for the sampling is that you do not want to undersample the # spectral resolution element of the template spectra. Here, I set # the sampling for the MILES templates to be a factor of 4 smaller # than the MaNGA spectrum to be fit (which is a bit of overkill # given the resolution difference). I set the sampling of the # MaStar templates to be the same as the galaxy data. # Template pixel scale a factor of 4 smaller than galaxy data sc_velscale_ratio = 4 # Template sampling is the same as the galaxy data el_velscale_ratio = 1 # You then need to identify the database that defines the # emission-line passbands (elmom_key) for the non-parametric # emission-line moment calculations, and the emission-line # parameters (elfit_key) for the Gaussian emission-line modeling. # See # https://sdss-mangadap.readthedocs.io/en/latest/emissionlines.html. elmom_key = 'ELBMPL9' elfit_key = 'ELPMPL11' # If you want to also calculate the spectral indices, you can # provide a keyword that indicates the database with the passband # definitions for both the absorption-line and bandhead/color # indices to measure. The script allows these to be None, if you # don't want to calculate the spectral indices. See # https://sdss-mangadap.readthedocs.io/en/latest/spectralindices.html absindx_key = 'EXTINDX' bhdindx_key = 'BHBASIC' # Now we want to construct a pixel mask that excludes regions with # known artifacts and emission lines. The 'BADSKY' artifact # database only masks the 5577, which can have strong left-over # residuals after sky-subtraction. The list of emission lines (set # by the ELPMPL8 keyword) can be different from the list of # emission lines fit below. sc_pixel_mask = SpectralPixelMask(artdb=ArtifactDB.from_key('BADSKY'), emldb=EmissionLineDB.from_key('ELPMPL11')) # Mask the 5577 sky line el_pixel_mask = SpectralPixelMask(artdb=ArtifactDB.from_key('BADSKY')) # Finally, you can set whether or not to show a set of plots. # # Show the ppxf-generated plots for each fit stage. fit_plots = False # Show summary plots usr_plots = True #------------------------------------------------------------------- #------------------------------------------------------------------- # Fit the stellar continuum # First, we construct the template library. The keyword that # selects the template library (sc_tpl_key) is defined above. The # following call reads in the template library and processes the # data to have the appropriate pixel sampling. Note that *no* # matching of the spectral resolution to the galaxy spectrum is # performed. sc_tpl = TemplateLibrary(sc_tpl_key, match_resolution=False, velscale_ratio=sc_velscale_ratio, spectral_step=spectral_step, log=True, hardcopy=False) # This calculation of the mean spectral resolution is a kludge. The # template library should provide spectra that are *all* at the # same spectral resolution. Otherwise, one cannot freely combine # the spectra to fit the Doppler broadening of the galaxy spectrum # in a robust (constrained) way (without substantially more # effort). There should be no difference between what's done below # and simply taking the spectral resolution to be that of the first # template spectrum (i.e., sc_tpl['SPECRES'].data[0]) sc_tpl_sres = numpy.mean(sc_tpl['SPECRES'].data, axis=0).ravel() # Instantiate the fitting class, including the mask that it should # use to flag the data. [[This mask should just be default...]] ppxf = PPXFFit(StellarContinuumModelBitMask()) # The following call performs the fit to the spectrum. Specifically # note that the code only fits the first two moments, uses an # 8th-order additive polynomial, and uses the 'no_global_wrej' # iteration mode. See # https://sdss-mangadap.readthedocs.io/en/latest/api/mangadap.proc.ppxffit.html#mangadap.proc.ppxffit.PPXFFit.fit cont_wave, cont_flux, cont_mask, cont_par \ = ppxf.fit(sc_tpl['WAVE'].data.copy(), sc_tpl['FLUX'].data.copy(), wave, flux, ferr, z, dispersion, iteration_mode='no_global_wrej', reject_boxcar=100, ensemble=False, velscale_ratio=sc_velscale_ratio, mask=sc_pixel_mask, matched_resolution=False, tpl_sres=sc_tpl_sres, obj_sres=sres, degree=8, moments=2, plot=fit_plots) # The returned objects from the fit are the wavelength, model, and # mask vectors and the record array with the best-fitting model # parameters. The datamodel of the best-fitting model parameters is # set by: # https://sdss-mangadap.readthedocs.io/en/latest/api/mangadap.proc.spectralfitting.html#mangadap.proc.spectralfitting.StellarKinematicsFit._per_stellar_kinematics_dtype # Remask the continuum fit sc_continuum = StellarContinuumModel.reset_continuum_mask_window( numpy.ma.MaskedArray(cont_flux, mask=cont_mask>0)) # Show the fit and residual if usr_plots: pyplot.plot(wave, flux[0,:], label='Data') pyplot.plot(wave, sc_continuum[0,:], label='Model') pyplot.plot(wave, flux[0,:] - sc_continuum[0,:], label='Resid') pyplot.legend() pyplot.xlabel('Wavelength') pyplot.ylabel('Flux') pyplot.show() #------------------------------------------------------------------- #------------------------------------------------------------------- # Get the emission-line moments using the fitted stellar continuum # Read the database that define the emission lines and passbands momdb = EmissionMomentsDB.from_key(elmom_key) # Measure the moments elmom = EmissionLineMoments.measure_moments(momdb, wave, flux, continuum=sc_continuum, redshift=z) #------------------------------------------------------------------- #------------------------------------------------------------------- # Fit the emission-line model # Set the emission-line continuum templates if different from those # used for the stellar continuum if sc_tpl_key == el_tpl_key: # If the keywords are the same, just copy over the previous # library ... el_tpl = sc_tpl el_tpl_sres = sc_tpl_sres # ... and the best fitting stellar kinematics stellar_kinematics = cont_par['KIN'] else: # If the template sets are different, we need to match the # spectral resolution to the galaxy data ... _sres = SpectralResolution(wave, sres[0,:], log10=True) el_tpl = TemplateLibrary(el_tpl_key, sres=_sres, velscale_ratio=el_velscale_ratio, spectral_step=spectral_step, log=True, hardcopy=False) el_tpl_sres = numpy.mean(el_tpl['SPECRES'].data, axis=0).ravel() # ... and use the corrected velocity dispersions. stellar_kinematics = cont_par['KIN'] stellar_kinematics[:,1] = numpy.ma.sqrt(numpy.square(cont_par['KIN'][:,1]) - numpy.square(cont_par['SIGMACORR_EMP'])) # Read the emission line fitting database emldb = EmissionLineDB.from_key(elfit_key) # Instantiate the fitting class emlfit = Sasuke(EmissionLineModelBitMask()) # Perform the fit efit_t = time.perf_counter() eml_wave, model_flux, eml_flux, eml_mask, eml_fit_par, eml_eml_par \ = emlfit.fit(emldb, wave, flux, obj_ferr=ferr, obj_mask=el_pixel_mask, obj_sres=sres, guess_redshift=z, guess_dispersion=dispersion, reject_boxcar=101, stpl_wave=el_tpl['WAVE'].data, stpl_flux=el_tpl['FLUX'].data, stpl_sres=el_tpl_sres, stellar_kinematics=stellar_kinematics, etpl_sinst_mode='offset', etpl_sinst_min=10., velscale_ratio=el_velscale_ratio, matched_resolution=False, mdegree=8, plot=fit_plots) print('TIME: ', time.perf_counter() - efit_t) # Line-fit metrics eml_eml_par = EmissionLineFit.line_metrics(emldb, wave, flux, ferr, model_flux, eml_eml_par, model_mask=eml_mask, bitmask=emlfit.bitmask) # Get the stellar continuum that was fit for the emission lines elcmask = eml_mask.ravel() > 0 goodpix = numpy.arange(elcmask.size)[numpy.invert(elcmask)] start, end = goodpix[0], goodpix[-1]+1 elcmask[start:end] = False el_continuum = numpy.ma.MaskedArray(model_flux - eml_flux, mask=elcmask.reshape(model_flux.shape)) # Plot the result if usr_plots: pyplot.plot(wave, flux[0,:], label='Data') pyplot.plot(wave, model_flux[0,:], label='Model') pyplot.plot(wave, el_continuum[0,:], label='EL Cont.') pyplot.plot(wave, sc_continuum[0,:], label='SC Cont.') pyplot.legend() pyplot.xlabel('Wavelength') pyplot.ylabel('Flux') pyplot.show() # Remeasure the emission-line moments with the new continuum new_elmom = EmissionLineMoments.measure_moments(momdb, wave, flux, continuum=el_continuum, redshift=z) # Compare the summed flux and Gaussian-fitted flux for all the # fitted lines if usr_plots: pyplot.scatter(emldb['restwave'], (new_elmom['FLUX']-eml_eml_par['FLUX']).ravel(), c=eml_eml_par['FLUX'].ravel(), cmap='viridis', marker='.', s=60, lw=0, zorder=4) pyplot.grid() pyplot.xlabel('Wavelength') pyplot.ylabel('Summed-Gaussian Difference') pyplot.show() #------------------------------------------------------------------- #------------------------------------------------------------------- # Measure the spectral indices if absindx_key is None or bhdindx_key is None: # Neither are defined, so we're done print('Elapsed time: {0} seconds'.format(time.perf_counter() - t)) return # Setup the databases that define the indices to measure absdb = None if absindx_key is None else AbsorptionIndexDB.from_key(absindx_key) bhddb = None if bhdindx_key is None else BandheadIndexDB.from_key(bhdindx_key) # Remove the modeled emission lines from the spectra flux_noeml = flux - eml_flux redshift = stellar_kinematics[:,0] / astropy.constants.c.to('km/s').value sp_indices = SpectralIndices.measure_indices(absdb, bhddb, wave, flux_noeml, ivar=ivar, redshift=redshift) # Calculate the velocity dispersion corrections # - Construct versions of the best-fitting model spectra with and without # the included dispersion continuum = Sasuke.construct_continuum_models(emldb, el_tpl['WAVE'].data, el_tpl['FLUX'].data, wave, flux.shape, eml_fit_par) continuum_dcnvlv = Sasuke.construct_continuum_models(emldb, el_tpl['WAVE'].data, el_tpl['FLUX'].data, wave, flux.shape, eml_fit_par, redshift_only=True) # - Get the dispersion corrections and fill the relevant columns of the # index table sp_indices['BCONT_MOD'], sp_indices['BCONT_CORR'], sp_indices['RCONT_MOD'], \ sp_indices['RCONT_CORR'], sp_indices['MCONT_MOD'], sp_indices['MCONT_CORR'], \ sp_indices['AWGT_MOD'], sp_indices['AWGT_CORR'], \ sp_indices['INDX_MOD'], sp_indices['INDX_CORR'], \ sp_indices['INDX_BF_MOD'], sp_indices['INDX_BF_CORR'], \ good_les, good_ang, good_mag, is_abs \ = SpectralIndices.calculate_dispersion_corrections(absdb, bhddb, wave, flux, continuum, continuum_dcnvlv, redshift=redshift, redshift_dcnvlv=redshift) # Apply the index corrections. This is only done here for the # Worthey/Trager definition of the indices, as an example corrected_indices = numpy.zeros(sp_indices['INDX'].shape, dtype=float) corrected_indices_err = numpy.zeros(sp_indices['INDX'].shape, dtype=float) # Unitless indices corrected_indices[good_les], corrected_indices_err[good_les] \ = SpectralIndices.apply_dispersion_corrections(sp_indices['INDX'][good_les], sp_indices['INDX_CORR'][good_les], err=sp_indices['INDX_ERR'][good_les]) # Indices in angstroms corrected_indices[good_ang], corrected_indices_err[good_ang] \ = SpectralIndices.apply_dispersion_corrections(sp_indices['INDX'][good_ang], sp_indices['INDX_CORR'][good_ang], err=sp_indices['INDX_ERR'][good_ang], unit='ang') # Indices in magnitudes corrected_indices[good_mag], corrected_indices_err[good_mag] \ = SpectralIndices.apply_dispersion_corrections(sp_indices['INDX'][good_mag], sp_indices['INDX_CORR'][good_mag], err=sp_indices['INDX_ERR'][good_mag], unit='mag') # Print the results for a few indices index_names = numpy.append(absdb['name'], bhddb['name']) print('-'*73) print(f'{"NAME":<8} {"Raw Index":>12} {"err":>12} {"Index Corr":>12} {"Index":>12} {"err":>12}') print(f'{"-"*8:<8} {"-"*12:<12} {"-"*12:<12} {"-"*12:<12} {"-"*12:<12} {"-"*12:<12}') for name in ['Hb', 'HDeltaA', 'Mgb', 'Dn4000']: i = numpy.where(index_names == name)[0][0] print(f'{name:<8} {sp_indices["INDX"][0,i]:12.4f} {sp_indices["INDX_ERR"][0,i]:12.4f} ' f'{sp_indices["INDX_CORR"][0,i]:12.4f} {corrected_indices[0,i]:12.4f} ' f'{corrected_indices_err[0,i]:12.4f}') print('-'*73) embed() print('Elapsed time: {0} seconds'.format(time.perf_counter() - t))