예제 #1
0
파일: uratmatch.py 프로젝트: sdss/lvmtarget
def find_urat_files(objs, neighbors=True, radec=False):
    """Find full paths to URAT healpix files for objects by RA/Dec.

    Parameters
    ----------
    objs : :class:`~numpy.ndarray`
        Array of objects. Must contain the columns "RA" and "DEC".
    neighbors : :class:`bool`, optional, defaults to ``True``
        Also return all pixels that touch the files of interest
        to prevent edge effects (e.g. if a URAT source is 1 arcsec
        away from a primary source and so in an adjacent pixel).
    radec : :class:`bool`, optional, defaults to ``False``
        If ``True`` then the passed `objs` is an [RA, Dec] list
        instead of a rec array that contains "RA" and "DEC".

    Returns
    -------
    :class:`list`
        A list of all URAT files to read to account for objects at
        the passed locations.

    Notes
    -----
        - The environment variable $URAT_DIR must be set.
    """
    # ADM the resolution at which the URAT HEALPix files are stored.
    nside = _get_urat_nside()

    # ADM check that the URAT_DIR is set and retrieve it.
    uratdir = _get_urat_dir()
    hpxdir = os.path.join(uratdir, 'healpix')

    # ADM which flavor of RA/Dec was passed.
    if radec:
        ra, dec = objs
    else:
        ra, dec = objs["RA"], objs["DEC"]

    # ADM convert RA/Dec to co-latitude and longitude in radians.
    theta, phi = np.radians(90 - dec), np.radians(ra)

    # ADM retrieve the pixels in which the locations lie.
    pixnum = hp.ang2pix(nside, theta, phi, nest=True)
    # ADM if neighbors was sent, then retrieve all pixels that touch each
    # ADM pixel covered by the provided locations, to prevent edge effects...
    if neighbors:
        pixnum = add_hp_neighbors(nside, pixnum)

    # ADM reformat file names in the URAT healpix format.
    uratfiles = [
        os.path.join(hpxdir, 'healpix-{:05d}.fits'.format(pn)) for pn in pixnum
    ]

    # ADM restrict to only files/HEALPixels actually covered by URAT.
    uratfiles = [fn for fn in uratfiles if os.path.exists(fn)]

    return uratfiles
예제 #2
0
def find_gaia_files_tiles(tiles=None, neighbors=True):
    """
    Parameters
    ----------
    tiles : :class:`~numpy.ndarray`
        Array of tiles, or ``None`` to use all DESI tiles from
        :func:`desimodel.io.load_tiles`.
    neighbors : :class:`bool`, optional, defaults to ``True``
        Also return all neighboring pixels that touch the files of interest
        in order to prevent edge effects (e.g. if a Gaia source is 1 arcsec
        away from a primary source and so in an adjacent pixel).

    Returns
    -------
    :class:`list`
        A list of all Gaia files that touch the passed tiles.

    Notes
    -----
        - The environment variables $GAIA_DIR and $DESIMODEL must be set.
    """
    # ADM check that the DESIMODEL environement variable is set.
    if os.environ.get('DESIMODEL') is None:
        msg = "DESIMODEL environment variable must be set!!!"
        log.critical(msg)
        raise ValueError(msg)

    # ADM the resolution at which the healpix files are stored.
    nside = _get_gaia_nside()

    # ADM check that the GAIA_DIR is set and retrieve it.
    gaiadir = _get_gaia_dir()
    hpxdir = os.path.join(gaiadir, 'healpix')

    # ADM determine the pixels that touch the tiles.
    from desimodel.footprint import tiles2pix
    pixnum = tiles2pix(nside, tiles=tiles)

    # ADM if neighbors was sent, then retrieve all pixels that touch each
    # ADM pixel covered by the provided locations, to prevent edge effects...
    if neighbors:
        pixnum = add_hp_neighbors(nside, pixnum)

    # ADM reformat in the Gaia healpix format used by desitarget.
    gaiafiles = [
        os.path.join(hpxdir, 'healpix-{:05d}.fits'.format(pn)) for pn in pixnum
    ]

    return gaiafiles
