Ejemplo n.º 1
0
    def _get_spectra(self):
        #- Setup data for a Resolution matrix
        sigma = 4.0
        ndiag = 21
        xx = np.linspace(-(ndiag - 1) / 2.0, +(ndiag - 1) / 2.0, ndiag)
        Rdata = np.zeros((self.nspec, ndiag, self.nwave))
        for i in range(self.nspec):
            for j in range(self.nwave):
                kernel = np.exp(-xx**2 / (2 * sigma))
                kernel /= sum(kernel)
                Rdata[i, :, j] = kernel

        flux = np.zeros((self.nspec, self.nwave))
        ivar = np.ones((self.nspec, self.nwave))
        mask = np.zeros((self.nspec, self.nwave), dtype=int)
        for i in range(self.nspec):
            R = Resolution(Rdata[i])
            flux[i] = R.dot(self.flux)

        fibermap = lvmspec.io.empty_fibermap(self.nspec, 1500)
        fibermap['OBJTYPE'][0::2] = 'SKY'

        return Frame(self.wave,
                     flux,
                     ivar,
                     mask,
                     Rdata,
                     spectrograph=2,
                     fibermap=fibermap)
Ejemplo n.º 2
0
def get_frame_data(nspec=10, objtype=None):
    """
    Return basic test data for lvmspec.frame object:
    """
    nwave = 100

    wavemin, wavemax = 4000, 4100
    wave, model_flux = get_models(nspec,
                                  nwave,
                                  wavemin=wavemin,
                                  wavemax=wavemax)
    resol_data = set_resolmatrix(nspec, nwave)

    calib = np.sin((wave - wavemin) * np.pi / np.max(wave))
    flux = np.zeros((nspec, nwave))
    for i in range(nspec):
        flux[i] = Resolution(resol_data[i]).dot(model_flux[i] * calib)

    sigma = 0.01
    # flux += np.random.normal(scale=sigma, size=flux.shape)

    ivar = np.ones(flux.shape) / sigma**2
    mask = np.zeros(flux.shape, dtype=int)
    fibermap = empty_fibermap(nspec, 1500)
    if objtype is None:
        fibermap['OBJTYPE'] = 'QSO'
        fibermap['OBJTYPE'][0:3] = 'STD'  # For flux tests
    else:
        fibermap['OBJTYPE'] = objtype

    frame = Frame(wave, flux, ivar, mask, resol_data, fibermap=fibermap)
    frame.meta = {}
    frame.meta['EXPTIME'] = 1.  # For flux tests
    return frame
Ejemplo n.º 3
0
    def test_resolution(self):
        """
        Test that identical spectra convolved with different resolutions
        results in identical fiberflats
        """
        wave, flux, ivar, mask = _get_data()
        nspec, nwave = flux.shape

        #- Setup a Resolution matrix that varies with fiber and wavelength
        #- Note: this is actually the transpose of the resolution matrix
        #- I wish I was creating, but as long as we self-consistently
        #- use it for convolving and solving, that shouldn't matter.
        sigma = np.linspace(2, 10, nwave * nspec)
        ndiag = 21
        xx = np.linspace(-ndiag / 2.0, +ndiag / 2.0, ndiag)
        Rdata = np.zeros((nspec, len(xx), nwave))
        for i in range(nspec):
            for j in range(nwave):
                kernel = np.exp(-xx**2 / (2 * sigma[i * nwave + j]**2))
                kernel /= sum(kernel)
                Rdata[i, :, j] = kernel

        #- Convolve the data with the resolution matrix
        convflux = np.empty_like(flux)
        for i in range(nspec):
            convflux[i] = Resolution(Rdata[i]).dot(flux[i])

        #- Run the code
        frame = Frame(wave, convflux, ivar, mask, Rdata, spectrograph=0)
        ff = compute_fiberflat(frame)

        #- These fiber flats should all be ~1
        self.assertTrue(np.all(np.abs(ff.fiberflat - 1) < 0.001))
Ejemplo n.º 4
0
 def _getdata(self, n=10):
     wave = np.linspace(5000, 5100, n)
     flux = np.random.uniform(0, 1, size=n)
     ivar = np.random.uniform(0, 1, size=n)
     ### mask = np.random.randint(0, 256, size=n)
     rdat = np.ones((3, n))
     rdat[0] *= 0.25
     rdat[1] *= 0.5
     rdat[2] *= 0.25
     R = Resolution(rdat)
     ### return wave, flux, ivar, mask, R
     return wave, flux, ivar, None, R
Ejemplo n.º 5
0
    def test_throughput_resolution(self):
        """
        Test that spectra with different throughputs and different resolutions
        result in fiberflat variations that are only due to throughput.
        """
        wave, flux, ivar, mask = _get_data()
        nspec, nwave = flux.shape

        #- Setup a Resolution matrix that varies with fiber and wavelength
        #- Note: this is actually the transpose of the resolution matrix
        #- I wish I was creating, but as long as we self-consistently
        #- use it for convolving and solving, that shouldn't matter.
        sigma = np.linspace(2, 10, nwave * nspec)
        ndiag = 21
        xx = np.linspace(-ndiag / 2.0, +ndiag / 2.0, ndiag)
        Rdata = np.zeros((nspec, len(xx), nwave))
        for i in range(nspec):
            for j in range(nwave):
                kernel = np.exp(-xx**2 / (2 * sigma[i * nwave + j]**2))
                kernel /= sum(kernel)
                Rdata[i, :, j] = kernel

        #- Vary the input flux prior to calculating the fiber flat
        flux[1] *= 1.1
        flux[2] *= 1.2
        flux[3] /= 1.1
        flux[4] /= 1.2

        #- Convolve the data with the varying resolution matrix
        convflux = np.empty_like(flux)
        for i in range(nspec):
            convflux[i] = Resolution(Rdata[i]).dot(flux[i])

        #- Run the code
        frame = Frame(wave, convflux, ivar, mask, Rdata, spectrograph=0)
        #- Set an accuracy for this
        accuracy = 1.e-9
        ff = compute_fiberflat(frame, accuracy=accuracy)

        #- Compare variation with middle fiber
        mid = ff.fiberflat.shape[0] // 2

        diff = (ff.fiberflat[1] / 1.1 - ff.fiberflat[mid])
        self.assertLess(np.max(np.abs(diff)), accuracy)

        diff = (ff.fiberflat[2] / 1.2 - ff.fiberflat[mid])
        self.assertLess(np.max(np.abs(diff)), accuracy)

        diff = (ff.fiberflat[3] * 1.1 - ff.fiberflat[mid])
        self.assertLess(np.max(np.abs(diff)), accuracy)

        diff = (ff.fiberflat[4] * 1.2 - ff.fiberflat[mid])
        self.assertLess(np.max(np.abs(diff)), accuracy)
Ejemplo n.º 6
0
    def test_errors(self):
        #- Bad shaped input
        data = np.random.uniform(size=(10,5))
        with self.assertRaises(ValueError):
            R = Resolution(data)

        #- Meaningless type for input
        with self.assertRaises(ValueError):
            R = Resolution('blat')

        #- Non-uniform x spacing
        with self.assertRaises(ValueError):
            R = lvmspec.resolution._gauss_pix([-1,0,2])

        #- missing offsets
        with self.assertRaises(ValueError):
            lvmspec.resolution._sort_and_symmeterize(data, [-2,-1,0,1,3])

        #- length of offsets too large or small
        with self.assertRaises(ValueError):
            Resolution(data, offsets=[1,2])

        with self.assertRaises(ValueError):
            Resolution(data, offsets=np.arange(10*lvmspec.resolution.default_ndiag))
Ejemplo n.º 7
0
    def test_main(self):
        """
        Test the main program.
        """
        # generate the frame data
        wave, flux, ivar, mask = _get_data()
        nspec, nwave = flux.shape

        #- Setup data for a Resolution matrix
        sigma = 4.0
        ndiag = 11
        xx = np.linspace(-(ndiag - 1) / 2.0, +(ndiag - 1) / 2.0, ndiag)
        Rdata = np.zeros((nspec, ndiag, nwave))
        kernel = np.exp(-xx**2 / (2 * sigma))
        kernel /= sum(kernel)
        for i in range(nspec):
            for j in range(nwave):
                Rdata[i, :, j] = kernel

        #- Convolve the data with the resolution matrix
        convflux = np.empty_like(flux)
        for i in range(nspec):
            convflux[i] = Resolution(Rdata[i]).dot(flux[i])

        # create a fake fibermap
        fibermap = io.empty_fibermap(nspec, nwave)
        for i in range(0, nspec):
            fibermap['OBJTYPE'][i] = 'FAKE'
        io.write_fibermap(self.testfibermap, fibermap)

        #- write out the frame
        frame = Frame(wave,
                      convflux,
                      ivar,
                      mask,
                      Rdata,
                      spectrograph=0,
                      fibermap=fibermap,
                      meta=dict(FLAVOR='flat'))
        write_frame(self.testframe, frame, fibermap=fibermap)

        # set program arguments
        argstr = ['--infile', self.testframe, '--outfile', self.testflat]

        # run it
        args = ffscript.parse(options=argstr)
        ffscript.main(args)
