Example #1
0
def generate_safe_locations(sourcemask, Nperradius=1):
    """Given a bright source mask, generate SAFE (BADSKY) locations at its periphery.

    Parameters
    ----------
    sourcemask : :class:`recarray`
        A recarray containing a bright mask as made by, e.g.,
        :mod:`desitarget.brightmask.make_bright_star_mask` or
        :mod:`desitarget.brightmask.make_bright_source_mask`.
    Nperradius : :class:`int`, optional, defaults to 1 per arcsec of radius
        The number of safe locations to generate scaled by the radius of each mask
        in ARCSECONDS (i.e. the number of positions per arcsec of radius).

    Returns
    -------
    ra : array_like.
        The Right Ascensions of the SAFE (BADSKY) locations.
    dec : array_like.
        The Declinations of the SAFE (BADSKY) locations.

    Notes
    -----
        - See `Tech Note 2346`_ for details.
    """

    # ADM the radius of each mask in arcseconds with a 0.1% kick to
    # ADM ensure that positions are beyond the mask edges.
    radius = sourcemask["IN_RADIUS"]*1.001

    # ADM determine the number of SAFE locations to assign to each
    # ADM mask given the passed number of locations per unit radius.
    Nsafe = np.ceil(radius*Nperradius).astype('i')

    # ADM need to differentiate targets that are in ellipse-on-the-sky masks
    # ADM from targets that are in circle-on-the-sky masks.
    rex_or_psf = _rexlike(sourcemask["TYPE"]) | _psflike(sourcemask["TYPE"])
    w_ellipse = np.where(~rex_or_psf)
    w_circle = np.where(rex_or_psf)

    # ADM set up an array to hold coordinates around the mask peripheries.
    ras, decs = np.array([]), np.array([])

    # ADM generate the safe location for circular masks (which is quicker).
    if len(w_circle[0]) > 0:
        circras, circdecs = circle_boundaries(sourcemask[w_circle]["RA"],
                                              sourcemask[w_circle]["DEC"],
                                              radius[w_circle], Nsafe[w_circle])
        ras, decs = np.concatenate((ras, circras)), np.concatenate((decs, circdecs))

    # ADM generate the safe location for elliptical masks
    # ADM (which is slower as it requires a loop).
    if len(w_ellipse[0]) > 0:
        for w in w_ellipse[0]:
            ellras, elldecs = ellipse_boundary(sourcemask[w]["RA"],
                                               sourcemask[w]["DEC"], radius[w],
                                               sourcemask[w]["E1"],
                                               sourcemask[w]["E2"], Nsafe[w])
            ras, decs = np.concatenate((ras, ellras)), np.concatenate((decs, elldecs))

    return ras, decs
Example #2
0
def isSV0_BGS(rflux=None, objtype=None, primary=None):
    """Simplified SV-like Bright Galaxy Survey selection (for MzLS/BASS imaging).

    Parameters
    ----------
    rflux : :class:`array_like` or :class:`None`
        Galactic-extinction-corrected flux in nano-maggies in r-band.
    objtype : :class:`array_like` or :class:`None`
        The Legacy Surveys `TYPE`.
    primary : :class:`array_like` or :class:`None`
        ``True`` for objects that should be passed through the selection.

    Returns
    -------
    :class:`array_like`
        ``True`` if and only if the object is an initial BGS target for SV.

    Notes
    -----
    - Returns the equivalent of a combination of the "bright" and "faint"
      BGS SV classes from version 37 (02/05/19) of `the SV wiki`_ without
      some of the more complex flag cuts.
    """
    if primary is None:
        primary = np.ones_like(rflux, dtype='?')
    isbgs = primary.copy()

    # ADM simple selection is objects brighter than r of 20.1...
    isbgs &= rflux > 10**((22.5 - 20.1) / 2.5)
    # ADM ...that are not point-like.
    isbgs &= ~_psflike(objtype)

    return isbgs
Example #3
0
def isSV0_BGS(rflux=None, objtype=None, primary=None):
    """Initial SV-like Bright Galaxy Survey selection (for MzLS/BASS imaging).

    Parameters
    ----------
    rflux : :class:`array_like` or :class:`None`
        Galactic-extinction-corrected flux in nano-maggies in r-band.
    objtype : :class:`array_like` or :class:`None`
        The Legacy Surveys `TYPE`.
    primary : :class:`array_like` or :class:`None`
        ``True`` for objects that should be passed through the selection.

    Returns
    -------
    :class:`array_like`
        ``True`` if and only if the object is an initial BGS target for SV.

    Notes
    -----
    - Returns the equivalent of ALL BGS classes (for the northern imaging).
    """
    if primary is None:
        primary = np.ones_like(rflux, dtype='?')
    isbgs = primary.copy()

    # ADM simple selection is objects brighter than r of 20...
    isbgs &= rflux > 10**((22.5 - 20.0) / 2.5)
    # ADM ...that are not point-like.
    isbgs &= ~_psflike(objtype)

    return isbgs
Example #4
0
def collect_bright_stars(
        bands,
        maglim,
        numproc=4,
        rootdirname='/global/project/projectdirs/cosmo/data/legacysurvey/dr3.1/sweep/3.1',
        outfilename=None):
    """Extract a structure from the sweeps containing only bright stars in a given band to a given magnitude limit.

    Parameters
    ----------
    bands : :class:`str`
        A magnitude band from the sweeps, e.g., "G", "R", "Z".
        Can pass multiple bands as string, e.g. "GRZ", in which case maglim has to be a
        list of the same length as the string.
    maglim : :class:`float`
        The upper limit in that magnitude band for which to assemble a list of bright stars.
        Can pass a list of magnitude limits, in which case bands has to be a string of the
        same length (e.g., "GRZ" for [12.3,12.7,12.6]
    numproc : :class:`int`, optional
        Number of processes over which to parallelize
    rootdirname : :class:`str`, optional, defaults to dr3
        Root directory containing either sweeps or tractor files...e.g. for dr3 this might be
        /global/project/projectdirs/cosmo/data/legacysurvey/dr3/sweep/dr3.1
    outfilename : :class:`str`, optional, defaults to not writing anything to file
        (FITS) File name to which to write the output structure of bright stars

    Returns
    -------
    :class:`recarray`
        The structure of bright stars from the sweeps limited in the passed band(s) to the
        passed maglim(s).
    """
    # ADM set up default logger.
    from desiutil.log import get_logger
    log = get_logger()

    # ADM this is just a special case of collect_bright_sources.
    sourcestruc = collect_bright_sources(bands,
                                         maglim,
                                         numproc=numproc,
                                         rootdirname=rootdirname,
                                         outfilename=None)
    # ADM check if a source is unresolved.
    psflike = _psflike(sourcestruc["TYPE"])
    wstar = np.where(psflike)
    if len(wstar[0]) > 0:
        done = sourcestruc[wstar]
        if outfilename is not None:
            fitsio.write(outfilename, done, clobber=True)
        return done
    else:
        log.error(
            'No PSF-like objects brighter than {} in {} in files in {}'.format(
                str(maglim), bands, rootdirname))
        return -1