예제 #3
0
def find_gaia_files_box(gaiabounds, neighbors=True):
    """Find full paths to all relevant Gaia healpix files in an RA/Dec box.

    Parameters
    ----------
    gaiabounds : :class:`list`
        A region of the sky bounded by RA/Dec. Pass as a 4-entry list to
        represent an area bounded by [RAmin, RAmax, DECmin, DECmax]
    neighbors : :class:`bool`, optional, defaults to ``True``
        Also return all neighboring pixels that touch the files of interest
        in order to prevent edge effects (e.g. if a Gaia source is 1 arcsec
        away from a primary source and so in an adjacent pixel)

    Returns
    -------
    :class:`list`
        A list of all Gaia files that need to be read in to account for objects
        in the passed box.

    Notes
    -----
        - Uses the `healpy` routines that rely on `fact`, so the usual
          warnings about returning different pixel sets at different values
          of `fact` apply. See:
          https://healpy.readthedocs.io/en/latest/generated/healpy.query_polygon.html
        - The environment variable $GAIA_DIR must be set.
    """
    # ADM the resolution at which the healpix files are stored.
    nside = _get_gaia_nside()

    # ADM check that the GAIA_DIR is set and retrieve it.
    gaiadir = _get_gaia_dir()
    hpxdir = os.path.join(gaiadir, 'healpix')

    # ADM determine the pixels that touch the box.
    pixnum = hp_in_box(nside, gaiabounds, inclusive=True, fact=4)

    # ADM if neighbors was sent, then retrieve all pixels that touch each
    # ADM pixel covered by the provided locations, to prevent edge effects...
    if neighbors:
        pixnum = add_hp_neighbors(nside, pixnum)

    # ADM reformat in the Gaia healpix format used by desitarget.
    gaiafiles = [
        os.path.join(hpxdir, 'healpix-{:05d}.fits'.format(pn)) for pn in pixnum
    ]

    return gaiafiles
예제 #4
0
def find_gaia_files_beyond_gal_b(mingalb, neighbors=True):
    """Find full paths to Gaia healpix files beyond a Galactic b.

    Parameters
    ----------
    mingalb : :class:`float`
        Closest latitude to Galactic plane to return HEALPixels
        (e.g. send 10 to limit to pixels beyond -10o <= b < 10o).
    neighbors : :class:`bool`, optional, defaults to ``True``
        Also return files corresponding to neighboring pixels that touch
        in order to prevent edge effects (e.g. if a Gaia source might be
        1 arcsec beyond mingalb and so in an adjacent pixel).

    Returns
    -------
    :class:`list`
        All Gaia files that need to be read in to account for objects
        further from the Galactic plane than `mingalb`.

    Notes
    -----
        - The environment variable $GAIA_DIR must be set.
        - :func:`desitarget.geomask.hp_beyond_gal_b()` is already quite
          inclusive, so you may retrieve some extra files along the
          `mingalb` boundary.
    """
    # ADM the resolution at which the healpix files are stored.
    nside = _get_gaia_nside()

    # ADM check that the GAIA_DIR is set and retrieve it.
    gaiadir = _get_gaia_dir()
    hpxdir = os.path.join(gaiadir, 'healpix')

    # ADM determine the pixels beyond mingalb.
    pixnum = hp_beyond_gal_b(nside, mingalb, neighbors=True)

    # ADM if neighbors was sent, retrieve all pixels that touch each
    # ADM retrieved, to prevent edge effects...
    if neighbors:
        pixnum = add_hp_neighbors(nside, pixnum)

    # ADM reformat in the Gaia healpix format used by desitarget.
    gaiafiles = [
        os.path.join(hpxdir, 'healpix-{:05d}.fits'.format(pn)) for pn in pixnum
    ]

    return gaiafiles
예제 #5
0
def find_tycho_files_hp(nside, pixlist, neighbors=True):
    """Find full paths to Tycho healpix files in a set of HEALPixels.

    Parameters
    ----------
    nside : :class:`int`
        (NESTED) HEALPixel nside.
    pixlist : :class:`list` or `int`
        A set of HEALPixels at `nside`.
    neighbors : :class:`bool`, optional, defaults to ``True``
        Also return files corresponding to all neighbors that touch the
        pixels in `pixlist` to prevent edge effects (e.g. a Tycho source
        is 1 arcsec outside of `pixlist` and so in an adjacent pixel).

    Returns
    -------
    :class:`list`
        A list of all Tycho files that need to be read in to account for
        objects in the passed list of pixels.

    Notes
    -----
        - The environment variable $TYCHO_DIR must be set.
    """
    # ADM the resolution at which the healpix files are stored.
    filenside = get_tycho_nside()

    # ADM check that the TYCHO_DIR is set and retrieve it.
    tychodir = get_tycho_dir()
    hpxdir = os.path.join(tychodir, 'healpix')

    # ADM work with pixlist as an array.
    pixlist = np.atleast_1d(pixlist)

    # ADM determine the pixels that touch the passed pixlist.
    pixnum = nside2nside(nside, filenside, pixlist)

    # ADM if neighbors was sent, then retrieve all pixels that touch each
    # ADM pixel covered by the provided locations, to prevent edge effects...
    if neighbors:
        pixnum = add_hp_neighbors(filenside, pixnum)

    # ADM reformat in the healpix format used by desitarget.
    tychofiles = [os.path.join(hpxdir, io.hpx_filename(pn)) for pn in pixnum]

    return tychofiles