Ejemplo n.º 8
0
    def test_throughput(self):
        """
        Test that spectra with different throughputs but the same resolution
        produce a fiberflat mirroring the variations in throughput
        """
        wave, flux, ivar, mask = _get_data()
        nspec, nwave = flux.shape

        #- Setup data for a Resolution matrix
        sigma = 4.0
        ndiag = 21
        xx = np.linspace(-(ndiag - 1) / 2.0, +(ndiag - 1) / 2.0, ndiag)
        Rdata = np.zeros((nspec, ndiag, nwave))
        kernel = np.exp(-xx**2 / (2 * sigma))
        kernel /= sum(kernel)
        for i in range(nspec):
            for j in range(nwave):
                Rdata[i, :, j] = kernel

        #- Vary the input flux prior to calculating the fiber flat
        flux[1] *= 1.1
        flux[2] *= 1.2
        flux[3] *= 0.8

        #- Convolve with the (common) resolution matrix
        convflux = np.empty_like(flux)
        for i in range(nspec):
            convflux[i] = Resolution(Rdata[i]).dot(flux[i])

        frame = Frame(wave, convflux, ivar, mask, Rdata, spectrograph=0)
        ff = compute_fiberflat(frame)

        #- flux[1] is brighter, so should fiberflat[1].  etc.
        self.assertTrue(np.allclose(ff.fiberflat[0], ff.fiberflat[1] / 1.1))
        self.assertTrue(np.allclose(ff.fiberflat[0], ff.fiberflat[2] / 1.2))
        self.assertTrue(np.allclose(ff.fiberflat[0], ff.fiberflat[3] / 0.8))
Ejemplo n.º 9
0
def sim_spectra(wave,
                flux,
                program,
                spectra_filename,
                obsconditions=None,
                sourcetype=None,
                expid=0,
                seed=0):
    """
    Simulate spectra from an input set of wavelength and flux and writes a FITS file in the Spectra format that can
    be used as input to the redshift fitter.

    Args:
        wave : 1D np.array of wavelength in Angstrom (in vacuum) in observer frame (i.e. redshifted)
        flux : 1D or 2D np.array. 1D array must have same size as wave, 2D array must have shape[1]=wave.size
               flux has to be in units of 10^-17 ergs/s/cm2/A
        spectra_filename : path to output FITS file in the Spectra format

    Optional:
        obsconditions : dictionnary of observation conditions with SEEING EXPTIME AIRMASS MOONFRAC MOONALT MOONSEP
        sourcetype : list of string, allowed values are (sky,elg,lrg,qso,bgs,star), type of sources, used for fiber aperture loss , default is star
        expid : this expid number will be saved in the Spectra fibermap
        seed : random seed
    """
    log = get_logger()

    if len(flux.shape) == 1:
        flux = flux.reshape((1, flux.size))
    nspec = flux.shape[0]

    log.info("Starting simulation of {} spectra".format(nspec))

    if sourcetype is None:
        sourcetype = np.array(["star" for i in range(nspec)])
    log.debug("sourcetype = {}".format(sourcetype))

    tileid = 0
    telera = 0
    teledec = 0
    dateobs = time.gmtime()
    night = lvmsim.obs.get_night(utc=dateobs)
    program = program.lower()

    frame_fibermap = lvmspec.io.fibermap.empty_fibermap(nspec)
    frame_fibermap.meta["FLAVOR"] = "custom"
    frame_fibermap.meta["NIGHT"] = night
    frame_fibermap.meta["EXPID"] = expid

    # add LVM_TARGET
    tm = lvmtarget.desi_mask
    frame_fibermap['LVM_TARGET'][sourcetype == "star"] = tm.STD_FSTAR
    frame_fibermap['LVM_TARGET'][sourcetype == "lrg"] = tm.LRG
    frame_fibermap['LVM_TARGET'][sourcetype == "elg"] = tm.ELG
    frame_fibermap['LVM_TARGET'][sourcetype == "qso"] = tm.QSO
    frame_fibermap['LVM_TARGET'][sourcetype == "sky"] = tm.SKY
    frame_fibermap['LVM_TARGET'][sourcetype == "bgs"] = tm.BGS_ANY

    # add dummy TARGETID
    frame_fibermap['TARGETID'] = np.arange(nspec).astype(int)

    # spectra fibermap has two extra fields : night and expid
    # This would be cleaner if lvmspec would provide the spectra equivalent
    # of lvmspec.io.empty_fibermap()
    spectra_fibermap = lvmspec.io.empty_fibermap(nspec)
    spectra_fibermap = lvmspec.io.util.add_columns(
        spectra_fibermap,
        ['NIGHT', 'EXPID', 'TILEID'],
        [np.int32(night), np.int32(expid),
         np.int32(tileid)],
    )

    for s in range(nspec):
        for tp in frame_fibermap.dtype.fields:
            spectra_fibermap[s][tp] = frame_fibermap[s][tp]

    if obsconditions is None:
        if program in ['dark', 'lrg', 'qso']:
            obsconditions = lvmsim.simexp.reference_conditions['DARK']
        elif program in ['elg', 'gray', 'grey']:
            obsconditions = lvmsim.simexp.reference_conditions['GRAY']
        elif program in ['mws', 'bgs', 'bright']:
            obsconditions = lvmsim.simexp.reference_conditions['BRIGHT']
        else:
            raise ValueError('unknown program {}'.format(program))
    elif isinstance(obsconditions, str):
        try:
            obsconditions = lvmsim.simexp.reference_conditions[
                obsconditions.upper()]
        except KeyError:
            raise ValueError('obsconditions {} not in {}'.format(
                obsconditions.upper(),
                list(lvmsim.simexp.reference_conditions.keys())))
    try:
        params = lvmmodel.io.load_desiparams()
        wavemin = params['ccd']['b']['wavemin']
        wavemax = params['ccd']['z']['wavemax']
    except KeyError:
        wavemin = lvmmodel.io.load_throughput('b').wavemin
        wavemax = lvmmodel.io.load_throughput('z').wavemax

    if wave[0] > wavemin:
        log.warning(
            'Minimum input wavelength {}>{}; padding with zeros'.format(
                wave[0], wavemin))
        dwave = wave[1] - wave[0]
        npad = int((wave[0] - wavemin) / dwave + 1)
        wavepad = np.arange(npad) * dwave
        wavepad += wave[0] - dwave - wavepad[-1]
        fluxpad = np.zeros((flux.shape[0], len(wavepad)), dtype=flux.dtype)
        wave = np.concatenate([wavepad, wave])
        flux = np.hstack([fluxpad, flux])
        assert flux.shape[1] == len(wave)
        assert np.allclose(dwave, np.diff(wave))
        assert wave[0] <= wavemin

    if wave[-1] < wavemax:
        log.warning(
            'Maximum input wavelength {}<{}; padding with zeros'.format(
                wave[-1], wavemax))
        dwave = wave[-1] - wave[-2]
        npad = int((wavemax - wave[-1]) / dwave + 1)
        wavepad = wave[-1] + dwave + np.arange(npad) * dwave
        fluxpad = np.zeros((flux.shape[0], len(wavepad)), dtype=flux.dtype)
        wave = np.concatenate([wave, wavepad])
        flux = np.hstack([flux, fluxpad])
        assert flux.shape[1] == len(wave)
        assert np.allclose(dwave, np.diff(wave))
        assert wavemax <= wave[-1]

    ii = (wavemin <= wave) & (wave <= wavemax)

    flux_unit = 1e-17 * u.erg / (u.Angstrom * u.s * u.cm**2)

    wave = wave[ii] * u.Angstrom
    flux = flux[:, ii] * flux_unit

    sim = lvmsim.simexp.simulate_spectra(wave,
                                         flux,
                                         fibermap=frame_fibermap,
                                         obsconditions=obsconditions,
                                         seed=seed)

    random_state = np.random.RandomState(seed)
    sim.generate_random_noise(random_state)

    scale = 1e17
    specdata = None

    resolution = {}
    for camera in sim.instrument.cameras:
        R = Resolution(camera.get_output_resolution_matrix())
        resolution[camera.name] = np.tile(R.to_fits_array(), [nspec, 1, 1])

    for table in sim.camera_output:

        wave = table['wavelength'].astype(float)
        flux = (table['observed_flux'] + table['random_noise_electrons'] *
                table['flux_calibration']).T.astype(float)
        ivar = table['flux_inverse_variance'].T.astype(float)

        band = table.meta['name'].strip()[0]

        flux = flux * scale
        ivar = ivar / scale**2
        mask = np.zeros(flux.shape).astype(int)

        spec = Spectra([band], {band: wave}, {band: flux}, {band: ivar},
                       resolution_data={band: resolution[band]},
                       mask={band: mask},
                       fibermap=spectra_fibermap,
                       meta=None,
                       single=True)

        if specdata is None:
            specdata = spec
        else:
            specdata.update(spec)

    lvmspec.io.write_spectra(spectra_filename, specdata)
    log.info('Wrote ' + spectra_filename)