Example #5
0
def passesSTD_logic(gfracflux=None,
                    rfracflux=None,
                    zfracflux=None,
                    objtype=None,
                    gaia=None,
                    pmra=None,
                    pmdec=None,
                    aen=None,
                    dupsource=None,
                    paramssolved=None,
                    primary=None):
    """The default logic/mask cuts for commissioning stars.

    Parameters
    ----------
    gfracflux, rfracflux, zfracflux : :class:`array_like` or :class:`None`
        Profile-weighted fraction of the flux from other sources divided
        by the total flux in g, r and z bands.
    objtype : :class:`array_like` or :class:`None`
        The Legacy Surveys `TYPE` to restrict to point sources.
    gaia : :class:`boolean array_like` or :class:`None`
        ``True`` if there is a match between this object in the Legacy
        Surveys and in Gaia.
    pmra, pmdec : :class:`array_like` or :class:`None`
        Gaia-based proper motion in RA and Dec.
    aen : :class:`array_like` or :class:`None`
        Gaia Astrometric Excess Noise.
    dupsource : :class:`array_like` or :class:`None`
        Whether the source is a duplicate in Gaia.
    paramssolved : :class:`array_like` or :class:`None`
        How many parameters were solved for in Gaia.
    primary : :class:`array_like` or :class:`None`
        ``True`` for objects that should be passed through the selection.

    Returns
    -------
    :class:`array_like`
        ``True`` if and only if the object passes the logic cuts for cmx stars.

    Notes
    -----
    - This version (08/30/18) is version 4 on `the cmx wiki`_.
    - All Gaia quantities are as in `the Gaia data model`_.
    """
    if primary is None:
        primary = np.ones_like(gaia, dtype='?')

    std = primary.copy()

    # ADM A point source with a Gaia match.
    std &= _psflike(objtype)
    std &= gaia

    # ADM An Isolated source.
    fracflux = [gfracflux, rfracflux, zfracflux]
    with warnings.catch_warnings():
        warnings.simplefilter('ignore')  # fracflux can be Inf/NaN.
        for bandint in (0, 1, 2):  # g, r, z.
            std &= fracflux[bandint] < 0.01

    # ADM No obvious issues with the astrometry.
    std &= (aen < 1) & (paramssolved == 31)

    # ADM Finite proper motions.
    std &= np.isfinite(pmra) & np.isfinite(pmdec)

    # ADM Unique source (not a duplicated source).
    std &= ~dupsource

    return std
Example #6
0
def isSV0_STD(gflux=None,
              rflux=None,
              zflux=None,
              primary=None,
              gfracflux=None,
              rfracflux=None,
              zfracflux=None,
              gfracmasked=None,
              rfracmasked=None,
              zfracmasked=None,
              gnobs=None,
              rnobs=None,
              znobs=None,
              gfluxivar=None,
              rfluxivar=None,
              zfluxivar=None,
              objtype=None,
              gaia=None,
              astrometricexcessnoise=None,
              paramssolved=None,
              pmra=None,
              pmdec=None,
              parallax=None,
              dupsource=None,
              gaiagmag=None,
              gaiabmag=None,
              gaiarmag=None,
              bright=False):
    """Select STD targets using color cuts and photometric quality cuts.

    Parameters
    ----------
    bright : :class:`boolean`, defaults to ``False``
        if ``True`` apply magnitude cuts for "bright" conditions; otherwise,
        choose "normal" brightness standards. Cut is performed on `gaiagmag`.

    Returns
    -------
    :class:`array_like`
        ``True`` if and only if the object is a STD star.

    Notes
    -----
    - This version (11/05/18) is version 24 on `the SV wiki`_.
    - See :func:`~desitarget.cuts.set_target_bits` for other parameters.
    """
    if primary is None:
        primary = np.ones_like(gflux, dtype='?')
    std = primary.copy()

    # ADM apply type=PSF cut.
    std &= _psflike(objtype)

    # ADM apply fracflux, S/N cuts and number of observations cuts.
    fracflux = [gfracflux, rfracflux, zfracflux]
    fluxivar = [gfluxivar, rfluxivar, zfluxivar]
    nobs = [gnobs, rnobs, znobs]
    fracmasked = [gfracmasked, rfracmasked, zfracmasked]
    with warnings.catch_warnings():
        warnings.simplefilter('ignore')  # fracflux can be Inf/NaN
        for bandint in (0, 1, 2):  # g, r, z
            std &= fracflux[bandint] < 0.01
            std &= fluxivar[bandint] > 0
            std &= nobs[bandint] > 0
            std &= fracmasked[bandint] < 0.6

    # ADM apply the Legacy Surveys (optical) magnitude and color cuts.
    std &= isSTD_colors(primary=primary, zflux=zflux, rflux=rflux, gflux=gflux)

    # ADM apply the Gaia quality cuts.
    std &= isSTD_gaia(primary=primary,
                      gaia=gaia,
                      astrometricexcessnoise=astrometricexcessnoise,
                      pmra=pmra,
                      pmdec=pmdec,
                      parallax=parallax,
                      dupsource=dupsource,
                      paramssolved=paramssolved,
                      gaiagmag=gaiagmag,
                      gaiabmag=gaiabmag,
                      gaiarmag=gaiarmag)

    # ADM brightness cuts in Gaia G-band.
    if bright:
        gbright, gfaint = 15., 18.
    else:
        gbright, gfaint = 16., 19.

    std &= gaiagmag >= gbright
    std &= gaiagmag < gfaint

    return std
