def test_sasuke(): # Read the data specfile = data_test_file('MaNGA_test_spectra.fits.gz') hdu = fits.open(specfile) drpbm = DRPFitsBitMask() flux = numpy.ma.MaskedArray(hdu['FLUX'].data, mask=drpbm.flagged( hdu['MASK'].data, MaNGADataCube.do_not_fit_flags())) ferr = numpy.ma.power(hdu['IVAR'].data, -0.5) flux[ferr.mask] = numpy.ma.masked ferr[flux.mask] = numpy.ma.masked nspec = flux.shape[0] # Instantiate the template libary velscale_ratio = 4 tpl = TemplateLibrary('MILESHC', match_resolution=False, velscale_ratio=velscale_ratio, spectral_step=1e-4, log=True, hardcopy=False) tpl_sres = numpy.mean(tpl['SPECRES'].data, axis=0) # Get the pixel mask pixelmask = SpectralPixelMask(artdb=ArtifactDB.from_key('BADSKY'), emldb=EmissionLineDB.from_key('ELPSCMSK')) # Instantiate the fitting class ppxf = PPXFFit(StellarContinuumModelBitMask()) # Perform the fit sc_wave, sc_flux, sc_mask, sc_par \ = ppxf.fit(tpl['WAVE'].data.copy(), tpl['FLUX'].data.copy(), hdu['WAVE'].data, flux, ferr, hdu['Z'].data, numpy.full(nspec, 100.), iteration_mode='no_global_wrej', reject_boxcar=100, ensemble=False, velscale_ratio=velscale_ratio, mask=pixelmask, matched_resolution=False, tpl_sres=tpl_sres, obj_sres=hdu['SRES'].data, degree=8, moments=2) # Mask the 5577 sky line pixelmask = SpectralPixelMask(artdb=ArtifactDB.from_key('BADSKY')) # Read the emission line fitting database emldb = EmissionLineDB.from_key('ELPMILES') assert emldb['name'][ 18] == 'Ha', 'Emission-line database names or ordering changed' # Instantiate the fitting class emlfit = Sasuke(EmissionLineModelBitMask()) # Perform the fit el_wave, model, el_flux, el_mask, el_fit, el_par \ = emlfit.fit(emldb, hdu['WAVE'].data, flux, obj_ferr=ferr, obj_mask=pixelmask, obj_sres=hdu['SRES'].data, guess_redshift=hdu['Z'].data, guess_dispersion=numpy.full(nspec, 100.), reject_boxcar=101, stpl_wave=tpl['WAVE'].data, stpl_flux=tpl['FLUX'].data, stpl_sres=tpl_sres, stellar_kinematics=sc_par['KIN'], etpl_sinst_mode='offset', etpl_sinst_min=10., velscale_ratio=velscale_ratio, matched_resolution=False) # Rejected pixels assert numpy.sum(emlfit.bitmask.flagged(el_mask, flag='PPXF_REJECT')) == 266, \ 'Different number of rejected pixels' # Unable to fit assert numpy.array_equal(emlfit.bitmask.flagged_bits(el_fit['MASK'][5]), ['NO_FIT']), \ 'Expected NO_FIT in 6th spectrum' # No *attempted* fits should fail assert numpy.sum(emlfit.bitmask.flagged(el_fit['MASK'], flag='FIT_FAILED')) == 0, \ 'Fits should not fail' # Number of used templates assert numpy.array_equal(numpy.sum(numpy.absolute(el_fit['TPLWGT']) > 1e-10, axis=1), [25, 22, 34, 32, 27, 0, 16, 22]), \ 'Different number of templates with non-zero weights' # No additive coefficients assert numpy.all(el_fit['ADDCOEF'] == 0), \ 'No additive coefficients should exist' # No multiplicative coefficients assert numpy.all(el_fit['MULTCOEF'] == 0), \ 'No multiplicative coefficients should exist' # Fit statistics assert numpy.all( numpy.absolute( el_fit['RCHI2'] - numpy.array([2.34, 1.22, 1.58, 1.88, 3.20, 0., 1.05, 0.88])) < 0.02 ), 'Reduced chi-square are too different' assert numpy.all( numpy.absolute(el_fit['RMS'] - numpy.array( [0.036, 0.019, 0.036, 0.024, 0.051, 0.000, 0.012, 0.012])) < 0.001 ), 'RMS too different' assert numpy.all(numpy.absolute(el_fit['FRMS'] - numpy.array([0.021, 0.025, 0.025, 0.033, 0.018, 0.000, 1.052, 0.101])) < 0.001), \ 'Fractional RMS too different' assert numpy.all(numpy.absolute(el_fit['RMSGRW'][:,2] - numpy.array([0.070, 0.038, 0.071, 0.047, 0.101, 0.000, 0.026, 0.024])) < 0.001), \ 'Median absolute residual too different' # All lines should have the same velocity assert numpy.all(numpy.all(el_par['KIN'][:,:,0] == el_par['KIN'][:,None,0,0], axis=1)), \ 'All velocities should be the same' # Test velocity values # TODO: Need some better examples! assert numpy.all(numpy.absolute(el_par['KIN'][:,0,0] - numpy.array([14704.9, 14869.3, 14767.1, 8161.9, 9258.7, 0.0, 5130.9, 5430.3])) < 0.1), \ 'Velocities are too different' # H-alpha dispersions assert numpy.all(numpy.absolute(el_par['KIN'][:,18,1] - numpy.array([1000.5, 1000.5, 224.7, 124.9, 171.2, 0.0, 81.2, 50.0])) < 1e-1), \ 'H-alpha dispersions are too different'
def test_moments_with_continuum(): # Read the data specfile = data_test_file('MaNGA_test_spectra.fits.gz') hdu = fits.open(specfile) drpbm = DRPFitsBitMask() flux = numpy.ma.MaskedArray(hdu['FLUX'].data, mask=drpbm.flagged( hdu['MASK'].data, MaNGADataCube.do_not_fit_flags())) ferr = numpy.ma.power(hdu['IVAR'].data, -0.5) flux[ferr.mask] = numpy.ma.masked ferr[flux.mask] = numpy.ma.masked nspec = flux.shape[0] # Instantiate the template libary velscale_ratio = 4 tpl = TemplateLibrary('MILESHC', match_resolution=False, velscale_ratio=velscale_ratio, spectral_step=1e-4, log=True, hardcopy=False) tpl_sres = numpy.mean(tpl['SPECRES'].data, axis=0) # Get the pixel mask pixelmask = SpectralPixelMask(artdb=ArtifactDB.from_key('BADSKY'), emldb=EmissionLineDB.from_key('ELPSCMSK')) # Instantiate the fitting class ppxf = PPXFFit(StellarContinuumModelBitMask()) # Perform the fit fit_wave, fit_flux, fit_mask, fit_par \ = ppxf.fit(tpl['WAVE'].data.copy(), tpl['FLUX'].data.copy(), hdu['WAVE'].data, flux, ferr, hdu['Z'].data, numpy.full(nspec, 100.), iteration_mode='no_global_wrej', reject_boxcar=100, ensemble=False, velscale_ratio=velscale_ratio, mask=pixelmask, matched_resolution=False, tpl_sres=tpl_sres, obj_sres=hdu['SRES'].data, degree=8, moments=2) # Remask the continuum fit sc_continuum = StellarContinuumModel.reset_continuum_mask_window( numpy.ma.MaskedArray(fit_flux, mask=fit_mask > 0)) # Read the database that define the emission lines and passbands momdb = EmissionMomentsDB.from_key('ELBMILES') # Measure the moments elmombm = EmissionLineMomentsBitMask() elmom = EmissionLineMoments.measure_moments(momdb, hdu['WAVE'].data, flux, continuum=sc_continuum, redshift=hdu['Z'].data, bitmask=elmombm) # Measure the EW based on the moments include_band = numpy.array([numpy.invert(momdb.dummy)]*nspec) \ & numpy.invert(elmombm.flagged(elmom['MASK'], flag=['BLUE_EMPTY', 'RED_EMPTY'])) line_center = (1.0 + hdu['Z'].data)[:, None] * momdb['restwave'][None, :] elmom['BMED'], elmom['RMED'], pos, elmom['EWCONT'], elmom['EW'], elmom['EWERR'] \ = emission_line_equivalent_width(hdu['WAVE'].data, flux, momdb['blueside'], momdb['redside'], line_center, elmom['FLUX'], redshift=hdu['Z'].data, line_flux_err=elmom['FLUXERR'], include_band=include_band) # Check the flags reference = { 'BLUE_INCOMP': 21, 'MAIN_JUMP': 0, 'UNDEFINED_MOM2': 42, 'JUMP_BTWN_SIDEBANDS': 0, 'RED_JUMP': 0, 'DIVBYZERO': 0, 'NO_ABSORPTION_CORRECTION': 0, 'RED_EMPTY': 21, 'UNDEFINED_BANDS': 8, 'DIDNOTUSE': 0, 'UNDEFINED_MOM1': 0, 'FORESTAR': 0, 'NON_POSITIVE_CONTINUUM': 0, 'LOW_SNR': 0, 'MAIN_EMPTY': 21, 'BLUE_JUMP': 0, 'RED_INCOMP': 21, 'MAIN_INCOMP': 21, 'BLUE_EMPTY': 21 } assert numpy.all([ reference[k] == numpy.sum(elmombm.flagged(elmom['MASK'], flag=k)) for k in elmombm.keys() ]), 'Number of flagged measurements changed' # Check that the values are finite assert numpy.all([ numpy.all(numpy.isfinite(elmom[n])) for n in elmom.dtype.names]), \ 'Found non-finite values in output' # Check the band definitions assert numpy.all(numpy.equal(elmom['REDSHIFT'], hdu['Z'].data)), 'Redshift changed' assert numpy.all(numpy.isclose(numpy.mean(momdb['blueside'], axis=1)[None,:], elmom['BCEN']/(1+hdu['Z'].data[:,None])) | elmombm.flagged(elmom['MASK'], flag='UNDEFINED_BANDS')), \ 'Blue passband center incorrect' assert numpy.all(numpy.isclose(numpy.mean(momdb['redside'], axis=1)[None,:], elmom['RCEN']/(1+hdu['Z'].data[:,None])) | elmombm.flagged(elmom['MASK'], flag='UNDEFINED_BANDS')), \ 'Red passband center incorrect' # Check the values assert numpy.all( numpy.absolute(elmom['FLUX'][0] - numpy.array([ 0.63, 0.00, 0.22, -1.32, -0.88, -0.68, -0.44, -0.13, -1.14, -0.07, -0.11, 0.01, 0.38, 0.73, 0.71, 0.44, 0.08, 0.74, 1.30, 2.34, 0.55, 0.44 ])) < 0.01), 'Fluxes too different' assert numpy.all(numpy.absolute(elmom['MOM1'][0] - numpy.array([ 14682.6, 0.0, 14843.2, 14865.8, 14890.4, 14404.7, 14208.6, 12376.0, 14662.5, 14148.5, 15804.1, 17948.4, 14874.5, 14774.9, 14840.5, 14746.0, 15093.1, 14857.8, 14839.0, 14840.2, 14876.0, 14859.5])) < 0.1), \ '1st moments too different' assert numpy.all(numpy.absolute(elmom['MOM2'][0] - numpy.array([322.2, 0.0, 591.4, 436.4, 474.6, 0.0, 0.0, 0.0, 364.6, 0.0, 0.0, 0.0, 289.1, 226.9, 282.6, 283.8, 227.0, 207.7, 207.7, 253.6, 197.0, 212.4])) < 0.1), \ '2nd moments too different' assert numpy.all(numpy.absolute(elmom['EW'][0] - numpy.array([ 0.63, 0.00, 0.20, -1.28, -0.76, -0.54, -0.30, -0.09, -0.61, -0.03, -0.04, 0.00, 0.13, 0.25, 0.24, 0.13, 0.02, 0.22, 0.38, 0.69, 0.17, 0.13])) < 0.01), \ 'EW too different'
def fit(self, binned_spectra, par=None, loggers=None, quiet=False): if par is not None: self.par = par # Check the parameter keys required_keys = [ 'guess_redshift', 'stellar_continuum', 'emission_lines', 'degree', 'mdegree' ] if numpy.any([ reqk not in self.par.keys() for reqk in required_keys ]): raise ValueError('Parameter dictionary does not have all the required keys.') # Wavelengths are in vacuum wave0 = binned_spectra['WAVE'].data.copy() # Velocity step per pixel velscale = spectrum_velocity_scale(wave0) # Get the best-fitting stellar kinematics for the binned spectra # And correct sigmas with instrumental resolutions (if convolved templates are to be used) if self.par['stellar_continuum'] is None \ or not isinstance(self.par['stellar_continuum'], StellarContinuumModel): raise ValueError('Must provide StellarContinuumModel object as the ' '\'stellar_continuum\' item in the parameter dictionary') stars_vel, stars_sig = self.par['stellar_continuum'].matched_guess_kinematics( binned_spectra, cz=True, corrected=True, nearest=True) # Convert the input stellar velocity from redshift (c*z) to ppxf velocity (c*log(1+z)) stars_vel = PPXFFit.revert_velocity(stars_vel,0)[0] # Get the stellar templates and the template resolution; # shape is (Nstartpl, Ntplwave) stars_templates = self.par['stellar_continuum'].get_template_library( velocity_offset=numpy.median(stars_vel), match_to_drp_resolution=True) stars_templates_wave = stars_templates['WAVE'].data.copy() template_sres = stars_templates['SPECRES'].data[0,:] stars_templates = stars_templates['FLUX'].data.copy() velscale_ratio = self.par['stellar_continuum'].method['fitpar']['velscale_ratio'] # Set mask for the galaxy spectra according to the templates wave range mask = PPXFFit.fitting_mask(tpl_wave=stars_templates_wave, obj_wave=wave0, velscale=velscale, velscale_ratio=velscale_ratio, velocity_offset=numpy.median(stars_vel))[0] wave = wave0[mask] # Calculate the velocity offset between the masked spectra and the tempaltes dv = -PPXFFit.ppxf_tpl_obj_voff(stars_templates_wave, wave, velscale, velscale_ratio=velscale_ratio) # UNBINNED DATA: # Flux and noise masked arrays; shape is (Nspaxel,Nwave) where # Nspaxel is Nx*Ny # Bin ID from VOR10 reference file used to mask buffer spaxels #binid0 = binned_spectra['BINID'].data # Create mask for parsing the input data #binid = binid0.reshape(-1) #mask_spaxel = ~(binid==-1) # pPXF would run into problems if dircetly masked arrays were used # So both fluxes and their masks are to be provided as input # flux00 = binned_spectra.drpf.copy_to_masked_array(flag=['DONOTUSE', 'FORESTAR']) # mask_drp0 = ~flux00.mask # flux = binned_spectra.drpf.copy_to_array(ext='FLUX') # ivar = binned_spectra.drpf.copy_to_array(ext='IVAR') # flux0, ivar0 = binned_spectra.galext.apply(flux, ivar=ivar, deredden=True) # noise0 = numpy.power(ivar0.data, -0.5) # mask_drp = mask_drp0.reshape(-1, mask_drp0.shape[-1])[:,mask] # flux = flux0.data[:,mask] # noise = noise0[:,mask] flux0 = binned_spectra.drpf.copy_to_masked_array(flag=['DONOTUSE', 'FORESTAR']) ivar0 = binned_spectra.drpf.copy_to_masked_array(ext='IVAR', flag=['DONOTUSE', 'FORESTAR']) flux0, ivar0 = binned_spectra.galext.apply(flux0, ivar=ivar0, deredden=True) noise = numpy.ma.power(ivar0, -0.5) noise[numpy.invert(noise > 0)] = numpy.ma.masked mask_drp = numpy.invert(flux0.mask | noise.mask)[:,mask] flux = flux0.data[:,mask] noise = noise.filled(0.0)[:,mask] # stack_sres sets whether or not the spectral resolution is # determined on a per-spaxel basis or with a single vector sres = binned_spectra.drpf.spectral_resolution(toarray=True, fill=True) \ if binned_spectra.method['stackpar']['stack_sres'] else \ binned_spectra.drpf.spectral_resolution(ext='SPECRES', toarray=True, fill=True) sres = sres[:,mask] # Spaxel coordinates; shape is (Nspaxel,) x = binned_spectra.rdxqa['SPECTRUM'].data['SKY_COO'][:,0] y = binned_spectra.rdxqa['SPECTRUM'].data['SKY_COO'][:,1] # BINNED DATA: # Binned flux and binned noise masked arrays; shape is (Nbin,Nwave) # flux_binned00 = binned_spectra.copy_to_masked_array(flag=binned_spectra.do_not_fit_flags()) # mask_binned0 = ~flux_binned00.mask # flux_binned0 = binned_spectra.copy_to_array(ext='FLUX') # noise_binned0 = numpy.power(binned_spectra.copy_to_array(ext='IVAR'), -0.5) # mask_binned = mask_binned0[:,mask] # flux_binned = flux_binned0[:,mask] # noise_binned = noise_binned0[:,mask] # sres_binned0 = binned_spectra.copy_to_array(ext='SPECRES') # sres_binned = sres_binned0[:,mask] flux_binned = binned_spectra.copy_to_masked_array(flag=binned_spectra.do_not_fit_flags()) noise_binned = numpy.ma.power(binned_spectra.copy_to_masked_array(ext='IVAR', flag=binned_spectra.do_not_fit_flags()), -0.5) noise_binned[numpy.invert(noise_binned > 0)] = numpy.ma.masked mask_binned = numpy.invert(flux_binned.mask | noise_binned.mask)[:,mask] flux_binned = flux_binned.data[:,mask] noise_binned = noise_binned.filled(0.0)[:,mask] sres_binned = binned_spectra.copy_to_array(ext='SPECRES')[:,mask] # Bin coordinates; shape is (Nbin,) x_binned = binned_spectra['BINS'].data['SKY_COO'][:,0] y_binned = binned_spectra['BINS'].data['SKY_COO'][:,1] # Set initial guesses for the velocity and velocity dispersion if self.par['guess_redshift'] is not None: # Use guess_redshift if provided guess_vel0 = self.par['guess_redshift'] * astropy.constants.c.to('km/s').value guess_vel = PPXFFit.revert_velocity(guess_vel0,0)[0] # And set default velocity dispersion to 100 km/s guess_sig = numpy.full(guess_vel.size, 100, dtype=float) elif self.par['stellar_continuum'] is not None: # Otherwise use the stellar-continuum result guess_vel, guess_sig = stars_vel.copy(), stars_sig.copy() else: raise ValueError('Cannot set guess kinematics; must provide either \'guess_redshift\' ' 'or \'stellar_continuum\' in input parameter dictionary.') # Construct gas templates; shape is (Ngastpl, Ntplwave). # Template resolution matched between the stellar and gas # templates? # Decide whether to use convolved gas templates # Set the wavelength of lines to be in vacuum FWHM = wave/numpy.max(sres, axis=0) FWHM_binned = wave/sres_binned[0,:] def fwhm_drp(wave_len0): wave_len = wave_len0*(1 + numpy.median(self.par['guess_redshift'])) index = numpy.argmin(abs(wave-wave_len[:,None]),axis=1) \ if numpy.asarray(wave_len) is wave_len \ else numpy.argmin(abs(wave-wave_len)) return FWHM[index] def fwhm_binned(wave_len0): wave_len = wave_len0*(1 + numpy.median(self.par['guess_redshift'])) index = numpy.argmin(abs(wave-wave_len[:,None]),axis=1) \ if numpy.asarray(wave_len) is wave_len \ else numpy.argmin(abs(wave-wave_len)) return FWHM_binned[index] lam_range_gal = numpy.array([numpy.min(wave), numpy.max(wave)]) \ / (1 + numpy.median(self.par['guess_redshift'])) gas_templates, gas_names, gas_wave = \ ppxf_util.emission_lines(numpy.log(stars_templates_wave), lam_range_gal, fwhm_drp) gas_templates_binned, gas_names, gas_wave = \ ppxf_util.emission_lines(numpy.log(stars_templates_wave), lam_range_gal, fwhm_binned) # Default polynomial orders degree = -1 if self.par['degree'] is None else self.par['degree'] mdegree = 10 if self.par['mdegree'] is None else self.par['mdegree'] # -------------------------------------------------------------- # CALL TO EMLINE_FITTER_WITH_PPXF: # Input is: # - wave: wavelength vector; shape is (Nwave,) # - flux: observed, unbinned flux; masked array with shape # (Nspaxel,Nwave) # - noise: error in observed, unbinned flux; masked array with # shape (Nspaxel,Nwave) # - sres: spectral resolution (R=lambda/delta lambda) as a # function of wavelength for each unbinned spectrum; shape # is (Nspaxel,Nwave) # - flux_binned: binned flux; masked array with shape # (Nbin,Nwave) # - noise_binned: noise in binned flux; masked array with # shape (Nbin,Nwave) # - sres_binned: spectral resolution (R=lambda/delta lambda) # as a function of wavelength for each binned spectrum; # shape is (Nbin,Nwave) # - velscale: Velocity step per pixel # - velscale_ratio: Ratio of velocity step per pixel in the # observed data versus in the template data # - dv: Velocity offset between the galaxy and template data # due to the difference in the initial wavelength of the # spectra # - stars_vel: Velocity of the stellar component; shape is # (Nbins,) # - stars_sig: Velocity dispersion of the stellar component; # shape is (Nbins,) # - stars_templates: Stellar templates; shape is (Nstartpl, # Ntplwave) # - guess_vel: Initial guess velocity for the gas components; # shape is (Nbins,) # - guess_sig: Initial guess velocity dispersion for the gas # components; shape is (Nbins,) # - gas_templates: Gas template flux; shape is (Ngastpl, # Ntplwave) # - gas_names: Name of the gas templats; shape is (Ngastpl,) # - template_sres: spectral resolution (R=lambda/delta # lambda) as a function of wavelength for all the templates # templates; shape is (Ntplwave,) # - degree: Additive polynomial order # - mdegree: Multiplicative polynomial order # - x: On-sky spaxel x coordinates; shape is (Nspaxel,) # - y: On-sky spaxel y coordinates; shape is (Nspaxel,) # - x_binned: On-sky bin x coordinate; shape is (Nbin,) # - y_binned: On-sky bin y coordinate; shape is (Nbin,) model_flux0, model_eml_flux0, model_mask0, model_binid, eml_flux, eml_fluxerr, \ eml_kin, eml_kinerr, eml_sigmacorr \ = emline_fitter_with_ppxf(wave, flux, noise, sres, flux_binned, noise_binned, velscale, velscale_ratio, dv, stars_vel, stars_sig, stars_templates, guess_vel, guess_sig, gas_templates, gas_templates_binned, gas_names, template_sres, degree, mdegree, x, y, x_binned, y_binned, mask_binned, mask_drp, numpy.median(self.par['guess_redshift'])) #, debug=True) # Output is: # - model_flux: stellar-continuum + emission-line model; shape # is (Nmod, Nwave); first axis is ordered by model ID number # - model_eml_flux: model emission-line flux only; shape is # (Nmod, Nwave); first axis is ordered by model ID number # - model_mask: boolean or bit mask for fitted models; shape # is (Nmod, Nwave); first axis is ordered by model ID number # - model_binid: ID numbers assigned to each spaxel with a # fitted model; any spaxel without a model should have # model_binid = -1; the number of >-1 IDs must be Nmod; # shape is (Nx,Ny) which is equivalent to: # flux[:,0].reshape((numpy.sqrt(Nspaxel).astype(int),)*2).shape # - eml_flux: Flux of each emission line; shape is (Nmod,Neml) # - eml_fluxerr: Error in emission-line fluxes; shape is # (Nmod, Neml) # - eml_kin: Kinematics (velocity and velocity dispersion) of # each emission line; shape is (Nmod,Neml,Nkin) # - eml_kinerr: Error in the kinematics of each emission line # - eml_sigmacorr: Quadrature corrections required to obtain # the astrophysical velocity dispersion; shape is # (Nmod,Neml); corrections are expected to be applied as # follows: # sigma = numpy.ma.sqrt( numpy.square(eml_kin[:,:,1]) # - numpy.square(eml_sigmacorr)) # -------------------------------------------------------------- # Convert the output velocity back from ppxf velocity (c*log(1+z)) to redshift (c*z) eml_kin[:,:,0] = PPXFFit.convert_velocity(eml_kin[:,:,0],0)[0] # Mask output data according to model_binid model_binid = numpy.asarray(model_binid, dtype=numpy.int16) mask_id = (model_binid > -1) model_flux0 = model_flux0[mask_id] model_eml_flux0 = model_eml_flux0[mask_id] model_mask0 = model_mask0[mask_id] eml_flux = eml_flux[mask_id] eml_fluxerr = eml_fluxerr[mask_id] eml_kin = eml_kin[mask_id] eml_kinerr = eml_kinerr[mask_id] eml_sigmacorr = eml_sigmacorr[mask_id] #cube_binid = numpy.full_like(mask_spaxel, -1, dtype=numpy.int16) #cube_binid[mask_spaxel] = model_binid Nspaxel = x.shape[0] model_binid = model_binid.reshape((numpy.sqrt(Nspaxel).astype(int),)*2) # The ordered indices in the flatted bin ID map with/for each model model_srt = numpy.argsort(model_binid.ravel())[model_binid.ravel() > -1] # Construct the output emission-line database. The data type # defined by EmissionLineFit._per_emission_line_dtype(); shape # is (Nmod,); parameters must be ordered by model ID number nmod = len(model_srt) neml = eml_flux.shape[1] nkin = eml_kin.shape[-1] model_eml_par = init_record_array(nmod, EmissionLineFit._per_emission_line_dtype(neml, nkin, numpy.int16)) model_eml_par['BINID'] = model_binid.ravel()[model_srt] model_eml_par['BINID_INDEX'] = numpy.arange(nmod) model_eml_par['MASK'][:,:] = 0 model_eml_par['FLUX'] = eml_flux model_eml_par['FLUXERR'] = eml_fluxerr model_eml_par['KIN'] = eml_kin model_eml_par['KINERR'] = eml_kinerr model_eml_par['SIGMACORR'] = eml_sigmacorr # Include the equivalent width measurements if self.par['emission_lines'] is not None: EmissionLineFit.measure_equivalent_width(wave, flux[model_srt,:], par['emission_lines'], model_eml_par) # Change back the wavelength range of models to match that of the galaxy's model_flux = numpy.zeros(flux0[mask_id].shape) model_eml_flux = numpy.zeros(flux0[mask_id].shape) model_mask = numpy.full_like(flux0[mask_id], 0, dtype=bool) model_flux[:,mask] = model_flux0 model_eml_flux[:,mask] = model_eml_flux0 model_mask[:,mask] = model_mask0 # Calculate the "emission-line baseline" as the difference # between the stellar continuum model determined for the # kinematics and the one determined by the optimized # stellar-continuum + emission-line fit: if self.par['stellar_continuum'] is not None: # Construct the full 3D cube for the stellar continuum # models sc_model_flux, sc_model_mask \ = DAPFitsUtil.reconstruct_cube(binned_spectra.drpf.shape, self.par['stellar_continuum']['BINID'].data.ravel(), [ self.par['stellar_continuum']['FLUX'].data, self.par['stellar_continuum']['MASK'].data ]) # Set any masked pixels to 0 sc_model_flux[sc_model_mask>0] = 0.0 # Construct the full 3D cube of the new stellar continuum # from the combined stellar-continuum + emission-line fit el_continuum = DAPFitsUtil.reconstruct_cube(binned_spectra.drpf.shape, model_binid.ravel(), model_flux - model_eml_flux) # Get the difference, restructure it to match the shape # of the emission-line models, and zero any masked pixels model_eml_base = (el_continuum - sc_model_flux).reshape(-1,wave0.size)[model_srt,:] if model_mask is not None: model_eml_base[model_mask==0] = 0.0 else: model_eml_base = numpy.zeros(model_flux.shape, dtype=float) # Returned arrays are: # - model_eml_flux: model emission-line flux only; shape is # (Nmod, Nwave); first axis is ordered by model ID number # - model_eml_base: difference between the combined fit and the # stars-only fit; shape is (Nmod, Nwave); first axis is # ordered by model ID number # - model_mask: boolean or bit mask for fitted models; shape # is (Nmod, Nwave); first axis is ordered by model ID number # - model_fit_par: This provides the results of each fit; # TODO: The is set to None. Provide metrics of the ppxf fit # to each spectrum? # - model_eml_par: output model parameters; data type must be # EmissionLineFit._per_emission_line_dtype(); shape is # (Nmod,); parameters must be ordered by model ID number # - model_binid: ID numbers assigned to each spaxel with a # fitted model; any spaxel with a model should have # model_binid = -1; the number of >-1 IDs must be Nmod; # shape is (Nx,Ny) return model_eml_flux, model_eml_base, model_mask, None, model_eml_par, model_binid
sc_pixel_mask = SpectralPixelMask( artdb=ArtifactDB.from_key('BADSKY'), emldb=EmissionLineDB.from_key('ELPSCMSK')) # Construct the template library sc_tpl = TemplateLibrary(sc_tpl_key, tpllib_list=tpllib_list, match_resolution=False, velscale_ratio=velscale_ratio, spectral_step=1e-4, log=True, hardcopy=False) sc_tpl_sres = numpy.mean(sc_tpl['SPECRES'].data, axis=0).ravel() # Instantiate the fitting class ppxf = PPXFFit(StellarContinuumModelBitMask()) # Perform the 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=velscale_ratio, mask=sc_pixel_mask, matched_resolution=False, tpl_sres=sc_tpl_sres, obj_sres=sres, degree=8, moments=2, plot=False) # How many templates did it find: print(sc_tpl.ntpl) # From what files: print(sc_tpl.file_list) # What were the weights assigned to each template print(cont_par['TPLWGT'][0, :])
def test_ppxffit(): # Read the data specfile = data_test_file('MaNGA_test_spectra.fits.gz') hdu = fits.open(specfile) drpbm = DRPFitsBitMask() flux = numpy.ma.MaskedArray(hdu['FLUX'].data, mask=drpbm.flagged( hdu['MASK'].data, MaNGADataCube.do_not_fit_flags())) ferr = numpy.ma.power(hdu['IVAR'].data, -0.5) flux[ferr.mask] = numpy.ma.masked ferr[flux.mask] = numpy.ma.masked nspec = flux.shape[0] # Instantiate the template libary velscale_ratio = 4 tpl = TemplateLibrary('MILESHC', match_resolution=False, velscale_ratio=velscale_ratio, spectral_step=1e-4, log=True, hardcopy=False) tpl_sres = numpy.mean(tpl['SPECRES'].data, axis=0) # Get the pixel mask pixelmask = SpectralPixelMask(artdb=ArtifactDB.from_key('BADSKY'), emldb=EmissionLineDB.from_key('ELPSCMSK')) # Instantiate the fitting class ppxf = PPXFFit(StellarContinuumModelBitMask()) # Perform the fit fit_wave, fit_flux, fit_mask, fit_par \ = ppxf.fit(tpl['WAVE'].data.copy(), tpl['FLUX'].data.copy(), hdu['WAVE'].data, flux, ferr, hdu['Z'].data, numpy.full(nspec, 100.), iteration_mode='no_global_wrej', reject_boxcar=100, ensemble=False, velscale_ratio=velscale_ratio, mask=pixelmask, matched_resolution=False, tpl_sres=tpl_sres, obj_sres=hdu['SRES'].data, degree=8, moments=2) # Test the results # Rejected pixels assert numpy.sum(ppxf.bitmask.flagged(fit_mask, flag='PPXF_REJECT')) == 119, \ 'Different number of rejected pixels' # Unable to fit assert numpy.array_equal(ppxf.bitmask.flagged_bits(fit_par['MASK'][5]), ['NO_FIT']), \ 'Expected NO_FIT in 6th spectrum' # Number of used templates assert numpy.array_equal(numpy.sum(numpy.absolute(fit_par['TPLWGT']) > 1e-10, axis=1), [12, 13, 17, 15, 15, 0, 8, 12]), \ 'Different number of templates with non-zero weights' # Number of additive coefficients assert fit_par['ADDCOEF'].shape[ 1] == 9, 'Incorrect number of additive coefficients' # No multiplicative coefficients assert numpy.all(fit_par['MULTCOEF'] == 0), \ 'No multiplicative coefficients should exist' # Kinematics and errors assert numpy.all(numpy.absolute(fit_par['KIN'] - numpy.array([[ 14880.7, 292.9], [ 15053.4, 123.2], [ 14787.5, 236.4], [ 8291.8, 169.7], [ 9261.4, 202.7], [ 0.0, 0.0], [ 5123.5, 63.8], [ 5455.6, 51.8]])) < 0.1), \ 'Kinematics are too different' assert numpy.all(numpy.absolute(fit_par['KINERR'] - numpy.array([[2.0,1.9], [1.5,1.7], [ 2.4, 2.4], [2.2,2.3], [1.1,1.1], [0.0,0.0], [26.1,30.8], [4.7,7.5]])) < 0.1), \ 'Kinematic errors are too different' # Velocity dispersion corrections assert numpy.all(numpy.absolute(fit_par['SIGMACORR_SRES'] - numpy.array([23.5, 10.1, 27.3, 38.7, 22.3, 0.0, 63.8, 23.8])) < 0.1), \ 'SRES corrections are too different' assert numpy.all(numpy.absolute(fit_par['SIGMACORR_EMP'] - numpy.array([22.6, 0.0, 26.0, 38.2, 18.0, 0.0, 70.1, 0.0])) < 0.1), \ 'EMP corrections are too different' # Figures of merit assert numpy.all(numpy.absolute(fit_par['RCHI2'] - numpy.array([ 1.94, 1.18, 1.40, 1.53, 2.50, 0.00, 1.06, 0.86])) < 0.01), \ 'Reduced chi-square too different' assert numpy.all( numpy.absolute(fit_par['RMS'] - numpy.array( [0.033, 0.019, 0.034, 0.023, 0.046, 0.000, 0.015, 0.015])) < 0.001 ), 'RMS too different' assert numpy.all( numpy.absolute(fit_par['FRMS'] - numpy.array( [0.018, 0.023, 0.023, 0.032, 0.018, 0.000, 33.577, 0.148])) < 0.001 ), 'Fractional RMS too different' assert numpy.all( numpy.absolute(fit_par['RMSGRW'][:, 2] - numpy.array( [0.067, 0.037, 0.068, 0.046, 0.093, 0.000, 0.029, 0.027])) < 0.001 ), 'Median absolute residual too different'
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() arg = parse_args() if not os.path.isfile(arg.inp): raise FileNotFoundError('No file: {0}'.format(arg.inp)) directory_path = os.getcwd( ) if arg.output_root is None else os.path.abspath(arg.output_root) if not os.path.isdir(directory_path): os.makedirs(directory_path) data_file = os.path.abspath(arg.inp) fit_file = os.path.join(directory_path, arg.out) flag_db = None if arg.spec_flags is None else os.path.abspath( arg.spec_flags) # Read the data spectral_step = 1e-4 wave, flux, ferr, sres, redshift, fit_spectrum = object_data( data_file, flag_db) nspec, npix = flux.shape dispersion = numpy.full(nspec, 100., dtype=numpy.float) # fit_spectrum[:] = False # fit_spectrum[0] = True # fit_spectrum[171] = True # fit_spectrum[791] = True # Mask spectra that should not be fit indx = numpy.any(numpy.logical_not(numpy.ma.getmaskarray(flux)), axis=1) & fit_spectrum flux[numpy.logical_not(indx), :] = numpy.ma.masked print('Read: {0}'.format(arg.inp)) print('Contains {0} spectra'.format(nspec)) print(' each with {0} pixels'.format(npix)) print('Fitting {0} spectra.'.format(numpy.sum(fit_spectrum))) #------------------------------------------------------------------- #------------------------------------------------------------------- # Fit the stellar continuum # Construct the template library sc_tpl = TemplateLibrary(arg.sc_tpl, match_resolution=False, velscale_ratio=arg.sc_vsr, spectral_step=spectral_step, log=True, hardcopy=False) # Set the spectral resolution sc_tpl_sres = numpy.mean(sc_tpl['SPECRES'].data, axis=0).ravel() # Set the pixel mask sc_pixel_mask = SpectralPixelMask(artdb=ArtifactDB.from_key('BADSKY'), emldb=EmissionLineDB.from_key('ELPMPL8')) # Instantiate the fitting class 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, redshift, dispersion, iteration_mode='no_global_wrej', reject_boxcar=100, ensemble=False, velscale_ratio=arg.sc_vsr, mask=sc_pixel_mask, matched_resolution=False, tpl_sres=sc_tpl_sres, obj_sres=sres, degree=arg.sc_deg, moments=2) #, plot=True) if arg.sc_only: write(fit_file, wave, cont_flux, cont_mask, cont_par) print('Elapsed time: {0} seconds'.format(time.perf_counter() - t)) return # if numpy.any(cont_par['KIN'][:,1] < 0): # embed() # exit() #------------------------------------------------------------------- #------------------------------------------------------------------- #------------------------------------------------------------------- # Measure the emission-line moments # # Remask the continuum fit # sc_continuum = StellarContinuumModel.reset_continuum_mask_window( # numpy.ma.MaskedArray(cont_flux, mask=cont_mask>0)) # # Read the database that define the emission lines and passbands # momdb = EmissionMomentsDB.from_key(arg.el_band) # # Measure the moments # elmom = EmissionLineMoments.measure_moments(momdb, wave, flux, continuum=sc_continuum, # redshift=redshift) #------------------------------------------------------------------- #------------------------------------------------------------------- # Fit the emission-line model # Set the emission-line continuum templates if different from those # used for the stellar continuum if arg.sc_tpl == arg.el_tpl: # If the keywords are the same, just copy over the previous # library and the best fitting stellar kinematics el_tpl = sc_tpl el_tpl_sres = sc_tpl_sres stellar_kinematics = cont_par['KIN'].copy() else: # If the template sets are different, we need to match the # spectral resolution to the galaxy data and use the corrected # velocity dispersions. _sres = SpectralResolution(wave, sres[0, :], log10=True) el_tpl = TemplateLibrary(arg.el_tpl, sres=_sres, velscale_ratio=arg.el_vsr, spectral_step=spectral_step, log=True, hardcopy=False) el_tpl_sres = numpy.mean(el_tpl['SPECRES'].data, axis=0).ravel() stellar_kinematics = cont_par['KIN'].copy() stellar_kinematics[:, 1] = numpy.ma.sqrt( numpy.square(cont_par['KIN'][:, 1]) - numpy.square(cont_par['SIGMACORR_SRES'])).filled(0.0) # if numpy.any(cont_par['KIN'][:,1] < 0): # embed() # exit() # # if numpy.any(stellar_kinematics[:,1] < 0): # embed() # exit() # Mask the 5577 sky line el_pixel_mask = SpectralPixelMask(artdb=ArtifactDB.from_key('BADSKY')) # Read the emission line fitting database emldb = EmissionLineDB.from_key(arg.el_list) # Instantiate the fitting class emlfit = Sasuke(EmissionLineModelBitMask()) # TODO: Improve the initial velocity guess using the first moment... # Perform the fit elfit_time = time.perf_counter() model_wave, model_flux, eml_flux, model_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=redshift, 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=arg.el_vsr, matched_resolution=False, mdegree=arg.el_deg, ensemble=False)#, plot=True) print('EML FIT TIME: ', time.perf_counter() - elfit_time) # Line-fit metrics (should this be done in the fit method?) eml_eml_par = EmissionLineFit.line_metrics(emldb, wave, flux, ferr, model_flux, eml_eml_par, model_mask=model_mask, bitmask=emlfit.bitmask) # Equivalent widths EmissionLineFit.measure_equivalent_width(wave, flux, emldb, eml_eml_par, bitmask=emlfit.bitmask, checkdb=False) # Measure the emission-line moments # - Model continuum continuum = StellarContinuumModel.reset_continuum_mask_window(model_flux - eml_flux) # - Updated redshifts fit_redshift = eml_eml_par['KIN'][:,numpy.where(emldb['name'] == 'Ha')[0][0],0] \ / astropy.constants.c.to('km/s').value # - Set the moment database momdb = EmissionMomentsDB.from_key(arg.el_band) # - Set the moment bitmask mombm = EmissionLineMomentsBitMask() # - Measure the moments elmom = EmissionLineMoments.measure_moments(momdb, wave, flux, ivar=numpy.ma.power(ferr, -2), continuum=continuum, redshift=fit_redshift, bitmask=mombm) # - Select the bands that are valid include_band = numpy.array([numpy.logical_not(momdb.dummy)]*nspec) \ & numpy.logical_not(mombm.flagged(elmom['MASK'], flag=['BLUE_EMPTY', 'RED_EMPTY'])) # - Set the line center at the center of the primary passband line_center = (1.0 + fit_redshift)[:, None] * momdb['restwave'][None, :] elmom['BMED'], elmom['RMED'], pos, elmom['EWCONT'], elmom['EW'], elmom['EWERR'] \ = emission_line_equivalent_width(wave, flux, momdb['blueside'], momdb['redside'], line_center, elmom['FLUX'], redshift=fit_redshift, line_flux_err=elmom['FLUXERR'], include_band=include_band) # - Flag non-positive measurements indx = include_band & numpy.logical_not(pos) elmom['MASK'][indx] = mombm.turn_on(elmom['MASK'][indx], 'NON_POSITIVE_CONTINUUM') # - Set the binids elmom['BINID'] = numpy.arange(nspec) elmom['BINID_INDEX'] = numpy.arange(nspec) write(fit_file, wave, cont_flux, cont_mask, cont_par, model_flux=model_flux, model_mask=model_mask, eml_flux=eml_flux, eml_fit_par=eml_fit_par, eml_eml_par=eml_eml_par, elmom=elmom) print('Elapsed time: {0} seconds'.format(time.perf_counter() - t))
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))
#bins = numpy.linspace(0, 1, 11) #mid_bins = bins[:-1]+(numpy.diff(bins)/2.) #You need the template spectra. For now just use the MILESHC library: 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) abs_db = AbsorptionIndexDB(u"EXTINDX") band_db = BandheadIndexDB(u"BHBASIC") global indx_names indx_names = np.hstack([abs_db.data["name"], band_db.data["name"]]) specm = EmissionLineModelBitMask() elric = Elric(specm) global emlines emlines = EmissionLineDB(u"ELPFULL") c = con.c.to(un.km / un.s).value # if os.path.isfile("emission_line_params_log_scale_tq_"+str(len(tqs))+"_tau_"+str(len(taus))+"_to_"+str(len(time_steps))+"_Z_"+str(len(zmets))+"_"+".npy"): # eml = list(np.load("emission_line_params_log_scale_tq_"+str(len(tqs))+"_tau_"+str(len(taus))+"_to_"+str(len(time_steps))+"_Z_"+str(len(zmets))+"_"+".npy"))