예제 #6
0
def find_gaia_files(objs, neighbors=True):
    """Find full paths to all relevant Gaia healpix files for an object array.

    Parameters
    ----------
    objs : :class:`~numpy.ndarray`
        Array of objects. Must contain at least the columns "RA" and "DEC".
    neighbors : :class:`bool`, optional, defaults to ``True``
        Also return all neighboring pixels that touch the files of interest
        in order to prevent edge effects (e.g. if a Gaia source is 1 arcsec
        away from a primary source and so in an adjacent pixel)

    Returns
    -------
    :class:`list`
        A list of all Gaia files that need to be read in to account for objects
        at the passed locations.

    Notes
    -----
        - The environment variable $GAIA_DIR must be set.
    """
    # ADM the resolution at which the Gaia HEALPix files are stored.
    nside = _get_gaia_nside()

    # ADM check that the GAIA_DIR is set and retrieve it.
    gaiadir = _get_gaia_dir()
    hpxdir = os.path.join(gaiadir, 'healpix')

    # ADM convert RA/Dec to co-latitude and longitude in radians.
    theta, phi = np.radians(90 - objs["DEC"]), np.radians(objs["RA"])

    # ADM retrieve the pixels in which the locations lie.
    pixnum = hp.ang2pix(nside, theta, phi, nest=True)
    # ADM if neighbors was sent, then retrieve all pixels that touch each
    # ADM pixel covered by the provided locations, to prevent edge effects...
    if neighbors:
        pixnum = add_hp_neighbors(nside, pixnum)

    # ADM reformat in the Gaia healpix format used by desitarget.
    gaiafiles = [
        os.path.join(hpxdir, 'healpix-{:05d}.fits'.format(pn)) for pn in pixnum
    ]

    return gaiafiles
