Esempio n. 1
0
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'
Esempio n. 2
0
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'
Esempio n. 3
0
    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
Esempio n. 4
0
    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, :])
Esempio n. 5
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'
Esempio n. 6
0
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
Esempio n. 7
0
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))
Esempio n. 8
0
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))
Esempio n. 9
0
    #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"))