コード例 #1
0
ファイル: test_par.py プロジェクト: HSIYJND/mangadap
def test_bhddb():

    bhddb = BandheadIndexDB.from_key('BHBASIC')
    assert bhddb.size == 3

    _bhddb = BandheadIndexDB(bhddb.file)
    assert _bhddb.size == 3
コード例 #2
0
def test_avail():
    database_list = available_spectral_index_databases()
    assert len(database_list) > 0, 'No spectral index databases available'

    # For the available databases, make sure that the ancillary
    # databases can be loaded.
    for database in database_list:
        if database['artifacts'] is not None:
            artdb = ArtifactDB.from_key(database['artifacts'])
        if database['absindex'] is not None:
            absdb = AbsorptionIndexDB.from_key(database['absindex'])
        if database['bandhead'] is not None:
            bhddb = BandheadIndexDB.from_key(database['bandhead'])
コード例 #3
0
ファイル: test_specindices.py プロジェクト: HSIYJND/mangadap
def test_model_indices():

    # Setup
    bm = SpectralIndicesBitMask()
    absdb = AbsorptionIndexDB.from_key('EXTINDX')
    bhddb = BandheadIndexDB.from_key('BHBASIC')

    # Grab the model spectra
    tpl = TemplateLibrary('M11MILES', spectral_step=1e-4, log=True, hardcopy=False)
    flux = numpy.ma.MaskedArray(tpl['FLUX'].data,
                                mask=tpl.bitmask.flagged(tpl['MASK'].data,
                                                         flag=['NO_DATA', 'WAVE_INVALID',
                                                               'FLUX_INVALID', 'SPECRES_NOFLUX']))

    # Try to measure only absorption-line indices
    indices = SpectralIndices.measure_indices(absdb, None, tpl['WAVE'].data, flux[:2,:],
                                              bitmask=bm)

    # Try to measure only bandhead indices
    indices = SpectralIndices.measure_indices(None, bhddb, tpl['WAVE'].data, flux[:2,:],
                                              bitmask=bm)

    # Measure both
    indices = SpectralIndices.measure_indices(absdb, bhddb, tpl['WAVE'].data, flux[:2,:],
                                              bitmask=bm)

    # Test the output
    indx = numpy.ma.MaskedArray(indices['INDX'], mask=bm.flagged(indices['MASK']))
    indx_bf = numpy.ma.MaskedArray(indices['INDX_BF'], mask=bm.flagged(indices['MASK']))

    assert indx.shape == (2,46), 'Incorrect output shape'
    assert numpy.sum(indx.mask[0,:]) == 12, 'Incorrect number of masked indices'
    assert numpy.allclose(indx[1,:].compressed(),
                numpy.array([-2.67817275e-02,   1.94113040e-02,   2.92495672e-01,
                              1.91132126e+00,   1.86182351e+00,   1.13929718e+00,
                              2.45269081e+00,   2.24190385e+00,   3.55293194e+00,
                              5.25612762e+00,   2.09506842e-02,   5.47343625e-02,
                              2.29779119e-01,   1.92039303e+00,   1.95663049e+00,
                              9.81723064e-01,   7.44058546e-01,   5.46484409e-01,
                              1.56520293e+00,   8.93716574e-03,   1.82158534e-02,
                              2.46748795e+00,   1.03916389e+00,   2.27668045e+00,
                              2.68943564e+00,   6.77715967e+00,   1.31272416e+00,
                              9.12401084e-03,   3.52657124e-03,   1.92826953e-03,
                             -1.00946682e-02,   2.01732938e-02,   1.17072128e+00,
                              1.12525642e+00]),
                          rtol=0.0, atol=1e-4), 'Index values are different'
    assert numpy.std(indx.compressed() - indx_bf.compressed()) < 0.01, \
            'Index definitions are too different'
コード例 #4
0
nwalkers = 100 # number of monte carlo chains
nsteps= 100 # number of steps in the monte carlo chain
opstart = [0.7, 9.0, 1.25] # starting place of all the chains
burnin = 500 # number of steps in the burn in phase of the monte carlo chain
ndim = 3

ages = Planck15.age(mpl6agn['nsa_z']).value

define_em_db = SpectralFeatureDBDef(key='USEREM',
                              file_path='elpsnitch.par')
emlines  = EmissionLineDB(u"USEREM", emldb_list=define_em_db)
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")
indx_names = np.hstack([abs_db.data["name"], band_db.data["name"]])

for n in trange(len(mpl6agn)):
    if os.path.isfile('../data/manga-'+str(mpl6agn[n]['plate'])+'-'+str(mpl6agn[n]['ifudsgn'].strip())+'-LOGCUBE.fits.gz'):
    #     pass
    # else: 
    #     r = requests.get(top_level_url+str(mpl6agn[n]['plate'])+'/stack/manga-'+str(mpl6agn[n]['plate'])+'-'+str(mpl6agn[n]['ifudsgn'].strip())+'-LOGCUBE.fits.gz', auth=HTTPBasicAuth(up[0].rstrip('\n'), up[1].rstrip('\n')), stream=True)
    #     if r.ok:
    #         with open('../data/manga-'+str(mpl6agn[n]['plate'])+'-'+str(mpl6agn[n]['ifudsgn'].strip())+'-LOGCUBE.fits.gz', 'wb') as file:
    #             file.write(r.content) 
    #     s = requests.get(top_level_url+str(mpl6agn[n]['plate'])+'/stack/manga-'+str(mpl6agn[n]['plate'])+'-'+str(mpl6agn[n]['ifudsgn'].strip())+'-LOGRSS.fits.gz', auth=HTTPBasicAuth(up[0].rstrip('\n'), up[1].rstrip('\n')), stream=True)
    #     if s.ok:
    #         with open('../data/manga-'+str(mpl6agn[n]['plate'])+'-'+str(mpl6agn[n]['ifudsgn'].strip())+'-LOGRSS.fits.gz', 'wb') as sfile:
    #             sfile.write(s.content)
    #     p = requests.get(top_level_url_par+str(mpl6agn[n]['plate'])+'/'+str(mpl6agn[n]['ifudsgn'].strip())+'/ref/mangadap-'+str(mpl6agn[n]['plate'])+'-'+str(mpl6agn[n]['ifudsgn'].strip())+'-LOGCUBE-input.par', auth=HTTPBasicAuth(up[0].rstrip('\n'), up[1].rstrip('\n')), stream=True)         
コード例 #5
0
def test_basic():
    bhddb = BandheadIndexDB.from_key('BHBASIC')
    assert len(bhddb) == 3, 'Incorrect number of bandhead indices'
    assert 'Dn4000' in bhddb['name'], 'Does not contain Dn4000 in list'
コード例 #6
0
def test_read():
    dbs = BandheadIndexDB.available_databases()
    for key in dbs.keys():
        bhddb = BandheadIndexDB.from_key(key)
コード例 #7
0
ファイル: test_par.py プロジェクト: HSIYJND/mangadap
def test_bhd_channel_names():
    bhddb = BandheadIndexDB.from_key('BHBASIC')
    names = bhddb.channel_names()
    assert 'D4000' in list(names.keys())
    assert names['D4000'] == 0
コード例 #8
0
ファイル: functions.py プロジェクト: rjsmethurst/snitch
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
コード例 #9
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))