Example #7
0
def isSV0_QSO(gflux=None,
              rflux=None,
              zflux=None,
              w1flux=None,
              w2flux=None,
              objtype=None,
              release=None,
              dchisq=None,
              maskbits=None,
              primary=None):
    """Early SV QSO target class using random forest. Returns a boolean array.

    Parameters
    ----------
    - See :func:`~desitarget.cuts.set_target_bits` for other parameters.

    Returns
    -------
    :class:`array_like`
        ``True`` for objects that pass the quasar color/morphology/logic cuts.

    Notes
    -----
    - This version (06/05/19) is version 68 on `the SV wiki`_.
    """
    # BRICK_PRIMARY
    if primary is None:
        primary = np.ones_like(gflux, dtype=bool)

    # Build variables for random forest.
    nFeatures = 11  # Number of attributes describing each object to be classified by the rf.
    nbEntries = rflux.size
    # ADM shift the northern photometry to the southern system.
    # ADM we don't need to exactly correspond to SV for SV0.
    # gflux, rflux, zflux = shift_photo_north(gflux, rflux, zflux)

    # ADM photOK here should ensure (g > 0.) & (r > 0.) & (z > 0.) & (W1 > 0.) & (W2 > 0.)
    colors, r, photOK = _getColors(nbEntries, nFeatures, gflux, rflux, zflux,
                                   w1flux, w2flux)
    r = np.atleast_1d(r)

    # ADM Preselection to speed up the process
    rMax = 23.0  # r < 23.0 (different for SV)
    rMin = 17.5  # r > 17.5
    preSelection = (r < rMax) & (r > rMin) & photOK & primary

    # ADM relaxed morphology cut for SV.
    # ADM we never target sources with dchisq[..., 0] = 0, so force
    # ADM those to have large values of morph2 to avoid divide-by-zero.
    d1, d0 = dchisq[..., 1], dchisq[..., 0]
    bigmorph = np.array(np.zeros_like(d0) + 1e9)
    dcs = np.divide(d1 - d0, d0, out=bigmorph, where=d0 != 0)
    morph2 = dcs < 0.02
    preSelection &= _psflike(objtype) | morph2

    # ADM Reject objects in masks.
    # ADM BRIGHT BAILOUT GALAXY CLUSTER (1, 10, 12, 13) bits not set.
    if maskbits is not None:
        for bit in [1, 10, 12, 13]:
            preSelection &= ((maskbits & 2**bit) == 0)

    # "qso" mask initialized to "preSelection" mask
    qso = np.copy(preSelection)

    if np.any(preSelection):

        from desitarget.myRF import myRF

        # Data reduction to preselected objects
        colorsReduced = colors[preSelection]
        r_Reduced = r[preSelection]
        colorsIndex = np.arange(0, nbEntries, dtype=np.int64)
        colorsReducedIndex = colorsIndex[preSelection]

        # Path to random forest files
        pathToRF = resource_filename('desitarget', 'data')
        # ADM Use RF trained over DR7
        rf_fileName = pathToRF + '/rf_model_dr7.npz'
        rf_HighZ_fileName = pathToRF + '/rf_model_dr7_HighZ.npz'

        # rf initialization - colors data duplicated within "myRF"
        rf = myRF(colorsReduced, pathToRF, numberOfTrees=500, version=2)
        rf_HighZ = myRF(colorsReduced, pathToRF, numberOfTrees=500, version=2)
        # rf loading
        rf.loadForest(rf_fileName)
        rf_HighZ.loadForest(rf_HighZ_fileName)
        # Compute rf probabilities
        tmp_rf_proba = rf.predict_proba()
        tmp_rf_HighZ_proba = rf_HighZ.predict_proba()
        # Compute optimized proba cut (all different for SV/main).
        pcut = np.where(r_Reduced > 20.0, 0.65 - (r_Reduced - 20.0) * 0.075,
                        0.65)
        pcut[r_Reduced > 22.0] = 0.50 - 0.25 * (r_Reduced[r_Reduced > 22.0] -
                                                22.0)
        pcut_HighZ = np.where(r_Reduced > 20.5,
                              0.5 - (r_Reduced - 20.5) * 0.025, 0.5)

        # Add rf proba test result to "qso" mask
        qso[colorsReducedIndex] = \
            (tmp_rf_proba >= pcut) | (tmp_rf_HighZ_proba >= pcut_HighZ)

    # In case of call for a single object passed to the function with scalar arguments
    # Return "numpy.bool_" instead of "numpy.ndarray"
    if nbEntries == 1:
        qso = qso[0]

    return qso
Example #8
0
def isSV0_MWS(rflux=None,
              obs_rflux=None,
              objtype=None,
              paramssolved=None,
              gaiagmag=None,
              gaiabmag=None,
              gaiarmag=None,
              parallaxerr=None,
              pmra=None,
              pmdec=None,
              parallax=None,
              parallaxovererror=None,
              photbprpexcessfactor=None,
              astrometricsigma5dmax=None,
              gaiaaen=None,
              galb=None,
              gaia=None,
              primary=None):
    """Initial SV-like Milky Way Survey selections (MzLS/BASS imaging).

    Parameters
    ----------
    - See :func:`~desitarget.cuts.set_target_bits` for parameters.

    Returns
    -------
    :class:`array_like`
        ``True`` if and only if the object is a MWS_MAIN or MWS_NEARBY
        target from early SV/main survey classes.
    :class:`array_like`
        ``True`` if and only if the object is an early-SV/main survey
        MWS_WD target.

    Notes
    -----
    - All Gaia quantities are as in `the Gaia data model`_.
    - Returns the equivalent of PRIMARY target classes from version 181
      (07/07/19) of `the wiki`_ (the main survey wiki). Ignores target
      classes that "smell" like secondary targets (as they are outside
      of the footprint or are based on catalog-matching). Simplifies flag
      cuts, and simplifies the MWS_MAIN class to not include sub-classes.
    """
    if primary is None:
        primary = np.ones_like(rflux, dtype='?')
    ismws = primary.copy()
    isnear = primary.copy()
    iswd = primary.copy()

    # ADM apply the selection for all MWS-MAIN targets.
    # ADM main targets match to a Gaia source.
    ismws &= gaia
    # ADM main targets are point-like.
    ismws &= _psflike(objtype)
    # ADM main targets are 16 <= r < 19.
    ismws &= rflux > 10**((22.5 - 19.0) / 2.5)
    ismws &= rflux <= 10**((22.5 - 16.0) / 2.5)
    # ADM main targets are robs < 20.
    ismws &= obs_rflux > 10**((22.5 - 20.0) / 2.5)

    # ADM apply the selection for MWS-NEARBY targets.
    # ADM must be a Legacy Surveys object that matches a Gaia source.
    isnear &= gaia
    # ADM Gaia G mag of less than 20.
    isnear &= gaiagmag < 20.
    # ADM parallax cut corresponding to 100pc.
    isnear &= (parallax + parallaxerr) > 10.
    # ADM all astrometric parameters were measured.
    isnear &= paramssolved == 31

    # ADM do not target any WDs for which entries are NaN
    # ADM and turn off the NaNs for those entries.
    if photbprpexcessfactor is not None:
        nans = (np.isnan(gaiagmag) | np.isnan(gaiabmag) | np.isnan(gaiarmag)
                | np.isnan(parallax) | np.isnan(photbprpexcessfactor))
    else:
        nans = (np.isnan(gaiagmag) | np.isnan(gaiabmag) | np.isnan(gaiarmag)
                | np.isnan(parallax))
    w = np.where(nans)[0]
    if len(w) > 0:
        parallax, gaiagmag = parallax.copy(), gaiagmag.copy()
        gaiabmag, gaiarmag = gaiabmag.copy(), gaiarmag.copy()
        if photbprpexcessfactor is not None:
            photbprpexcessfactor = photbprpexcessfactor.copy()
            # ADM safe to make these zero regardless of cuts as...
            photbprpexcessfactor[w] = 0.
        parallax[w] = 0.
        gaiagmag[w], gaiabmag[w], gaiarmag[w] = 0., 0., 0.
        # ADM ...we'll turn off all bits here.
        iswd &= ~nans

    # ADM apply the selection for MWS-WD targets.
    # ADM must be a Legacy Surveys object that matches a Gaia source.
    iswd &= gaia
    # ADM all astrometric parameters were measured.
    iswd &= paramssolved == 31
    # ADM Gaia G mag of less than 20.
    iswd &= gaiagmag < 20.
    # ADM Galactic b at least 20o from the plane.
    iswd &= np.abs(galb) > 20.
    # ADM gentle cut on parallax significance.
    iswd &= parallaxovererror > 1.
    # ADM Color/absolute magnitude cuts of (defining the WD cooling sequence):
    # ADM Gabs > 5.
    # ADM Gabs > 5.93 + 5.047(Bp-Rp).
    # ADM Gabs > 6(Bp-Rp)3 - 21.77(Bp-Rp)2 + 27.91(Bp-Rp) + 0.897
    # ADM Bp-Rp < 1.7
    Gabs = gaiagmag + 5. * np.log10(parallax.clip(1e-16)) - 10.
    br = gaiabmag - gaiarmag
    iswd &= Gabs > 5.
    iswd &= Gabs > 5.93 + 5.047 * br
    iswd &= Gabs > 6 * br * br * br - 21.77 * br * br + 27.91 * br + 0.897
    iswd &= br < 1.7

    # ADM Finite proper motion to reject quasars.
    # ADM Inexplicably I'm getting a Runtimewarning here for
    # ADM a few values in the sqrt, so I'm catching it.
    with warnings.catch_warnings():
        warnings.simplefilter("ignore")
        pm = np.sqrt(pmra**2. + pmdec**2.)
    iswd &= pm > 2.

    # ADM As of DR7, photbprpexcessfactor and astrometricsigma5dmax are not in the
    # ADM imaging catalogs. Until they are, ignore these cuts.
    if photbprpexcessfactor is not None:
        # ADM remove problem objects, which often have bad astrometry.
        iswd &= photbprpexcessfactor < 1.7 + 0.06 * br * br

    if astrometricsigma5dmax is not None:
        # ADM Reject white dwarfs that have really poor astrometry while.
        # ADM retaining white dwarfs that only have relatively poor astrometry.
        iswd &= ((astrometricsigma5dmax < 1.5) |
                 ((gaiaaen < 1.) & (parallaxovererror > 4.) & (pm > 10.)))

    # ADM return any object that passes the MWS cuts.
    return ismws | isnear, iswd
