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]
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
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
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