예제 #7
0
def mask_targets(targs, inmaskdir, nside=2, pixlist=None):
    """Add bits for if objects occupy masks, and SAFE (BADSKY) locations.

    Parameters
    ----------
    targs : :class:`str` or `~numpy.ndarray`
        An array of targets/skies etc. created by, e.g.,
        :func:`desitarget.cuts.select_targets()` OR the filename of a
        file that contains such a set of targets/skies, etc.
    inmaskdir : :class:`str`, optional
        An input bright star mask file or HEALPixel-split directory as
        made by :func:`desitarget.brightmask.make_bright_star_mask()`
    nside : :class:`int`, optional, defaults to 2
        The nside at which the targets were generated. If the mask is
        a HEALPixel-split directory, then this helps to perform more
        efficient masking as only the subset of masks that are in
        pixels containing `targs` at this `nside` will be considered
        (together with neighboring pixels to account for edge effects).
    pixlist : :class:`list` or `int`, optional
        A set of HEALPixels corresponding to the `targs`. Only the subset
        of masks in HEALPixels in `pixlist` at `nside` will be considered
        (together with neighboring pixels to account for edge effects).
        If ``None``, then the pixels touched by `targs` is derived from
        from `targs` itself.

    Returns
    -------
    :class:`~numpy.ndarray`
        Input targets with the `DESI_TARGET` column updated to reflect
        the `BRIGHT_OBJECT` bits and SAFE (`BADSKY`) sky locations added
        around the perimeter of the mask.

    Notes
    -----
        - `Tech Note 2346`_ details SAFE (BADSKY) locations.
    """
    t0 = time()

    # ADM Check if targs is a file name or the structure itself.
    if isinstance(targs, str):
        if not os.path.exists(targs):
            raise ValueError("{} doesn't exist".format(targs))
        targs = fitsio.read(targs)

    # ADM determine which pixels are occupied by targets.
    if pixlist is None:
        theta, phi = np.radians(90-targs["DEC"]), np.radians(targs["RA"])
        pixlist = list(set(hp.ang2pix(nside, theta, phi, nest=True)))
    else:
        # ADM in case an integer was passed.
        pixlist = np.atleast_1d(pixlist)
    log.info("Masking using masks in {} at nside={} in HEALPixels={}".format(
        inmaskdir, nside, pixlist))
    pixlist = add_hp_neighbors(nside, pixlist)

    # ADM read in the (potentially HEALPixel-split) mask.
    sourcemask = io.read_targets_in_hp(inmaskdir, nside, pixlist)

    ntargs = len(targs)
    log.info('Total number of masks {}'.format(len(sourcemask)))
    log.info('Total number of targets {}...t={:.1f}s'.format(ntargs, time()-t0))

    # ADM update the bits depending on whether targets are in a mask.
    # ADM also grab masks that contain or are near a target.
    dt, mx = set_target_bits(targs, sourcemask, return_masks=True)
    targs["DESI_TARGET"] = dt
    inmasks, nearmasks = mx

    # ADM generate SAFE locations for masks that contain a target.
    safes = get_safe_targets(targs, sourcemask[inmasks])
    # ADM update the bits for the safe locations depending on whether
    # ADM they're in a mask.
    safes["DESI_TARGET"] = set_target_bits(safes, sourcemask)
    # ADM combine the targets and safe locations.
    done = np.concatenate([targs, safes])

    log.info('Generated {} SAFE (BADSKY) locations...t={:.1f}s'.format(
        len(done)-ntargs, time()-t0))

    # ADM remove any SAFE locations that are in bright masks (because they aren't really safe).
    ii = (((done["DESI_TARGET"] & desi_mask.BAD_SKY) == 0) |
          ((done["DESI_TARGET"] & desi_mask.IN_BRIGHT_OBJECT) == 0))
    done = done[ii]

    log.info("...of these, {} SAFE (BADSKY) locations aren't in masks...t={:.1f}s"
             .format(len(done)-ntargs, time()-t0))

    log.info('Finishing up...t={:.1f}s'.format(time()-t0))

    return done
