def get_bricks(self): ''' Returns a table of bricks. The caller owns the table. For read-only purposes, see *get_bricks_readonly()*, which uses a cached version. ''' return fits_table(self.find_file('bricks'))
def sky_fibers_for_brick( survey, brickname, nskies=144, bands=['g', 'r', 'z'], apertures_arcsec=[0.5, 0.75, 1., 1.5, 2., 3.5, 5., 7.]): """Produce DESI sky fiber locations in a brick, derived at the pixel-level Parameters ---------- survey : :class:`object` `LegacySurveyData` object for a given Data Release of the Legacy Surveys; see :func:`~desitarget.skyutilities.legacypipe.util.LegacySurveyData` for details. brickname : :class:`str` Name of the brick in which to generate sky locations. nskies : :class:`float`, optional, defaults to 144 (12 x 12) The minimum DENSITY of sky fibers to generate bands : :class:`list`, optional, defaults to ['g', 'r', 'z'] List of bands to be used to define good sky locations. apertures_arcsec : :class:`list`, optional, defaults to [0.5,0.75,1.,1.5,2.,3.5,5.,7.] Radii in arcsec of apertures for which to derive flux at a sky location. Returns ------- :class:`object` A FITS table that includes: - the brickid - the brickname - the x and y pixel positions of the fiber location from the blobs file - the distance from the nearest blob of this fiber location - the RA and Dec positions of the fiber location - the aperture flux and ivar at the passed `apertures_arcsec` Notes ----- - Initial version written by Dustin Lang (@dstndstn). """ fn = survey.find_file('blobmap', brick=brickname) # ADM if the file doesn't exist, warn and return immediately. if not os.path.exists(fn): log.warning('blobmap {} does not exist!!!'.format(fn)) return None blobs = fitsio.read(fn) # log.info('Blob maximum value and minimum value in brick {}: {} {}' # .format(brickname,blobs.min(),blobs.max())) header = fitsio.read_header(fn) wcs = WCS(header) goodpix = (blobs == -1) # ADM while looping through bands, check there's an image in # ADM at least one band, otherwise the blob map has no meaning # ADM for these bands, and aperture photometry is not possible onegoodband = False for band in bands: fn = survey.find_file('nexp', brick=brickname, band=band) if not os.path.exists(fn): # Skip continue nexp = fitsio.read(fn) goodpix[nexp == 0] = False onegoodband = True # ADM if there were no images in the passed bands, fail if not onegoodband: log.fatal('No images for passed bands: {}'.format(bands)) raise ValueError # Cut to unique brick area... required since the blob map drops # blobs that are completely outside the brick's unique area, thus # those locations are not masked. brick = survey.get_brick_by_name(brickname) # ADM the width and height of the image in pixels is just the # ADM shape of the input blobs file H, W = blobs.shape U = find_unique_pixels(wcs, W, H, None, brick.ra1, brick.ra2, brick.dec1, brick.dec2) goodpix[U == 0] = False del U # ADM the minimum safe grid size is the number of pixels along an # ADM axis divided by the number of sky locations along any axis. gridsize = np.min(blobs.shape / np.sqrt(nskies)).astype('int16') # log.info('Gridding at {} pixels in brick {}...t = {:.1f}s' # .format(gridsize,brickname,time()-start)) x, y, blobdist = sky_fiber_locations(goodpix, gridsize=gridsize) skyfibers = fits_table() skyfibers.brickid = np.zeros(len(x), np.int32) + brick.brickid skyfibers.brickname = np.array([brickname] * len(x)) skyfibers.x = x.astype(np.int16) skyfibers.y = y.astype(np.int16) skyfibers.blobdist = blobdist # ADM start at pixel 0,0 in the top-left (the numpy standard). skyfibers.ra, skyfibers.dec = wcs.all_pix2world(x, y, 0) # ADM find the pixel scale using the square root of the determinant # ADM of the CD matrix (and convert from degrees to arcseconds). pixscale = np.sqrt(np.abs(np.linalg.det(wcs.wcs.cd))) * 3600. apertures = np.array(apertures_arcsec) / pixscale naps = len(apertures) # Now, do aperture photometry at these points in the coadd images. for band in bands: imfn = survey.find_file('image', brick=brickname, band=band) ivfn = survey.find_file('invvar', brick=brickname, band=band) # ADM set the apertures for every band regardless of whether # ADM the file exists, so that we get zeros for missing bands. apflux = np.zeros((len(skyfibers), naps), np.float32) # ADM set any zero flux to have an infinite error (zero ivar). apiv = np.zeros((len(skyfibers), naps), np.float32) skyfibers.set('apflux_%s' % band, apflux) skyfibers.set('apflux_ivar_%s' % band, apiv) if not (os.path.exists(imfn) and os.path.exists(ivfn)): continue coimg = fitsio.read(imfn) coiv = fitsio.read(ivfn) with np.errstate(divide='ignore', invalid='ignore'): imsigma = 1. / np.sqrt(coiv) imsigma[coiv == 0] = 0 apxy = np.vstack((skyfibers.x, skyfibers.y)).T for irad, rad in enumerate(apertures): aper = photutils.CircularAperture(apxy, rad) p = photutils.aperture_photometry(coimg, aper, error=imsigma) apflux[:, irad] = p.field('aperture_sum') err = p.field('aperture_sum_err') # ADM where the error is 0, that actually means infinite error # ADM so, in reality, set the ivar to 0 for those cases and # ADM retain the true ivars where the error is non-zero. # ADM also catch the occasional NaN (which are very rare). ii = np.isnan(err) err[ii] = 0.0 wzero = np.where(err == 0) wnonzero = np.where(err > 0) apiv[:, irad][wnonzero] = 1. / err[wnonzero]**2 apiv[:, irad][wzero] = 0. header = fitsio.FITSHDR() for i, ap in enumerate(apertures_arcsec): header.add_record( dict(name='AP%i' % i, value=ap, comment='Aperture radius (arcsec)')) skyfibers._header = header return skyfibers