def _get_maskpoly_from_row(self, table_row): """ Get a mask polygon from a table row Parameters ---------- table_row : `np.ndarray` Row of a mask table with RAs and Decs Returns ------- maskpoly : `healsparse.Polygon` """ mask_poly = healsparse.Polygon(ra=np.array([ table_row['ra_1'], table_row['ra_2'], table_row['ra_3'], table_row['ra_4'] ]), dec=np.array([ table_row['dec_1'], table_row['dec_2'], table_row['dec_3'], table_row['dec_4'] ]), value=1) return mask_poly
def load_polygons(*, data, values, bands=None): """ load a set of polygons (rectangles) from the input data. EDGEBLEED is skipped for 'u' and 'Y' bands Parameters ---------- data: array with fields Must have ra_1, dec_1 ra_2, dec_2 ra_3, dec_3 ra_4, dec_4 badpix """ values = _extract_values(values, data.size) has_bands = 'band' in data.dtype.names has_badpix = 'badpix' in data.dtype.names if bands is not None and not has_bands: raise ValueError('bands= sent but no bands present in input data') EDGEBLEED = 128 polygons = [] for i in range(data.size): idata = data[i] if has_bands and bands is not None: band = idata['band'].strip() if band not in bands: continue if (has_bands and has_badpix and band in ['u', 'Y'] and idata['badpix'] == EDGEBLEED): print('skipping %s EDGEBLEED' % band) continue ra, dec = _extract_vert(idata) polygon = hs.Polygon( ra=ra, dec=dec, value=values[i], ) polygons.append(polygon) return polygons
def _render_catalog(self, maskmap, maskcat, indices): """ Render part of a maskcat into a mask map. Parameters ---------- maskmap : `healsparse.HealSparseMap` The map must be of `healsparse.WIDE_MASK` type maskcat : `np.ndarray` Catalog of region information indices : `np.ndarray` Array of indices from maskcat to render """ box = np.isnan(maskcat['radius'][indices]) circ = ~box ra = maskcat['ra'][indices[circ]] dec = maskcat['dec'][indices[circ]] radius = maskcat['radius'][indices[circ]] shapes = [] for i in range(ra.size): shapes.append( healsparse.Circle(ra=ra[i], dec=dec[i], radius=radius[i], value=[self.config.default_bit])) ra = maskcat['ra'][indices[box]] dec = maskcat['dec'][indices[box]] width = maskcat['width'][indices[box]] / np.cos( np.deg2rad(maskcat['dec'][indices[box]])) height = maskcat['height'][indices[box]] for i in range(ra.size): shapes.append( healsparse.Polygon(ra=[ ra[i] - width[i] / 2., ra[i] - width[i] / 2., ra[i] + width[i] / 2., ra[i] + width[i] / 2. ], dec=[ dec[i] - height[i] / 2., dec[i] + height[i] / 2., dec[i] + height[i] / 2., dec[i] - height[i] / 2. ], value=[self.config.default_bit])) healsparse.realize_geom(shapes, maskmap)
def _make_nexp_map(self): """ Make the number-of-exposures map Returns ------- nexp_map : `healsparse.HealSparseMap` """ # Figure out coverage ipnest = hp.ang2pix(self.config.nside_coverage, self.centers['ra_center'], self.centers['dec_center'], nest=True, lonlat=True) pixels = np.unique(ipnest) # Initialize the map and memory nexp_map = healsparse.HealSparseMap.make_empty(self.config.nside_coverage, self.config.nside, np.int32, sentinel=0, cov_pixels=pixels) bad = ((self.table['naxis1'] < self.config.border*2) | (self.table['naxis2'] < self.config.border*2)) # Loop over boxes for i, wcs in enumerate(self.wcs_list): if bad[i]: continue x_coords = np.array([self.config.border, self.config.border, self.table['naxis1'][i] - self.config.border, self.table['naxis1'][i] - self.config.border]) y_coords = np.array([self.config.border, self.table['naxis2'][i] - self.config.border, self.table['naxis2'][i] - self.config.border, self.config.border]) ra, dec = wcs.image2sky(x_coords, y_coords) poly = healsparse.Polygon(ra=ra, dec=dec, value=1) try: nexp_map[poly.get_pixels(nside=nexp_map.nside_sparse)] += 1 except ValueError: print('Bad WCS mapping for %d/%d' % (self.table[self.config.exp_field][i], self.table[self.config.ccd_field][i])) return nexp_map
def _extract(self): ra = [] dec = [] npair = len(self.data) // 2 ra = np.zeros(npair) dec = np.zeros(npair) for i in range(npair): ira = i * 2 idec = i * 2 + 1 ra[i] = self.data[ira] dec[i] = self.data[idec] self._geom = hs.Polygon( ra=ra, dec=dec, value=self.value, )
def worker(image, return_dict, procnum): #Read image header and get coordinates of the image print(i, 'working with image', image) hdr = pf.open(image) w = astrowcs.WCS(hdr[1].header) corners_image = w.calc_footprint(center=False) corners_pixels = w.calc_footprint(center=True) nx = hdr[1].header['naxis1'] ny = hdr[1].header['naxis2'] coords1 = corners_image[0] coords2 = corners_image[1] coords3 = corners_image[2] coords4 = corners_image[3] #Read image data image_data = pf.getdata(image) #Define healsparse polygon within the image ra = [coords1[0], coords4[0], coords3[0], coords2[0]] dec = [coords1[1], coords4[1], coords3[1], coords2[1]] raC = (np.max(ra) + np.min(ra)) / 2. decC = (np.max(dec) + np.min(dec)) / 2. nside = 2**17 poly = hs.Polygon(ra=ra, dec=dec, value=1) smap = poly.get_map(nside=nside, dtype=np.int16) a = smap.validPixels b = hp.nest2ring(nside, a) #a are pixels in NEST, b in RING #Get center coordinates of each pixel raF, decF = hp.pix2ang(nside, a, lonlat=True, nest=True) #Get nx, ny in image from healsparse pixels myhdr = hdr[1].header wcs = wc.WCS(myhdr) xn, yn = wcs.sky2image(raF, decF) #Get associated weight values values = [] for x, y in zip(xn, yn): values.append(image_data[int(y - 1), int(x - 1)]) values = np.array(values) #Define healsparse map hsp_map_2 = hs.HealSparseMap.makeEmpty(512, nside, dtype=np.int16) hsp_map_2.updateValues(a, values) #Degrade to nside=4096 low_res_hsp = hsp_map_2.degrade(4096) j = low_res_hsp.validPixels test_values_2 = low_res_hsp.getValuePixel(j) #Uncomment if you want in RING format #k=hp.nest2ring(4096,j) #hp_aux = np.zeros(hp.nside2npix(4096))+hp.UNSEEN #hp_aux[j] = test_values_2 hdr.close() return_dict[procnum] = np.array([j, test_values_2]).transpose()
def build_region_input_map(self, indices, tilename=None, hpix=None): """ Build input map for a given tile or hpix. Must specify tilename or hpix. Parameters ---------- indices : `np.ndarray` Indices of WCS table tilename : `str`, optional Name of tile hpix : `int`, optional Healpix number Returns ------- input_map : `healsparse.HealSparseMap` Wide mask map """ if tilename is None and hpix is None: raise RuntimeError("Must specify one of tilename or hpix.") if tilename is not None and hpix is not None: raise RuntimeError("Must specify one of tilename or hpix.") if self.config.use_two_amps: maxbits = len(indices) * 2 else: maxbits = len(indices) region_input_map = healsparse.HealSparseMap.make_empty( nside_coverage=self.nside_coverage_region, nside_sparse=self.config.nside, dtype=healsparse.WIDE_MASK, wide_mask_maxbits=maxbits) # Pre-match mask tables if available expnums = np.unique(dg.table[self.config.exp_field][indices]) if dg.streak_table is not None: _, streak_b = esutil.numpy_util.match( expnums, dg.streak_table[self.config.exp_field]) if dg.bleed_table is not None: _, bleed_b = esutil.numpy_util.match( expnums, dg.bleed_table[self.config.exp_field]) if dg.satstar_table is not None: _, satstar_b = esutil.numpy_util.match( expnums, dg.satstar_table[self.config.exp_field]) metadata = {} for i, ind in enumerate(indices): if self.config.use_two_amps: # Everything needs to be done with 2 amps bit_a = i * 2 bit_b = i * 2 + 1 metadata['B%04dCCD' % (bit_a)] = dg.table[self.config.ccd_field][ind] metadata['B%04dCCD' % (bit_b)] = dg.table[self.config.ccd_field][ind] metadata['B%04dEXP' % (bit_a)] = dg.table[self.config.exp_field][ind] metadata['B%04dEXP' % (bit_b)] = dg.table[self.config.exp_field][ind] metadata['B%04dAMP' % (bit_a)] = 'A' metadata['B%04dAMP' % (bit_b)] = 'B' metadata['B%04dWT' % (bit_a)] = self._compute_weight( self.config.skyvar_field + 'a', ind) metadata['B%04dWT' % (bit_b)] = self._compute_weight( self.config.skyvar_field + 'b', ind) else: bit = i metadata['B%04dCCD' % (bit)] = dg.table[self.config.ccd_field][ind] metadata['B%04dEXP' % (bit)] = dg.table[self.config.exp_field][ind] metadata['B%04dWT' % (bit)] = self._compute_weight( self.config.skyvar_field, ind) wcs = dg.wcs_list[ind] if wcs is None: continue if self.config.use_wcs: # Use the WCS and render the ccd boxes if self.config.use_two_amps: x_coords_a = np.array([ self.config.amp_boundary, self.config.amp_boundary, dg.table['naxis1'][ind] - self.config.border, dg.table['naxis1'][ind] - self.config.border ]) x_coords_b = np.array([ self.config.border, self.config.border, self.config.amp_boundary, self.config.amp_boundary ]) else: x_coords = np.array([ self.config.border, self.config.border, dg.table['naxis1'][ind] - self.config.border, dg.table['naxis1'][ind] - self.config.border ]) y_coords = np.array([ self.config.border, dg.table['naxis2'][ind] - self.config.border, dg.table['naxis2'][ind] - self.config.border, self.config.border ]) if self.config.use_two_amps: ra_a, dec_a = wcs.image2sky(x_coords_a, y_coords) ra_b, dec_b = wcs.image2sky(x_coords_b, y_coords) poly_a = healsparse.Polygon(ra=ra_a, dec=dec_a, value=[bit_a]) poly_b = healsparse.Polygon(ra=ra_b, dec=dec_b, value=[bit_b]) else: ra, dec = wcs.image2sky(x_coords, y_coords) poly = healsparse.Polygon(ra=ra, dec=dec, value=[bit]) else: # Don't use the WCS and use the bounding box specified in the table. # This is only possible with 1-amp mode. ra = np.array([ dg.table[field][ind] for field in self.config.ra_corner_fields ]) dec = np.array([ dg.table[field][ind] for field in self.config.dec_corner_fields ]) poly = healsparse.Polygon(ra=ra, dec=dec, value=[bit]) # Check if we have additional masking if (dg.streak_table is not None or dg.bleed_table is not None or dg.satstar_table is not None): if self.config.use_two_amps: poly_map_a = poly_a.get_map_like(region_input_map) poly_map_b = poly_b.get_map_like(region_input_map) else: poly_map = poly.get_map_like(region_input_map) mask_reg_list = [] if dg.streak_table is not None: sinds, = np.where( (dg.streak_table[self.config.exp_field][streak_b] == dg.table[self.config.exp_field][ind]) & (dg.streak_table[self.config.ccd_field][streak_b] == dg.table[self.config.ccd_field][ind])) for sind in streak_b[sinds]: mask_reg_list.append( self._get_maskpoly_from_row(dg.streak_table[sind])) if dg.bleed_table is not None: binds, = np.where( (dg.bleed_table[self.config.exp_field][bleed_b] == dg.table[self.config.exp_field][ind]) & (dg.bleed_table[self.config.ccd_field][bleed_b] == dg.table[self.config.ccd_field][ind])) for bind in bleed_b[binds]: mask_reg_list.append( self._get_maskpoly_from_row(dg.bleed_table[bind])) if dg.satstar_table is not None: sinds, = np.where( (dg.satstar_table[self.config.exp_field][satstar_b] == dg.table[self.config.exp_field][ind]) & (dg.satstar_table[self.config.ccd_field][satstar_b] == dg.table[self.config.ccd_field][ind])) for sind in satstar_b[sinds]: mask_reg_list.append( self._get_maskcircle_from_row( dg.satstar_table[sind])) mask_map = healsparse.HealSparseMap.make_empty( nside_coverage=self.nside_coverage_region, nside_sparse=self.config.nside, dtype=np.uint8) healsparse.realize_geom(mask_reg_list, mask_map) if self.config.use_two_amps: poly_map_a.apply_mask(mask_map) poly_map_b.apply_mask(mask_map) pixels_a = poly_map_a.valid_pixels pixels_b = poly_map_b.valid_pixels else: poly_map.apply_mask(mask_map) pixels = poly_map.valid_pixels else: # With no masking we can do a slightly faster version direct # with the pixels if self.config.use_two_amps: pixels_a = poly_a.get_pixels(nside=self.config.nside) pixels_b = poly_b.get_pixels(nside=self.config.nside) else: pixels = poly.get_pixels(nside=self.config.nside) # Check for bad amps -- only in two amp mode if self.config.use_two_amps: if int(dg.table[self.config.ccd_field][ind]) in list( self.config.bad_amps): ba = self.config.bad_amps[int( dg.table[self.config.ccd_field][ind])] for b in ba: if b.lower() == 'a': pixels_a = np.array([], dtype=np.int64) elif b.lower() == 'b': pixels_b = np.array([], dtype=np.int64) if int(dg.table[self.config.ccd_field] [ind]) in self.config.bad_ccds: if self.config.use_two_amps: pixels_a = np.array([], dtype=np.int64) pixels_b = np.array([], dtype=np.int64) else: pixels = np.array([], dtype=np.int64) # tilename implies DES, and use two amps if tilename is not None and self.config.use_two_amps: tind, = np.where(dg.tile_info['tilename'] == tilename) for pixels, bit in zip([pixels_a, pixels_b], [bit_a, bit_b]): pixra, pixdec = hp.pix2ang(self.config.nside, pixels, lonlat=True, nest=True) if dg.tile_info['crossra0'][tind] == 'Y': # Special for cross-ra0, where uramin will be very large uramin = dg.tile_info['uramin'][tind] - 360.0 pixra_rot = pixra.copy() hi, = np.where(pixra > 180.0) pixra_rot[hi] -= 360.0 ok = ((pixra_rot > uramin) & (pixra_rot < dg.tile_info['uramax'][tind]) & (pixdec > dg.tile_info['udecmin'][tind]) & (pixdec <= dg.tile_info['udecmax'][tind])) else: ok = ((pixra > dg.tile_info['uramin'][tind]) & (pixra <= dg.tile_info['uramax'][tind]) & (pixdec > dg.tile_info['udecmin'][tind]) & (pixdec <= dg.tile_info['udecmax'][tind])) region_input_map.set_bits_pix(pixels[ok], [bit]) else: # healpix mode bit_shift = 2 * int( np.round(np.log2( self.config.nside / self.config.nside_run))) npixels = 2**bit_shift pixel_min = hpix * npixels pixel_max = (hpix + 1) * npixels - 1 if self.config.use_two_amps: for pixels, bit in zip([pixels_a, pixels_b], [bit_a, bit_b]): ok = ((pixels >= pixel_min) & (pixels <= pixel_max)) region_input_map.set_bits_pix(pixels[ok], [bit]) else: ok = ((pixels >= pixel_min) & (pixels <= pixel_max)) region_input_map.set_bits_pix(pixels[ok], [bit]) region_input_map.metadata = metadata return region_input_map
def run(self, sky_map, tract, band, coadd_dict, input_map_dict, visit_summary_dict): """Run the healsparse property task. Parameters ---------- sky_map : Sky map object tract : `int` Tract number. band : `str` Band name for logging. coadd_dict : `dict` [`int`: `lsst.daf.butler.DeferredDatasetHandle`] Dictionary of coadd exposure references. Keys are patch numbers. input_map_dict : `dict` [`int`: `lsst.daf.butler.DeferredDatasetHandle`] Dictionary of input map references. Keys are patch numbers. visit_summary_dict : `dict` [`int`: `lsst.afw.table.ExposureCatalog`] Dictionary of visit summary tables. Keys are visit numbers. Raises ------ RepeatableQuantumError If visit_summary_dict is missing any visits or detectors found in an input map. This leads to an inconsistency between what is in the coadd (via the input map) and the visit summary tables which contain data to compute the maps. """ tract_info = sky_map[tract] tract_maps_initialized = False for patch in input_map_dict.keys(): self.log.info("Making maps for band %s, tract %d, patch %d.", band, tract, patch) patch_info = tract_info[patch] input_map = input_map_dict[patch].get() coadd_photo_calib = coadd_dict[patch].get(component="photoCalib") coadd_inputs = coadd_dict[patch].get(component="coaddInputs") coadd_zeropoint = 2.5*np.log10(coadd_photo_calib.getInstFluxAtZeroMagnitude()) # Crop input_map to the inner polygon of the patch poly_vertices = patch_info.getInnerSkyPolygon(tract_info.getWcs()).getVertices() patch_radec = self._vertices_to_radec(poly_vertices) patch_poly = hsp.Polygon(ra=patch_radec[:, 0], dec=patch_radec[:, 1], value=np.arange(input_map.wide_mask_maxbits)) patch_poly_map = patch_poly.get_map_like(input_map) input_map = hsp.and_intersection([input_map, patch_poly_map]) if not tract_maps_initialized: # We use the first input map nside information to initialize # the tract maps nside_coverage = self._compute_nside_coverage_tract(tract_info) nside = input_map.nside_sparse do_compute_approx_psf = False # Initialize the tract maps for property_map in self.property_maps: property_map.initialize_tract_maps(nside_coverage, nside) if property_map.requires_psf: do_compute_approx_psf = True tract_maps_initialized = True valid_pixels, vpix_ra, vpix_dec = input_map.valid_pixels_pos(return_pixels=True) # Check if there are no valid pixels for the inner (unique) patch region if valid_pixels.size == 0: continue # Initialize the value accumulators for property_map in self.property_maps: property_map.initialize_values(valid_pixels.size) property_map.zeropoint = coadd_zeropoint # Initialize the weight and counter accumulators total_weights = np.zeros(valid_pixels.size) total_inputs = np.zeros(valid_pixels.size, dtype=np.int32) for bit, ccd_row in enumerate(coadd_inputs.ccds): # Which pixels in the map are used by this visit/detector inmap, = np.where(input_map.check_bits_pix(valid_pixels, [bit])) # Check if there are any valid pixels in the map from this deteector. if inmap.size == 0: continue # visit, detector_id, weight = input_dict[bit] visit = ccd_row["visit"] detector_id = ccd_row["ccd"] weight = ccd_row["weight"] x, y = ccd_row.getWcs().skyToPixelArray(vpix_ra[inmap], vpix_dec[inmap], degrees=True) scalings = self._compute_calib_scale(ccd_row, x, y) if do_compute_approx_psf: psf_array = compute_approx_psf_size_and_shape(ccd_row, vpix_ra[inmap], vpix_dec[inmap]) else: psf_array = None total_weights[inmap] += weight total_inputs[inmap] += 1 # Retrieve the correct visitSummary row if visit not in visit_summary_dict: msg = f"Visit {visit} not found in visit_summaries." raise pipeBase.RepeatableQuantumError(msg) row = visit_summary_dict[visit].find(detector_id) if row is None: msg = f"Visit {visit} / detector_id {detector_id} not found in visit_summaries." raise pipeBase.RepeatableQuantumError(msg) # Accumulate the values for property_map in self.property_maps: property_map.accumulate_values(inmap, vpix_ra[inmap], vpix_dec[inmap], weight, scalings, row, psf_array=psf_array) # Finalize the mean values and set the tract maps for property_map in self.property_maps: property_map.finalize_mean_values(total_weights, total_inputs) property_map.set_map_values(valid_pixels)
def build_ccd_input_map(self, bbox, wcs, ccds): """Build a map from ccd valid polygons or bounding boxes. Parameters ---------- bbox : `lsst.geom.Box2I` Bounding box for region to build input map. wcs : `lsst.afw.geom.SkyWcs` WCS object for region to build input map. ccds : `lsst.afw.table.ExposureCatalog` Exposure catalog with ccd data from coadd inputs. """ with warnings.catch_warnings(): # Healsparse will emit a warning if nside coverage is greater than # 128. In the case of generating patch input maps, and not global # maps, high nside coverage works fine, so we can suppress this # warning. warnings.simplefilter("ignore") self.ccd_input_map = hsp.HealSparseMap.make_empty(nside_coverage=self.config.nside_coverage, nside_sparse=self.config.nside, dtype=hsp.WIDE_MASK, wide_mask_maxbits=len(ccds)) self._wcs = wcs self._bbox = bbox self._ccds = ccds pixel_scale = wcs.getPixelScale().asArcseconds() hpix_area_arcsec2 = hp.nside2pixarea(self.config.nside, degrees=True)*(3600.**2.) self._min_bad = self.config.bad_mask_min_coverage*hpix_area_arcsec2/(pixel_scale**2.) metadata = {} self._bits_per_visit_ccd = {} self._bits_per_visit = defaultdict(list) for bit, ccd_row in enumerate(ccds): metadata[f"B{bit:04d}CCD"] = ccd_row["ccd"] metadata[f"B{bit:04d}VIS"] = ccd_row["visit"] metadata[f"B{bit:04d}WT"] = ccd_row["weight"] self._bits_per_visit_ccd[(ccd_row["visit"], ccd_row["ccd"])] = bit self._bits_per_visit[ccd_row["visit"]].append(bit) ccd_poly = ccd_row.getValidPolygon() if ccd_poly is None: ccd_poly = afwGeom.Polygon(lsst.geom.Box2D(ccd_row.getBBox())) # Detectors need to be rendered with their own wcs. ccd_poly_radec = self._pixels_to_radec(ccd_row.getWcs(), ccd_poly.convexHull().getVertices()) # Create a ccd healsparse polygon poly = hsp.Polygon(ra=ccd_poly_radec[: -1, 0], dec=ccd_poly_radec[: -1, 1], value=[bit]) self.ccd_input_map.set_bits_pix(poly.get_pixels(nside=self.ccd_input_map.nside_sparse), [bit]) # Cut down to the overall bounding box with associated wcs. bbox_afw_poly = afwGeom.Polygon(lsst.geom.Box2D(bbox)) bbox_poly_radec = self._pixels_to_radec(self._wcs, bbox_afw_poly.convexHull().getVertices()) bbox_poly = hsp.Polygon(ra=bbox_poly_radec[: -1, 0], dec=bbox_poly_radec[: -1, 1], value=np.arange(self.ccd_input_map.wide_mask_maxbits)) bbox_poly_map = bbox_poly.get_map_like(self.ccd_input_map) self.ccd_input_map = hsp.and_intersection([self.ccd_input_map, bbox_poly_map]) self.ccd_input_map.metadata = metadata # Create a temporary map to hold the count of bad pixels in each healpix pixel self._ccd_input_pixels = self.ccd_input_map.valid_pixels dtype = [(f"v{visit}", np.int64) for visit in self._bits_per_visit.keys()] with warnings.catch_warnings(): # Healsparse will emit a warning if nside coverage is greater than # 128. In the case of generating patch input maps, and not global # maps, high nside coverage works fine, so we can suppress this # warning. warnings.simplefilter("ignore") self._ccd_input_bad_count_map = hsp.HealSparseMap.make_empty( nside_coverage=self.config.nside_coverage, nside_sparse=self.config.nside, dtype=dtype, primary=dtype[0][0]) # Don't set input bad map if there are no ccds which overlap the bbox. if len(self._ccd_input_pixels) > 0: self._ccd_input_bad_count_map[self._ccd_input_pixels] = np.zeros(1, dtype=dtype)
def setUp(self): tract = 0 band = 'r' patch = 0 visits = [100, 101] # Good to test crossing 0. ra_center = 0.0 dec_center = -45.0 pixel_scale = 0.2 coadd_zp = 27.0 # Generate a mock skymap with one patch config = DiscreteSkyMap.ConfigClass() config.raList = [ra_center] config.decList = [dec_center] config.radiusList = [150 * pixel_scale / 3600.] config.patchInnerDimensions = (350, 350) config.patchBorder = 50 config.tractOverlap = 0.0 config.pixelScale = pixel_scale sky_map = DiscreteSkyMap(config) visit_summaries = [ makeMockVisitSummary(visit, ra_center=ra_center, dec_center=dec_center) for visit in visits ] visit_summary_refs = [ MockVisitSummaryReference(visit_summary, visit) for visit_summary, visit in zip(visit_summaries, visits) ] self.visit_summary_dict = { visit: ref.get() for ref, visit in zip(visit_summary_refs, visits) } # Generate an input map. Note that this does not need to be consistent # with the visit_summary projections, we're just tracking values. input_map = hsp.HealSparseMap.make_empty( nside_coverage=256, nside_sparse=32768, dtype=hsp.WIDE_MASK, wide_mask_maxbits=len(visits) * 2) patch_poly = afwGeom.Polygon( geom.Box2D(sky_map[tract][patch].getOuterBBox())) sph_pts = sky_map[tract].getWcs().pixelToSky( patch_poly.convexHull().getVertices()) patch_poly_radec = np.array([(sph.getRa().asDegrees(), sph.getDec().asDegrees()) for sph in sph_pts]) poly = hsp.Polygon(ra=patch_poly_radec[:-1, 0], dec=patch_poly_radec[:-1, 1], value=[0]) poly_pixels = poly.get_pixels(nside=input_map.nside_sparse) # The input map has full coverage for bits 0 and 1 input_map.set_bits_pix(poly_pixels, [0]) input_map.set_bits_pix(poly_pixels, [1]) input_map_ref = MockInputMapReference(input_map, patch=patch, tract=tract) self.input_map_dict = {patch: input_map_ref} coadd = afwImage.ExposureF(sky_map[tract][patch].getOuterBBox(), sky_map[tract].getWcs()) instFluxMag0 = 10.**(coadd_zp / 2.5) pc = afwImage.makePhotoCalibFromCalibZeroPoint(instFluxMag0) coadd.setPhotoCalib(pc) # Mock the coadd input ccd table schema = afwTable.ExposureTable.makeMinimalSchema() schema.addField("ccd", type="I") schema.addField("visit", type="I") schema.addField("weight", type="F") ccds = afwTable.ExposureCatalog(schema) ccds.resize(2) ccds['id'] = np.arange(2) ccds['visit'][0] = visits[0] ccds['visit'][1] = visits[1] ccds['ccd'][0] = 0 ccds['ccd'][1] = 1 ccds['weight'] = 10.0 for ccd_row in ccds: summary = self.visit_summary_dict[ccd_row['visit']].find( ccd_row['ccd']) ccd_row.setWcs(summary.getWcs()) ccd_row.setPsf(summary.getPsf()) ccd_row.setBBox(summary.getBBox()) ccd_row.setPhotoCalib(summary.getPhotoCalib()) inputs = afwImage.CoaddInputs() inputs.ccds = ccds coadd.getInfo().setCoaddInputs(inputs) coadd_ref = MockCoaddReference(coadd, patch=patch, tract=tract) self.coadd_dict = {patch: coadd_ref} self.tract = tract self.band = band self.sky_map = sky_map self.input_map = input_map
def build_patch_input_map(self, butler, tract_wcs, patch_info, ccds, nside_coverage_patch): """ Build the patch input map. Parameters ---------- butler : `lsst.daf.persistence.Butler` gen2 butler tract_wcs : `lsst.afw.geom.SkyWcs` WCS object for the tract patch_info : `lsst.skymap.PatchInfo` Patch info object ccds : `lsst.afw.table.ExposureCatalog` Catalog of ccd information nside_coverage_patch : `int` Healpix nside for the coverage map Returns ------- patch_input_map : `healsparse.HealSparseMap` Healsparse map encoding input ccd information. """ patch_input_map = healsparse.HealSparseMap.make_empty( nside_coverage=nside_coverage_patch, nside_sparse=self.config.nside, dtype=healsparse.WIDE_MASK, wide_mask_maxbits=len(ccds)) if self.config.use_calexp_mask: # pixel_scale = tract_wcs.getPixelScale().asArcseconds() hpix_area_arcsec2 = hp.nside2pixarea(patch_input_map.nside_sparse, degrees=True) * (3600.**2.) bad_mask = afwImage.Mask.getPlaneBitMask( self.config.bad_mask_planes) metadata = {} for bit, ccd in enumerate(ccds): metadata['B%04dCCD' % (bit)] = ccd['ccd'] metadata['B%04dVIS' % (bit)] = ccd['visit'] metadata['B%04dWT' % (bit)] = ccd['weight'] wcs = ccd.getWcs() ccd_poly = ccd.getValidPolygon() if ccd_poly is None: ccd_poly = afwGeom.Polygon(lsst.geom.Box2D(ccd.getBBox())) ccd_poly_radec = pixels_to_radec( wcs, ccd_poly.convexHull().getVertices()) # Use polygons for all of these poly = healsparse.Polygon(ra=ccd_poly_radec[:-1, 0], dec=ccd_poly_radec[:-1, 1], value=[bit]) poly_map = poly.get_map_like(patch_input_map) dataId = { self.config.detector_id_name: int(ccd['ccd']), self.config.visit_id_name: int(ccd['visit']) } if self.config.use_calexp_mask: calexp = butler.get('calexp', dataId=dataId) calexp_metadata = calexp.getMetadata() mask = calexp.getMask() pixel_scale = tract_wcs.getPixelScale().asArcseconds() min_bad = self.config.bad_mask_coverage * hpix_area_arcsec2 / ( pixel_scale**2.) bad_pixels = np.where(mask.getArray() & bad_mask) # Convert bad pixels to position bad_radec = xy_to_radec(wcs, bad_pixels[1].astype(np.float64), bad_pixels[0].astype(np.float64)) # Convert position to healpixel bad_hpix = hp.ang2pix(patch_input_map.nside_sparse, bad_radec[:, 0], bad_radec[:, 1], lonlat=True, nest=True) min_bad_hpix = bad_hpix.min() bad_hpix_count = np.zeros(bad_hpix.max() - min_bad_hpix + 1, dtype=np.int32) np.add.at(bad_hpix_count, bad_hpix - min_bad_hpix, 1) hpix_to_mask = min_bad_hpix + np.where( bad_hpix_count > min_bad)[0] poly_map.clear_bits_pix(hpix_to_mask, [bit]) if 'SKYLEVEL' not in calexp_metadata: # We must recompute skylevel, skysigma skylevel, skysigma = self._compute_skylevel( butler, dataId, calexp) else: skylevel = calexp_metadata['SKYLEVEL'] skysigma = calexp_metadata['SKYSIGMA'] else: calexp_metadata = butler.get('calexp_md', dataId=dataId) if 'SKYLEVEL' not in calexp_metadata: # We want to log this skylevel = 0.0 skysigma = 0.0 else: skylevel = calexp_metadata['SKYLEVEL'] skysigma = calexp_metadata['SKYSIGMA'] metadata['B%04dSLV' % (bit)] = skylevel metadata['B%04dSSG' % (bit)] = skysigma metadata['B%04dBGM' % (bit)] = calexp_metadata['BGMEAN'] metadata['B%04dBGV' % (bit)] = calexp_metadata['BGVAR'] # Now we have the full masked ccd map, set the appropriate bit patch_input_map.set_bits_pix(poly_map.valid_pixels, [bit]) # Now cut down to the inner tract polygon poly_vertices = patch_info.getInnerSkyPolygon(tract_wcs).getVertices() patch_radec = vertices_to_radec(poly_vertices) patch_poly = healsparse.Polygon(ra=patch_radec[:, 0], dec=patch_radec[:, 1], value=np.arange( patch_input_map.wide_mask_maxbits)) # Realize the patch polygon patch_poly_map = patch_poly.get_map_like(patch_input_map) patch_input_map = healsparse.and_intersection( [patch_input_map, patch_poly_map]) # And set the metadata patch_input_map.metadata = metadata return patch_input_map