예제 #8
0
def match_secondary(infile, scxtargs, sep=1., scxdir=None):
    """Match secondary targets to primary targets and update bits.

    Parameters
    ----------
    infile : :class:`str`
        The full path to a file containing primary targets.
    scxtargs : :class:`~numpy.ndarray`
        An array of secondary targets.
    sep : :class:`float`, defaults to 1 arcsecond
        The separation at which to match in ARCSECONDS.
    scxdir : :class:`str`, optional, defaults to `None`
        Name of the directory that hosts secondary targets. If passed,
        this is written to the output primary file header as `SCNDDIR`.

    Returns
    -------
    :class:`~numpy.ndarray`
        The array of secondary targets, with the `TARGETID` bit
        updated with any matching primary targets from `infile`.

    Notes
    -----
        - The primary target `infiles` are written back to their
          original path with `.fits` changed to `-wscnd.fits` and the
          `SCND_TARGET` bit populated for matching targets.
    """
    # ADM just the file name for logging.
    fn = os.path.basename(infile)
    # ADM read in the primary targets.
    log.info('Reading primary targets file {}...t={:.1f}s'.format(
        infile,
        time() - start))
    intargs, hdr = fitsio.read(infile, extension="TARGETS", header=True)

    # ADM fail if file's already been matched to secondary targets.
    if "SCNDDIR" in hdr:
        msg = "{} already matched to secondary targets".format(fn) \
              + " (did you mean to remove {}?)!!!".format(fn)
        log.critical(msg)
        raise ValueError(msg)
    # ADM add the SCNDDIR to the primary targets file header.
    hdr["SCNDDIR"] = scxdir
    # ADM add a SCND_TARGET column to the primary targets.
    dt = intargs.dtype.descr
    dt.append(('SCND_TARGET', '>i8'))
    targs = np.zeros(len(intargs), dtype=dt)
    for col in intargs.dtype.names:
        targs[col] = intargs[col]

    # ADM match to all secondary targets for non-custom primary files.
    inhp = np.ones(len(scxtargs), dtype="?")
    # ADM as a speed-up, save memory by limiting the secondary targets
    # ADM to just HEALPixels that could touch the primary targets.
    if 'FILEHPX' in hdr:
        nside, pix = hdr['FILENSID'], hdr['FILEHPX']
        # ADM remember to grab adjacent pixels in case of edge effects.
        allpix = add_hp_neighbors(nside, pix)
        inhp = is_in_hp(scxtargs, nside, allpix)
        # ADM it's unlikely that the matching separation is comparable
        # ADM to the HEALPixel resolution, but guard against that anyway.
        halfpix = np.degrees(hp.max_pixrad(nside)) * 3600.
        if sep > halfpix:
            msg = 'sep ({}") exceeds (half) HEALPixel size ({}")'.format(
                sep, halfpix)
            log.critical(msg)
            raise ValueError(msg)
    # ADM warn the user if the secondary and primary samples are "large".
    big = 500000
    if np.sum(inhp) > big and len(intargs) > big:
        log.warning('Large secondary (N={}) and primary (N={}) samples'.format(
            np.sum(inhp), len(intargs)))
        log.warning('The code may run slowly')

    # ADM for each secondary target, determine if there is a match
    # ADM with a primary target. Note that sense is important, here
    # ADM (the primary targets must be passed first).
    log.info(
        'Matching primary and secondary targets for {} at {}"...t={:.1f}s'.
        format(fn, sep,
               time() - start))
    mtargs, mscx = radec_match_to(targs, scxtargs[inhp], sep=sep)
    # ADM recast the indices to the full set of secondary targets,
    # ADM instead of just those that were in the relevant HEALPixels.
    mscx = np.where(inhp)[0][mscx]

    # ADM loop through the matches and update the SCND_TARGET
    # ADM column in the primary target list. The np.unique is a
    # ADM speed-up to assign singular matches first.
    umtargs, inv, cnt = np.unique(mtargs,
                                  return_inverse=True,
                                  return_counts=True)
    # ADM number of times each primary target was matched, ordered
    # ADM the same as mtargs, i.e. n(mtargs) for each entry in mtargs.
    nmtargs = cnt[inv]
    # ADM assign anything with nmtargs = 1 directly.
    singular = nmtargs == 1
    targs["SCND_TARGET"][mtargs[singular]] = scxtargs["SCND_TARGET"][
        mscx[singular]]
    # ADM loop through things with nmtargs > 1 and combine the bits.
    for i in range(len((mtargs[~singular]))):
        targs["SCND_TARGET"][mtargs[~singular][i]] |= scxtargs["SCND_TARGET"][
            mscx[~singular][i]]
    # ADM also assign the SCND_ANY bit to the primary targets.
    desicols, desimasks, _ = main_cmx_or_sv(targs, scnd=True)
    targs[desicols[0]][umtargs] |= desimasks[0].SCND_ANY

    # ADM rename the SCND_TARGET column, in case this is an SV file.
    targs = rfn.rename_fields(targs, {'SCND_TARGET': desicols[3]})

    # ADM update the secondary targets with the primary TARGETID.
    scxtargs["TARGETID"][mscx] = targs["TARGETID"][mtargs]

    # ADM form the output primary file name and write the file.
    base, ext = os.path.splitext(infile)
    outfile = "{}{}{}".format(base, '-wscnd', ext)
    log.info('Writing updated primary targets to {}...t={:.1f}s'.format(
        outfile,
        time() - start))
    fitsio.write(outfile, targs, extname='TARGETS', header=hdr, clobber=True)

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

    return scxtargs
