예제 #1
0
def resolve(targets):
    """Resolve which targets are primary in imaging overlap regions.

    Parameters
    ----------
    targets : :class:`~numpy.ndarray`
        Rec array of targets. Must have columns "RA" and "DEC" and
        either "RELEASE" or "PHOTSYS".

    Returns
    -------
    :class:`~numpy.ndarray`
        The original target list trimmed to only objects from the "northern"
        photometry in the northern imaging area and objects from "southern"
        photometry in the southern imaging area.
    """
    # ADM retrieve the photometric system from the RELEASE.
    from desitarget.io import release_to_photsys, desitarget_resolve_dec
    if 'PHOTSYS' in targets.dtype.names:
        photsys = targets["PHOTSYS"]
    else:
        photsys = release_to_photsys(targets["RELEASE"])

    # ADM a flag of which targets are from the 'N' photometry.
    from desitarget.cuts import _isonnorthphotsys
    photn = _isonnorthphotsys(photsys)

    # ADM grab the declination used to resolve targets.
    split = desitarget_resolve_dec()

    # ADM determine which targets are north of the Galactic plane. As
    # ADM a speed-up, bin in ~1 sq.deg. HEALPixels and determine
    # ADM which of those pixels are north of the Galactic plane.
    # ADM We should never be as close as ~1o to the plane.
    from desitarget.geomask import is_in_gal_box, pixarea2nside
    nside = pixarea2nside(1)
    theta, phi = np.radians(90 - targets["DEC"]), np.radians(targets["RA"])
    pixnum = hp.ang2pix(nside, theta, phi, nest=True)
    # ADM find the pixels north of the Galactic plane...
    allpix = np.arange(hp.nside2npix(nside))
    theta, phi = hp.pix2ang(nside, allpix, nest=True)
    ra, dec = np.degrees(phi), 90 - np.degrees(theta)
    pixn = is_in_gal_box([ra, dec], [0., 360., 0., 90.], radec=True)
    # ADM which targets are in pixels north of the Galactic plane.
    galn = pixn[pixnum]

    # ADM which targets are in the northern imaging area.
    arean = (targets["DEC"] >= split) & galn

    # ADM retain 'N' targets in 'N' area and 'S' in 'S' area.
    keep = (photn & arean) | (~photn & ~arean)

    return targets[keep]
예제 #2
0
def gaia_in_file(infile, maglim=18, mindec=-30., mingalb=10.):
    """Retrieve the Gaia objects from a HEALPixel-split Gaia file.

    Parameters
    ----------
    infile : :class:`str`
        File name of a single Gaia "healpix" file.
    maglim : :class:`float`, optional, defaults to 18
        Magnitude limit for GFAs in Gaia G-band.
    mindec : :class:`float`, optional, defaults to -30
        Minimum declination (o) to include for output Gaia objects.
    mingalb : :class:`float`, optional, defaults to 10
        Closest latitude to Galactic plane for output Gaia objects
        (e.g. send 10 to limit to areas beyond -10o <= b < 10o)"

    Returns
    -------
    :class:`~numpy.ndarray`
        Gaia objects in the passed Gaia file brighter than `maglim`,
        formatted according to `desitarget.gfa.gfadatamodel`.

    Notes
    -----
       - A "Gaia healpix file" here is as made by, e.g.
         :func:`~desitarget.gaiamatch.gaia_fits_to_healpix()`
    """
    # ADM read in the Gaia file and limit to the passed magnitude.
    objs = read_gaia_file(infile)
    ii = objs['GAIA_PHOT_G_MEAN_MAG'] < maglim
    objs = objs[ii]

    # ADM rename GAIA_RA/DEC to RA/DEC, as that's what's used for GFAs.
    for radec in ["RA", "DEC"]:
        objs.dtype.names = [
            radec if col == "GAIA_" + radec else col
            for col in objs.dtype.names
        ]

    # ADM initiate the GFA data model.
    gfas = np.zeros(len(objs), dtype=gfadatamodel.dtype)
    # ADM make sure all columns initially have "ridiculous" numbers
    gfas[...] = -99.
    for col in gfas.dtype.names:
        if isinstance(gfas[col][0].item(), (bytes, str)):
            gfas[col] = 'U'
        if isinstance(gfas[col][0].item(), int):
            gfas[col] = -1
    # ADM some default special cases. Default to REF_EPOCH of Gaia DR2,
    # ADM make RA/Dec very precise for Gaia measurements.
    gfas["REF_EPOCH"] = 2015.5
    gfas["RA_IVAR"], gfas["DEC_IVAR"] = 1e16, 1e16

    # ADM populate the common columns in the Gaia/GFA data models.
    cols = set(gfas.dtype.names).intersection(set(objs.dtype.names))
    for col in cols:
        gfas[col] = objs[col]

    # ADM update the Gaia morphological type.
    gfas["TYPE"] = gaia_morph(gfas)

    # ADM populate the BRICKID columns.
    gfas["BRICKID"] = bricks.brickid(gfas["RA"], gfas["DEC"])

    # ADM limit by Dec first to speed transform to Galactic coordinates.
    decgood = is_in_box(gfas, [0., 360., mindec, 90.])
    gfas = gfas[decgood]
    # ADM now limit to requesed Galactic latitude range.
    bbad = is_in_gal_box(gfas, [0., 360., -mingalb, mingalb])
    gfas = gfas[~bbad]

    return gfas