Example #9
0
def isSV0_MWS(rflux=None,
              obs_rflux=None,
              objtype=None,
              gaiagmag=None,
              gaiabmag=None,
              gaiarmag=None,
              pmra=None,
              pmdec=None,
              parallax=None,
              parallaxovererror=None,
              photbprpexcessfactor=None,
              astrometricsigma5dmax=None,
              galb=None,
              gaia=None,
              primary=None):
    """Initial SV-like Milky Way Survey selection (for MzLS/BASS imaging).

    Parameters
    ----------
    rflux, obs_rflux : :class:`array_like` or :class:`None`
        Flux in nano-maggies in r-band, with (`rflux`) and
        without (`obs_rflux`) Galactic extinction correction.
    objtype : :class:`array_like` or :class:`None`
        The Legacy Surveys `TYPE` to restrict to point sources.
    gaiagmag, gaiabmag, gaiarmag : :class:`array_like` or :class:`None`
        Gaia-based g-, b- and r-band MAGNITUDES.
    pmra, pmdec, parallax : :class:`array_like` or :class:`None`
        Gaia-based proper motion in RA and Dec, and parallax.
    parallaxovererror : :class:`array_like` or :class:`None`
        Gaia-based parallax/error.
    photbprpexcessfactor : :class:`array_like` or :class:`None`
        Gaia_based BP/RP excess factor.
    astrometricsigma5dmax : :class:`array_like` or :class:`None`
        Longest semi-major axis of 5-d error ellipsoid.
    galb: : :class:`array_like` or :class:`None`
        Galactic latitude (degrees).
    gaia : :class:`boolean array_like` or :class:`None`
        ``True`` if there is a match between this object in the Legacy
        Surveys and in Gaia.
    primary : :class:`array_like` or :class:`None`
        ``True`` for objects that should be passed through the selection.

    Returns
    -------
    :class:`array_like`
        ``True`` if and only if the object is an initial MWS target for SV.

    Notes
    -----
    - Returns the equivalent of ALL MWS classes (for the northern imaging).
    - All Gaia quantities are as in `the Gaia data model`_.
    """
    if primary is None:
        primary = np.ones_like(rflux, dtype='?')
    ismws = primary.copy()
    isnear = primary.copy()
    iswd = primary.copy()

    # ADM apply the selection for all MWS-MAIN targets.
    # ADM main targets match to a Gaia source.
    ismws &= gaia
    # ADM main targets are point-like.
    ismws &= _psflike(objtype)
    # ADM main targets are 16 <= r < 19.
    ismws &= rflux > 10**((22.5 - 19.0) / 2.5)
    ismws &= rflux <= 10**((22.5 - 16.0) / 2.5)
    # ADM main targets are robs < 20.
    ismws &= obs_rflux > 10**((22.5 - 20.0) / 2.5)

    # ADM apply the selection for MWS-NEARBY targets.
    # ADM must be a Legacy Surveys object that matches a Gaia source.
    isnear &= gaia
    # ADM Gaia G mag of less than 20.
    isnear &= gaiagmag < 20.
    # ADM parallax cut corresponding to 100pc.
    isnear &= parallax > 10.
    # ADM NOTE TO THE MWS GROUP: There is no bright cut on G. IS THAT THE REQUIRED BEHAVIOR?

    # ADM apply the selection for MWS-WD targets.
    # ADM must be a Legacy Surveys object that matches a Gaia source.
    iswd &= gaia
    # ADM Gaia G mag of less than 20.
    iswd &= gaiagmag < 20.
    # ADM Galactic b at least 20o from the plane.
    iswd &= np.abs(galb) > 20.
    # ADM gentle cut on parallax significance.
    iswd &= parallaxovererror > 1.
    # ADM Color/absolute magnitude cuts of (defining the WD cooling sequence):
    # ADM Gabs > 5.
    # ADM Gabs > 5.93 + 5.047(Bp-Rp).
    # ADM Gabs > 6(Bp-Rp)3 - 21.77(Bp-Rp)2 + 27.91(Bp-Rp) + 0.897
    # ADM Bp-Rp < 1.7
    Gabs = gaiagmag + 5. * np.log10(parallax.clip(1e-16)) - 10.
    br = gaiabmag - gaiarmag
    iswd &= Gabs > 5.
    iswd &= Gabs > 5.93 + 5.047 * br
    iswd &= Gabs > 6 * br * br * br - 21.77 * br * br + 27.91 * br + 0.897
    iswd &= br < 1.7
    # ADM Finite proper motion to reject quasars.
    pm = np.sqrt(pmra**2. + pmdec**2.)
    iswd &= pm > 2.

    # ADM As of DR7, photbprpexcessfactor and astrometricsigma5dmax are not in the
    # ADM imaging catalogs. Until they are, ignore these cuts.
    if photbprpexcessfactor is not None:
        # ADM remove problem objects, which often have bad astrometry.
        iswd &= photbprpexcessfactor < 1.7 + 0.06 * br * br

    if astrometricsigma5dmax is not None:
        # ADM Reject white dwarfs that have really poor astrometry while.
        # ADM retaining white dwarfs that only have relatively poor astrometry.
        iswd &= ((astrometricsigma5dmax < 1.5) |
                 ((astrometricexcessnoise < 1.) & (parallaxovererror > 4.) &
                  (pm > 10.)))

    # ADM return any object that passes any of the MWS cuts.
    return ismws | isnear | iswd