Ejemplo n.º 10
0
def compute_sky(frame, nsig_clipping=4.,max_iterations=100,model_ivar=False,add_variance=True) :
    """Compute a sky model.

    Input has to correspond to sky fibers only.
    Input flux are expected to be flatfielded!
    We don't check this in this routine.

    Args:
        frame : Frame object, which includes attributes
          - wave : 1D wavelength grid in Angstroms
          - flux : 2D flux[nspec, nwave] density
          - ivar : 2D inverse variance of flux
          - mask : 2D inverse mask flux (0=good)
          - resolution_data : 3D[nspec, ndiag, nwave]  (only sky fibers)
        nsig_clipping : [optional] sigma clipping value for outlier rejection

    Optional:
        max_iterations : int , number of iterations
        model_ivar : replace ivar by a model to avoid bias due to correlated flux and ivar. this has a negligible effect on sims.

    returns SkyModel object with attributes wave, flux, ivar, mask
    """

    log=get_logger()
    log.info("starting")

    # Grab sky fibers on this frame
    skyfibers = np.where(frame.fibermap['OBJTYPE'] == 'SKY')[0]
    assert np.max(skyfibers) < 500  #- indices, not fiber numbers

    nwave=frame.nwave
    nfibers=len(skyfibers)

    current_ivar=frame.ivar[skyfibers].copy()*(frame.mask[skyfibers]==0)
    flux = frame.flux[skyfibers]
    Rsky = frame.R[skyfibers]

    input_ivar=None
    if model_ivar :
        log.info("use a model of the inverse variance to remove bias due to correlated ivar and flux")
        input_ivar=current_ivar.copy()
        median_ivar_vs_wave  = np.median(current_ivar,axis=0)
        median_ivar_vs_fiber = np.median(current_ivar,axis=1)
        median_median_ivar   = np.median(median_ivar_vs_fiber)
        for f in range(current_ivar.shape[0]) :
            threshold=0.01
            current_ivar[f] = median_ivar_vs_fiber[f]/median_median_ivar * median_ivar_vs_wave
            # keep input ivar for very low weights
            ii=(input_ivar[f]<=(threshold*median_ivar_vs_wave))
            #log.info("fiber {} keep {}/{} original ivars".format(f,np.sum(ii),current_ivar.shape[1]))
            current_ivar[f][ii] = input_ivar[f][ii]


    sqrtw=np.sqrt(current_ivar)
    sqrtwflux=sqrtw*flux

    chi2=np.zeros(flux.shape)

    #debug
    #nfibers=min(nfibers,2)

    nout_tot=0
    for iteration in range(max_iterations) :

        A=scipy.sparse.lil_matrix((nwave,nwave)).tocsr()
        B=np.zeros((nwave))
        # diagonal sparse matrix with content = sqrt(ivar)*flat of a given fiber
        SD=scipy.sparse.lil_matrix((nwave,nwave))
        # loop on fiber to handle resolution
        for fiber in range(nfibers) :
            if fiber%10==0 :
                log.info("iter %d fiber %d"%(iteration,fiber))
            R = Rsky[fiber]

            # diagonal sparse matrix with content = sqrt(ivar)
            SD.setdiag(sqrtw[fiber])

            sqrtwR = SD*R # each row r of R is multiplied by sqrtw[r]

            A = A+(sqrtwR.T*sqrtwR).tocsr()
            B += sqrtwR.T*sqrtwflux[fiber]

        log.info("iter %d solving"%iteration)

        w = A.diagonal()>0
        A_pos_def = A.todense()[w,:]
        A_pos_def = A_pos_def[:,w]
        skyflux = B*0
        try:
            skyflux[w]=cholesky_solve(A_pos_def,B[w])
        except:
            log.info("cholesky failed, trying svd in iteration {}".format(iteration))
            skyflux[w]=np.linalg.lstsq(A_pos_def,B[w])[0]

        log.info("iter %d compute chi2"%iteration)

        for fiber in range(nfibers) :

            S = Rsky[fiber].dot(skyflux)
            chi2[fiber]=current_ivar[fiber]*(flux[fiber]-S)**2

        log.info("rejecting")

        nout_iter=0
        if iteration<1 :
            # only remove worst outlier per wave
            # apply rejection iteratively, only one entry per wave among fibers
            # find waves with outlier (fastest way)
            nout_per_wave=np.sum(chi2>nsig_clipping**2,axis=0)
            selection=np.where(nout_per_wave>0)[0]
            for i in selection :
                worst_entry=np.argmax(chi2[:,i])
                current_ivar[worst_entry,i]=0
                sqrtw[worst_entry,i]=0
                sqrtwflux[worst_entry,i]=0
                nout_iter += 1

        else :
            # remove all of them at once
            bad=(chi2>nsig_clipping**2)
            current_ivar *= (bad==0)
            sqrtw *= (bad==0)
            sqrtwflux *= (bad==0)
            nout_iter += np.sum(bad)

        nout_tot += nout_iter

        sum_chi2=float(np.sum(chi2))
        ndf=int(np.sum(chi2>0)-nwave)
        chi2pdf=0.
        if ndf>0 :
            chi2pdf=sum_chi2/ndf
        log.info("iter #%d chi2=%f ndf=%d chi2pdf=%f nout=%d"%(iteration,sum_chi2,ndf,chi2pdf,nout_iter))

        if nout_iter == 0 :
            break

    log.info("nout tot=%d"%nout_tot)

    # no need restore original ivar to compute model error when modeling ivar
    # the sky inverse variances are very similar

    # solve once again to get deconvolved sky variance
    try :
        unused_skyflux,skycovar=cholesky_solve_and_invert(A.todense(),B)
    except np.linalg.linalg.LinAlgError :
        log.warning("cholesky_solve_and_invert failed, switching to np.linalg.lstsq and np.linalg.pinv")
        #skyflux = np.linalg.lstsq(A.todense(),B)[0]
        skycovar = np.linalg.pinv(A.todense())

    #- sky inverse variance, but incomplete and not needed anyway
    # skyvar=np.diagonal(skycovar)
    # skyivar=(skyvar>0)/(skyvar+(skyvar==0))

    # Use diagonal of skycovar convolved with mean resolution of all fibers
    # first compute average resolution
    mean_res_data=np.mean(frame.resolution_data,axis=0)
    R = Resolution(mean_res_data)
    # compute convolved sky and ivar
    cskycovar=R.dot(skycovar).dot(R.T.todense())
    cskyvar=np.diagonal(cskycovar)
    cskyivar=(cskyvar>0)/(cskyvar+(cskyvar==0))

    # convert cskyivar to 2D; today it is the same for all spectra,
    # but that may not be the case in the future
    cskyivar = np.tile(cskyivar, frame.nspec).reshape(frame.nspec, nwave)

    # Convolved sky
    cskyflux = np.zeros(frame.flux.shape)
    for i in range(frame.nspec):
        cskyflux[i] = frame.R[i].dot(skyflux)



    # look at chi2 per wavelength and increase sky variance to reach chi2/ndf=1
    if skyfibers.size > 1 and add_variance :
        log.info("Add a model error due to wavelength solution noise")

        tivar = util.combine_ivar(frame.ivar[skyfibers], cskyivar[skyfibers])

        # the chi2 at a given wavelength can be large because on a cosmic
        # and not a psf error or sky non uniformity
        # so we need to consider only waves for which
        # a reasonable sky model error can be computed

        # mean sky
        msky = np.mean(cskyflux,axis=0)
        dwave = np.mean(np.gradient(frame.wave))
        dskydw = np.zeros(msky.shape)
        dskydw[1:-1]=(msky[2:]-msky[:-2])/(frame.wave[2:]-frame.wave[:-2])
        dskydw = np.abs(dskydw)

        # now we consider a worst possible sky model error (20% error on flat, 0.5A )
        max_possible_var = 1./(tivar+(tivar==0)) + (0.2*msky)**2 + (0.5*dskydw)**2

        # exclude residuals inconsistent with this max possible variance (at 3 sigma)
        bad = (frame.flux[skyfibers]-cskyflux[skyfibers])**2 > 3**2*max_possible_var
        tivar[bad]=0
        ndata = np.sum(tivar>0,axis=0)
        ok=np.where(ndata>1)[0]
        print("ok.size=",ok.size)
        chi2  = np.zeros(frame.wave.size)
        chi2[ok] = np.sum(tivar*(frame.flux[skyfibers]-cskyflux[skyfibers])**2,axis=0)[ok]/(ndata[ok]-1)
        chi2[ndata<=1] = 1. # default

        # now we are going to evaluate a sky model error based on this chi2,
        # but only around sky flux peaks (>0.1*max)
        tmp   = np.zeros(frame.wave.size)
        tmp   = (msky[1:-1]>msky[2:])*(msky[1:-1]>msky[:-2])*(msky[1:-1]>0.1*np.max(msky))
        peaks = np.where(tmp)[0]+1
        dpix  = int(np.ceil(3/dwave)) # +- n Angstrom around each peak

        skyvar = 1./(cskyivar+(cskyivar==0))

        # loop on peaks
        for peak in peaks :
            b=peak-dpix
            e=peak+dpix+1
            mchi2  = np.mean(chi2[b:e]) # mean reduced chi2 around peak
            mndata = np.mean(ndata[b:e]) # mean number of fibers contributing

            # sky model variance = sigma_flat * msky  + sigma_wave * dmskydw
            sigma_flat=0.000 # the fiber flat error is already included in the flux ivar
            sigma_wave=0.005 # A, minimum value
            res2=(frame.flux[skyfibers,b:e]-cskyflux[skyfibers,b:e])**2
            var=1./(tivar[:,b:e]+(tivar[:,b:e]==0))
            nd=np.sum(tivar[:,b:e]>0)
            while(sigma_wave<2) :
                pivar=1./(var+(sigma_flat*msky[b:e])**2+(sigma_wave*dskydw[b:e])**2)
                pchi2=np.sum(pivar*res2)/nd
                if pchi2<=1 :
                    log.info("peak at {}A : sigma_wave={}".format(int(frame.wave[peak]),sigma_wave))
                    skyvar[:,b:e] += ( (sigma_flat*msky[b:e])**2 + (sigma_wave*dskydw[b:e])**2 )
                    break
                sigma_wave += 0.005

        modified_cskyivar = (cskyivar>0)/skyvar
    else :
        modified_cskyivar = cskyivar.copy()

    # need to do better here
    mask = (cskyivar==0).astype(np.uint32)

    return SkyModel(frame.wave.copy(), cskyflux, modified_cskyivar, mask,
                    nrej=nout_tot, stat_ivar = cskyivar) # keep a record of the statistical ivar for QA