예제 #3
0
def supplement_skies(nskiespersqdeg=None,
                     numproc=16,
                     gaiadir=None,
                     mindec=-30.,
                     mingalb=10.,
                     radius=2.,
                     minobjid=0):
    """Generate supplemental sky locations using Gaia-G-band avoidance.

    Parameters
    ----------
    nskiespersqdeg : :class:`float`, optional
        The minimum DENSITY of sky fibers to generate. Defaults to
        reading from :func:`~desimodel.io` with a margin of 4x.
    numproc : :class:`int`, optional, defaults to 16
        The number of processes over which to parallelize.
    gaiadir : :class:`str`, optional, defaults to $GAIA_DIR
        The GAIA_DIR environment variable is set to this directory.
        If None is passed, then it's assumed to already exist.
    mindec : :class:`float`, optional, defaults to -30
        Minimum declination (o) to include for output sky locations.
    mingalb : :class:`float`, optional, defaults to 10
        Closest latitude to Galactic plane for output sky locations
        (e.g. send 10 to limit to areas beyond -10o <= b < 10o).
    radius : :class:`float`, optional, defaults to 2
        Radius at which to avoid (all) Gaia sources (arcseconds).
    minobjid : :class:`int`, optional, defaults to 0
        The minimum OBJID to start counting from in a brick. Used
        to make sure supplemental skies have different OBJIDs from
        regular skies.

    Returns
    -------
    :class:`~numpy.ndarray`
        a structured array of supplemental sky positions in the DESI sky
        target format within the passed `mindec` and `mingalb` limits.

    Notes
    -----
        - The environment variable $GAIA_DIR must be set, or `gaiadir`
          must be passed.
    """
    log.info("running on {} processors".format(numproc))

    # ADM if the GAIA directory was passed, set it.
    if gaiadir is not None:
        os.environ["GAIA_DIR"] = gaiadir

    # ADM if needed, determine the density of sky fibers to generate.
    if nskiespersqdeg is None:
        nskiespersqdeg = density_of_sky_fibers(margin=4)

    # ADM determine the HEALPixel nside of the standard Gaia files.
    anyfiles = find_gaia_files([0, 0], radec=True)
    hdr = fitsio.read_header(anyfiles[0], "GAIAHPX")
    nside = hdr["HPXNSIDE"]

    # ADM create a set of random locations accounting for mindec.
    log.info("Generating supplemental sky locations at Dec > {}o...t={:.1f}s".
             format(mindec,
                    time() - start))
    from desitarget.randoms import randoms_in_a_brick_from_edges
    ras, decs = randoms_in_a_brick_from_edges(0.,
                                              360.,
                                              mindec,
                                              90.,
                                              density=nskiespersqdeg,
                                              wrap=False)

    # ADM limit randoms by mingalb.
    log.info(
        "Generated {} sky locations. Limiting to |b| > {}o...t={:.1f}s".format(
            len(ras), mingalb,
            time() - start))
    bnorth = is_in_gal_box([ras, decs], [0, 360, mingalb, 90], radec=True)
    bsouth = is_in_gal_box([ras, decs], [0, 360, -90, -mingalb], radec=True)
    ras, decs = ras[bnorth | bsouth], decs[bnorth | bsouth]

    # ADM find HEALPixels for the random points.
    log.info(
        "Cut to {} sky locations. Finding their HEALPixels...t={:.1f}s".format(
            len(ras),
            time() - start))
    theta, phi = np.radians(90 - decs), np.radians(ras)
    pixels = hp.ang2pix(nside, theta, phi, nest=True)
    upixels = np.unique(pixels)
    npixels = len(upixels)
    log.info("Running across {} HEALPixels.".format(npixels))

    # ADM parallelize across pixels. The function to run on every pixel.
    def _get_supp(pix):
        """wrapper on get_supp_skies() given a HEALPixel"""
        ii = (pixels == pix)
        return get_supp_skies(ras[ii], decs[ii], radius=radius)

    # ADM this is just to count pixels in _update_status.
    npix = np.zeros((), dtype='i8')
    t0 = time()

    def _update_status(result):
        """wrapper function for the critical reduction operation,
        that occurs on the main parallel process"""
        if npix % 500 == 0 and npix > 0:
            rate = npix / (time() - t0)
            log.info('{}/{} HEALPixels; {:.1f} pixels/sec'.format(
                npix, npixels, rate))
        npix[...] += 1  # this is an in-place modification.
        return result

    # - Parallel process across the unique pixels.
    if numproc > 1:
        pool = sharedmem.MapReduce(np=numproc)
        with pool:
            supp = pool.map(_get_supp, upixels, reduce=_update_status)
    else:
        supp = []
        for upix in upixels:
            supp.append(_update_status(_get_supp(upix)))

    # ADM Concatenate the parallelized results into one rec array.
    supp = np.concatenate(supp)

    # ADM build the OBJIDs from the number of sources per brick.
    # ADM the for loop doesn't seem the smartest way, but it is O(n).
    log.info("Begin assigning OBJIDs to bricks...t={:.1f}s".format(time() -
                                                                   start))
    brxid = supp["BRICKID"]
    # ADM start each brick counting from minobjid.
    cntr = np.zeros(np.max(brxid) + 1, dtype=int) + minobjid
    objid = []
    for ibrx in brxid:
        cntr[ibrx] += 1
        objid.append(cntr[ibrx])
    # ADM ensure the number of sky positions that were generated doesn't exceed
    # ADM the largest possible OBJID (which is unlikely).
    if np.any(cntr > 2**targetid_mask.OBJID.nbits):
        log.fatal(
            '{} sky locations requested in brick {}, but OBJID cannot exceed {}'
            .format(nskies, brickname, 2**targetid_mask.OBJID.nbits))
        raise ValueError
    supp["OBJID"] = np.array(objid)
    log.info("Assigned OBJIDs to bricks...t={:.1f}s".format(time() - start))

    # ADM add the TARGETID, DESITARGET bits etc.
    nskies = len(supp)
    desi_target = np.zeros(nskies, dtype='>i8')
    desi_target |= desi_mask.SKY
    desi_target |= desi_mask.SUPP_SKY
    dum = np.zeros_like(desi_target)
    supp = finalize(supp, desi_target, dum, dum, sky=1)

    log.info('Done...t={:.1f}s'.format(time() - start))

    return supp
