def generate_safe_locations(sourcemask, Nperradius=1): """Given a bright source mask, generate SAFE (BADSKY) locations at its periphery. Parameters ---------- sourcemask : :class:`recarray` A recarray containing a bright mask as made by, e.g., :mod:`desitarget.brightmask.make_bright_star_mask` or :mod:`desitarget.brightmask.make_bright_source_mask`. Nperradius : :class:`int`, optional, defaults to 1 per arcsec of radius The number of safe locations to generate scaled by the radius of each mask in ARCSECONDS (i.e. the number of positions per arcsec of radius). Returns ------- ra : array_like. The Right Ascensions of the SAFE (BADSKY) locations. dec : array_like. The Declinations of the SAFE (BADSKY) locations. Notes ----- - See `Tech Note 2346`_ for details. """ # ADM the radius of each mask in arcseconds with a 0.1% kick to # ADM ensure that positions are beyond the mask edges. radius = sourcemask["IN_RADIUS"]*1.001 # ADM determine the number of SAFE locations to assign to each # ADM mask given the passed number of locations per unit radius. Nsafe = np.ceil(radius*Nperradius).astype('i') # ADM need to differentiate targets that are in ellipse-on-the-sky masks # ADM from targets that are in circle-on-the-sky masks. rex_or_psf = _rexlike(sourcemask["TYPE"]) | _psflike(sourcemask["TYPE"]) w_ellipse = np.where(~rex_or_psf) w_circle = np.where(rex_or_psf) # ADM set up an array to hold coordinates around the mask peripheries. ras, decs = np.array([]), np.array([]) # ADM generate the safe location for circular masks (which is quicker). if len(w_circle[0]) > 0: circras, circdecs = circle_boundaries(sourcemask[w_circle]["RA"], sourcemask[w_circle]["DEC"], radius[w_circle], Nsafe[w_circle]) ras, decs = np.concatenate((ras, circras)), np.concatenate((decs, circdecs)) # ADM generate the safe location for elliptical masks # ADM (which is slower as it requires a loop). if len(w_ellipse[0]) > 0: for w in w_ellipse[0]: ellras, elldecs = ellipse_boundary(sourcemask[w]["RA"], sourcemask[w]["DEC"], radius[w], sourcemask[w]["E1"], sourcemask[w]["E2"], Nsafe[w]) ras, decs = np.concatenate((ras, ellras)), np.concatenate((decs, elldecs)) return ras, decs
def plot_mask(mask, limits=None, radius="IN_RADIUS", show=True): """Plot a mask or masks. Parameters ---------- mask : :class:`recarray` A mask, as constructed by, e.g. :func:`make_bright_star_mask()`. limits : :class:`list`, optional RA/Dec plot limits in the form [ramin, ramax, decmin, decmax]. radius : :class: `str`, optional Which mask radius to plot (``IN_RADIUS`` or ``NEAR_RADIUS``). show : :class:`boolean` If ``True``, then display the plot, Otherwise, just execute the plot commands so it can be added to or saved to file later. Returns ------- Nothing """ # ADM make this work even for a single mask. mask = np.atleast_1d(mask) # ADM set up the plot. fig, ax = plt.subplots(1, figsize=(8, 8)) plt.xlabel('RA (o)') plt.ylabel('Dec (o)') # ADM set up some default plot limits if they weren't passed. if limits is None: maskra, maskdec, tol = mask["RA"], mask["DEC"], mask[radius]/3600. limits = [np.max(maskra-tol), np.min(maskra+tol), np.min(maskdec-tol), np.max(maskdec+tol)] ax.axis(limits) # ADM only consider a limited mask range corresponding to a few # ADM times the largest mask radius beyond the requested limits. # ADM remember that the passed mask sizes are in arcseconds. tol = 3.*np.max(mask[radius])/3600. # ADM the np.min/np.max combinations are to guard against people # ADM passing flipped RAs (so RA increases to the east). ii = ((mask["RA"] > np.min(limits[:2])-tol) & (mask["RA"] < np.max(limits[:2])+tol) & (mask["DEC"] > np.min(limits[-2:])-tol) & (mask["DEC"] < np.max(limits[-2:])+tol)) if np.sum(ii) == 0: msg = 'No mask entries within specified limits ({})'.format(limits) log.error(msg) raise ValueError(msg) else: mask = mask[ii] # ADM create ellipse polygons for each entry in the mask and # ADM make a list of matplotlib patches for them. patches = [] for i, ellipse in enumerate(mask): # ADM create points on the ellipse boundary. ras, decs = ellipse_boundary( ellipse["RA"], ellipse["DEC"], ellipse[radius], ellipse["E1"], ellipse["E2"]) polygon = Polygon(np.array(list(zip(ras, decs))), True) patches.append(polygon) p = PatchCollection(patches, alpha=0.4, facecolors='b', edgecolors='b') ax.add_collection(p) if show: plt.show() return
def plot_mask(mask, limits=None, radius="IN_RADIUS", show=True): """Make a plot of a mask and either display it or retain the plot object for over-plotting. Parameters ---------- mask : :class:`recarray` A mask constructed by ``make_bright_source_mask`` (or read in from file in the ``make_bright_source_mask`` format). limits : :class:`list`, optional The RA/Dec limits of the plot in the form [ramin, ramax, decmin, decmax]. radius : :class: `str`, optional Which mask radius to plot (``IN_RADIUS`` or ``NEAR_RADIUS``). Both can be plotted by calling this function twice with show=False and then with ``over=True``. show : :class:`boolean` If ``True``, then display the plot, Otherwise, just execute the plot commands so it can be added to, shown or saved to file later. Returns ------- Nothing """ # ADM set up the default log. from desiutil.log import get_logger, DEBUG log = get_logger(DEBUG) # ADM make this work even for a single mask. mask = np.atleast_1d(mask) # ADM set up the plot. fig, ax = plt.subplots(1, figsize=(8, 8)) plt.xlabel('RA (o)') plt.ylabel('Dec (o)') # ADM set up some default plot limits if they weren't passed. if limits is None: maskra, maskdec, tol = mask["RA"], mask["DEC"], mask[radius] / 3600. limits = [ np.max(maskra - tol), np.min(maskra + tol), np.min(maskdec - tol), np.max(maskdec + tol) ] ax.axis(limits) # ADM only consider a limited mask range corresponding to a few # ADM times the largest mask radius beyond the requested limits. # ADM remember that the passed mask sizes are in arcseconds. tol = 3. * np.max(mask[radius]) / 3600. # ADM the np.min/np.max combinations are to guard against people # ADM passing flipped RAs (so RA increases to the east). w = np.where((mask["RA"] > np.min(limits[:2]) - tol) & (mask["RA"] < np.max(limits[:2]) + tol) & (mask["DEC"] > np.min(limits[-2:]) - tol) & (mask["DEC"] < np.max(limits[-2:]) + tol)) if len(w[0]) == 0: log.error( 'No mask entries within specified limits ({})'.format(limits)) else: mask = mask[w] # ADM create ellipse polygons for each entry in the mask and # ADM make a list of matplotlib patches for them. patches = [] for i, ellipse in enumerate(mask): # ADM create points on the ellipse boundary. ras, decs = ellipse_boundary(ellipse["RA"], ellipse["DEC"], ellipse[radius], ellipse["E1"], ellipse["E2"]) polygon = Polygon(np.array(list(zip(ras, decs))), True) patches.append(polygon) p = PatchCollection(patches, alpha=0.4, facecolors='b', edgecolors='b') ax.add_collection(p) if show: plt.show() return