예제 #9
0
def match_secondary(primtargs, scxdir, scndout, sep=1., pix=None, nside=None):
    """Match secondary targets to primary targets and update bits.

    Parameters
    ----------
    primtargs : :class:`~numpy.ndarray`
        An array of primary targets.
    scndout : :class`~numpy.ndarray`
        Name of a sub-directory to which to write the information in
        `desitarget.secondary.outdatamodel` with `TARGETID` and (the
        highest) `PRIORITY_INIT` updated with matching primary info.
    scxdir : :class:`str`, optional, defaults to `None`
        Name of the directory that hosts secondary targets.
    sep : :class:`float`, defaults to 1 arcsecond
        The separation at which to match in ARCSECONDS.
    pix : :class:`list`, optional, defaults to `None`
        Limit secondary targets to (NESTED) HEALpixels that touch
        pix at the supplied `nside`, as a speed-up.
    nside : :class:`int`, optional, defaults to `None`
        The (NESTED) HEALPixel nside to be used with `pixlist`.

    Returns
    -------
    :class:`~numpy.ndarray`
        The array of primary targets, with the `SCND_TARGET` bit
        populated for matches to secondary targets
    """
    # ADM add a SCND_TARGET column to the primary targets.
    dt = primtargs.dtype.descr
    dt.append(('SCND_TARGET', '>i8'))
    targs = np.zeros(len(primtargs), dtype=dt)
    for col in primtargs.dtype.names:
        targs[col] = primtargs[col]

    # ADM check if this is an SV or main survey file.
    cols, mx, surv = main_cmx_or_sv(targs, scnd=True)
    log.info('running on the {} survey...'.format(surv))
    if surv != 'main':
        scxdir = os.path.join(scxdir, surv)

    # ADM read in non-OVERRIDE secondary targets.
    scxtargs = read_files(scxdir, mx[3])
    scxtargs = scxtargs[~scxtargs["OVERRIDE"]]

    # ADM match primary targets to non-OVERRIDE secondary targets.
    inhp = np.ones(len(scxtargs), dtype="?")
    # ADM as a speed-up, save memory by limiting the secondary targets
    # ADM to just HEALPixels that could touch the primary targets.
    if nside is not None and pix is not None:
        # ADM remember to grab adjacent pixels in case of edge effects.
        allpix = add_hp_neighbors(nside, pix)
        inhp = is_in_hp(scxtargs, nside, allpix)
        # ADM it's unlikely that the matching separation is comparable
        # ADM to the HEALPixel resolution, but guard against that anyway.
        halfpix = np.degrees(hp.max_pixrad(nside)) * 3600.
        if sep > halfpix:
            msg = 'sep ({}") exceeds (half) HEALPixel size ({}")'.format(
                sep, halfpix)
            log.critical(msg)
            raise ValueError(msg)

    # ADM warn the user if the secondary and primary samples are "large".
    big = 500000
    if np.sum(inhp) > big and len(primtargs) > big:
        log.warning('Large secondary (N={}) and primary (N={}) samples'.format(
            np.sum(inhp), len(primtargs)))
        log.warning('The code may run slowly')

    # ADM for each secondary target, determine if there is a match
    # ADM with a primary target. Note that sense is important, here
    # ADM (the primary targets must be passed first).
    log.info(
        'Matching primary and secondary targets for {} at {}"...t={:.1f}s'.
        format(scndout, sep,
               time() - start))
    mtargs, mscx = radec_match_to(targs, scxtargs[inhp], sep=sep)
    # ADM recast the indices to the full set of secondary targets,
    # ADM instead of just those that were in the relevant HEALPixels.
    mscx = np.where(inhp)[0][mscx]

    # ADM loop through the matches and update the SCND_TARGET
    # ADM column in the primary target list. The np.unique is a
    # ADM speed-up to assign singular matches first.
    umtargs, inv, cnt = np.unique(mtargs,
                                  return_inverse=True,
                                  return_counts=True)
    # ADM number of times each primary target was matched, ordered
    # ADM the same as mtargs, i.e. n(mtargs) for each entry in mtargs.
    nmtargs = cnt[inv]
    # ADM assign anything with nmtargs = 1 directly.
    singular = nmtargs == 1
    targs["SCND_TARGET"][mtargs[singular]] = scxtargs["SCND_TARGET"][
        mscx[singular]]
    # ADM loop through things with nmtargs > 1 and combine the bits.
    for i in range(len((mtargs[~singular]))):
        targs["SCND_TARGET"][mtargs[~singular][i]] |= scxtargs["SCND_TARGET"][
            mscx[~singular][i]]
    # ADM also assign the SCND_ANY bit to the primary targets.
    desicols, desimasks, _ = main_cmx_or_sv(targs, scnd=True)
    desi_mask = desimasks[0]

    targs[desicols[0]][umtargs] |= desi_mask.SCND_ANY

    # ADM rename the SCND_TARGET column, in case this is an SV file.
    targs = rfn.rename_fields(targs, {'SCND_TARGET': desicols[3]})

    # APC Secondary target bits only affect PRIORITY, NUMOBS and
    # APC obsconditions for specific DESI_TARGET bits
    # APC See https://github.com/desihub/desitarget/pull/530

    # APC Only consider primary targets with secondary bits set
    scnd_update = (targs[desicols[0]] & desi_mask['SCND_ANY']) != 0
    if np.any(scnd_update):
        # APC Allow changes to primaries if the DESI_TARGET bitmask has
        # APC only the following bits set, in any combination.
        log.info(
            'Testing if secondary targets can update {} matched primaries'.
            format(scnd_update.sum()))
        update_from_scnd_bits = desi_mask['SCND_ANY'] | desi_mask[
            'MWS_ANY'] | desi_mask['STD_BRIGHT'] | desi_mask[
                'STD_FAINT'] | desi_mask['STD_WD']
        scnd_update &= ((targs[desicols[0]] & ~update_from_scnd_bits) == 0)
        log.info(
            'Setting new priority, numobs and obsconditions from secondary for {} matched primaries'
            .format(scnd_update.sum()))

        # APC Primary and secondary obsconditions are or'd
        scnd_obscon = set_obsconditions(targs[scnd_update], scnd=True)
        targs['OBSCONDITIONS'][scnd_update] &= scnd_obscon

        # APC bit of a hack here
        # APC Check for _BRIGHT, _DARK split in column names
        darkbright = 'NUMOBS_INIT_DARK' in targs.dtype.names
        if darkbright:
            ender, obscon = ["_DARK", "_BRIGHT"], ["DARK|GRAY", "BRIGHT"]
        else:
            ender, obscon = [""], [
                "DARK|GRAY|BRIGHT|POOR|TWILIGHT12|TWILIGHT18"
            ]

        # APC secondaries can increase priority and numobs
        for edr, oc in zip(ender, obscon):
            pc, nc = "PRIORITY_INIT" + edr, "NUMOBS_INIT" + edr
            scnd_priority, scnd_numobs = initial_priority_numobs(
                targs[scnd_update], obscon=oc, scnd=True)
            targs[nc][scnd_update] = np.maximum(targs[nc][scnd_update],
                                                scnd_numobs)
            targs[pc][scnd_update] = np.maximum(targs[pc][scnd_update],
                                                scnd_priority)

    # ADM update the secondary targets with the primary information.
    scxtargs["TARGETID"][mscx] = targs["TARGETID"][mtargs]
    # ADM the maximum priority will be used to break ties in the
    # ADM unlikely event that a secondary matches two primaries.
    hipri = np.maximum(targs["PRIORITY_INIT_DARK"],
                       targs["PRIORITY_INIT_BRIGHT"])
    scxtargs["PRIORITY_INIT"][mscx] = hipri[mtargs]

    # ADM write the secondary targets that have updated TARGETIDs.
    ii = scxtargs["TARGETID"] != -1
    nmatches = np.sum(ii)
    log.info('Writing {} secondary target matches to {}...t={:.1f}s'.format(
        nmatches, scndout,
        time() - start))
    if nmatches > 0:
        hdr = fitsio.FITSHDR()
        hdr["SURVEY"] = surv
        fitsio.write(scndout,
                     scxtargs[ii],
                     extname='SCND_TARG',
                     header=hdr,
                     clobber=True)

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

    return targs