예제 #4
0
def gaia_in_file(infile,
                 maglim=18,
                 mindec=-30.,
                 mingalb=10.,
                 nside=None,
                 pixlist=None,
                 addobjid=False):
    """Retrieve the Gaia objects from a HEALPixel-split Gaia file.

    Parameters
    ----------
    infile : :class:`str`
        File name of a single Gaia "healpix" file.
    maglim : :class:`float`, optional, defaults to 18
        Magnitude limit for GFAs in Gaia G-band.
    mindec : :class:`float`, optional, defaults to -30
        Minimum declination (o) to include for output Gaia objects.
    mingalb : :class:`float`, optional, defaults to 10
        Closest latitude to Galactic plane for output Gaia objects
        (e.g. send 10 to limit to areas beyond -10o <= b < 10o)"
    nside : :class:`int`, optional, defaults to `None`
        (NESTED) HEALPix `nside` to use with `pixlist`.
    pixlist : :class:`list` or `int`, optional, defaults to `None`
        Only return sources in a set of (NESTED) HEALpixels at the
        supplied `nside`.
    addobjid : :class:`bool`, optional, defaults to ``False``
        If ``True``, include, in the output, a column "GAIA_OBJID"
        that is the integer number of each row read from file.

    Returns
    -------
    :class:`~numpy.ndarray`
        Gaia objects in the passed Gaia file brighter than `maglim`,
        formatted according to `desitarget.gfa.gfadatamodel`.

    Notes
    -----
       - A "Gaia healpix file" here is as made by, e.g.
         :func:`~desitarget.gaiamatch.gaia_fits_to_healpix()`
    """
    # ADM read in the Gaia file and limit to the passed magnitude.
    objs = read_gaia_file(infile, addobjid=addobjid)
    ii = objs['GAIA_PHOT_G_MEAN_MAG'] < maglim
    objs = objs[ii]

    # ADM rename GAIA_RA/DEC to RA/DEC, as that's what's used for GFAs.
    for radec in ["RA", "DEC"]:
        objs.dtype.names = [
            radec if col == "GAIA_" + radec else col
            for col in objs.dtype.names
        ]

    # ADM initiate the GFA data model.
    dt = gfadatamodel.dtype.descr
    if addobjid:
        for tup in ('GAIA_BRICKID', '>i4'), ('GAIA_OBJID', '>i4'):
            dt.append(tup)

    gfas = np.zeros(len(objs), dtype=dt)
    # ADM make sure all columns initially have "ridiculous" numbers
    gfas[...] = -99.
    for col in gfas.dtype.names:
        if isinstance(gfas[col][0].item(), (bytes, str)):
            gfas[col] = 'U'
        if isinstance(gfas[col][0].item(), int):
            gfas[col] = -1
    # ADM some default special cases. Default to REF_EPOCH of Gaia DR2,
    # ADM make RA/Dec very precise for Gaia measurements.
    gfas["REF_EPOCH"] = 2015.5
    gfas["RA_IVAR"], gfas["DEC_IVAR"] = 1e16, 1e16

    # ADM populate the common columns in the Gaia/GFA data models.
    cols = set(gfas.dtype.names).intersection(set(objs.dtype.names))
    for col in cols:
        gfas[col] = objs[col]

    # ADM update the Gaia morphological type.
    gfas["TYPE"] = gaia_morph(gfas)

    # ADM populate the BRICKID columns.
    gfas["BRICKID"] = bricks.brickid(gfas["RA"], gfas["DEC"])

    # ADM limit by HEALPixel first as that's the fastest.
    if pixlist is not None:
        inhp = is_in_hp(gfas, nside, pixlist)
        gfas = gfas[inhp]
    # ADM limit by Dec first to speed transform to Galactic coordinates.
    decgood = is_in_box(gfas, [0., 360., mindec, 90.])
    gfas = gfas[decgood]
    # ADM now limit to requesed Galactic latitude range.
    if mingalb > 1e-9:
        bbad = is_in_gal_box(gfas, [0., 360., -mingalb, mingalb])
        gfas = gfas[~bbad]

    return gfas