Example #10
0
def is_in_bright_mask(targs, sourcemask, inonly=False):
    """Determine whether a set of targets is in a bright star mask.

    Parameters
    ----------
    targs : :class:`recarray`
        A recarray of targets, skies etc., as made by, e.g.,
        :func:`desitarget.cuts.select_targets()`.
    sourcemask : :class:`recarray`
        A recarray containing a mask as made by, e.g.,
        :func:`desitarget.brightmask.make_bright_star_mask()`
    inonly : :class:`boolean`, optional, defaults to False
        If ``True``, then only calculate the `in_mask` return but not
        the `near_mask` return, which is about a factor of 2 faster.

    Returns
    -------
    :class:`list`
        [`in_mask`, `near_mask`] where `in_mask` (`near_mask`) is a
        boolean array that is ``True`` for `targs` that are IN (NEAR) a
        mask. If `inonly` is ``True`` then this is just [`in_mask`].
    :class: `list`
        [`used_in_mask`, `used_near_mask`] where `used_in_mask`
        (`used_near_mask`) is a boolean array that is ``True`` for masks
        in `sourcemask` that contain a target at the IN (NEAR) radius.
        If `inonly` is ``True`` then this is just [`used_in_mask`].
    """
    t0 = time()

    # ADM initialize arrays of all False (nothing is yet in a mask).
    in_mask = np.zeros(len(targs), dtype=bool)
    near_mask = np.zeros(len(targs), dtype=bool)
    used_in_mask = np.zeros(len(sourcemask), dtype=bool)
    used_near_mask = np.zeros(len(sourcemask), dtype=bool)

    # ADM turn the mask and target coordinates into SkyCoord objects.
    ctargs = SkyCoord(targs["RA"]*u.degree, targs["DEC"]*u.degree)
    cmask = SkyCoord(sourcemask["RA"]*u.degree, sourcemask["DEC"]*u.degree)

    # ADM this is the largest search radius we should need to consider.
    # ADM In the future an obvious speed up is to split on radius
    # ADM as large radii are rarer but take longer.
    maxrad = max(sourcemask["IN_RADIUS"])*u.arcsec
    if not inonly:
        maxrad = max(sourcemask["NEAR_RADIUS"])*u.arcsec

    # ADM coordinate match the masks and the targets.
    # ADM assuming all of the masks are circles-on-the-sky.
    idtargs, idmask, d2d, d3d = cmask.search_around_sky(ctargs, maxrad)

    # ADM catch the case where nothing fell in a mask.
    if len(idmask) == 0:
        if inonly:
            return [in_mask], [used_in_mask]
        return [in_mask, near_mask], [used_in_mask, used_near_mask]

    # ADM need to differentiate targets that are in ellipse-on-the-sky
    # ADM masks from targets that are in circle-on-the-sky masks.
    rex_or_psf = _rexlike(sourcemask[idmask]["TYPE"]) | _psflike(
        sourcemask[idmask]["TYPE"])
    w_ellipse = np.where(~rex_or_psf)

    # ADM only continue if there are any elliptical masks.
    if len(w_ellipse[0]) > 0:
        idelltargs = idtargs[w_ellipse]
        idellmask = idmask[w_ellipse]

        log.info('Testing {} targets against {} elliptical masks...t={:.1f}s'
                 .format(len(set(idelltargs)), len(set(idellmask)), time()-t0))

        # ADM to speed the calculation, make a dictionary of which
        # ADM targets (the values) associate with each mask (the keys).
        targidineachmask = {}
        # ADM first initiate a list for each relevant key (mask ID).
        for maskid in set(idellmask):
            targidineachmask[maskid] = []
        # ADM then append those lists until they contain the IDs of each
        # ADM relevant target as the values.
        for index, targid in enumerate(idelltargs):
            targidineachmask[idellmask[index]].append(targid)

        # ADM loop through the masks and determine which relevant points
        # ADM occupy them for both the IN_RADIUS and the NEAR_RADIUS.
        for maskid in targidineachmask:
            targids = targidineachmask[maskid]
            ellras, elldecs = targs[targids]["RA"], targs[targids]["DEC"]
            mask = sourcemask[maskid]
            # ADM Refine being in a mask based on the elliptical masks.
            in_ell = is_in_ellipse(
                ellras, elldecs, mask["RA"], mask["DEC"],
                mask["IN_RADIUS"], mask["E1"], mask["E2"])
            in_mask[targids] |= in_ell
            used_in_mask[maskid] |= np.any(in_ell)
            if not inonly:
                in_ell = is_in_ellipse(ellras, elldecs,
                                       mask["RA"], mask["DEC"],
                                       mask["NEAR_RADIUS"],
                                       mask["E1"], mask["E2"])
                near_mask[targids] |= in_ell
                used_near_mask[maskid] |= np.any(in_ell)

        log.info('Done with elliptical masking...t={:1f}s'.format(time()-t0))

    # ADM Finally, record targets in a circles-on-the-sky mask, which
    # ADM trumps any information about just being in an elliptical mask.
    # ADM Find separations less than the mask radius for circle masks
    # ADM matches meeting these criteria are in at least one circle mask.
    w_in = (d2d.arcsec < sourcemask[idmask]["IN_RADIUS"]) & (rex_or_psf)
    in_mask[idtargs[w_in]] = True
    used_in_mask[idmask[w_in]] = True

    if not inonly:
        w_near = (d2d.arcsec < sourcemask[idmask]["NEAR_RADIUS"]) & (rex_or_psf)
        near_mask[idtargs[w_near]] = True
        used_near_mask[idmask[w_near]] = True
        return [in_mask, near_mask], [used_in_mask, used_near_mask]

    return [in_mask], [used_in_mask]