예제 #10
0
파일: secondary.py 프로젝트: sdss/lvmtarget
def match_secondary(primtargs, scxdir, scndout, sep=1., pix=None, nside=None):
    """Match secondary targets to primary targets and update bits.

    Parameters
    ----------
    primtargs : :class:`~numpy.ndarray`
        An array of primary targets.
    scndout : :class`~numpy.ndarray`
        Name of a sub-directory to which to write the information in
        `desitarget.secondary.outdatamodel` with `TARGETID` and (the
        highest) `PRIORITY_INIT` updated with matching primary info.
    scxdir : :class:`str`, optional, defaults to `None`
        Name of the directory that hosts secondary targets.
    sep : :class:`float`, defaults to 1 arcsecond
        The separation at which to match in ARCSECONDS.
    pix : :class:`list`, optional, defaults to `None`
        Limit secondary targets to (NESTED) HEALpixels that touch
        pix at the supplied `nside`, as a speed-up.
    nside : :class:`int`, optional, defaults to `None`
        The (NESTED) HEALPixel nside to be used with `pixlist`.

    Returns
    -------
    :class:`~numpy.ndarray`
        The array of primary targets, with the `SCND_TARGET` bit
        populated for matches to secondary targets
    """
    # ADM add a SCND_TARGET column to the primary targets.
    dt = primtargs.dtype.descr
    dt.append(('SCND_TARGET', '>i8'))
    targs = np.zeros(len(primtargs), dtype=dt)
    for col in primtargs.dtype.names:
        targs[col] = primtargs[col]

    # ADM check if this is an SV or main survey file.
    cols, mx, surv = main_cmx_or_sv(targs, scnd=True)
    log.info('running on the {} survey...'.format(surv))
    if surv != 'main':
        scxdir = os.path.join(scxdir, surv)

    # ADM read in non-OVERRIDE secondary targets.
    scxtargs = read_files(scxdir, mx[3])
    scxtargs = scxtargs[~scxtargs["OVERRIDE"]]

    # ADM match primary targets to non-OVERRIDE secondary targets.
    inhp = np.ones(len(scxtargs), dtype="?")
    # ADM as a speed-up, save memory by limiting the secondary targets
    # ADM to just HEALPixels that could touch the primary targets.
    if nside is not None and pix is not None:
        # ADM remember to grab adjacent pixels in case of edge effects.
        allpix = add_hp_neighbors(nside, pix)
        inhp = is_in_hp(scxtargs, nside, allpix)
        # ADM it's unlikely that the matching separation is comparable
        # ADM to the HEALPixel resolution, but guard against that anyway.
        halfpix = np.degrees(hp.max_pixrad(nside)) * 3600.
        if sep > halfpix:
            msg = 'sep ({}") exceeds (half) HEALPixel size ({}")'.format(
                sep, halfpix)
            log.critical(msg)
            raise ValueError(msg)
    # ADM warn the user if the secondary and primary samples are "large".
    big = 500000
    if np.sum(inhp) > big and len(primtargs) > big:
        log.warning('Large secondary (N={}) and primary (N={}) samples'.format(
            np.sum(inhp), len(primtargs)))
        log.warning('The code may run slowly')

    # ADM for each secondary target, determine if there is a match
    # ADM with a primary target. Note that sense is important, here
    # ADM (the primary targets must be passed first).
    log.info(
        'Matching primary and secondary targets for {} at {}"...t={:.1f}s'.
        format(scndout, sep,
               time() - start))
    mtargs, mscx = radec_match_to(targs, scxtargs[inhp], sep=sep)
    # ADM recast the indices to the full set of secondary targets,
    # ADM instead of just those that were in the relevant HEALPixels.
    mscx = np.where(inhp)[0][mscx]

    # ADM loop through the matches and update the SCND_TARGET
    # ADM column in the primary target list. The np.unique is a
    # ADM speed-up to assign singular matches first.
    umtargs, inv, cnt = np.unique(mtargs,
                                  return_inverse=True,
                                  return_counts=True)
    # ADM number of times each primary target was matched, ordered
    # ADM the same as mtargs, i.e. n(mtargs) for each entry in mtargs.
    nmtargs = cnt[inv]
    # ADM assign anything with nmtargs = 1 directly.
    singular = nmtargs == 1
    targs["SCND_TARGET"][mtargs[singular]] = scxtargs["SCND_TARGET"][
        mscx[singular]]
    # ADM loop through things with nmtargs > 1 and combine the bits.
    for i in range(len((mtargs[~singular]))):
        targs["SCND_TARGET"][mtargs[~singular][i]] |= scxtargs["SCND_TARGET"][
            mscx[~singular][i]]
    # ADM also assign the SCND_ANY bit to the primary targets.
    desicols, desimasks, _ = main_cmx_or_sv(targs, scnd=True)
    targs[desicols[0]][umtargs] |= desimasks[0].SCND_ANY

    # ADM rename the SCND_TARGET column, in case this is an SV file.
    targs = rfn.rename_fields(targs, {'SCND_TARGET': desicols[3]})

    # ADM update the secondary targets with the primary information.
    scxtargs["TARGETID"][mscx] = targs["TARGETID"][mtargs]
    # ADM the maximum priority will be used to break ties in the
    # ADM unlikely event that a secondary matches two primaries.
    hipri = np.maximum(targs["PRIORITY_INIT_DARK"],
                       targs["PRIORITY_INIT_BRIGHT"])
    scxtargs["PRIORITY_INIT"][mscx] = hipri[mtargs]

    # ADM write the secondary targets that have updated TARGETIDs.
    ii = scxtargs["TARGETID"] != -1
    nmatches = np.sum(ii)
    log.info('Writing {} secondary target matches to {}...t={:.1f}s'.format(
        nmatches, scndout,
        time() - start))
    if nmatches > 0:
        hdr = fitsio.FITSHDR()
        hdr["SURVEY"] = surv
        fitsio.write(scndout,
                     scxtargs[ii],
                     extname='SCND_TARG',
                     header=hdr,
                     clobber=True)

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

    return targs