"""
desitarget.sv2.sv2_cuts
=======================
Target Selection for DESI Survey Validation derived from `the SV2 wiki`_.
A collection of helpful (static) methods to check whether an object's
flux passes a given selection criterion (*e.g.* LRG, ELG or QSO).
.. _`the Gaia data model`: https://gea.esac.esa.int/archive/documentation/GDR2/Gaia_archive/chap_datamodel/sec_dm_main_tables/ssec_dm_gaia_source.html
.. _`the SV2 wiki`: https://desi.lbl.gov/trac/wiki/TargetSelectionWG/SV2
"""
import warnings
from time import time
import os.path
import numbers
import sys
import fitsio
import numpy as np
import healpy as hp
from importlib import resources
import numpy.lib.recfunctions as rfn
from importlib import import_module
import astropy.units as u
from astropy.coordinates import SkyCoord
from astropy.table import Table, Row
from desitarget import io
from desitarget.internal import sharedmem
from desitarget.gaiamatch import match_gaia_to_primary, find_gaia_files_hp
from desitarget.gaiamatch import pop_gaia_coords, pop_gaia_columns, unextinct_gaia_mags
from desitarget.gaiamatch import gaia_dr_from_ref_cat, is_in_Galaxy, gaia_psflike
from desitarget.targets import finalize, resolve
from desitarget.geomask import bundle_bricks, pixarea2nside, sweep_files_touch_hp
from desitarget.geomask import box_area, hp_in_box, is_in_box, is_in_hp
from desitarget.geomask import cap_area, hp_in_cap, is_in_cap, imaging_mask
# ADM set up the DESI default logger
from desiutil.log import get_logger
log = get_logger()
# ADM start the clock
start = time()
[docs]
def shift_photo_north(gflux=None, rflux=None, zflux=None):
"""Convert fluxes in the northern (BASS/MzLS) to the southern (DECaLS) system.
Parameters
----------
gflux, rflux, zflux : :class:`array_like` or `float`
The flux in nano-maggies of g, r, z bands.
Returns
-------
The equivalent fluxes shifted to the southern system.
Notes
-----
- see also https://desi.lbl.gov/DocDB/cgi-bin/private/RetrieveFile?docid=3390;filename=Raichoor_DESI_05Dec2017.pdf;version=1
- Update for DR9 https://desi.lbl.gov/trac/attachment/wiki/TargetSelectionWG/TargetSelection/North_vs_South_dr9.png
"""
# ADM if floats were sent, treat them like arrays.
flt = False
if _is_row(gflux):
flt = True
gflux = np.atleast_1d(gflux)
rflux = np.atleast_1d(rflux)
zflux = np.atleast_1d(zflux)
# ADM only use the g-band color shift when r and g are non-zero
gshift = gflux * 10**(-0.4*0.004)
w = np.where((gflux != 0) & (rflux != 0))
gshift[w] = (gflux[w] * 10**(-0.4*0.004) * (gflux[w]/rflux[w])**complex(-0.059)).real
# ADM only use the r-band color shift when r and z are non-zero
# ADM and only use the z-band color shift when r and z are non-zero
w = np.where((rflux != 0) & (zflux != 0))
rshift = rflux * 10**(0.4*0.003)
zshift = zflux * 10**(0.4*0.013)
rshift[w] = (rflux[w] * 10**(0.4*0.003) * (rflux[w]/zflux[w])**complex(-0.024)).real
zshift[w] = (zflux[w] * 10**(0.4*0.013) * (rflux[w]/zflux[w])**complex(+0.015)).real
if flt:
return gshift[0], rshift[0], zshift[0]
return gshift, rshift, zshift
[docs]
def isGAIA_STD(ra=None, dec=None, galb=None, gaiaaen=None, pmra=None, pmdec=None,
parallax=None, parallaxovererror=None, ebv=None, gaiabprpfactor=None,
gaiasigma5dmax=None, gaiagmag=None, gaiabmag=None, gaiarmag=None,
gaiadupsource=None, gaiaparamssolved=None,
primary=None, test=False, nside=2):
"""Standards based solely on Gaia data.
Parameters
----------
ebv : :class:`array_like`
E(B-V) values from the SFD dust maps.
test : :class:`bool`, optional, defaults to ``False``
If ``True``, then we're running unit tests and don't have to
find and read every possible Gaia file.
nside : :class:`int`, optional, defaults to 2
(NESTED) HEALPix nside, if targets are being parallelized.
The default of 2 should be benign for serial processing.
Returns
-------
:class:`array_like`
``True`` if the object is a bright "GAIA_STD_FAINT" target.
:class:`array_like`
``True`` if the object is a faint "GAIA_STD_BRIGHT" target.
:class:`array_like`
``True`` if the object is a white dwarf "GAIA_STD_WD" target.
Notes
-----
- Current version (03/09/21) is version 1 on `the SV2 wiki`_.
- See :func:`~desitarget.cuts.set_target_bits` for other parameters.
"""
if primary is None:
primary = np.ones_like(gaiagmag, dtype='?')
# ADM restrict all classes to dec >= -30.
primary &= dec >= -30.
std = primary.copy()
# ADM the regular "standards" codes need to know whether something has
# ADM a Gaia match. Here, everything is a Gaia match.
gaia = np.ones_like(gaiagmag, dtype='?')
# ADM determine the Gaia-based white dwarf standards.
std_wd = isMWS_WD(
primary=primary, gaia=gaia, galb=galb, astrometricexcessnoise=gaiaaen,
pmra=pmra, pmdec=pmdec, parallax=parallax,
parallaxovererror=parallaxovererror, photbprpexcessfactor=gaiabprpfactor,
astrometricsigma5dmax=gaiasigma5dmax, gaiagmag=gaiagmag,
gaiabmag=gaiabmag, gaiarmag=gaiarmag
)
# ADM apply the Gaia quality cuts for standards.
std &= isSTD_gaia(primary=primary, gaia=gaia, astrometricexcessnoise=gaiaaen,
pmra=pmra, pmdec=pmdec, parallax=parallax,
dupsource=gaiadupsource, paramssolved=gaiaparamssolved,
gaiagmag=gaiagmag, gaiabmag=gaiabmag, gaiarmag=gaiarmag)
# ADM restrict to point sources.
ispsf = gaia_psflike(gaiaaen, gaiagmag)
std &= ispsf
# ADM de-extinct the magnitudes before applying color cuts.
gd, bd, rd = unextinct_gaia_mags(gaiagmag, gaiabmag, gaiarmag, ebv)
# ADM apply the Gaia color cuts for standards.
bprp = bd - rd
gbp = gd - bd
std &= bprp > 0.2
std &= bprp < 0.9
std &= gbp > -1.*bprp/2.0
std &= gbp < 0.3-bprp/2.0
# ADM remove any sources that have neighbors in Gaia within 3.5"...
# ADM for speed, run only sources for which std is still True.
log.info("Isolating Gaia-only standards...t={:.1f}s".format(time()-start))
ii_true = np.where(std)[0]
if len(ii_true) > 0:
# ADM determine the pixels of interest.
theta, phi = np.radians(90-dec), np.radians(ra)
pixlist = list(set(hp.ang2pix(nside, theta, phi, nest=True)))
# ADM read in the necessary Gaia files.
fns = find_gaia_files_hp(nside, pixlist, neighbors=True)
gaiaobjs = []
gaiacols = ["RA", "DEC", "PHOT_G_MEAN_MAG", "PHOT_RP_MEAN_MAG"]
for i, fn in enumerate(fns):
if i % 25 == 0:
log.info("Read {}/{} files for Gaia-only standards...t={:.1f}s"
.format(i, len(fns), time()-start))
try:
gaiaobjs.append(fitsio.read(fn, columns=gaiacols))
except OSError:
if test:
pass
else:
msg = "failed to find or open the following file: (ffopen) "
msg += fn
log.critical(msg)
raise OSError
gaiaobjs = np.concatenate(gaiaobjs)
# ADM match the standards to the broader Gaia sources at 3.5".
matchrad = 3.5*u.arcsec
cstd = SkyCoord(ra[ii_true]*u.degree, dec[ii_true]*u.degree)
cgaia = SkyCoord(gaiaobjs["RA"]*u.degree, gaiaobjs["DEC"]*u.degree)
idstd, idgaia, d2d, _ = cgaia.search_around_sky(cstd, matchrad)
# ADM remove source matches with d2d=0 (i.e. the source itself!).
idgaia, idstd = idgaia[d2d > 0], idstd[d2d > 0]
# ADM remove matches within 5 mags of a Gaia source.
badmag = (
(gaiagmag[ii_true][idstd] + 5 > gaiaobjs["PHOT_G_MEAN_MAG"][idgaia]) |
(gaiarmag[ii_true][idstd] + 5 > gaiaobjs["PHOT_RP_MEAN_MAG"][idgaia]))
std[ii_true[idstd][badmag]] = False
# ADM add the brightness cuts in Gaia G-band.
std_bright = std.copy()
std_bright &= gaiagmag >= 16
std_bright &= gaiagmag < 18
std_faint = std.copy()
std_faint &= gaiagmag >= 16
std_faint &= gaiagmag < 19
return std_faint, std_bright, std_wd
[docs]
def isBACKUP(ra=None, dec=None, gaiagmag=None, primary=None):
"""BACKUP targets based on Gaia magnitudes.
Parameters
----------
ra, dec: :class:`array_like` or :class:`None`
Right Ascension and Declination in degrees.
gaiagmag: :class:`array_like` or :class:`None`
Gaia-based g MAGNITUDE (not Galactic-extinction-corrected).
(same units as `the Gaia data model`_).
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 a bright "BACKUP" target.
:class:`array_like`
``True`` if and only if the object is a faint "BACKUP" target.
:class:`array_like`
``True`` if and only if the object is a very faint "BACKUP"
target.
Notes
-----
- Current version (03/09/21) is version 1 on `the SV2 wiki`_.
"""
if primary is None:
primary = np.ones_like(gaiagmag, dtype='?')
# ADM restrict all classes to dec >= -30.
primary &= dec >= -30.
isbackupbright = primary.copy()
isbackupfaint = primary.copy()
isbackupveryfaint = primary.copy()
# ADM determine which sources are close to the Galaxy.
in_gal = is_in_Galaxy([ra, dec], radec=True)
# ADM bright targets are 10 < G < 16.
isbackupbright &= gaiagmag >= 10
isbackupbright &= gaiagmag < 16
# ADM faint targets are 16 < G < 18.
isbackupfaint &= gaiagmag >= 16
isbackupfaint &= gaiagmag < 18.
# ADM and are "far from" the Galaxy.
isbackupfaint &= ~in_gal
# ADM very faint targets are 18. < G < 19.
isbackupveryfaint &= gaiagmag >= 18.
isbackupveryfaint &= gaiagmag < 19
# ADM and are "far from" the Galaxy.
isbackupveryfaint &= ~in_gal
return isbackupbright, isbackupfaint, isbackupveryfaint
[docs]
def isLRG(gflux=None, rflux=None, zflux=None, w1flux=None, w2flux=None,
zfiberflux=None, rfluxivar=None, zfluxivar=None, w1fluxivar=None,
gaiagmag=None, gnobs=None, rnobs=None, znobs=None, maskbits=None,
primary=None, south=True):
"""
Parameters
----------
south: boolean, defaults to ``True``
Use cuts appropriate to the Northern imaging surveys (BASS/MzLS)
if ``south=False``, otherwise use cuts appropriate to the
Southern imaging survey (DECaLS).
Returns
-------
:class:`array_like`
``True`` if and only if the object is an LRG target.
Notes
-----
- Current version (03/09/21) is version 1 on `the SV2 wiki`_.
- See :func:`~desitarget.cuts.set_target_bits` for other parameters.
"""
# ADM LRG targets.
if primary is None:
primary = np.ones_like(rflux, dtype='?')
lrg = primary.copy()
# ADM basic quality cuts.
lrg &= notinLRG_mask(
primary=primary, rflux=rflux, zflux=zflux, w1flux=w1flux,
zfiberflux=zfiberflux, gnobs=gnobs, rnobs=rnobs, znobs=znobs,
rfluxivar=rfluxivar, zfluxivar=zfluxivar, w1fluxivar=w1fluxivar,
gaiagmag=gaiagmag, maskbits=maskbits
)
# ADM color-based selection of LRGs.
lrg &= isLRG_colors(
gflux=gflux, rflux=rflux, zflux=zflux, w1flux=w1flux,
zfiberflux=zfiberflux, south=south, primary=primary
)
return lrg
[docs]
def notinLRG_mask(primary=None, rflux=None, zflux=None, w1flux=None,
zfiberflux=None, gnobs=None, rnobs=None, znobs=None,
rfluxivar=None, zfluxivar=None, w1fluxivar=None,
gaiagmag=None, maskbits=None):
"""See :func:`~desitarget.cuts.isLRG` for details.
Returns
-------
:class:`array_like`
``True`` if and only if the object is NOT masked for poor quality.
"""
if primary is None:
primary = np.ones_like(rflux, dtype='?')
lrg = primary.copy()
# ADM to maintain backwards-compatibility with mocks.
if zfiberflux is None:
log.warning('Setting zfiberflux to zflux!!!')
zfiberflux = zflux.copy()
lrg &= (rfluxivar > 0) & (rflux > 0) # ADM quality in r.
lrg &= (zfluxivar > 0) & (zflux > 0) & (zfiberflux > 0) # ADM quality in z.
lrg &= (w1fluxivar > 0) & (w1flux > 0) # ADM quality in W1.
lrg &= (gaiagmag == 0) | (gaiagmag > 18) # remove bright GAIA sources
# ADM observed in every band.
lrg &= (gnobs > 0) & (rnobs > 0) & (znobs > 0)
# ADM default mask bits from the Legacy Surveys not set.
lrg &= imaging_mask(maskbits)
return lrg
[docs]
def isLRG_colors(gflux=None, rflux=None, zflux=None, w1flux=None,
zfiberflux=None, ggood=None,
w2flux=None, primary=None, south=True):
"""(see, e.g., :func:`~desitarget.cuts.isLRG`).
Notes:
- the `ggood` and `w2flux` inputs are an attempt to maintain
backwards-compatibility with the mocks.
"""
if primary is None:
primary = np.ones_like(rflux, dtype='?')
lrg = primary.copy()
# ADM to maintain backwards-compatibility with mocks.
if zfiberflux is None:
log.warning('Setting zfiberflux to zflux!!!')
zfiberflux = zflux.copy()
gmag = 22.5 - 2.5 * np.log10(gflux.clip(1e-7))
# ADM safe as these fluxes are set to > 0 in notinLRG_mask.
rmag = 22.5 - 2.5 * np.log10(rflux.clip(1e-7))
zmag = 22.5 - 2.5 * np.log10(zflux.clip(1e-7))
w1mag = 22.5 - 2.5 * np.log10(w1flux.clip(1e-7))
zfibermag = 22.5 - 2.5 * np.log10(zfiberflux.clip(1e-7))
if south:
lrg &= zmag - w1mag > 0.8 * (rmag-zmag) - 0.6 # non-stellar cut.
lrg &= (
(((gmag - rmag) > 0.3 * (rmag - w1mag)+0.9) & ((gmag - rmag) > -1.55*(rmag - w1mag)+3.13))
| (rmag - w1mag > 1.8) # low-z cut.
)
lrg &= rmag - w1mag > (w1mag - 17.27) * 1.8 # double sliding cut 1.
lrg &= rmag - w1mag > (w1mag - 16.37) * 1. # double sliding cut 2.
else:
lrg &= zmag - w1mag > 0.8 * (rmag-zmag) - 0.6 # non-stellar cut.
lrg &= (
(((gmag - rmag) > 0.3 * (rmag - w1mag)+0.9) & ((gmag - rmag) > -1.55*(rmag - w1mag)+3.23))
| (rmag - w1mag > 1.8) # low-z cut.
)
lrg &= rmag - w1mag > (w1mag - 17.23) * 1.8 # double sliding cut 1.
lrg &= rmag - w1mag > (w1mag - 16.33) * 1. # double sliding cut 2.
lrg &= zfibermag < 21.5 # faint limit.
return lrg
[docs]
def isELG(gflux=None, rflux=None, zflux=None, w1flux=None, w2flux=None,
gfiberflux=None, gsnr=None, rsnr=None, zsnr=None,
gnobs=None, rnobs=None, znobs=None,
maskbits=None, south=True, primary=None):
"""Definition of ELG target classes. Returns a boolean array.
(see :func:`~desitarget.cuts.set_target_bits` for parameters).
Notes:
- Current version (03/16/21) is version 5 on `the SV2 wiki`_.
"""
if primary is None:
primary = np.ones_like(rflux, dtype='?')
elg = primary.copy()
elg &= notinELG_mask(maskbits=maskbits, gsnr=gsnr, rsnr=rsnr, zsnr=zsnr,
gnobs=gnobs, rnobs=rnobs, znobs=znobs, primary=primary)
elg &= isELG_colors(gflux=gflux, rflux=rflux, zflux=zflux, w1flux=w1flux,
w2flux=w2flux, gfiberflux=gfiberflux, south=south,
primary=primary)
return elg
[docs]
def notinELG_mask(maskbits=None, gsnr=None, rsnr=None, zsnr=None,
gnobs=None, rnobs=None, znobs=None, primary=None):
"""Standard set of masking cuts used by all ELG target selection classes.
(see :func:`~desitarget.cuts.set_target_bits` for parameters).
"""
if primary is None:
primary = np.ones_like(maskbits, dtype='?')
elg = primary.copy()
# ADM good signal-to-noise in all bands.
elg &= (gsnr > 0) & (rsnr > 0) & (zsnr > 0)
# ADM observed in every band.
elg &= (gnobs > 0) & (rnobs > 0) & (znobs > 0)
# ADM default mask bits from the Legacy Surveys not set.
elg &= imaging_mask(maskbits)
return elg
[docs]
def isELG_colors(gflux=None, rflux=None, zflux=None, w1flux=None,
w2flux=None, gfiberflux=None, south=True, primary=None):
"""Color cuts for ELG target selection classes
(see, e.g., :func:`~desitarget.cuts.set_target_bits` for parameters).
"""
if primary is None:
primary = np.ones_like(rflux, dtype='?')
elg = primary.copy()
# ADM work in magnitudes instead of fluxes. NOTE THIS IS ONLY OK AS
# ADM the snr masking in ALL OF g, r AND z ENSURES positive fluxes.
g = 22.5 - 2.5*np.log10(gflux.clip(1e-16))
r = 22.5 - 2.5*np.log10(rflux.clip(1e-16))
z = 22.5 - 2.5*np.log10(zflux.clip(1e-16))
gfib = 22.5 - 2.5*np.log10(gfiberflux.clip(1e-16))
# ADM cuts shared by the northern and southern selections.
elg &= g > 20 # bright cut.
elg &= r - z > 0.15 # blue cut.
# elg &= r - z < 1.6 # red cut.
elg &= g - r < -1.2*(r - z) + 1.6 # OII flux cut.
# ADM cuts that are unique to the north or south. Identical for sv2
# ADM but keep the north/south formalism in case we use it for sv3.
if south:
elg &= gfib < 24.1 # faint cut.
elg &= g - r < 0.5*(r - z) + 0.1 # remove stars and low-z galaxies.
else:
elg &= gfib < 24.1 # faint cut.
elg &= g - r < 0.5*(r - z) + 0.1 # remove stars and low-z galaxies.
return elg
[docs]
def isSTD_colors(gflux=None, rflux=None, zflux=None, w1flux=None, w2flux=None,
primary=None, south=True):
"""Select STD stars based on Legacy Surveys color cuts. Returns a boolean array.
Args:
gflux, rflux, zflux, w1flux, w2flux: array_like
The flux in nano-maggies of g, r, z, w1, and w2 bands.
primary: array_like or None
Set to ``True`` for objects to initially consider as possible targets.
Defaults to everything being ``True``.
south: boolean, defaults to ``True``
Use color-cuts based on photometry from the "south" (DECaLS) as
opposed to the "north" (MzLS+BASS).
Returns:
mask : boolean array, True if the object has colors like a STD star target
Notes:
- Current version (03/09/21) is version 1 on `the SV2 wiki`_.
"""
if primary is None:
primary = np.ones_like(gflux, dtype='?')
std = primary.copy()
# Clip to avoid warnings from negative numbers.
# ADM we're pretty bright for the STDs, so this should be safe.
gflux = gflux.clip(1e-16)
rflux = rflux.clip(1e-16)
zflux = zflux.clip(1e-16)
# ADM optical colors for halo TO or bluer.
grcolor = 2.5 * np.log10(rflux / gflux)
rzcolor = 2.5 * np.log10(zflux / rflux)
# Currently no difference in north vs south color-cuts.
if south:
std &= rzcolor < 0.2
std &= grcolor > 0.
std &= grcolor < 0.35
else:
std &= rzcolor < 0.2
std &= grcolor > 0.
std &= grcolor < 0.35
return std
[docs]
def isSTD_gaia(primary=None, gaia=None, astrometricexcessnoise=None,
pmra=None, pmdec=None, parallax=None,
dupsource=None, paramssolved=None,
gaiagmag=None, gaiabmag=None, gaiarmag=None):
"""Gaia quality cuts used to define STD star targets.
Args:
primary: array_like or None
Set to ``True`` for objects to initially consider as possible targets.
Defaults to everything being ``True``.
gaia: boolean array_like or None
True if there is a match between this object in
`the Legacy Surveys`_ and in Gaia.
astrometricexcessnoise: array_like or None
Excess noise of the source in Gaia (as in `the Gaia data model`_).
pmra, pmdec, parallax: array_like or None
Gaia-based proper motion in RA and Dec and parallax
(same units as the Gaia data model).
dupsource: array_like or None
Whether the source is a duplicate in Gaia (as in `the Gaia data model`_).
paramssolved: array_like or None
How many parameters were solved for in Gaia (as in `the Gaia data model`_).
gaiagmag, gaiabmag, gaiarmag: array_like or None
Gaia-based g, b and r MAGNITUDES (not Galactic-extinction-corrected).
(same units as `the Gaia data model`_).
Returns:
mask : boolean array, True if the object passes Gaia quality cuts.
Notes:
- Current version (03/09/21) is version 1 on `the SV2 wiki`_.
"""
if primary is None:
primary = np.ones_like(gflux, dtype='?')
std = primary.copy()
# ADM Bp and Rp are both measured.
std &= ~np.isnan(gaiabmag - gaiarmag)
# ADM no obvious issues with the astrometry solution.
std &= astrometricexcessnoise < 1
std &= paramssolved == 31
# ADM finite proper motions.
std &= np.isfinite(pmra)
std &= np.isfinite(pmdec)
# ADM a parallax smaller than 1 mas.
std &= parallax < 1.
# ADM calculate the overall proper motion magnitude
# 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.)
# ADM a proper motion larger than 2 mas/yr.
std &= pm > 2.
# ADM fail if dupsource is not Boolean, as was the case for the 7.0 sweeps
# ADM otherwise logic checks on dupsource will be misleading.
if not (dupsource.dtype.type == np.bool_):
log.error('GAIA_DUPLICATED_SOURCE (dupsource) should be boolean!')
raise IOError
# ADM a unique Gaia source.
std &= ~dupsource
return std
[docs]
def isSTD(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,
usegaia=True, maskbits=None, south=True):
"""Select STD targets using color cuts and photometric quality cuts (PSF-like
and fracflux). See isSTD_colors() for additional info.
Args:
gflux, rflux, zflux: array_like
The flux in nano-maggies of g, r, z bands.
primary: array_like or None
Set to ``True`` for objects to initially consider as possible targets.
Defaults to everything being ``True``.
gfracflux, rfracflux, zfracflux: array_like
Profile-weighted fraction of the flux from other sources divided
by the total flux in g, r and z bands.
gfracmasked, rfracmasked, zfracmasked: array_like
Fraction of masked pixels in the g, r and z bands.
gnobs, rnobs, znobs: array_like
The number of observations (in the central pixel) in g, r and z.
gfluxivar, rfluxivar, zfluxivar: array_like
The flux inverse variances in g, r, and z bands.
objtype: array_like or None
The TYPE column of the catalogue to restrict to point sources.
gaia: boolean array_like or None
True if there is a match between this object in
`the Legacy Surveys`_ and in Gaia.
astrometricexcessnoise: array_like or None
Excess noise of the source in Gaia.
paramssolved: array_like or None
How many parameters were solved for in Gaia.
pmra, pmdec, parallax: array_like or None
Gaia-based proper motion in RA and Dec and parallax
dupsource: array_like or None
Whether the source is a duplicate in Gaia.
gaiagmag, gaiabmag, gaiarmag: array_like or None
Gaia-based g-, b- and r-band MAGNITUDES.
bright: boolean, defaults to ``False``
if ``True`` apply magnitude cuts for "bright" conditions; otherwise,
choose "normal" brightness standards. Cut is performed on `gaiagmag`.
usegaia: boolean, defaults to ``True``
if ``True`` then call :func:`~desitarget.cuts.isSTD_gaia` to set the
logic cuts. If Gaia is not available (perhaps if you're using mocks)
then send ``False``, in which case we use the LS r-band magnitude as
a proxy for the Gaia G-band magnitude (ignoring---incorrectly---that
we have already corrected for Galactic extinction.)
south: boolean, defaults to ``True``
Use color-cuts based on photometry from the "south" (DECaLS) as
opposed to the "north" (MzLS+BASS).
Returns:
mask : boolean array, True if the object has colors like a STD star.
Notes:
- Gaia-based quantities are as in `the Gaia data model`_.
- Current version (03/09/21) is version 1 on `the SV2 wiki`_.
"""
if primary is None:
primary = np.ones_like(gflux, dtype='?')
std = primary.copy()
# ADM apply the Legacy Surveys (optical) magnitude and color cuts.
std &= isSTD_colors(primary=primary, zflux=zflux, rflux=rflux, gflux=gflux, south=south)
# ADM apply the Gaia quality cuts.
if usegaia:
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 apply type=PSF cut
std &= _psflike(objtype)
# ADM don't target standards in Legacy Surveys mask regions.
std &= imaging_mask(maskbits, mwsmask=True)
# 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 brightness cuts in Gaia G-band
if bright:
gbright = 16.
gfaint = 18.
else:
gbright = 16.
gfaint = 19.
if usegaia:
std &= gaiagmag >= gbright
std &= gaiagmag < gfaint
else:
# Use LS r-band as a Gaia G-band proxy.
gaiamag_proxy = 22.5 - 2.5 * np.log10(rflux.clip(1e-16))
std &= gaiamag_ >= gbright
std &= gaiamag_ < gfaint
return std
[docs]
def isMWS_main(gflux=None, rflux=None, zflux=None, w1flux=None, w2flux=None,
gnobs=None, rnobs=None, gfracmasked=None, rfracmasked=None,
pmra=None, pmdec=None, parallax=None, parallaxerr=None,
obs_rflux=None, objtype=None, gaia=None,
gaiagmag=None, gaiabmag=None, gaiarmag=None,
gaiaaen=None, gaiadupsource=None, paramssolved=None,
primary=None, south=True, maskbits=None):
"""Set bits for main ``MWS`` targets.
Args:
see :func:`~desitarget.cuts.set_target_bits` for parameters.
Returns:
mask1 : array_like.
``True`` if and only if the object is a ``MWS_BROAD`` target.
mask2 : array_like.
``True`` if and only if the object is a ``MWS_MAIN_RED`` target.
mask3 : array_like.
``True`` if and only if the object is a ``MWS_MAIN_BLUE`` target.
Notes:
- Current version (03/09/21) is version 1 on `the SV2 wiki`_.
"""
if primary is None:
primary = np.ones_like(gaia, dtype='?')
mws = primary.copy()
# ADM currently no difference between N/S for MWS, so easiest
# ADM just to use one selection
# if south:
mws &= notinMWS_main_mask(gaia=gaia, gfracmasked=gfracmasked, gnobs=gnobs,
gflux=gflux, rfracmasked=rfracmasked, rnobs=rnobs,
rflux=rflux, gaiadupsource=gaiadupsource,
primary=primary, maskbits=maskbits)
# ADM pass the mws that pass cuts as primary, to restrict to the
# ADM sources that weren't in a mask/logic cut.
mws, red, blue = isMWS_main_colors(
gflux=gflux, rflux=rflux, zflux=zflux, w1flux=w1flux, w2flux=w2flux,
pmra=pmra, pmdec=pmdec, parallax=parallax, parallaxerr=parallaxerr,
obs_rflux=obs_rflux, objtype=objtype, gaiagmag=gaiagmag,
gaiabmag=gaiabmag, gaiarmag=gaiarmag, gaiaaen=gaiaaen,
paramssolved=paramssolved, primary=mws, south=south
)
return mws, red, blue
[docs]
def notinMWS_main_mask(gaia=None, gfracmasked=None, gnobs=None, gflux=None,
rfracmasked=None, rnobs=None, rflux=None, maskbits=None,
gaiadupsource=None, primary=None):
"""Standard set of masking-based cuts used by MWS target selection classes
(see, e.g., :func:`~desitarget.cuts.isMWS_main` for parameters).
"""
if primary is None:
primary = np.ones_like(gaia, dtype='?')
mws = primary.copy()
# ADM don't target MWS-like targets in Legacy Surveys mask regions.
mws &= imaging_mask(maskbits, mwsmask=True)
# ADM apply the mask/logic selection for all MWS-MAIN targets
# ADM main targets match to a Gaia source
mws &= gaia
mws &= (gfracmasked < 0.5) & (gflux > 0) & (gnobs > 0)
mws &= (rfracmasked < 0.5) & (rflux > 0) & (rnobs > 0)
mws &= ~gaiadupsource
return mws
[docs]
def isMWS_main_colors(gflux=None, rflux=None, zflux=None, w1flux=None, w2flux=None,
pmra=None, pmdec=None, parallax=None, parallaxerr=None,
obs_rflux=None, objtype=None, paramssolved=None,
gaiagmag=None, gaiabmag=None, gaiarmag=None, gaiaaen=None,
primary=None, south=True):
"""Set of color-based cuts used by MWS target selection classes
(see, e.g., :func:`~desitarget.cuts.isMWS_main` for parameters).
"""
if primary is None:
primary = np.ones_like(rflux, dtype='?')
mws = primary.copy()
# ADM main targets are point-like based on DECaLS morphology
# ADM and GAIA_ASTROMETRIC_NOISE.
mws &= _psflike(objtype)
mws &= gaiaaen < 3.0
# ADM main targets are 16 <= r < 19
mws &= rflux > 10**((22.5-19.0)/2.5)
mws &= rflux <= 10**((22.5-16.0)/2.5)
# ADM main targets are robs < 20
mws &= obs_rflux > 10**((22.5-20.0)/2.5)
# ADM calculate the overall proper motion magnitude
# 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.)
# ADM make a copy of the main bits for a red/blue split
red = mws.copy()
blue = mws.copy()
# ADM MWS-BLUE is g-r < 0.7
blue &= rflux < gflux * 10**(0.7/2.5) # (g-r)<0.7
# ADM Turn off any NaNs for astrometric quantities to suppress
# ADM warnings. Won't target these, using cuts on paramssolved
# ADM (or will explicitly target them based on paramsssolved).
ii = paramssolved != 31
parallax = parallax.copy()
parallax[ii], pm[ii] = 0., 0.
# ADM MWS-RED and MWS-BROAD have g-r >= 0.7
red &= rflux >= gflux * 10**(0.7/2.5) # (g-r)>=0.7
broad = red.copy()
# ADM MWS-RED also has parallax < max(3parallax_err,1)mas
# ADM and proper motion < 7
# ADM and all astrometric parameters are measured.
red &= parallax < np.maximum(3*parallaxerr, 1)
red &= pm < 7.
red &= paramssolved == 31
# ADM MWS-BROAD has parallax > max(3parallax_err,1)mas
# ADM OR proper motion > 7.
# ADM OR astrometric parameters not measured.
broad &= ((parallax >= np.maximum(3*parallaxerr, 1)) |
(pm >= 7.)
| (paramssolved != 31))
return broad, red, blue
[docs]
def isMWS_nearby(gflux=None, rflux=None, zflux=None, w1flux=None, w2flux=None,
objtype=None, gaia=None, primary=None, paramssolved=None,
pmra=None, pmdec=None, parallax=None, parallaxerr=None,
obs_rflux=None, gaiagmag=None, gaiabmag=None, gaiarmag=None):
"""Set bits for NEARBY Milky Way Survey targets.
Args:
see :func:`~desitarget.cuts.set_target_bits` for parameters.
Returns:
mask : array_like.
True if and only if the object is a MWS-NEARBY target.
Notes:
- Current version (03/09/21) is version 1 on `the SV2 wiki`_.
"""
if primary is None:
primary = np.ones_like(gaia, dtype='?')
mws = primary.copy()
# ADM do not target any objects for which entries are NaN
# ADM and turn off the NaNs for those entries
nans = np.isnan(gaiagmag) | np.isnan(parallax)
w = np.where(nans)[0]
if len(w) > 0:
# ADM make copies as we are reassigning values
parallax, gaiagmag = parallax.copy(), gaiagmag.copy()
parallax[w], gaiagmag[w] = 0., 0.
mws &= ~nans
log.info('{}/{} NaNs in file...t = {:.1f}s'
.format(len(w), len(mws), time()-start))
# ADM apply the selection for all MWS-NEARBY targets
# ADM must be a Legacy Surveys object that matches a Gaia source
mws &= gaia
# ADM Gaia G mag of less than 20
mws &= gaiagmag < 20.
# APC Gaia G mag of more than 16
mws &= gaiagmag > 16.
# ADM all astrometric parameters are measured.
mws &= paramssolved == 31
# ADM parallax cut corresponding to 100pc
mws &= (parallax + parallaxerr) > 10. # NB: "+" is correct
return mws
[docs]
def isMWS_bhb(primary=None, objtype=None,
gaia=None, gaiaaen=None, gaiadupsource=None, gaiagmag=None,
gflux=None, rflux=None, zflux=None,
w1flux=None, w1snr=None, maskbits=None,
gnobs=None, rnobs=None, znobs=None,
gfracmasked=None, rfracmasked=None, zfracmasked=None,
parallax=None, parallaxerr=None):
"""Set bits for BHB Milky Way Survey targets
Parameters
----------
see :func:`~desitarget.cuts.set_target_bits` for other parameters.
Returns
-------
mask : array_like.
True if and only if the object is a MWS-BHB target.
Notes
-----
- Criteria supplied by Sergey Koposov
- gflux, rflux, zflux, w1flux have been corrected for extinction
(unlike other MWS selections, which use obs_flux).
- Current version (03/09/21) is version 1 on `the SV2 wiki`_.
"""
if primary is None:
primary = np.ones_like(gaia, dtype='?')
mws = primary.copy()
# ADM do not target any objects for which entries are NaN
# ADM and turn off the NaNs for those entries
nans = np.isnan(gflux) | np.isnan(rflux) | np.isnan(zflux) | np.isnan(w1flux) | np.isnan(parallax) | np.isnan(gaiagmag)
w = np.where(nans)[0]
if len(w) > 0:
# ADM make copies as we are reassigning values
rflux, gflux, zflux, w1flux = rflux.copy(), gflux.copy(), zflux.copy(), w1flux.copy()
parallax = parallax.copy()
gaigmag = gaiagmag.copy()
rflux[w], gflux[w], zflux[w], w1flux[w] = 0., 0., 0., 0.
parallax[w] = 0.
gaiagmag[w] = 0.
mws &= ~nans
log.info('{}/{} NaNs in file...t = {:.1f}s'
.format(len(w), len(mws), time()-start))
gmag = 22.5 - 2.5 * np.log10(gflux.clip(1e-7))
rmag = 22.5 - 2.5 * np.log10(rflux.clip(1e-7))
zmag = 22.5 - 2.5 * np.log10(zflux.clip(1e-7))
gmr = gmag-rmag
rmz = rmag-zmag
# ADM don't target MWS-like targets in Legacy Surveys mask regions.
mws &= imaging_mask(maskbits, mwsmask=True)
# APC must be a Legacy Surveys object that matches a Gaia source
mws &= gaia
# APC type must be PSF
mws &= _psflike(objtype)
# APC no sources brighter than Gaia G = 10
mws &= gaiagmag > 10.
# APC exclude nearby sources by parallax
mws &= parallax <= 0.1 + 3*parallaxerr
mws &= (gfracmasked < 0.5) & (gflux > 0) & (gnobs > 0)
mws &= (rfracmasked < 0.5) & (rflux > 0) & (rnobs > 0)
mws &= (zfracmasked < 0.5) & (zflux > 0) & (znobs > 0)
# APC no gaia duplicated sources
mws &= ~gaiadupsource
# APC gaia astrometric excess noise < 3
mws &= gaiaaen < 3.0
# APC BHB extinction-corrected color range -0.35 <= gmr <= -0.02
mws &= (gmr >= -0.35) & (gmr <= -0.02)
# Coefficients from Sergey Koposov
bhb_sel = rmz - (1.07163*gmr**5 - 1.42272*gmr**4 + 0.69476*gmr**3 - 0.12911*gmr**2 + 0.66993*gmr - 0.11368)
mws &= (bhb_sel >= -0.05) & (bhb_sel <= 0.05)
# APC back out the WISE error = 1/sqrt(ivar) from the SNR = flux*sqrt(ivar)
w1fluxerr = w1flux/(w1snr.clip(1e-7))
w1mag_faint = 22.5 - 2.5 * np.log10((w1flux-3*w1fluxerr).clip(1e-7))
# APC WISE cut (Sergey Koposov)
mws &= rmag - 2.3*gmr - w1mag_faint < -1.5
# APC Legacy magnitude limits
mws &= (rmag >= 16.) & (rmag <= 20.)
return mws
[docs]
def isMWS_WD(primary=None, gaia=None, galb=None, astrometricexcessnoise=None,
pmra=None, pmdec=None, parallax=None, parallaxovererror=None,
photbprpexcessfactor=None, astrometricsigma5dmax=None,
gaiagmag=None, gaiabmag=None, gaiarmag=None, paramssolved=None):
"""Set bits for WHITE DWARF Milky Way Survey targets.
Args:
see :func:`~desitarget.cuts.set_target_bits` for parameters.
Returns:
mask : array_like.
True if and only if the object is a MWS-WD target.
Notes:
- Current version (03/09/21) is version 1 on `the SV2 wiki`_.
"""
if primary is None:
primary = np.ones_like(gaia, dtype='?')
mws = primary.copy()
# ADM do not target any objects 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.
mws &= ~nans
# log.info('{}/{} NaNs in file...t = {:.1f}s'
# .format(len(w), len(mws), time()-start))
# ADM apply the selection for all MWS-WD targets
# ADM must be a Legacy Surveys object that matches a Gaia source
mws &= gaia
# ADM and all astrometric parameters are measured.
mws &= paramssolved == 31
# ADM Gaia G mag of less than 20
mws &= gaiagmag < 20.
# ADM Galactic b at least 20o from the plane
mws &= np.abs(galb) > 20.
# ADM gentle cut on parallax significance
mws &= 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
mws &= Gabs > 5.
mws &= Gabs > 5.93 + 5.047*br
mws &= Gabs > 6*br*br*br - 21.77*br*br + 27.91*br + 0.897
mws &= 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.)
mws &= 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
mws &= 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
mws &= ((astrometricsigma5dmax < 1.5) |
((astrometricexcessnoise < 1.) & (parallaxovererror > 4.) & (pm > 10.)))
return mws
[docs]
def isMWSSTAR_colors(gflux=None, rflux=None, zflux=None,
w1flux=None, w2flux=None, primary=None, south=True):
"""A reasonable range of g-r for MWS targets. Returns a boolean array.
Args:
gflux, rflux, zflux, w1flux, w2flux: array_like
The flux in nano-maggies of g, r, z, w1, and w2 bands.
primary: array_like or None
Set to ``True`` for objects to initially consider as possible targets.
Defaults to everything being ``True``.
south: boolean, defaults to ``True``
Use color-cuts based on photometry from the "south" (DECaLS) as
opposed to the "north" (MzLS+BASS).
Returns:
mask : boolean array, True if the object has colors like an old stellar population,
which is what we expect for the main MWS sample
Notes:
The full MWS target selection also includes PSF-like and fracflux
cuts and will include Gaia information; this function is only to enforce
a reasonable range of color/TEFF when simulating data.
"""
# ----- Old stars, g-r > 0
if primary is None:
primary = np.ones_like(gflux, dtype='?')
mwsstar = primary.copy()
# - colors g-r > 0
with warnings.catch_warnings():
warnings.simplefilter('ignore')
grcolor = 2.5 * np.log10(rflux / gflux)
# Assume no difference in north vs south color-cuts.
if south:
mwsstar &= (grcolor > 0.0)
else:
mwsstar &= (grcolor > 0.0)
return mwsstar
[docs]
def _check_BGS_targtype(targtype):
"""Fail if `targtype` is not one of the strings 'bright', 'faint' or 'wise'.
"""
targposs = ['faint', 'bright', 'wise']
if targtype not in targposs:
msg = 'targtype must be one of {} not {}'.format(targposs, targtype)
log.critical(msg)
raise ValueError(msg)
[docs]
def isBGS(rfiberflux=None, gflux=None, rflux=None, zflux=None, w1flux=None, w2flux=None,
gnobs=None, rnobs=None, znobs=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, maskbits=None, Grr=None, refcat=None,
w1snr=None, gaiagmag=None, objtype=None, primary=None, south=True, targtype=None):
"""Definition of BGS target classes. Returns a boolean array.
Args
----
targtype: str, optional, defaults to ``faint``
Pass ``bright`` to use colors appropriate to the ``BGS_BRIGHT`` selection
or ``faint`` to use colors appropriate to the ``BGS_FAINT`` selection
or ``wise`` to use colors appropriate to the ``BGS_WISE`` selection.
Returns
-------
:class:`array_like`
``True`` if and only if the object is a BGS target of type ``targtype``.
Notes
-----
- Current version (03/09/21) is version 1 on `the SV2 wiki`_.
- See :func:`~desitarget.cuts.set_target_bits` for other parameters.
"""
_check_BGS_targtype(targtype)
# ------ Bright Galaxy Survey
if primary is None:
primary = np.ones_like(rflux, dtype='?')
bgs = primary.copy()
bgs &= notinBGS_mask(gnobs=gnobs, rnobs=rnobs, znobs=znobs, primary=primary,
gfracmasked=gfracmasked, rfracmasked=rfracmasked, zfracmasked=zfracmasked,
gfracflux=gfracflux, rfracflux=rfracflux, zfracflux=zfracflux,
gfracin=gfracin, rfracin=rfracin, zfracin=zfracin, w1snr=w1snr,
gfluxivar=gfluxivar, rfluxivar=rfluxivar, zfluxivar=zfluxivar, Grr=Grr,
gaiagmag=gaiagmag, maskbits=maskbits, targtype=targtype)
bgs &= isBGS_colors(rfiberflux=rfiberflux, gflux=gflux, rflux=rflux, zflux=zflux, w1flux=w1flux,
w2flux=w2flux, south=south, targtype=targtype, primary=primary)
bgs |= isBGS_lslga(gflux=gflux, rflux=rflux, zflux=zflux, w1flux=w1flux, refcat=refcat,
maskbits=maskbits, south=south, targtype=targtype)
return bgs
[docs]
def notinBGS_mask(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, targtype=None):
"""Standard set of masking cuts used by all BGS target selection classes
(see, e.g., :func:`~desitarget.cuts.isBGS` for parameters).
"""
_check_BGS_targtype(targtype)
if primary is None:
primary = np.ones_like(gnobs, dtype='?')
bgs = primary.copy()
bgs &= (gnobs >= 1) & (rnobs >= 1) & (znobs >= 1)
bgs &= (gfracmasked < 0.4) & (rfracmasked < 0.4) & (zfracmasked < 0.4)
bgs &= (gfracflux < 5.0) & (rfracflux < 5.0) & (zfracflux < 5.0)
bgs &= (gfracin > 0.3) & (rfracin > 0.3) & (zfracin > 0.3)
bgs &= (gfluxivar > 0) & (rfluxivar > 0) & (zfluxivar > 0)
# ADM geometric masking cuts from the Legacy Surveys.
bgs &= imaging_mask(maskbits)
if targtype == 'bright':
bgs &= ((Grr > 0.6) | (gaiagmag == 0))
elif targtype == 'faint':
bgs &= ((Grr > 0.6) | (gaiagmag == 0))
elif targtype == 'wise':
bgs &= Grr < 0.4
bgs &= Grr > -1
bgs &= w1snr > 5
return bgs
[docs]
def isBGS_colors(rfiberflux=None, gflux=None, rflux=None, zflux=None, w1flux=None,
w2flux=None, south=True, targtype=None, primary=None):
"""Standard set of color-based cuts used by all BGS target selection classes
(see, e.g., :func:`~desitarget.cuts.isBGS` for parameters).
"""
_check_BGS_targtype(targtype)
# ADM to maintain backwards-compatibility with mocks.
if rfiberflux is None:
log.warning('Setting rfiberflux to rflux!!!')
rfiberflux = rflux.copy()
if primary is None:
primary = np.ones_like(rflux, dtype='?')
bgs = primary.copy()
fmc = np.zeros_like(rflux, dtype='?')
if south:
bgs &= rflux > gflux * 10**(-1.0/2.5)
bgs &= rflux < gflux * 10**(4.0/2.5)
bgs &= zflux > rflux * 10**(-1.0/2.5)
bgs &= zflux < rflux * 10**(4.0/2.5)
else:
bgs &= rflux > gflux * 10**(-1.0/2.5)
bgs &= rflux < gflux * 10**(4.0/2.5)
bgs &= zflux > rflux * 10**(-1.0/2.5)
bgs &= zflux < rflux * 10**(4.0/2.5)
g = 22.5 - 2.5*np.log10(gflux.clip(1e-16))
r = 22.5 - 2.5*np.log10(rflux.clip(1e-16))
z = 22.5 - 2.5*np.log10(zflux.clip(1e-16))
rfib = 22.5 - 2.5*np.log10(rfiberflux.clip(1e-16))
# Fibre Magnitude Cut (FMC) -- This is a low surface brightness cut
# with the aim of increase the redshift success rate.
fmc |= ((rfib < (2.9 + 1.2 + 1.0) + r) & (r < 17.8))
fmc |= ((rfib < 22.9) & (r < 20.0) & (r > 17.8))
fmc |= ((rfib < 2.9 + r) & (r > 20))
bgs &= fmc
if targtype == 'bright':
bgs &= rflux > 10**((22.5-19.5)/2.5)
elif targtype == 'faint':
bgs &= rflux > 10**((22.5-20.0)/2.5)
bgs &= rflux <= 10**((22.5-19.5)/2.5)
elif targtype == 'wise':
bgs &= rflux > 10**((22.5-20.0)/2.5)
bgs &= w1flux*gflux > (zflux*rflux)*10**(-0.2)
return bgs
[docs]
def isBGS_lslga(gflux=None, rflux=None, zflux=None, w1flux=None, refcat=None,
maskbits=None, south=True, targtype=None):
"""Module to recover the LSLGA objects in all BGS target selection classes
(see, e.g., :func:`~desitarget.cuts.isBGS` for parameters).
"""
_check_BGS_targtype(targtype)
bgs = np.zeros_like(rflux, dtype='?')
# the LSLGA galaxies.
LX = bgs.copy()
# ADM Could check on "L2" for DR8, need to check on "LX" post-DR8.
if refcat is not None:
rc1d = np.atleast_1d(refcat)
if isinstance(rc1d[0], str):
LX = [(rc[0] == "L") if len(rc) > 0 else False for rc in rc1d]
else:
LX = [(rc.decode()[0] == "L") if len(rc) > 0 else False for rc in rc1d]
if np.ndim(refcat) == 0:
LX = np.array(LX[0], dtype=bool)
else:
LX = np.array(LX, dtype=bool)
bgs |= LX
# ADM geometric masking cuts from the Legacy Surveys.
bgs &= imaging_mask(maskbits, bgsmask=True)
if targtype == 'bright':
bgs &= rflux > 10**((22.5-19.5)/2.5)
elif targtype == 'faint':
bgs &= rflux > 10**((22.5-20.0)/2.5)
bgs &= rflux <= 10**((22.5-19.5)/2.5)
elif targtype == 'wise':
bgs &= rflux > 10**((22.5-20.0)/2.5)
bgs &= w1flux*gflux > (zflux*rflux)*10**(-0.2)
return bgs
[docs]
def isQSO_cuts(gflux=None, rflux=None, zflux=None, w1flux=None, w2flux=None,
w1snr=None, w2snr=None, deltaChi2=None, maskbits=None,
gnobs=None, rnobs=None, znobs=None,
release=None, objtype=None, primary=None, optical=False, south=True):
"""Definition of QSO target classes from color cuts. Returns a boolean array.
Parameters
----------
optical : :class:`boolean`, defaults to ``False``
Apply just optical color-cuts.
south : :class:`boolean`, defaults to ``True``
Use cuts appropriate to the Northern imaging surveys (BASS/MzLS) if ``south=False``,
otherwise use cuts appropriate to the Southern imaging survey (DECaLS).
Returns
-------
:class:`array_like`
``True`` for objects that pass the quasar color/morphology/logic cuts.
Notes
-----
- Current version (03/09/21) is version 1 on `the SV2 wiki`_.
- See :func:`~desitarget.cuts.set_target_bits` for other parameters.
"""
if not south:
gflux, rflux, zflux = shift_photo_north(gflux, rflux, zflux)
qso = isQSO_colors(gflux=gflux, rflux=rflux, zflux=zflux,
w1flux=w1flux, w2flux=w2flux,
optical=optical, south=south)
if south:
qso &= w1snr > 4
qso &= w2snr > 2
else:
qso &= w1snr > 4
qso &= w2snr > 3
# ADM observed in every band.
qso &= (gnobs > 0) & (rnobs > 0) & (znobs > 0)
# ADM default to RELEASE of 6000 if nothing is passed.
if release is None:
release = np.zeros_like(gflux, dtype='?')+6000
qso &= ((deltaChi2 > 40.) | (release >= 5000))
if primary is not None:
qso &= primary
if objtype is not None:
qso &= _psflike(objtype)
# ADM default mask bits from the Legacy Surveys not set.
qso &= imaging_mask(maskbits)
return qso
[docs]
def isQSO_colors(gflux=None, rflux=None, zflux=None, w1flux=None, w2flux=None,
optical=False, south=True):
"""Tests if sources have quasar-like colors in a color box.
(see, e.g., :func:`~desitarget.cuts.isQSO_cuts`).
"""
# ----- Quasars
# Create some composite fluxes.
wflux = 0.75*w1flux + 0.25*w2flux
grzflux = (gflux + 0.8*rflux + 0.5*zflux) / 2.3
qso = np.ones_like(gflux, dtype='?')
qso &= rflux < 10**((22.5-17.5)/2.5) # r>17.5
qso &= rflux > 10**((22.5-22.7)/2.5) # r<22.7
qso &= grzflux < 10**((22.5-17)/2.5) # grz>17
qso &= rflux < gflux * 10**(1.3/2.5) # (g-r)<1.3
qso &= zflux > rflux * 10**(-0.4/2.5) # (r-z)>-0.4
qso &= zflux < rflux * 10**(1.1/2.5) # (r-z)<1.1
if not optical:
if south:
qso &= w2flux > w1flux * 10**(-0.4/2.5) # (W1-W2)>-0.4
else:
qso &= w2flux > w1flux * 10**(-0.3/2.5) # (W1-W2)>-0.3
qso &= wflux * gflux > zflux * grzflux * 10**(-1.0/2.5) # (grz-W)>(g-z)-1.0
# Harder cut on stellar contamination
mainseq = rflux > gflux * 10**(0.20/2.5) # g-r>0.2
# Clip to avoid warnings from negative numbers raised to fractional powers.
rflux = rflux.clip(0)
zflux = zflux.clip(0)
mainseq &= rflux**(1+1.53) > gflux * zflux**1.53 * 10**((-0.100+0.20)/2.5)
mainseq &= rflux**(1+1.53) < gflux * zflux**1.53 * 10**((+0.100+0.20)/2.5)
if not optical:
mainseq &= w2flux < w1flux * 10**(0.3/2.5)
qso &= ~mainseq
return qso
[docs]
def isQSO_randomforest(gflux=None, rflux=None, zflux=None, maskbits=None,
w1flux=None, w2flux=None, objtype=None, release=None,
gnobs=None, rnobs=None, znobs=None, deltaChi2=None,
primary=None, ra=None, dec=None, south=True, return_probs=False):
"""Define QSO targets from a Random Forest. Returns a boolean array.
Parameters
----------
south : :class:`boolean`, defaults to ``True``
If ``False``, shift photometry to the Northern (BASS/MzLS)
imaging system.
return_probs : :class:`boolean`, defaults to ``False``
If ``True``, return the QSO/high-z QSO probabilities in addition
to the QSO target booleans. Only coded up for DR8 or later of the
Legacy Surveys. Will return arrays of zeros for earlier DRs.
Returns
-------
:class:`array_like`
``True`` for objects that are Random Forest quasar targets.
:class:`array_like`
``True`` for objects that are high-z RF quasar targets.
:class:`array_like`
The (float) probability that a target is a quasar. Only returned
if `return_probs` is ``True``.
:class:`array_like`
The (float) probability that a target is a high-z quasar. Only
returned if `return_probs` is ``True``.
Notes
-----
- Current version (03/16/21) is version 8 on `the SV2 wiki`_.
- See :func:`~desitarget.cuts.set_target_bits` for other parameters.
"""
# ADM Primary (True for anything to initially consider as a possible target).
if primary is None:
primary = np.ones_like(gflux, dtype=bool)
# RELEASE
# ADM default to RELEASE of 5000 if nothing is passed.
if release is None:
release = np.zeros_like(gflux, dtype='?') + 5000
release = np.atleast_1d(release)
# Build variables for random forest
nFeatures = 11 # Number of attributes describing each object to be classified by the rf
nbEntries = rflux.size
if not south:
gflux, rflux, zflux = shift_photo_north(gflux, rflux, zflux)
colors, r, photOK = _getColors(nbEntries, nFeatures, gflux, rflux, zflux, w1flux, w2flux)
r = np.atleast_1d(r)
# For the last version of QSO target selection we add a cut on the Band W1 and W2!
limitInf = 1.e-04
w1flux, w2flux = w1flux.clip(limitInf), w2flux.clip(limitInf)
W1 = np.atleast_1d(np.where(w1flux > limitInf, 22.5-2.5*np.log10(w1flux), 0.))
W2 = np.atleast_1d(np.where(w2flux > limitInf, 22.5-2.5*np.log10(w2flux), 0.))
W1_cut, W2_cut = 22.3, 22.3
# Preselection to speed up the process
rMax = 23.0 # r < 23.0
rMin = 17.5 # r > 17.5
preSelection = (r < rMax) & (r > rMin) & photOK & primary
# ADM targets have to be observed in every band.
preSelection &= (gnobs > 0) & (rnobs > 0) & (znobs > 0)
if objtype is not None:
preSelection &= _psflike(objtype)
if deltaChi2 is not None:
deltaChi2 = np.atleast_1d(deltaChi2)
preSelection[release < 5000] &= deltaChi2[release < 5000] > 30.
# ADM Reject objects in masks.
# ADM BRIGHT BAILOUT GALAXY CLUSTER (1, 10, 12, 13) bits not set.
# ALLMASK_G | ALLMASK_R | ALLMASK_Z (5, 6, 7) bits not set.
# Now only 1, 12, 13
if maskbits is not None:
# ADM default mask bits from the Legacy Surveys not set.
preSelection &= imaging_mask(maskbits)
# "qso" mask initialized to "preSelection" mask.
qso = np.copy(preSelection)
# ADM to specifically store the selection from the "HighZ" RF.
qsohiz = np.copy(preSelection)
# ADM these store the probabilities, should they need returned.
pqso = np.zeros_like(qso, dtype='>f4')
pqsohiz = np.zeros_like(qso, dtype='>f4')
if np.any(preSelection):
from desitarget.myRF import myRF
# Data reduction to preselected objects
colorsReduced = colors[preSelection]
releaseReduced = release[preSelection]
r_Reduced = r[preSelection]
W1_Reduced, W2_Reduced = W1[preSelection], W2[preSelection]
colorsIndex = np.arange(0, nbEntries, dtype=np.int64)
colorsReducedIndex = colorsIndex[preSelection]
# Path to random forest files
pathToRF = str(resources.files('desitarget').joinpath('data'))
# rf filenames
rf_DR3_fileName = pathToRF + '/rf_model_dr3.npz'
rf_DR7_fileName = pathToRF + '/rf_model_dr7.npz'
rf_DR7_HighZ_fileName = pathToRF + '/rf_model_dr7_HighZ.npz'
rf_DR8_fileName = pathToRF + '/rf_model_dr8.npz'
rf_DR8_HighZ_fileName = pathToRF + '/rf_model_dr8_HighZ.npz'
rf_DR9_fileName = pathToRF + '/rf_model_dr9_final.npz'
tmpReleaseOK = releaseReduced < 5000
if np.any(tmpReleaseOK):
# rf initialization - colors data duplicated within "myRF"
rf_DR3 = myRF(colorsReduced[tmpReleaseOK], pathToRF,
numberOfTrees=200, version=1)
# rf loading
rf_DR3.loadForest(rf_DR3_fileName)
# Compute rf probabilities
tmp_rf_proba = rf_DR3.predict_proba()
tmp_r_Reduced = r_Reduced[tmpReleaseOK]
# Compute optimized proba cut
pcut = np.where(tmp_r_Reduced > 20.0,
0.95 - (tmp_r_Reduced - 20.0) * 0.08, 0.95)
# Add rf proba test result to "qso" mask
qso[colorsReducedIndex[tmpReleaseOK]] = tmp_rf_proba >= pcut
# ADM no high-z selection for DR3.
qsohiz &= False
tmpReleaseOK = (releaseReduced >= 5000) & (releaseReduced < 8000)
if np.any(tmpReleaseOK):
# rf initialization - colors data duplicated within "myRF"
rf = myRF(colorsReduced[tmpReleaseOK], pathToRF,
numberOfTrees=500, version=2)
rf_HighZ = myRF(colorsReduced[tmpReleaseOK], pathToRF,
numberOfTrees=500, version=2)
# rf loading
rf.loadForest(rf_DR7_fileName)
rf_HighZ.loadForest(rf_DR7_HighZ_fileName)
# Compute rf probabilities
tmp_rf_proba = rf.predict_proba()
tmp_rf_HighZ_proba = rf_HighZ.predict_proba()
# Compute optimized proba cut
tmp_r_Reduced = r_Reduced[tmpReleaseOK]
pcut = np.where(tmp_r_Reduced > 20.8,
0.83 - (tmp_r_Reduced - 20.8) * 0.025, 0.83)
pcut[tmp_r_Reduced > 21.5] = 0.8125 - 0.15 * (tmp_r_Reduced[tmp_r_Reduced > 21.5] - 21.5)
pcut[tmp_r_Reduced > 22.3] = 0.6925 - 0.70 * (tmp_r_Reduced[tmp_r_Reduced > 22.3] - 22.3)
pcut_HighZ = np.where(tmp_r_Reduced > 20.5,
0.55 - (tmp_r_Reduced - 20.5) * 0.025, 0.55)
# Add rf proba test result to "qso" mask
qso[colorsReducedIndex[tmpReleaseOK]] = \
(tmp_rf_proba >= pcut) | (tmp_rf_HighZ_proba >= pcut_HighZ)
# ADM populate a mask specific to the "HighZ" selection.
qsohiz[colorsReducedIndex[tmpReleaseOK]] = \
(tmp_rf_HighZ_proba >= pcut_HighZ)
tmpReleaseOK = (releaseReduced >= 8000) & (releaseReduced < 9000)
if np.any(tmpReleaseOK):
# rf initialization - colors data duplicated within "myRF"
rf = myRF(colorsReduced[tmpReleaseOK], pathToRF,
numberOfTrees=500, version=2)
rf_HighZ = myRF(colorsReduced[tmpReleaseOK], pathToRF,
numberOfTrees=500, version=2)
# rf loading
rf.loadForest(rf_DR8_fileName)
rf_HighZ.loadForest(rf_DR8_HighZ_fileName)
# Compute rf probabilities
tmp_rf_proba = rf.predict_proba()
tmp_rf_HighZ_proba = rf_HighZ.predict_proba()
# Compute optimized proba cut
tmp_r_Reduced = r_Reduced[tmpReleaseOK]
pcut = 0.88 - 0.03*np.tanh(tmp_r_Reduced - 20.5)
pcut_HighZ = 0.55
# Add rf proba test result to "qso" mask
qso[colorsReducedIndex[tmpReleaseOK]] = \
(tmp_rf_proba >= pcut) | (tmp_rf_HighZ_proba >= pcut_HighZ)
# ADM populate a mask specific to the "HighZ" selection.
qsohiz[colorsReducedIndex[tmpReleaseOK]] = \
(tmp_rf_HighZ_proba >= pcut_HighZ)
# ADM store the probabilities in case they need returned.
pqso[colorsReducedIndex[tmpReleaseOK]] = tmp_rf_proba
# ADM populate a mask specific to the "HighZ" selection.
pqsohiz[colorsReducedIndex[tmpReleaseOK]] = tmp_rf_HighZ_proba
tmpReleaseOK = releaseReduced >= 9000
if np.any(tmpReleaseOK):
# rf initialization - colors data duplicated within "myRF"
rf = myRF(colorsReduced[tmpReleaseOK], pathToRF,
numberOfTrees=500, version=2)
# rf loading
rf.loadForest(rf_DR9_fileName)
# Compute rf probabilities
tmp_rf_proba = rf.predict_proba()
# Compute optimized proba cut
tmp_r_Reduced = r_Reduced[tmpReleaseOK]
tmp_W1_Reduced, tmp_W2_Reduced = W1_Reduced[tmpReleaseOK], W2_Reduced[tmpReleaseOK]
# NO SECOND RF ! ==> keep the structure but no impact on the selection
tmp_rf_HighZ_proba, pcut_HighZ = np.zeros(tmp_r_Reduced.size), 1.1
if not south:
# threshold selection for North footprint.
pcut = 0.88 - 0.04*np.tanh(tmp_r_Reduced - 20.5)
else:
pcut = np.ones(tmp_rf_proba.size)
is_des = (gnobs[preSelection][tmpReleaseOK] > 4) &\
(rnobs[preSelection][tmpReleaseOK] > 4) &\
(znobs[preSelection][tmpReleaseOK] > 4) &\
((ra[preSelection][tmpReleaseOK] >= 320) | (ra[preSelection][tmpReleaseOK] <= 100)) &\
(dec[preSelection][tmpReleaseOK] <= 10)
# threshold selection for DES footprint.
pcut[is_des] = 0.7 - 0.05*np.tanh(tmp_r_Reduced[is_des] - 20.5)
# threshold selection for South footprint.
pcut[~is_des] = 0.84 - 0.04*np.tanh(tmp_r_Reduced[~is_des] - 20.5)
# Add rf proba test result to "qso" mask
qso[colorsReducedIndex[tmpReleaseOK]] = \
(tmp_rf_proba >= pcut) & (tmp_W1_Reduced <= W1_cut) & (tmp_W2_Reduced <= W2_cut)
# (NO IMPACT) ADM populate a mask specific to the "HighZ" selection.
qsohiz[colorsReducedIndex[tmpReleaseOK]] = \
(tmp_rf_HighZ_proba >= pcut_HighZ)
# ADM store the probabilities in case they need returned.
pqso[colorsReducedIndex[tmpReleaseOK]] = tmp_rf_proba
# (NO IMPACT) ADM populate a mask specific to the "HighZ" selection.
pqsohiz[colorsReducedIndex[tmpReleaseOK]] = tmp_rf_HighZ_proba
# 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]
qsohiz = qsohiz[0]
pqso = pqso[0]
pqsohiz = pqsohiz[0]
# ADM if requested, return the probabilities as well.
if return_probs:
return qso, qsohiz, pqso, pqsohiz
return qso, qsohiz
[docs]
def _psflike(psftype):
""" If the object is PSF """
# ADM explicitly checking for NoneType. I can't see why we'd ever want to
# ADM run this test on empty information. In the past we have had bugs where
# ADM we forgot to pass objtype=objtype in, e.g., isSTD
if psftype is None:
raise ValueError("NoneType submitted to _psfflike function")
psftype = np.asarray(psftype)
# ADM in Python3 these string literals become byte-like
# ADM so to retain Python2 compatibility we need to check
# ADM against both bytes and unicode
# ADM, also 'PSF' for astropy.io.fits; 'PSF ' for fitsio (sigh)
psflike = ((psftype == 'PSF') | (psftype == b'PSF') |
(psftype == 'PSF ') | (psftype == b'PSF '))
return psflike
[docs]
def _isonnorthphotsys(photsys):
""" If the object is from the northen photometric system """
# ADM explicitly checking for NoneType. I can't see why we'd ever want to
# ADM run this test on empty information. In the past we have had bugs where
# ADM we forgot to populate variables before passing them
if photsys is None:
raise ValueError("NoneType submitted to _isonnorthphotsys function")
psftype = np.asarray(photsys)
# ADM in Python3 these string literals become byte-like
# ADM so to retain Python2 compatibility we need to check
# ADM against both bytes and unicode
northern = ((photsys == 'N') | (photsys == b'N'))
return northern
def _getColors(nbEntries, nfeatures, gflux, rflux, zflux, w1flux, w2flux):
limitInf = 1.e-04
gflux = gflux.clip(limitInf)
rflux = rflux.clip(limitInf)
zflux = zflux.clip(limitInf)
w1flux = w1flux.clip(limitInf)
w2flux = w2flux.clip(limitInf)
g = np.where(gflux > limitInf, 22.5-2.5*np.log10(gflux), 0.)
r = np.where(rflux > limitInf, 22.5-2.5*np.log10(rflux), 0.)
z = np.where(zflux > limitInf, 22.5-2.5*np.log10(zflux), 0.)
W1 = np.where(w1flux > limitInf, 22.5-2.5*np.log10(w1flux), 0.)
W2 = np.where(w2flux > limitInf, 22.5-2.5*np.log10(w2flux), 0.)
photOK = (g > 0.) & (r > 0.) & (z > 0.) & (W1 > 0.) & (W2 > 0.)
colors = np.zeros((nbEntries, nfeatures))
colors[:, 0] = g-r
colors[:, 1] = r-z
colors[:, 2] = g-z
colors[:, 3] = g-W1
colors[:, 4] = r-W1
colors[:, 5] = z-W1
colors[:, 6] = g-W2
colors[:, 7] = r-W2
colors[:, 8] = z-W2
colors[:, 9] = W1-W2
colors[:, 10] = r
return colors, r, photOK
[docs]
def _is_row(table):
"""Return True/False if this is a row of a table instead of a full table.
supports numpy.ndarray, astropy.io.fits.FITS_rec, and astropy.table.Table
"""
import astropy.io.fits.fitsrec
import astropy.table.row
if isinstance(table, (astropy.io.fits.fitsrec.FITS_record, astropy.table.row.Row)) or \
np.isscalar(table):
return True
else:
return False
[docs]
def set_target_bits(photsys_north, photsys_south, obs_rflux,
gflux, rflux, zflux, w1flux, w2flux,
gfiberflux, rfiberflux, zfiberflux, gfibertotflux,
rfibertotflux, zfibertotflux, objtype, release,
ra, dec, gfluxivar, rfluxivar, zfluxivar, w1fluxivar,
gnobs, rnobs, znobs, gfracflux, rfracflux, zfracflux,
gfracmasked, rfracmasked, zfracmasked,
gfracin, rfracin, zfracin, gallmask, rallmask, zallmask,
gsnr, rsnr, zsnr, w1snr, w2snr, deltaChi2, dchisq,
gaia, pmra, pmdec, parallax, parallaxovererror, parallaxerr,
gaiagmag, gaiabmag, gaiarmag, gaiaaen, gaiadupsource,
gaiaparamssolved, gaiabprpfactor, gaiasigma5dmax, galb,
tcnames, qso_optical_cuts, qso_selection,
maskbits, Grr, refcat, primary, resolvetargs=True):
"""Perform target selection on parameters, return target mask arrays.
Parameters
----------
photsys_north, photsys_south : :class:`~numpy.ndarray`
``True`` for objects that were drawn from northern (MzLS/BASS) or
southern (DECaLS) imaging, respectively.
obs_rflux : :class:`~numpy.ndarray`
`rflux` but WITHOUT any Galactic extinction correction.
gflux, rflux, zflux, w1flux, w2flux : :class:`~numpy.ndarray`
The flux in nano-maggies of g, r, z, W1 and W2 bands.
Corrected for Galactic extinction.
gfiberflux, rfiberflux, zfiberflux : :class:`~numpy.ndarray`
Predicted fiber flux from object in 1 arcsecond seeing in g/r/z.
Corrected for Galactic extinction.
gfibertotflux, rfibertotflux, zfibertotflux : :class:`~numpy.ndarray`
Predicted fiber flux from ALL sources at object's location in 1
arcsecond seeing in g/r/z. NOT corrected for Galactic extinction.
objtype, release : :class:`~numpy.ndarray`
`The Legacy Surveys`_ imaging ``TYPE`` and ``RELEASE`` columns.
gfluxivar, rfluxivar, zfluxivar, w1fluxivar: :class:`~numpy.ndarray`
The flux inverse variances in g, r, z and W1 bands.
gnobs, rnobs, znobs: :class:`~numpy.ndarray`
The number of observations (in the central pixel) in g, r and z.
gfracflux, rfracflux, zfracflux: :class:`~numpy.ndarray`
Profile-weighted fraction of the flux from other sources divided
by the total flux in g, r and z bands.
gfracmasked, rfracmasked, zfracmasked: :class:`~numpy.ndarray`
Fraction of masked pixels in the g, r and z bands.
gallmask, rallmask, zallmask: :class:`~numpy.ndarray`
Bitwise mask set if the central pixel from all images
satisfy each condition in g, r, z.
gsnr, rsnr, zsnr, w1snr, w2snr: :class:`~numpy.ndarray`
Signal-to-noise in g, r, z, W1 and W2 defined as the flux per
band divided by sigma (flux x sqrt of the inverse variance).
deltaChi2: :class:`~numpy.ndarray`
chi2 difference between PSF and SIMP, dchisq_PSF - dchisq_SIMP.
dchisq: :class:`~numpy.ndarray`
Difference in chi2 between successively more-complex model fits.
Columns are model fits, in order, of PSF, REX, EXP, DEV, COMP.
gaia: :class:`~numpy.ndarray`
``True`` if there is a match between this object in
`the Legacy Surveys`_ and in Gaia.
pmra, pmdec, parallax, parallaxovererror: :class:`~numpy.ndarray`
Gaia-based proper motion in RA and Dec, and parallax and error.
gaiagmag, gaiabmag, gaiarmag: :class:`~numpy.ndarray`
Gaia-based g-, b- and r-band MAGNITUDES.
gaiaaen, gaiadupsource, gaiaparamssolved: :class:`~numpy.ndarray`
Gaia-based measures of Astrometric Excess Noise, whether the
source is a duplicate, and how many parameters were solved for.
gaiabprpfactor, gaiasigma5dmax: :class:`~numpy.ndarray`
Gaia_based BP/RP excess factor and longest semi-major axis
of 5-d error ellipsoid.
galb: :class:`~numpy.ndarray`
Galactic latitude (degrees).
tcnames : :class:`list`, defaults to running all target classes
A list of strings, e.g. ['QSO','LRG']. If passed, process only
only those specific target classes. A useful speed-up for tests.
Options include ["ELG", "QSO", "LRG", "MWS", "BGS", "STD"].
qso_optical_cuts : :class:`boolean` defaults to ``False``
Apply just optical color-cuts when selecting QSOs with
``qso_selection="colorcuts"``.
qso_selection : :class:`str`, optional, defaults to `'randomforest'`
The algorithm to use for QSO selection; valid options are
`'colorcuts'` and `'randomforest'`
maskbits: boolean array_like or None
General `Legacy Surveys mask`_ bits.
Grr: array_like or None
Gaia G band magnitude minus observational r magnitude.
primary : :class:`~numpy.ndarray`
``True`` for objects that should be considered when setting bits.
survey : :class:`str`, defaults to ``'main'``
Specifies which target masks yaml file and target selection cuts
to use. Options are ``'main'`` and ``'svX``' (X is 1, 2, etc.)
for the main survey and different iterations of SV, respectively.
resolvetargs : :class:`boolean`, optional, defaults to ``True``
If ``True``, if only northern (southern) sources are passed then
only apply the northern (southern) cuts to those sources.
ra, dec : :class:`~numpy.ndarray`
The Ra, Dec position of objects
Returns
-------
:class:`~numpy.ndarray`
(desi_target, bgs_target, mws_target) where each element is
an ndarray of target selection bitmask flags for each object.
Notes
-----
- Gaia quantities have units as for `the Gaia data model`_.
"""
from desitarget.sv2.sv2_targetmask import desi_mask, bgs_mask, mws_mask
# ADM if resolvetargs is set, limit to only sending north/south objects
# ADM through north/south cuts.
south_cuts = [False, True]
if resolvetargs:
# ADM if only southern objects were sent this will be [True], if
# ADM only northern it will be [False], else it wil be both.
south_cuts = list(set(np.atleast_1d(photsys_south)))
# ADM default for target classes we WON'T process is all False.
tcfalse = primary & False
# ADM initially set everything to arrays of False for the LRG selection
# ADM the zeroth element stores the northern targets bits (south=False).
lrg_classes = [tcfalse, tcfalse]
if "LRG" in tcnames:
for south in south_cuts:
lrg_classes[int(south)] = isLRG(
primary=primary,
gflux=gflux, rflux=rflux, zflux=zflux, w1flux=w1flux,
zfiberflux=zfiberflux, gnobs=gnobs, rnobs=rnobs, znobs=znobs,
rfluxivar=rfluxivar, zfluxivar=zfluxivar, w1fluxivar=w1fluxivar,
gaiagmag=gaiagmag, maskbits=maskbits, south=south
)
lrg_north, lrg_south = lrg_classes
# ADM combine LRG target bits for an LRG target based on any imaging.
lrg = (lrg_north & photsys_north) | (lrg_south & photsys_south)
# ADM initially set everything to arrays of False for the ELG selection
# ADM the zeroth element stores the northern targets bits (south=False).
elg_classes = [tcfalse, tcfalse]
if "ELG" in tcnames:
for south in south_cuts:
elg_classes[int(south)] = isELG(
primary=primary, gflux=gflux, rflux=rflux, zflux=zflux,
gfiberflux=gfiberflux, gsnr=gsnr, rsnr=rsnr, zsnr=zsnr,
gnobs=gnobs, rnobs=rnobs, znobs=znobs, maskbits=maskbits,
south=south
)
elg_north, elg_south = elg_classes
# ADM combine ELG target bits for an ELG target based on any imaging.
elg = (elg_north & photsys_north) | (elg_south & photsys_south)
# ADM initially set everything to arrays of False for the QSO selection
# ADM the zeroth element stores the northern targets bits (south=False).
qso_classes = [[tcfalse, tcfalse], [tcfalse, tcfalse]]
if "QSO" in tcnames:
for south in south_cuts:
if qso_selection == 'colorcuts':
# ADM determine quasar targets in the north and the south separately
# ADM the [0] here is critical as isQSO_cuts only returns one bit
# ADM and the other bit (which is the "high-z" bit from the Random
# ADM Forest needs to be set to all "False".
qso_classes[int(south)][0] = isQSO_cuts(
primary=primary, zflux=zflux, rflux=rflux, gflux=gflux,
w1flux=w1flux, w2flux=w2flux,
deltaChi2=deltaChi2, maskbits=maskbits,
gnobs=gnobs, rnobs=rnobs, znobs=znobs,
objtype=objtype, w1snr=w1snr, w2snr=w2snr, release=release,
optical=qso_optical_cuts, south=south
)
elif qso_selection == 'randomforest':
# ADM determine quasar targets in the north and the south separately
qso_classes[int(south)] = isQSO_randomforest(
primary=primary, zflux=zflux, rflux=rflux, gflux=gflux,
w1flux=w1flux, w2flux=w2flux, deltaChi2=deltaChi2,
maskbits=maskbits, gnobs=gnobs, rnobs=rnobs, znobs=znobs,
objtype=objtype, release=release, ra=ra, dec=dec, south=south
)
else:
raise ValueError('Unknown qso_selection {}; valid options are {}'.format(
qso_selection, qso_selection_options))
qso_north, qso_hiz_north = qso_classes[0]
qso_south, qso_hiz_south = qso_classes[1]
# ADM combine QSO targeting bits for a QSO selected in any imaging.
qso = (qso_north & photsys_north) | (qso_south & photsys_south)
qsohiz = (qso_hiz_north & photsys_north) | (qso_hiz_south & photsys_south)
# ADM initially set everything to arrays of False for the BGS selection
# ADM the zeroth element stores the northern targets bits (south=False).
bgs_classes = [[tcfalse, tcfalse, tcfalse], [tcfalse, tcfalse, tcfalse]]
# ADM set the BGS bits
if "BGS" in tcnames:
for south in south_cuts:
bgs_store = []
for targtype in ["bright", "faint", "wise"]:
bgs_store.append(
isBGS(
rfiberflux=rfiberflux, gflux=gflux, rflux=rflux, zflux=zflux,
w1flux=w1flux, w2flux=w2flux, gnobs=gnobs, rnobs=rnobs, znobs=znobs,
gfracmasked=gfracmasked, rfracmasked=rfracmasked, zfracmasked=zfracmasked,
gfracflux=gfracflux, rfracflux=rfracflux, zfracflux=zfracflux,
gfracin=gfracin, rfracin=rfracin, zfracin=zfracin,
gfluxivar=gfluxivar, rfluxivar=rfluxivar, zfluxivar=zfluxivar,
maskbits=maskbits, Grr=Grr, refcat=refcat, w1snr=w1snr, gaiagmag=gaiagmag,
objtype=objtype, primary=primary, south=south, targtype=targtype
)
)
bgs_classes[int(south)] = bgs_store
bgs_bright_north, bgs_faint_north, bgs_wise_north = bgs_classes[0]
bgs_bright_south, bgs_faint_south, bgs_wise_south = bgs_classes[1]
# ADM combine BGS targeting bits for a BGS selected in any imaging.
bgs_bright = (bgs_bright_north & photsys_north) | (bgs_bright_south & photsys_south)
bgs_faint = (bgs_faint_north & photsys_north) | (bgs_faint_south & photsys_south)
bgs_wise = (bgs_wise_north & photsys_north) | (bgs_wise_south & photsys_south)
# ADM 10% of the BGS_FAINT sources need the BGS_FAINT_HIP bit set.
# ADM form a seed using RA/Dec in case we parallelized by HEALPixel.
# SJB seeds must be within 0 - 2**32-1
# SJB np1.18 scalar vs. vector support, but note that HIP won't be
# set identically for vector vs. calling scalar N times.
uniqseed = int(np.mean(zflux)*1e5) % (2**32 - 1)
np.random.seed(uniqseed)
hip = None
if np.isscalar(bgs_faint):
if bgs_faint:
nbgsf = 1
hip = np.random.uniform(0, 1) < 0.1
else:
w = np.where(bgs_faint)[0]
nbgsf = len(w)
if nbgsf > 0:
hip = np.random.choice(w, nbgsf//10, replace=False)
# ADM initially set everything to arrays of False for the MWS selection
# ADM the zeroth element stores the northern targets bits (south=False).
mws_classes = [[tcfalse, tcfalse, tcfalse], [tcfalse, tcfalse, tcfalse]]
mws_nearby = tcfalse
mws_bhb = tcfalse
if "MWS" in tcnames:
mws_nearby = isMWS_nearby(
gaia=gaia, gaiagmag=gaiagmag, parallax=parallax,
parallaxerr=parallaxerr, paramssolved=gaiaparamssolved
)
mws_bhb = isMWS_bhb(
primary=primary,
objtype=objtype,
gaia=gaia, gaiaaen=gaiaaen, gaiadupsource=gaiadupsource, gaiagmag=gaiagmag,
gflux=gflux, rflux=rflux, zflux=zflux,
w1flux=w1flux, w1snr=w1snr,
gnobs=gnobs, rnobs=rnobs, znobs=znobs,
gfracmasked=gfracmasked, rfracmasked=rfracmasked, zfracmasked=zfracmasked,
parallax=parallax, parallaxerr=parallaxerr, maskbits=maskbits
)
# ADM run the MWS target types for (potentially) both north and south.
for south in south_cuts:
mws_classes[int(south)] = isMWS_main(
gaia=gaia, gaiaaen=gaiaaen, gaiadupsource=gaiadupsource,
gflux=gflux, rflux=rflux, obs_rflux=obs_rflux, objtype=objtype,
gnobs=gnobs, rnobs=rnobs, gfracmasked=gfracmasked,
rfracmasked=rfracmasked, pmra=pmra, pmdec=pmdec,
parallax=parallax, parallaxerr=parallaxerr, maskbits=maskbits,
paramssolved=gaiaparamssolved, primary=primary, south=south
)
mws_broad_n, mws_red_n, mws_blue_n = mws_classes[0]
mws_broad_s, mws_red_s, mws_blue_s = mws_classes[1]
# ADM treat the MWS WD selection specially, as we have to run the
# ADM white dwarfs for standards and MWS science targets.
mws_wd = tcfalse
if "MWS" in tcnames or "STD" in tcnames:
mws_wd = isMWS_WD(
gaia=gaia, galb=galb, astrometricexcessnoise=gaiaaen,
pmra=pmra, pmdec=pmdec, parallax=parallax,
parallaxovererror=parallaxovererror, paramssolved=gaiaparamssolved,
photbprpexcessfactor=gaiabprpfactor, astrometricsigma5dmax=gaiasigma5dmax,
gaiagmag=gaiagmag, gaiabmag=gaiabmag, gaiarmag=gaiarmag
)
# ADM initially set everything to False for the standards.
std_faint, std_bright, std_wd = tcfalse, tcfalse, tcfalse
if "STD" in tcnames:
# ADM run the MWS_MAIN target types for both faint and bright.
# ADM Make sure to pass all of the needed columns! At one point we stopped
# ADM passing objtype, which meant no standards were being returned.
std_classes = []
for bright in [False, True]:
std_classes.append(
isSTD(
primary=primary, zflux=zflux, rflux=rflux, gflux=gflux, maskbits=maskbits,
gfracflux=gfracflux, rfracflux=rfracflux, zfracflux=zfracflux,
gfracmasked=gfracmasked, rfracmasked=rfracmasked, objtype=objtype,
zfracmasked=zfracmasked, gnobs=gnobs, rnobs=rnobs, znobs=znobs,
gfluxivar=gfluxivar, rfluxivar=rfluxivar, zfluxivar=zfluxivar,
gaia=gaia, astrometricexcessnoise=gaiaaen, paramssolved=gaiaparamssolved,
pmra=pmra, pmdec=pmdec, parallax=parallax, dupsource=gaiadupsource,
gaiagmag=gaiagmag, gaiabmag=gaiabmag, gaiarmag=gaiarmag, bright=bright
)
)
std_faint, std_bright = std_classes
# ADM the standard WDs are currently identical to the MWS WDs.
std_wd = mws_wd
# ADM combine the north/south MWS bits.
mws_broad = (mws_broad_n & photsys_north) | (mws_broad_s & photsys_south)
mws_blue = (mws_blue_n & photsys_north) | (mws_blue_s & photsys_south)
mws_red = (mws_red_n & photsys_north) | (mws_red_s & photsys_south)
# Construct the targetflag bits for DECaLS (i.e. South).
desi_target = lrg_south * desi_mask.LRG_SOUTH
desi_target |= elg_south * desi_mask.ELG_SOUTH
desi_target |= qso_south * desi_mask.QSO_SOUTH
# Construct the targetflag bits for MzLS and BASS (i.e. North).
desi_target |= lrg_north * desi_mask.LRG_NORTH
desi_target |= elg_north * desi_mask.ELG_NORTH
desi_target |= qso_north * desi_mask.QSO_NORTH
# Construct the targetflag bits combining north and south.
desi_target |= lrg * desi_mask.LRG
desi_target |= elg * desi_mask.ELG
desi_target |= qso * desi_mask.QSO
desi_target |= qsohiz * desi_mask.QSO_HIZ
# ADM Standards.
desi_target |= std_faint * desi_mask.STD_FAINT
desi_target |= std_bright * desi_mask.STD_BRIGHT
desi_target |= std_wd * desi_mask.STD_WD
# BGS targets, south.
bgs_target = bgs_bright_south * bgs_mask.BGS_BRIGHT_SOUTH
bgs_target |= bgs_faint_south * bgs_mask.BGS_FAINT_SOUTH
# ADM turn off BGS_WISE until we're sure we'll use it.
# bgs_target |= bgs_wise_south * bgs_mask.BGS_WISE_SOUTH
# BGS targets, north.
bgs_target |= bgs_bright_north * bgs_mask.BGS_BRIGHT_NORTH
bgs_target |= bgs_faint_north * bgs_mask.BGS_FAINT_NORTH
# ADM turn off BGS_WISE until we're sure we'll use it.
# bgs_target |= bgs_wise_north * bgs_mask.BGS_WISE_NORTH
# BGS targets, combined.
bgs_target |= bgs_bright * bgs_mask.BGS_BRIGHT
bgs_target |= bgs_faint * bgs_mask.BGS_FAINT
# ADM turn off BGS_WISE until we're sure we'll use it.
# bgs_target |= bgs_wise * bgs_mask.BGS_WISE
# ADM set 10% of the BGS_FAINT targets to BGS_FAINT_HIP.
if hip is not None:
if hip is True:
bgs_target |= bgs_mask.BGS_FAINT_HIP
else:
bgs_target[hip] |= bgs_mask.BGS_FAINT_HIP
# ADM MWS main, nearby, and WD.
mws_target = mws_broad * mws_mask.MWS_BROAD
mws_target |= mws_wd * mws_mask.MWS_WD
mws_target |= mws_nearby * mws_mask.MWS_NEARBY
mws_target |= mws_bhb * mws_mask.MWS_BHB
# ADM MWS main north/south split.
mws_target |= mws_broad_n * mws_mask.MWS_BROAD_NORTH
mws_target |= mws_broad_s * mws_mask.MWS_BROAD_SOUTH
# ADM MWS main blue/red split.
mws_target |= mws_blue * mws_mask.MWS_MAIN_BLUE
mws_target |= mws_blue_n * mws_mask.MWS_MAIN_BLUE_NORTH
mws_target |= mws_blue_s * mws_mask.MWS_MAIN_BLUE_SOUTH
mws_target |= mws_red * mws_mask.MWS_MAIN_RED
mws_target |= mws_red_n * mws_mask.MWS_MAIN_RED_NORTH
mws_target |= mws_red_s * mws_mask.MWS_MAIN_RED_SOUTH
# Are any BGS or MWS bit set? Tell desi_target too.
desi_target |= (bgs_target != 0) * desi_mask.BGS_ANY
desi_target |= (mws_target != 0) * desi_mask.MWS_ANY
return desi_target, bgs_target, mws_target