Example #11
0
def notinBGS_mask(gflux=None,
                  rflux=None,
                  zflux=None,
                  gnobs=None,
                  rnobs=None,
                  znobs=None,
                  primary=None,
                  gfracmasked=None,
                  rfracmasked=None,
                  zfracmasked=None,
                  gfracflux=None,
                  rfracflux=None,
                  zfracflux=None,
                  gfracin=None,
                  rfracin=None,
                  zfracin=None,
                  w1snr=None,
                  gfluxivar=None,
                  rfluxivar=None,
                  zfluxivar=None,
                  Grr=None,
                  gaiagmag=None,
                  maskbits=None,
                  objtype=None,
                  targtype=None):
    """
    Standard set of masking cuts used by all BGS target selection classes
    (see, e.g., :func:`~desitarget.cuts.isBGS_faint` for parameters).
    """
    _check_BGS_targtype_sv(targtype)

    if primary is None:
        primary = np.ones_like(gnobs, dtype='?')

    bgs_qcs = primary.copy()
    bgs = primary.copy()

    # quality cuts definitions
    bgs_qcs &= (gnobs >= 1) & (rnobs >= 1) & (znobs >= 1)
    bgs_qcs &= (gfracmasked < 0.4) & (rfracmasked < 0.4) & (zfracmasked < 0.4)
    bgs_qcs &= (gfracflux < 5.0) & (rfracflux < 5.0) & (zfracflux < 5.0)
    bgs_qcs &= (gfracin > 0.3) & (rfracin > 0.3) & (zfracin > 0.3)
    bgs_qcs &= (gfluxivar > 0) & (rfluxivar > 0) & (zfluxivar > 0)
    bgs_qcs &= (maskbits & 2**1) == 0

    # color box
    bgs_qcs &= rflux > gflux * 10**(-1.0 / 2.5)
    bgs_qcs &= rflux < gflux * 10**(4.0 / 2.5)
    bgs_qcs &= zflux > rflux * 10**(-1.0 / 2.5)
    bgs_qcs &= zflux < rflux * 10**(4.0 / 2.5)

    if targtype == 'lowq':
        bgs &= Grr > 0.6
        bgs |= gaiagmag == 0
        bgs |= (Grr < 0.6) & (~_psflike(objtype)) & (gaiagmag != 0)
        bgs &= ~bgs_qcs

    else:
        bgs &= Grr > 0.6
        bgs |= gaiagmag == 0
        bgs |= (Grr < 0.6) & (~_psflike(objtype)) & (gaiagmag != 0)
        bgs &= bgs_qcs

    return bgs
Example #12
0
def set_BGSMASKBITS(gflux=None,
                    rflux=None,
                    zflux=None,
                    gnobs=None,
                    rnobs=None,
                    znobs=None,
                    primary=None,
                    gfracmasked=None,
                    rfracmasked=None,
                    zfracmasked=None,
                    gfracflux=None,
                    rfracflux=None,
                    zfracflux=None,
                    gfracin=None,
                    rfracin=None,
                    zfracin=None,
                    gfluxivar=None,
                    rfluxivar=None,
                    zfluxivar=None,
                    Grr=None,
                    gaiagmag=None,
                    maskbits=None,
                    objtype=None,
                    gaia_aen=None):
    """                                                                                                                                                                                                                                 
    MASKBITS equivalents for quality cuts applied. 
    """
    result = np.zeros_like(gflux, dtype=np.int16)

    # quality cuts definitions
    result[~((gnobs >= 1) & (rnobs >= 1) &
             (znobs >= 1))] += BGS_MASKBITS['BMB_NOBS']
    result[~((gfracmasked < 0.4) & (rfracmasked < 0.4) &
             (zfracmasked < 0.4))] += BGS_MASKBITS['BMB_FRACMASK']
    result[~((gfracflux < 5.0) & (rfracflux < 5.0) &
             (zfracflux < 5.0))] += BGS_MASKBITS['BMB_FRACFLUX']
    result[~((gfracin > 0.3) & (rfracin > 0.3) &
             (zfracin > 0.3))] += BGS_MASKBITS['BMB_FRACIN']
    result[~((gfluxivar > 0) & (rfluxivar > 0) &
             (zfluxivar > 0))] += BGS_MASKBITS['BMB_FLUXIVAR']
    result[~((maskbits & 2**1) == 0)] += BGS_MASKBITS['BMB_BRIGHTMASK']

    # color box
    result[~(rflux > gflux * 10**(-1.0 / 2.5))] += BGS_MASKBITS['BMB_RMGLO']
    result[~(rflux < gflux * 10**(4.0 / 2.5))] += BGS_MASKBITS['BMB_RMGHI']
    result[~(zflux > rflux * 10**(-1.0 / 2.5))] += BGS_MASKBITS['BMB_ZMRLO']
    result[~(zflux < rflux * 10**(4.0 / 2.5))] += BGS_MASKBITS['BMB_ZMRHI']

    result[((gaiagmag > 0.0) & (Grr <= 0.6))] += BGS_MASKBITS['BMB_GRR']
    result[~(gaiagmag == 0.0)] += BGS_MASKBITS['BMB_ZEROGAIAMAG']
    result[~(~_psflike(objtype))] += BGS_MASKBITS['BMB_PSF']

    ispsf_byaen = (gaiagmag < 18) & (gaia_aen < 10.**0.5)
    ispsf_byaen = ispsf_byaen | ((gaiagmag >= 18) &
                                 (gaia_aen < 10.**(0.5 + 0.2 *
                                                   (gaiagmag - 18.))))

    ##  Is is GAIA to start.
    ispsf_byaen = ispsf_byaen & (gaiagmag > 0.0)

    result[ispsf_byaen] += BGS_MASKBITS['BMB_AEN']

    return result