Ejemplo n.º 11
0
def main(args):

    # Set up the logger
    if args.verbose:
        log = get_logger(DEBUG)
    else:
        log = get_logger()

    # Make sure all necessary environment variables are set
    setup_envs()

    # Initialize random number generator to use.
    np.random.seed(args.seed)
    random_state = np.random.RandomState(args.seed)

    # Derive spectrograph number from nstart if needed
    if args.spectrograph is None:
        args.spectrograph = args.nstart / args.n_fibers

    # Read fibermapfile to get object type, night and expid
    fibermap, objtype, night, expid = get_fibermap(args.fibermap,
                                                   log=log,
                                                   nspec=args.nspec)

    # Initialize the spectral simulator
    log.info("Initializing SpecSim with config {}".format(args.config))
    lvmparams = load_lvmparams(config=args.config, telescope=args.telescope)
    qsim = get_simulator(args.config, num_fibers=1, params=lvmparams)

    if args.simspec:
        # Read the input file
        log.info('Reading input file {}'.format(args.simspec))
        simspec = lvmsim.io.read_simspec(args.simspec)
        nspec = simspec.nspec
        if simspec.flavor == 'arc':
            # - TODO: do we need quickgen to support arcs?  For full pipeline
            # - arcs are used to measure PSF but aren't extracted except for
            # - debugging.
            # - TODO: if we do need arcs, this needs to be redone.
            # - conversion from phot to flux doesn't include throughput,
            # - and arc lines are rebinned to nearest 0.2 A.

            # Create full wavelength and flux arrays for arc exposure
            wave_b = np.array(simspec.wave['b'])
            wave_r = np.array(simspec.wave['r'])
            wave_z = np.array(simspec.wave['z'])
            phot_b = np.array(simspec.phot['b'][0])
            phot_r = np.array(simspec.phot['r'][0])
            phot_z = np.array(simspec.phot['z'][0])
            sim_wave = np.concatenate((wave_b, wave_r, wave_z))
            sim_phot = np.concatenate((phot_b, phot_r, phot_z))
            wavelengths = np.arange(3533., 9913.1, 0.2)
            phot = np.zeros(len(wavelengths))
            for i in range(len(sim_wave)):
                wavelength = sim_wave[i]
                flux_index = np.argmin(abs(wavelength - wavelengths))
                phot[flux_index] = sim_phot[i]
            # Convert photons to flux: following specter conversion method
            dw = np.gradient(wavelengths)
            exptime = 5.  # typical BOSS exposure time in s
            fibarea = const.pi * (1.07e-2 /
                                  2)**2  # cross-sectional fiber area in cm^2
            hc = 1.e17 * const.h * const.c  # convert to erg A
            spectra = (hc * exptime * fibarea * dw * phot) / wavelengths
        else:
            wavelengths = simspec.wave['brz']
            spectra = simspec.flux
        if nspec < args.nspec:
            log.info("Only {} spectra in input file".format(nspec))
            args.nspec = nspec

    else:
        # Initialize the output truth table.
        spectra = []
        wavelengths = qsim.source.wavelength_out.to(u.Angstrom).value
        npix = len(wavelengths)
        truth = dict()
        meta = Table()
        truth['OBJTYPE'] = np.zeros(args.nspec, dtype=(str, 10))
        truth['FLUX'] = np.zeros((args.nspec, npix))
        truth['WAVE'] = wavelengths
        jj = list()

        for thisobj in set(true_objtype):
            ii = np.where(true_objtype == thisobj)[0]
            nobj = len(ii)
            truth['OBJTYPE'][ii] = thisobj
            log.info('Generating {} template'.format(thisobj))

            # Generate the templates
            if thisobj == 'ELG':
                elg = lvmsim.templates.ELG(wave=wavelengths,
                                           add_SNeIa=args.add_SNeIa)
                flux, tmpwave, meta1 = elg.make_templates(
                    nmodel=nobj,
                    seed=args.seed,
                    zrange=args.zrange_elg,
                    sne_rfluxratiorange=args.sne_rfluxratiorange)
            elif thisobj == 'LRG':
                lrg = lvmsim.templates.LRG(wave=wavelengths,
                                           add_SNeIa=args.add_SNeIa)
                flux, tmpwave, meta1 = lrg.make_templates(
                    nmodel=nobj,
                    seed=args.seed,
                    zrange=args.zrange_lrg,
                    sne_rfluxratiorange=args.sne_rfluxratiorange)
            elif thisobj == 'QSO':
                qso = lvmsim.templates.QSO(wave=wavelengths)
                flux, tmpwave, meta1 = qso.make_templates(
                    nmodel=nobj, seed=args.seed, zrange=args.zrange_qso)
            elif thisobj == 'BGS':
                bgs = lvmsim.templates.BGS(wave=wavelengths,
                                           add_SNeIa=args.add_SNeIa)
                flux, tmpwave, meta1 = bgs.make_templates(
                    nmodel=nobj,
                    seed=args.seed,
                    zrange=args.zrange_bgs,
                    rmagrange=args.rmagrange_bgs,
                    sne_rfluxratiorange=args.sne_rfluxratiorange)
            elif thisobj == 'STD':
                fstd = lvmsim.templates.FSTD(wave=wavelengths)
                flux, tmpwave, meta1 = fstd.make_templates(nmodel=nobj,
                                                           seed=args.seed)
            elif thisobj == 'QSO_BAD':  # use STAR template no color cuts
                star = lvmsim.templates.STAR(wave=wavelengths)
                flux, tmpwave, meta1 = star.make_templates(nmodel=nobj,
                                                           seed=args.seed)
            elif thisobj == 'MWS_STAR' or thisobj == 'MWS':
                mwsstar = lvmsim.templates.MWS_STAR(wave=wavelengths)
                flux, tmpwave, meta1 = mwsstar.make_templates(nmodel=nobj,
                                                              seed=args.seed)
            elif thisobj == 'WD':
                wd = lvmsim.templates.WD(wave=wavelengths)
                flux, tmpwave, meta1 = wd.make_templates(nmodel=nobj,
                                                         seed=args.seed)
            elif thisobj == 'SKY':
                flux = np.zeros((nobj, npix))
                meta1 = Table(dict(REDSHIFT=np.zeros(nobj, dtype=np.float32)))
            elif thisobj == 'TEST':
                flux = np.zeros((args.nspec, npix))
                indx = np.where(wave > 5800.0 - 1E-6)[0][0]
                ref_integrated_flux = 1E-10
                ref_cst_flux_density = 1E-17
                single_line = (np.arange(args.nspec) % 2 == 0).astype(
                    np.float32)
                continuum = (np.arange(args.nspec) % 2 == 1).astype(np.float32)

                for spec in range(args.nspec):
                    flux[spec, indx] = single_line[
                        spec] * ref_integrated_flux / np.gradient(wavelengths)[
                            indx]  # single line
                    flux[spec] += continuum[
                        spec] * ref_cst_flux_density  # flat continuum

                meta1 = Table(
                    dict(REDSHIFT=np.zeros(args.nspec, dtype=np.float32),
                         LINE=wave[indx] *
                         np.ones(args.nspec, dtype=np.float32),
                         LINEFLUX=single_line * ref_integrated_flux,
                         CONSTFLUXDENSITY=continuum * ref_cst_flux_density))
            else:
                log.fatal('Unknown object type {}'.format(thisobj))
                sys.exit(1)

            # Pack it in.
            truth['FLUX'][ii] = flux
            meta = vstack([meta, meta1])
            jj.append(ii.tolist())

            # Sanity check on units; templates currently return ergs, not 1e-17 ergs...
            # assert (thisobj == 'SKY') or (np.max(truth['FLUX']) < 1e-6)

        # Sort the metadata table.
        jj = sum(jj, [])
        meta_new = Table()
        for k in range(args.nspec):
            index = int(np.where(np.array(jj) == k)[0])
            meta_new = vstack([meta_new, meta[index]])
        meta = meta_new

        # Add TARGETID and the true OBJTYPE to the metadata table.
        meta.add_column(
            Column(true_objtype, dtype=(str, 10), name='TRUE_OBJTYPE'))
        meta.add_column(Column(targetids, name='TARGETID'))

        # Rename REDSHIFT -> TRUEZ anticipating later table joins with zbest.Z
        meta.rename_column('REDSHIFT', 'TRUEZ')

    # ---------- end simspec

    # explicitly set location on focal plane if needed to support airmass
    # variations when using specsim v0.5
    if qsim.source.focal_xy is None:
        qsim.source.focal_xy = (u.Quantity(0, 'mm'), u.Quantity(100, 'mm'))

    # Set simulation parameters from the simspec header or lvmparams
    bright_objects = ['bgs', 'mws', 'bright', 'BGS', 'MWS', 'BRIGHT_MIX']
    gray_objects = ['gray', 'grey']
    if args.simspec is None:
        object_type = objtype
        flavor = None
    elif simspec.flavor == 'science':
        object_type = None
        flavor = simspec.header['PROGRAM']
    else:
        object_type = None
        flavor = simspec.flavor
        log.warning(
            'Maybe using an outdated simspec file with flavor={}'.format(
                flavor))

    # Set airmass
    if args.airmass is not None:
        qsim.atmosphere.airmass = args.airmass
    elif args.simspec and 'AIRMASS' in simspec.header:
        qsim.atmosphere.airmass = simspec.header['AIRMASS']
    else:
        qsim.atmosphere.airmass = 1.25  # Science Req. Doc L3.3.2

    # Set site location
    if args.location is not None:
        qsim.observation.observatory = args.location
    else:
        qsim.observation.observatory = 'APO'

    # Set exptime
    if args.exptime is not None:
        qsim.observation.exposure_time = args.exptime * u.s
    elif args.simspec and 'EXPTIME' in simspec.header:
        qsim.observation.exposure_time = simspec.header['EXPTIME'] * u.s
    elif objtype in bright_objects:
        qsim.observation.exposure_time = lvmparams['exptime_bright'] * u.s
    else:
        qsim.observation.exposure_time = lvmparams['exptime_dark'] * u.s

    # Set Moon Phase
    if args.moon_phase is not None:
        qsim.atmosphere.moon.moon_phase = args.moon_phase
    elif args.simspec and 'MOONFRAC' in simspec.header:
        qsim.atmosphere.moon.moon_phase = simspec.header['MOONFRAC']
    elif flavor in bright_objects or object_type in bright_objects:
        qsim.atmosphere.moon.moon_phase = 0.7
    elif flavor in gray_objects:
        qsim.atmosphere.moon.moon_phase = 0.1
    else:
        qsim.atmosphere.moon.moon_phase = 0.5

    # Set Moon Zenith
    if args.moon_zenith is not None:
        qsim.atmosphere.moon.moon_zenith = args.moon_zenith * u.deg
    elif args.simspec and 'MOONALT' in simspec.header:
        qsim.atmosphere.moon.moon_zenith = simspec.header['MOONALT'] * u.deg
    elif flavor in bright_objects or object_type in bright_objects:
        qsim.atmosphere.moon.moon_zenith = 30 * u.deg
    elif flavor in gray_objects:
        qsim.atmosphere.moon.moon_zenith = 80 * u.deg
    else:
        qsim.atmosphere.moon.moon_zenith = 100 * u.deg

    # Set Moon - Object Angle
    if args.moon_angle is not None:
        qsim.atmosphere.moon.separation_angle = args.moon_angle * u.deg
    elif args.simspec and 'MOONSEP' in simspec.header:
        qsim.atmosphere.moon.separation_angle = simspec.header[
            'MOONSEP'] * u.deg
    elif flavor in bright_objects or object_type in bright_objects:
        qsim.atmosphere.moon.separation_angle = 50 * u.deg
    elif flavor in gray_objects:
        qsim.atmosphere.moon.separation_angle = 60 * u.deg
    else:
        qsim.atmosphere.moon.separation_angle = 60 * u.deg

    # Initialize per-camera output arrays that will be saved
    waves, trueflux, noisyflux, obsivar, resolution, sflux = {}, {}, {}, {}, {}, {}

    maxbin = 0
    nmax = args.nspec
    for camera in qsim.instrument.cameras:
        # Lookup this camera's resolution matrix and convert to the sparse format used in lvmspec.
        R = Resolution(camera.get_output_resolution_matrix())
        resolution[camera.name] = np.tile(R.to_fits_array(),
                                          [args.nspec, 1, 1])
        waves[camera.name] = (camera.output_wavelength.to(
            u.Angstrom).value.astype(np.float32))
        nwave = len(waves[camera.name])
        maxbin = max(maxbin, len(waves[camera.name]))
        nobj = np.zeros((nmax, 3, maxbin))  # object photons
        nsky = np.zeros((nmax, 3, maxbin))  # sky photons
        nivar = np.zeros((nmax, 3, maxbin))  # inverse variance (object+sky)
        cframe_observedflux = np.zeros(
            (nmax, 3, maxbin))  # calibrated object flux
        cframe_ivar = np.zeros(
            (nmax, 3, maxbin))  # inverse variance of calibrated object flux
        cframe_rand_noise = np.zeros(
            (nmax, 3, maxbin))  # random Gaussian noise to calibrated flux
        sky_ivar = np.zeros((nmax, 3, maxbin))  # inverse variance of sky
        sky_rand_noise = np.zeros(
            (nmax, 3, maxbin))  # random Gaussian noise to sky only
        frame_rand_noise = np.zeros(
            (nmax, 3, maxbin))  # random Gaussian noise to nobj+nsky
        trueflux[camera.name] = np.empty(
            (args.nspec, nwave))  # calibrated flux
        noisyflux[camera.name] = np.empty(
            (args.nspec, nwave))  # observed flux with noise
        obsivar[camera.name] = np.empty(
            (args.nspec, nwave))  # inverse variance of flux
        if args.simspec:
            dw = np.gradient(simspec.wave[camera.name])
        else:
            sflux = np.empty((args.nspec, npix))

    # - Check if input simspec is for a continuum flat lamp instead of science
    # - This does not convolve to per-fiber resolution
    if args.simspec:
        if simspec.flavor == 'flat':
            log.info("Simulating flat lamp exposure")
            for i, camera in enumerate(qsim.instrument.cameras):
                channel = camera.name
                assert camera.output_wavelength.unit == u.Angstrom
                num_pixels = len(waves[channel])
                dw = np.gradient(simspec.wave[channel])
                meanspec = resample_flux(
                    waves[channel], simspec.wave[channel],
                    np.average(simspec.phot[channel] / dw, axis=0))
                fiberflat = random_state.normal(loc=1.0,
                                                scale=1.0 / np.sqrt(meanspec),
                                                size=(nspec, num_pixels))
                ivar = np.tile(meanspec, [nspec, 1])
                mask = np.zeros((simspec.nspec, num_pixels), dtype=np.uint32)

                for kk in range((args.nspec + args.nstart - 1) //
                                args.n_fibers + 1):
                    camera = channel + str(kk)
                    outfile = lvmspec.io.findfile('fiberflat', night, expid,
                                                  camera)
                    start = max(args.n_fibers * kk, args.nstart)
                    end = min(args.n_fibers * (kk + 1), nmax)

                    if (args.spectrograph <= kk):
                        log.info(
                            "Writing files for channel:{}, spectrograph:{}, spectra:{} to {}"
                            .format(channel, kk, start, end))

                    ff = FiberFlat(waves[channel],
                                   fiberflat[start:end, :],
                                   ivar[start:end, :],
                                   mask[start:end, :],
                                   meanspec,
                                   header=dict(CAMERA=camera))
                    write_fiberflat(outfile, ff)
                    filePath = lvmspec.io.findfile("fiberflat", night, expid,
                                                   camera)
                    log.info("Wrote file {}".format(filePath))

            sys.exit(0)

    # Repeat the simulation for all spectra
    scale = 1e-17
    fluxunits = scale * u.erg / (u.s * u.cm**2 * u.Angstrom)
    for j in range(args.nspec):

        thisobjtype = objtype[j]
        sys.stdout.flush()
        if flavor == 'arc':
            qsim.source.update_in('Quickgen source {0}'.format, 'perfect',
                                  wavelengths * u.Angstrom,
                                  spectra * fluxunits)
        else:
            qsim.source.update_in('Quickgen source {0}'.format(j),
                                  thisobjtype.lower(),
                                  wavelengths * u.Angstrom,
                                  spectra[j, :] * fluxunits)
        qsim.source.update_out()

        qsim.simulate()
        qsim.generate_random_noise(random_state)

        for i, output in enumerate(qsim.camera_output):
            assert output['observed_flux'].unit == 1e17 * fluxunits
            # Extract the simulation results needed to create our uncalibrated
            # frame output file.
            num_pixels = len(output)
            nobj[j, i, :num_pixels] = output['num_source_electrons'][:, 0]
            nsky[j, i, :num_pixels] = output['num_sky_electrons'][:, 0]
            nivar[j, i, :num_pixels] = 1.0 / output['variance_electrons'][:, 0]

            # Get results for our flux-calibrated output file.
            cframe_observedflux[
                j, i, :num_pixels] = 1e17 * output['observed_flux'][:, 0]
            cframe_ivar[
                j,
                i, :num_pixels] = 1e-34 * output['flux_inverse_variance'][:, 0]

            # Fill brick arrays from the results.
            camera = output.meta['name']
            trueflux[camera][j][:] = 1e17 * output['observed_flux'][:, 0]
            noisyflux[camera][j][:] = 1e17 * (
                output['observed_flux'][:, 0] +
                output['flux_calibration'][:, 0] *
                output['random_noise_electrons'][:, 0])
            obsivar[camera][j][:] = 1e-34 * output['flux_inverse_variance'][:,
                                                                            0]

            # Use the same noise realization in the cframe and frame, without any
            # additional noise from sky subtraction for now.
            frame_rand_noise[
                j, i, :num_pixels] = output['random_noise_electrons'][:, 0]
            cframe_rand_noise[j, i, :num_pixels] = 1e17 * (
                output['flux_calibration'][:, 0] *
                output['random_noise_electrons'][:, 0])

            # The sky output file represents a model fit to ~40 sky fibers.
            # We reduce the variance by a factor of 25 to account for this and
            # give the sky an independent (Gaussian) noise realization.
            sky_ivar[
                j,
                i, :num_pixels] = 25.0 / (output['variance_electrons'][:, 0] -
                                          output['num_source_electrons'][:, 0])
            sky_rand_noise[j, i, :num_pixels] = random_state.normal(
                scale=1.0 / np.sqrt(sky_ivar[j, i, :num_pixels]),
                size=num_pixels)

    armName = {"b": 0, "r": 1, "z": 2}
    for channel in 'brz':

        # Before writing, convert from counts/bin to counts/A (as in Pixsim output)
        # Quicksim Default:
        # FLUX - input spectrum resampled to this binning; no noise added [1e-17 erg/s/cm2/s/Ang]
        # COUNTS_OBJ - object counts in 0.5 Ang bin
        # COUNTS_SKY - sky counts in 0.5 Ang bin

        num_pixels = len(waves[channel])
        dwave = np.gradient(waves[channel])
        nobj[:, armName[channel], :num_pixels] /= dwave
        frame_rand_noise[:, armName[channel], :num_pixels] /= dwave
        nivar[:, armName[channel], :num_pixels] *= dwave**2
        nsky[:, armName[channel], :num_pixels] /= dwave
        sky_rand_noise[:, armName[channel], :num_pixels] /= dwave
        sky_ivar[:, armName[channel], :num_pixels] /= dwave**2

        # Now write the outputs in DESI standard file system. None of the output file can have more than args.n_fibers spectra

        # Looping over spectrograph
        for ii in range((args.nspec + args.nstart - 1) // args.n_fibers + 1):

            start = max(args.n_fibers * ii,
                        args.nstart)  # first spectrum for a given spectrograph
            end = min(args.n_fibers * (ii + 1),
                      nmax)  # last spectrum for the spectrograph

            if (args.spectrograph <= ii):
                camera = "{}{}".format(channel, ii)
                log.info(
                    "Writing files for channel:{}, spectrograph:{}, spectra:{} to {}"
                    .format(channel, ii, start, end))
                num_pixels = len(waves[channel])

                # Write frame file
                framefileName = lvmspec.io.findfile("frame", night, expid,
                                                    camera)

                frame_flux = nobj[start:end, armName[channel], :num_pixels] + \
                    nsky[start:end, armName[channel], :num_pixels] + \
                    frame_rand_noise[start:end, armName[channel], :num_pixels]
                frame_ivar = nivar[start:end, armName[channel], :num_pixels]

                # required for slicing the resolution metric, resolusion matrix has (nspec, ndiag, wave)
                # for example if nstart =400, nspec=150: two spectrographs:
                # 400-499=> 0 spectrograph, 500-549 => 1
                sh1 = frame_flux.shape[0]

                if (args.nstart == start):
                    resol = resolution[channel][:sh1, :, :]
                else:
                    resol = resolution[channel][-sh1:, :, :]

                # must create lvmspec.Frame object
                frame = Frame(waves[channel],
                              frame_flux,
                              frame_ivar,
                              resolution_data=resol,
                              spectrograph=ii,
                              fibermap=fibermap[start:end],
                              meta=dict(CAMERA=camera, FLAVOR=simspec.flavor))
                lvmspec.io.write_frame(framefileName, frame)

                framefilePath = lvmspec.io.findfile("frame", night, expid,
                                                    camera)
                log.info("Wrote file {}".format(framefilePath))

                if args.frameonly or simspec.flavor == 'arc':
                    continue

                # Write cframe file
                cframeFileName = lvmspec.io.findfile("cframe", night, expid,
                                                     camera)
                cframeFlux = cframe_observedflux[start:end, armName[channel], :num_pixels] + \
                    cframe_rand_noise[start:end, armName[channel], :num_pixels]
                cframeIvar = cframe_ivar[start:end,
                                         armName[channel], :num_pixels]

                # must create lvmspec.Frame object
                cframe = Frame(waves[channel],
                               cframeFlux,
                               cframeIvar,
                               resolution_data=resol,
                               spectrograph=ii,
                               fibermap=fibermap[start:end],
                               meta=dict(CAMERA=camera, FLAVOR=simspec.flavor))
                lvmspec.io.frame.write_frame(cframeFileName, cframe)

                cframefilePath = lvmspec.io.findfile("cframe", night, expid,
                                                     camera)
                log.info("Wrote file {}".format(cframefilePath))

                # Write sky file
                skyfileName = lvmspec.io.findfile("sky", night, expid, camera)
                skyflux = nsky[start:end, armName[channel], :num_pixels] + \
                    sky_rand_noise[start:end, armName[channel], :num_pixels]
                skyivar = sky_ivar[start:end, armName[channel], :num_pixels]
                skymask = np.zeros(skyflux.shape, dtype=np.uint32)

                # must create lvmspec.Sky object
                skymodel = SkyModel(waves[channel],
                                    skyflux,
                                    skyivar,
                                    skymask,
                                    header=dict(CAMERA=camera))
                lvmspec.io.sky.write_sky(skyfileName, skymodel)

                skyfilePath = lvmspec.io.findfile("sky", night, expid, camera)
                log.info("Wrote file {}".format(skyfilePath))

                # Write calib file
                calibVectorFile = lvmspec.io.findfile("calib", night, expid,
                                                      camera)
                flux = cframe_observedflux[start:end,
                                           armName[channel], :num_pixels]
                phot = nobj[start:end, armName[channel], :num_pixels]
                calibration = np.zeros_like(phot)
                jj = (flux > 0)
                calibration[jj] = phot[jj] / flux[jj]

                # - TODO: what should calibivar be?
                # - For now, model it as the noise of combining ~10 spectra
                calibivar = 10 / cframe_ivar[start:end,
                                             armName[channel], :num_pixels]
                # mask=(1/calibivar>0).astype(int)??
                mask = np.zeros(calibration.shape, dtype=np.uint32)

                # write flux calibration
                fluxcalib = FluxCalib(waves[channel], calibration, calibivar,
                                      mask)
                write_flux_calibration(calibVectorFile, fluxcalib)

                calibfilePath = lvmspec.io.findfile("calib", night, expid,
                                                    camera)
                log.info("Wrote file {}".format(calibfilePath))
Ejemplo n.º 12
0
def main(args=None):
    '''
    Converts simspec -> frame files; see fastframe --help for usage options
    '''
    #- TODO: use lvmutil.log

    if isinstance(args, (list, tuple, type(None))):
        args = parse(args)

    print('Reading files')
    simspec = lvmsim.io.read_simspec(args.simspec)

    if simspec.flavor == 'arc':
        print('arc exposure; no frames to output')
        return

    fibermap = simspec.fibermap
    obs = simspec.obs
    night = simspec.header['NIGHT']
    expid = simspec.header['EXPID']

    firstspec = args.firstspec
    nspec = min(args.nspec, len(fibermap) - firstspec)

    print('Simulating spectra {}-{}'.format(firstspec, firstspec + nspec))
    wave = simspec.wave['brz']
    flux = simspec.flux
    ii = slice(firstspec, firstspec + nspec)
    if simspec.flavor == 'science':
        sim = lvmsim.simexp.simulate_spectra(wave,
                                             flux[ii],
                                             fibermap=fibermap[ii],
                                             obsconditions=obs,
                                             dwave_out=1.0)
    elif simspec.flavor in ['arc', 'flat', 'calib']:
        x = fibermap['X_TARGET']
        y = fibermap['Y_TARGET']
        fiber_area = lvmsim.simexp.fiber_area_arcsec2(fibermap['X_TARGET'],
                                                      fibermap['Y_TARGET'])
        surface_brightness = (flux.T / fiber_area).T
        config = lvmsim.simexp._specsim_config_for_wave(wave, dwave_out=1.0)
        # sim = specsim.simulator.Simulator(config, num_fibers=nspec)
        sim = lvmsim.specsim.get_simulator(config, num_fibers=nspec)
        sim.observation.exposure_time = simspec.header['EXPTIME'] * u.s
        sbunit = 1e-17 * u.erg / (u.Angstrom * u.s * u.cm**2 * u.arcsec**2)
        xy = np.vstack([x, y]).T * u.mm
        sim.simulate(calibration_surface_brightness=surface_brightness[ii] *
                     sbunit,
                     focal_positions=xy[ii])
    else:
        raise ValueError('Unknown simspec flavor {}'.format(simspec.flavor))

    sim.generate_random_noise()

    for i, results in enumerate(sim.camera_output):
        results = sim.camera_output[i]
        wave = results['wavelength']
        scale = 1e17
        if args.cframe:
            phot = scale * (results['observed_flux'] +
                            results['random_noise_electrons'] *
                            results['flux_calibration']).T
            ivar = 1. / scale**2 * results['flux_inverse_variance'].T
        else:
            phot = (results['num_source_electrons'] + \
                    results['num_sky_electrons'] + \
                    results['num_dark_electrons'] + \
                    results['random_noise_electrons']).T
            ivar = 1.0 / results['variance_electrons'].T

        R = Resolution(
            sim.instrument.cameras[i].get_output_resolution_matrix())
        Rdata = np.tile(R.data.T, nspec).T.reshape(nspec, R.data.shape[0],
                                                   R.data.shape[1])
        assert np.all(Rdata[0] == R.data)
        assert phot.shape == (nspec, len(wave))
        for spectro in range(10):
            imin = max(firstspec, spectro * 500) - firstspec
            imax = min(firstspec + nspec, (spectro + 1) * 500) - firstspec
            if imax <= imin:
                continue
            xphot = phot[imin:imax]
            xivar = ivar[imin:imax]
            xfibermap = fibermap[ii][imin:imax]
            camera = '{}{}'.format(sim.camera_names[i], spectro)
            meta = simspec.header.copy()
            meta['CAMERA'] = camera
            if args.cframe:
                units = '1e-17 erg/(s cm2 A)'
            else:
                units = 'photon/bin'
            if 'BUNIT' in meta:
                meta['BUNIT'] = units

            frame = Frame(wave,
                          xphot,
                          xivar,
                          resolution_data=Rdata[0:imax - imin],
                          spectrograph=spectro,
                          fibermap=xfibermap,
                          meta=meta)
            if args.cframe:
                outfile = lvmspec.io.findfile('cframe',
                                              night,
                                              expid,
                                              camera,
                                              outdir=args.outdir)
            else:
                outfile = lvmspec.io.findfile('frame',
                                              night,
                                              expid,
                                              camera,
                                              outdir=args.outdir)
            print('writing {}'.format(outfile))
            lvmspec.io.write_frame(outfile, frame, units=units)
Ejemplo n.º 13
0
    def __init__(self,
                 wave,
                 flux,
                 ivar,
                 mask=None,
                 resolution_data=None,
                 fibers=None,
                 spectrograph=None,
                 meta=None,
                 fibermap=None,
                 chi2pix=None,
                 wsigma=None,
                 ndiag=21):
        """
        Lightweight wrapper for multiple spectra on a common wavelength grid

        x.wave, x.flux, x.ivar, x.mask, x.resolution_data, x.header, sp.R

        Args:
            wave: 1D[nwave] wavelength in Angstroms
            flux: 2D[nspec, nwave] flux
            ivar: 2D[nspec, nwave] inverse variance of flux

        Optional:
            mask: 2D[nspec, nwave] integer bitmask of flux.  0=good.
            resolution_data: 3D[nspec, ndiag, nwave]
                             diagonals of resolution matrix data
            fibers: ndarray of which fibers these spectra are
            spectrograph: integer, which spectrograph [0-9]
            meta: dict-like object (e.g. FITS header)
            fibermap: fibermap table
            chi2pix: 2D[nspec, nwave] chi2 of 2D model to pixel-level data
                for pixels that contributed to each flux bin
        Parameters below allow on-the-fly resolution calculation
            wsigma: 2D[nspec,nwave] sigma widths for each wavelength bin for all fibers
        Notes:
            spectrograph input is used only if fibers is None.  In this case,
            it assumes nspec_per_spectrograph = flux.shape[0] and calculates
            the fibers array for this spectrograph, i.e.
            fibers = spectrograph * flux.shape[0] + np.arange(flux.shape[0])

        Attributes:
            All input args become object attributes.
            nspec : number of spectra, flux.shape[0]
            nwave : number of wavelengths, flux.shape[1]
            specmin : minimum fiber number
            R: array of sparse Resolution matrix objects converted
               from resolution_data
            fibermap: fibermap table if provided
        """
        assert wave.ndim == 1
        assert flux.ndim == 2
        assert wave.shape[0] == flux.shape[1]
        assert ivar.shape == flux.shape
        assert (mask is None) or mask.shape == flux.shape
        assert (mask is None) or mask.dtype in \
            (int, np.int64, np.int32, np.uint64, np.uint32), "Bad mask type "+str(mask.dtype)

        self.wave = wave
        self.flux = flux
        self.ivar = ivar
        self.meta = meta
        self.fibermap = fibermap
        self.nspec, self.nwave = self.flux.shape
        self.chi2pix = chi2pix
        self.ndiag = ndiag
        fibers_per_spectrograph = 500  #- hardcode; could get from lvmmodel

        if mask is None:
            self.mask = np.zeros(flux.shape, dtype=np.uint32)
        else:
            self.mask = util.mask32(mask)

        if resolution_data is not None:
            if resolution_data.ndim != 3 or \
               resolution_data.shape[0] != self.nspec or \
               resolution_data.shape[2] != self.nwave:
                raise ValueError(
                    "Wrong dimensions for resolution_data[nspec, ndiag, nwave]"
                )

        #- Maybe setup non-None identity matrix resolution matrix instead?
        self.wsigma = wsigma
        self.resolution_data = resolution_data
        if resolution_data is not None:
            self.wsigma = None  #ignore width coefficients if resolution data is given explicitly
            self.ndiag = None
            self.R = np.array([Resolution(r) for r in resolution_data])
        elif wsigma is not None:
            from lvmspec.quicklook.qlresolution import QuickResolution
            assert ndiag is not None
            r = []
            for sigma in wsigma:
                r.append(QuickResolution(sigma=sigma, ndiag=self.ndiag))
            self.R = np.array(r)
        else:
            #SK I believe this should be error, but looking at the
            #tests frame objects are allowed to not to have resolution data
            # thus I changed value error to a simple warning message.
            log = get_logger()
            log.warning("Frame object is constructed without resolution data or respective "\
                        "sigma widths. Resolution will not be available")
            # raise ValueError("Need either resolution_data or coefficients to generate it")
        self.spectrograph = spectrograph

        # Deal with Fibers (these must be set!)
        if fibers is not None:
            fibers = np.asarray(fibers)
            if len(fibers) != self.nspec:
                raise ValueError("len(fibers) != nspec ({} != {})".format(
                    len(fibers), self.nspec))
            if fibermap is not None and np.any(fibers != fibermap['FIBER']):
                raise ValueError("fibermap doesn't match fibers")
            if (spectrograph is not None):
                minfiber = spectrograph * fibers_per_spectrograph
                maxfiber = (spectrograph + 1) * fibers_per_spectrograph
                if np.any(fibers < minfiber) or np.any(maxfiber <= fibers):
                    raise ValueError('fibers inconsistent with spectrograph')
            self.fibers = fibers
        else:
            if fibermap is not None:
                self.fibers = fibermap['FIBER']
            elif spectrograph is not None:
                self.fibers = spectrograph * fibers_per_spectrograph + np.arange(
                    self.nspec, dtype=int)
            elif (self.meta is not None) and ('FIBERMIN' in self.meta):
                self.fibers = self.meta['FIBERMIN'] + np.arange(self.nspec,
                                                                dtype=int)
            else:
                raise ValueError("Must set fibers by one of the methods!")

        if self.meta is not None:
            self.meta['FIBERMIN'] = np.min(self.fibers)
Ejemplo n.º 14
0
    def test_resolution_dense(self):
        #- dense with no offsets specified
        data = np.random.uniform(size=(10,10))
        R = Resolution(data)
        Rdense = R.todense()
        self.assertTrue(np.all(Rdense == data))

        #- with offsets
        offsets = np.arange(-2,4)
        R = Resolution(data, offsets)
        Rdense = R.todense()
        for i in offsets:
            self.assertTrue(np.all(Rdense.diagonal(i) == data.diagonal(i)), \
                "diagonal {} doesn't match".format(i))

        #- dense without offsets but larger than default_ndiag
        ndiag = lvmspec.resolution.default_ndiag + 5
        data = np.random.uniform(size=(ndiag, ndiag))
        Rdense = Resolution(data).todense()

        for i in range(ndiag):
            if i <= lvmspec.resolution.default_ndiag//2:
                self.assertTrue(np.all(Rdense.diagonal(i) == data.diagonal(i)), \
                    "diagonal {} doesn't match".format(i))
                self.assertTrue(np.all(Rdense.diagonal(-i) == data.diagonal(-i)), \
                    "diagonal {} doesn't match".format(-i))
            else:
                self.assertTrue(np.all(Rdense.diagonal(i) == 0.0), \
                    "diagonal {} not 0s".format(i))
                self.assertTrue(np.all(Rdense.diagonal(-i) == 0.0), \
                    "diagonal {} not 0s".format(-i))
Ejemplo n.º 15
0
    def test_resolution_sparsedia(self):
        data = np.random.uniform(size=(5,10))
        offsets = np.arange(-2,3)

        #- Original case: symetric and odd number of diagonals
        Rdia = scipy.sparse.dia_matrix((data, offsets), shape=(10,10))
        R = Resolution(Rdia)
        self.assertTrue(np.all(R.diagonal() == Rdia.diagonal()))

        #- Non symetric but still odd number of diagonals
        Rdia = scipy.sparse.dia_matrix((data, offsets+1), shape=(10,10))
        R = Resolution(Rdia)
        self.assertTrue(np.all(R.diagonal() == Rdia.diagonal()))

        #- Even number of diagonals
        Rdia = scipy.sparse.dia_matrix((data[1:,:], offsets[1:]), shape=(10,10))
        R = Resolution(Rdia)
        self.assertTrue(np.all(R.diagonal() == Rdia.diagonal()))

        #- Unordered diagonals
        data = np.random.uniform(size=(5,10))
        offsets = [0,1,-1,2,-2]

        Rdia = scipy.sparse.dia_matrix((data, offsets), shape=(10,10))
        R1 = Resolution(Rdia)
        R2 = Resolution(data, offsets)
        self.assertTrue(np.all(R1.diagonal() == Rdia.diagonal()))
        self.assertTrue(np.all(R2.diagonal() == Rdia.diagonal()))
        self.assertTrue(np.all(R1.data == R2.data))
Ejemplo n.º 16
0
    def test_resolution(self, n = 100):
        dense = np.arange(n*n).reshape(n,n)
        R1 = Resolution(dense)
        assert scipy.sparse.isspmatrix_dia(R1),'Resolution is not recognized as a scipy.sparse.dia_matrix.'
        assert len(R1.offsets) == lvmspec.resolution.default_ndiag, 'Resolution.offsets has wrong size'

        R2 = Resolution(R1)
        assert np.array_equal(R1.toarray(),R2.toarray()),'Constructor broken for dia_matrix input.'

        R3 = Resolution(R1.data)
        assert np.array_equal(R1.toarray(),R3.toarray()),'Constructor broken for array data input.'

        sparse = scipy.sparse.dia_matrix((R1.data[::-1],R1.offsets[::-1]),(n,n))
        R4 = Resolution(sparse)
        assert np.array_equal(R1.toarray(),R4.toarray()),'Constructor broken for permuted offsets input.'

        R5 = Resolution(R1.to_fits_array())
        assert np.array_equal(R1.toarray(),R5.toarray()),'to_fits_array() is broken.'

        #- test different sizes of input diagonals
        for ndiag in [3,5,11]:
            R6 = Resolution(np.ones((ndiag, n)))
            assert len(R6.offsets) == ndiag, 'Constructor broken for ndiag={}'.format(ndiag)

        #- An even number if diagonals is not allowed
        try:
            ndiag = 10
            R7 = Resolution(np.ones((ndiag, n)))
            raise RuntimeError('Incorrectly created Resolution with even number of diagonals')
        except ValueError as err:
            #- it correctly raised an error, so pass
            pass

        #- Test creation with sigmas - it should conserve flux
        R9 = Resolution(np.linspace(1.0, 2.0, n))
        self.assertTrue(np.allclose(np.sum(R9.data, axis=0), 1.0))