Example #13
0
def is_in_bright_mask(targs, sourcemask, inonly=False):
    """Determine whether a set of targets is in a bright source mask.

    Parameters
    ----------
    targs : :class:`recarray`
        A recarray of targets as made by, e.g., :mod:`desitarget.cuts.select_targets`.
    sourcemask : :class:`recarray`
        A recarray containing a mask as made by, e.g.,
        :mod:`desitarget.brightmask.make_bright_star_mask` or
        :mod:`desitarget.brightmask.make_bright_source_mask`.
    inonly : :class:`boolean`, optional, defaults to False
        If True, then only calculate the in_mask return but not the near_mask return,
        which is about a factor of 2 faster.

    Returns
    -------
    in_mask : array_like.
        ``True`` for array entries that correspond to a target that is IN a mask.
    near_mask : array_like.
        ``True`` for array entries that correspond to a target that is NEAR a mask.
    """

    t0 = time()

    # ADM set up default logger.
    from desiutil.log import get_logger
    log = get_logger()

    # ADM initialize an array of all False (nothing is yet in a mask).
    in_mask = np.zeros(len(targs), dtype=bool)
    near_mask = np.zeros(len(targs), dtype=bool)

    # ADM turn the coordinates of the masks and the targets into SkyCoord objects.
    ctargs = SkyCoord(targs["RA"] * u.degree, targs["DEC"] * u.degree)
    cmask = SkyCoord(sourcemask["RA"] * u.degree, sourcemask["DEC"] * u.degree)

    # ADM this is the largest search radius we should need to consider
    # ADM in the future an obvious speed up is to split on radius
    # ADM as large radii are rarer but take longer.
    maxrad = max(sourcemask["IN_RADIUS"]) * u.arcsec
    if not inonly:
        maxrad = max(sourcemask["NEAR_RADIUS"]) * u.arcsec

    # ADM coordinate match the masks and the targets.
    # ADM assuming all of the masks are circles-on-the-sky.
    idtargs, idmask, d2d, d3d = cmask.search_around_sky(ctargs, maxrad)

    # ADM catch the case where nothing fell in a mask.
    if len(idmask) == 0:
        if inonly:
            return in_mask
        return in_mask, near_mask

    # ADM need to differentiate targets that are in ellipse-on-the-sky masks
    # ADM from targets that are in circle-on-the-sky masks.
    rex_or_psf = _rexlike(sourcemask[idmask]["TYPE"]) | _psflike(
        sourcemask[idmask]["TYPE"])
    w_ellipse = np.where(~rex_or_psf)

    # ADM only continue if there are any elliptical masks.
    if len(w_ellipse[0]) > 0:
        idelltargs = idtargs[w_ellipse]
        idellmask = idmask[w_ellipse]

        log.info(
            'Testing {} total targets against {} total elliptical masks...t={:.1f}s'
            .format(len(set(idelltargs)), len(set(idellmask)),
                    time() - t0))

        # ADM to speed the calculation, make a dictionary of which targets (the
        # ADM values) are associated with each mask (the keys).
        targidineachmask = {}
        # ADM first initiate a list for each relevant key (mask ID).
        for maskid in set(idellmask):
            targidineachmask[maskid] = []
        # ADM then append those lists until they contain the IDs of each
        # ADM relevant target as the values.
        for index, targid in enumerate(idelltargs):
            targidineachmask[idellmask[index]].append(targid)

        # ADM loop through the masks and determine which relevant points occupy
        # ADM them for both the IN_RADIUS and the NEAR_RADIUS.
        for maskid in targidineachmask:
            targids = targidineachmask[maskid]
            ellras, elldecs = targs[targids]["RA"], targs[targids]["DEC"]
            mask = sourcemask[maskid]
            # ADM Refine True/False for being in a mask based on the elliptical masks.
            in_mask[targids] |= is_in_ellipse(ellras, elldecs, mask["RA"],
                                              mask["DEC"], mask["IN_RADIUS"],
                                              mask["E1"], mask["E2"])
            if not inonly:
                near_mask[targids] |= is_in_ellipse(ellras, elldecs,
                                                    mask["RA"], mask["DEC"],
                                                    mask["NEAR_RADIUS"],
                                                    mask["E1"], mask["E2"])

        log.info('Done with elliptical masking...t={:1f}s'.format(time() - t0))

    # ADM finally, record targets that were in a circles-on-the-sky mask, which
    # ADM trumps any information about just being in an elliptical mask.
    # ADM find angular separations less than the mask radius for circle masks
    # ADM matches that meet these criteria are in a circle mask (at least one).
    w_in = np.where((d2d.arcsec < sourcemask[idmask]["IN_RADIUS"])
                    & rex_or_psf)
    in_mask[idtargs[w_in]] = True

    if not inonly:
        w_near = np.where((d2d.arcsec < sourcemask[idmask]["NEAR_RADIUS"])
                          & rex_or_psf)
        near_mask[idtargs[w_near]] = True
        return in_mask, near_mask

    return in_mask
Example #14
0
def make_bright_source_mask(
        bands,
        maglim,
        numproc=4,
        rootdirname='/global/project/projectdirs/cosmo/data/legacysurvey/dr5/sweep/5.0',
        infilename=None,
        outfilename=None):
    """Make a mask of bright sources from a structure of bright sources drawn from the sweeps.

    Parameters
    ----------
    bands : :class:`str`
        A magnitude band from the sweeps, e.g., "G", "R", "Z".
        Can pass multiple bands as string, e.g. ``"GRZ"``, in which case maglim has to be a
        list of the same length as the string.
    maglim : :class:`float`
        The upper limit in that magnitude band for which to assemble a list of bright sources.
        Can pass a list of magnitude limits, in which case bands has to be a string of the
        same length (e.g., ``"GRZ"`` for [12.3,12.7,12.6]).
    numproc : :class:`int`, optional
        Number of processes over which to parallelize.
    rootdirname : :class:`str`, optional, defaults to dr3
        Root directory containing either sweeps or tractor files...e.g. for dr5 this might be
        ``/global/project/projectdirs/cosmo/data/legacysurvey/dr5/sweep/dr5.0``. This is only
        used if ``infilename`` is not passed.
    infilename : :class:`str`, optional,
        if this exists, then the list of bright sources is read in from the file of this name.
        if this is not passed, then code defaults to deriving the recarray of bright sources
        from ``rootdirname`` via a call to ``collect_bright_sources``.
    outfilename : :class:`str`, optional, defaults to not writing anything to file
        (FITS) File name to which to write the output bright source mask.

    Returns
    -------
    :class:`recarray`
        - The bright source mask in the form ``RA`, ``DEC``, ``TARGETID``, ``IN_RADIUS``,
          ``NEAR_RADIUS``, ``E1``, ``E2``, ``TYPE``
          (may also be written to file if ``outfilename`` is passed).
        - ``TARGETID`` is as calculated in :mod:`desitarget.targets.encode_targetid`.
        - The radii are in ARCSECONDS (they default to equivalents of half-light radii for ellipses).
        - ``E1`` and ``E2`` are the ellipticity components as defined at the bottom of, e.g.:
          http://legacysurvey.org/dr5/catalogs/.
        - ``TYPE`` is the ``TYPE`` from the sweeps files, see, e.g.:
          http://legacysurvey.org/dr5/files/#sweep-catalogs.

    Notes
    -----
        - ``IN_RADIUS`` is a smaller radius that corresponds to the ``IN_BRIGHT_OBJECT`` bit in
          ``data/targetmask.yaml`` (and is in ARCSECONDS).
        - ``NEAR_RADIUS`` is a radius that corresponds to the ``NEAR_BRIGHT_OBJECT`` bit in
          ``data/targetmask.yaml`` (and is in ARCSECONDS).
        - Currently uses the radius-as-a-function-of-B-mag for Tycho stars from the BOSS mask
          (in every band) to set the ``NEAR_RADIUS``:
          R = (0.0802B*B - 1.860B + 11.625) (see Eqn. 9 of https://arxiv.org/pdf/1203.6594.pdf)
          and half that radius to set the ``IN_RADIUS``. We convert this from arcminutes to arcseconds.
        - It's an open question as to what the correct radii are for DESI observations.
    """

    # ADM set bands to uppercase if passed as lower case.
    bands = bands.upper()
    # ADM the band names and nobs columns as arrays instead of strings.
    bandnames = np.array(["FLUX_" + band for band in bands])
    nobsnames = np.array(["NOBS_" + band for band in bands])

    # ADM force the input maglim to be a list (in case a single value was passed).
    if isinstance(maglim, int) or isinstance(maglim, float):
        maglim = [maglim]

    if len(bandnames) != len(maglim):
        msg = "bands has to be the same length as maglim and {} does not equal {}".format(
            len(bandnames), len(maglim))
        raise IOError(msg)

    # ADM change input magnitude(s) to a flux to test against.
    fluxlim = 10.**((22.5 - np.array(maglim)) / 2.5)

    if infilename is not None:
        objs = io.read_tractor(infilename)
    else:
        objs = collect_bright_sources(bands, maglim, numproc, rootdirname,
                                      outfilename)

    # ADM write the fluxes and bands as arrays instead of named columns
    # ADM to circumvent a numpy future warning, this requires two copies of the fluxes array.
    fluxcheck = objs[bandnames].view(
        objs[bandnames].dtype[0]).reshape(objs[bandnames].shape + (-1, ))
    fluxes = fluxcheck.copy()
    nobs = objs[nobsnames].view(
        objs[nobsnames].dtype[0]).reshape(objs[nobsnames].shape + (-1, ))

    # ADM set any observations with NOBS = 0 to have small flux so glitches don't end up as bright object masks.
    w = np.where(nobs == 0)
    if len(w[0]) > 0:
        fluxes[w] = 0.

    # ADM limit to the passed faint limit.
    w = np.where(np.any(fluxes > fluxlim, axis=1))
    fluxes = fluxes[w]
    objs = objs[w]

    # ADM grab the (GRZ) magnitudes for observations
    # ADM and record only the largest flux (smallest magnitude).
    fluxmax = np.max(fluxes, axis=1)
    mags = 22.5 - 2.5 * np.log10(fluxmax)

    # ADM each object's TYPE.
    objtype = objs["TYPE"]

    # ADM calculate the TARGETID.
    targetid = encode_targetid(objid=objs['OBJID'],
                               brickid=objs['BRICKID'],
                               release=objs['RELEASE'])

    # ADM first set the shape parameters assuming everything is an exponential
    # ADM this will correctly assign e1, e2 of 0 to things with zero shape.
    in_radius = objs['SHAPEEXP_R']
    e1 = objs['SHAPEEXP_E1']
    e2 = objs['SHAPEEXP_E2']
    # ADM now to account for deVaucouleurs objects, or things that are dominated by
    # ADM deVaucouleurs profiles, update objects with a larger "DEV" than "EXP" radius.
    wdev = np.where(objs['SHAPEDEV_R'] > objs['SHAPEEXP_R'])
    if len(wdev[0]) > 0:
        in_radius[wdev] = objs[wdev]['SHAPEDEV_R']
        e1[wdev] = objs[wdev]['SHAPEDEV_E1']
        e2[wdev] = objs[wdev]['SHAPEDEV_E2']
    # ADM finally use the Tycho radius (see the notes above) for PSF or star-like objects.
    # ADM More consideration will be needed to derive correct numbers for this for DESI!!!
    # ADM this calculation was for "near" Tycho objects and was in arcmin, so we convert
    # ADM it to arcsec and multiply it by infac (see the top of the module).
    tycho_in_radius = infac * (0.0802 * mags * mags - 1.860 * mags +
                               11.625) * 60.
    wpsf = np.where(_psflike(objtype))
    in_radius[wpsf] = tycho_in_radius[wpsf]

    # ADM set "near" as a multiple of "in" radius using the factor at the top of the code.
    near_radius = in_radius * nearfac

    # ADM create an output recarray that is just RA, Dec, TARGETID and the radius.
    done = objs[['RA', 'DEC']].copy()
    done = rfn.append_fields(
        done, ["TARGETID", "IN_RADIUS", "NEAR_RADIUS", "E1", "E2", "TYPE"],
        [targetid, in_radius, near_radius, e1, e2, objtype],
        usemask=False,
        dtypes=['>i8', '>f4', '>f4', '>f4', '>f4', '|S4'])

    if outfilename is not None:
        fitsio.write(outfilename, done, clobber=True)

    return done
Example #15
0
def make_bright_star_mask(
        bands,
        maglim,
        numproc=4,
        rootdirname='/global/project/projectdirs/cosmo/data/legacysurvey/dr3.1/sweep/3.1',
        infilename=None,
        outfilename=None):
    """Make a bright star mask from a structure of bright stars drawn from the sweeps.

    Parameters
    ----------
    bands : :class:`str`
        A magnitude band from the sweeps, e.g., "G", "R", "Z".
        Can pass multiple bands as string, e.g. ``"GRZ"``, in which case maglim has to be a
        list of the same length as the string.
    maglim : :class:`float`
        The upper limit in that magnitude band for which to assemble a list of bright stars.
        Can pass a list of magnitude limits, in which case bands has to be a string of the
        same length (e.g., ``"GRZ"`` for [12.3,12.7,12.6]).
    numproc : :class:`int`, optional
        Number of processes over which to parallelize.
    rootdirname : :class:`str`, optional, defaults to dr3
        Root directory containing either sweeps or tractor files...e.g. for dr3 this might be
        /global/project/projectdirs/cosmo/data/legacysurvey/dr3/sweep/dr3.1. This is only
        used if ``infilename`` is not passed.
    infilename : :class:`str`, optional,
        if this exists, then the list of bright stars is read in from the file of this name
        if this is not passed, then code defaults to deriving the recarray of bright stars
        from ``rootdirname`` via a call to ``collect_bright_stars``.
    outfilename : :class:`str`, optional, defaults to not writing anything to file
        (FITS) File name to which to write the output bright star mask.

    Returns
    -------
    :class:`recarray`
        - The bright source mask in the form ``RA``, ``DEC``, ``TARGETID``,
          ``IN_RADIUS``, ``NEAR_RADIUS``, ``E1``, ``E2``, ``TYPE``
          (may also be written to file if "outfilename" is passed).
        - TARGETID is as calculated in :mod:`desitarget.targets.encode_targetid`.
        - The radii are in ARCSECONDS (they default to equivalents of half-light radii for ellipses).
        - `E1` and `E2` are ellipticity components, which are 0 for unresolved objects.
        - `TYPE` is always `PSF` for star-like objects. This is taken from the sweeps files, see, e.g.:
          http://legacysurvey.org/dr5/files/#sweep-catalogs.

    Notes
    -----
        - ``IN_RADIUS`` is a smaller radius that corresponds to the ``IN_BRIGHT_OBJECT`` bit in
          ``data/targetmask.yaml`` (and is in ARCSECONDS).
        - ``NEAR_RADIUS`` is a radius that corresponds to the ``NEAR_BRIGHT_OBJECT`` bit in
          ``data/targetmask.yaml`` (and is in ARCSECONDS).
        - Currently uses the radius-as-a-function-of-B-mag for Tycho stars from the BOSS mask
          (in every band) to set the ``NEAR_RADIUS``:
          R = (0.0802B*B - 1.860B + 11.625) (see Eqn. 9 of https://arxiv.org/pdf/1203.6594.pdf)
          and half that radius to set the ``IN_RADIUS``. We convert this from arcminutes to arcseconds.
        - It's an open question as to what the correct radii are for DESI observations.
    """
    # ADM set up default logger
    from desiutil.log import get_logger
    log = get_logger()

    # ADM this is just a special case of make_bright_source_mask
    sourcemask = make_bright_source_mask(bands,
                                         maglim,
                                         numproc=numproc,
                                         rootdirname=rootdirname,
                                         infilename=infilename,
                                         outfilename=None)
    # ADM check if a source is unresolved.
    psflike = _psflike(sourcemask["TYPE"])
    wstar = np.where(psflike)
    if len(wstar[0]) > 0:
        done = sourcemask[wstar]
        if outfilename is not None:
            fitsio.write(outfilename, done, clobber=True)
        return done
    else:
        log.error('No PSF-like objects brighter than {} in {}'.format(
            str(maglim), bands))
        return -1