22 from collections
import defaultdict
26 import healsparse
as hsp
32 from lsst.daf.butler
import Formatter
35 __all__ = [
"HealSparseInputMapTask",
"HealSparseInputMapConfig",
36 "HealSparseMapFormatter"]
40 """Interface for reading and writing healsparse.HealSparseMap files
42 unsupportedParameters = frozenset()
43 supportedExtensions = frozenset({
".hsp",
".fit",
".fits"})
46 def read(self, component=None):
48 path = self.fileDescriptor.location.path
50 if component ==
'coverage':
52 data = hsp.HealSparseCoverage.read(path)
53 except (OSError, RuntimeError):
54 raise ValueError(f
"Unable to read healsparse map with URI {self.fileDescriptor.location.uri}")
58 if self.fileDescriptor.parameters
is None:
62 pixels = self.fileDescriptor.parameters.get(
'pixels',
None)
63 degrade_nside = self.fileDescriptor.parameters.get(
'degrade_nside',
None)
65 data = hsp.HealSparseMap.read(path, pixels=pixels, degrade_nside=degrade_nside)
66 except (OSError, RuntimeError):
67 raise ValueError(f
"Unable to read healsparse map with URI {self.fileDescriptor.location.uri}")
71 def write(self, inMemoryDataset):
74 self.fileDescriptor.location.updateExtension(self.
extensionextension)
75 inMemoryDataset.write(self.fileDescriptor.location.path)
78 def _is_power_of_two(value):
79 """Check that value is a power of two.
88 is_power_of_two : `bool`
89 True if value is a power of two; False otherwise, or
90 if value is not an integer.
92 if not isinstance(value, numbers.Integral):
99 return (value & (value - 1) == 0)
and value != 0
103 """Configuration parameters for HealSparseInputMapTask"""
104 nside = pexConfig.Field(
105 doc=
"Mapping healpix nside. Must be power of 2.",
108 check=_is_power_of_two,
110 nside_coverage = pexConfig.Field(
111 doc=
"HealSparse coverage map nside. Must be power of 2.",
114 check=_is_power_of_two,
116 bad_mask_min_coverage = pexConfig.Field(
117 doc=(
"Minimum area fraction of a map healpixel pixel that must be "
118 "covered by bad pixels to be removed from the input map. "
119 "This is approximate."),
126 """Task for making a HealSparse input map."""
128 ConfigClass = HealSparseInputMapConfig
129 _DefaultName =
"healSparseInputMap"
132 pipeBase.Task.__init__(self, **kwargs)
137 """Build a map from ccd valid polygons or bounding boxes.
141 bbox : `lsst.geom.Box2I`
142 Bounding box for region to build input map.
143 wcs : `lsst.afw.geom.SkyWcs`
144 WCS object for region to build input map.
145 ccds : `lsst.afw.table.ExposureCatalog`
146 Exposure catalog with ccd data from coadd inputs.
148 self.
ccd_input_mapccd_input_map = hsp.HealSparseMap.make_empty(nside_coverage=self.config.nside_coverage,
149 nside_sparse=self.config.nside,
151 wide_mask_maxbits=len(ccds))
153 self.
_bbox_bbox = bbox
154 self.
_ccds_ccds = ccds
156 pixel_scale = wcs.getPixelScale().asArcseconds()
157 hpix_area_arcsec2 = hp.nside2pixarea(self.config.nside, degrees=
True)*(3600.**2.)
158 self.
_min_bad_min_bad = self.config.bad_mask_min_coverage*hpix_area_arcsec2/(pixel_scale**2.)
163 for bit, ccd
in enumerate(ccds):
164 metadata[f
'B{bit:04d}CCD'] = ccd[
'ccd']
165 metadata[f
'B{bit:04d}VIS'] = ccd[
'visit']
166 metadata[f
'B{bit:04d}WT'] = ccd[
'weight']
171 ccd_poly = ccd.getValidPolygon()
175 ccd_poly_radec = self.
_pixels_to_radec_pixels_to_radec(ccd.getWcs(), ccd_poly.convexHull().getVertices())
178 poly = hsp.Polygon(ra=ccd_poly_radec[: -1, 0],
179 dec=ccd_poly_radec[: -1, 1],
187 bbox_afw_poly.convexHull().getVertices())
188 bbox_poly = hsp.Polygon(ra=bbox_poly_radec[: -1, 0], dec=bbox_poly_radec[: -1, 1],
189 value=np.arange(self.
ccd_input_mapccd_input_map.wide_mask_maxbits))
190 bbox_poly_map = bbox_poly.get_map_like(self.
ccd_input_mapccd_input_map)
197 dtype = [(f
'v{visit}',
'i4')
for visit
in self.
_bits_per_visit_bits_per_visit.keys()]
199 cov = self.config.nside_coverage
200 ns = self.config.nside
210 """Mask a subregion from a visit.
211 This must be run after build_ccd_input_map initializes
216 bbox : `lsst.geom.Box2I`
217 Bounding box from region to mask.
219 Visit number corresponding to warp with mask.
220 mask : `lsst.afw.image.MaskX`
221 Mask plane from warp exposure.
222 bit_mask_value : `int`
223 Bit mask to check for bad pixels.
227 RuntimeError : Raised if build_ccd_input_map was not run first.
230 raise RuntimeError(
"Must run build_ccd_input_map before mask_warp_bbox")
233 bad_pixels = np.where(mask.array & bit_mask_value)
234 if len(bad_pixels[0]) == 0:
239 bad_ra, bad_dec = self.
_wcs_wcs.pixelToSkyArray(bad_pixels[1].astype(np.float64),
240 bad_pixels[0].astype(np.float64),
242 bad_hpix = hp.ang2pix(self.config.nside, bad_ra, bad_dec,
243 lonlat=
True, nest=
True)
246 min_bad_hpix = bad_hpix.min()
247 bad_hpix_count = np.zeros(bad_hpix.max() - min_bad_hpix + 1, dtype=np.int32)
248 np.add.at(bad_hpix_count, bad_hpix - min_bad_hpix, 1)
253 pix_to_add, = np.where(bad_hpix_count > 0)
256 count_map_arr[primary] = np.clip(count_map_arr[primary], 0,
None)
258 count_map_arr[f
'v{visit}'] = np.clip(count_map_arr[f
'v{visit}'], 0,
None)
259 count_map_arr[f
'v{visit}'] += bad_hpix_count[pix_to_add]
264 """Use accumulated mask information to finalize the masking of
269 RuntimeError : Raised if build_ccd_input_map was not run first.
272 raise RuntimeError(
"Must run build_ccd_input_map before finalize_ccd_input_map_mask.")
276 to_mask, = np.where(count_map_arr[f
'v{visit}'] > self.
_min_bad_min_bad)
277 if to_mask.size == 0:
285 def _pixels_to_radec(self, wcs, pixels):
286 """Convert pixels to ra/dec positions using a wcs.
290 wcs : `lsst.afw.geom.SkyWcs`
292 pixels : `list` [`lsst.geom.Point2D`]
293 List of pixels to convert.
297 radec : `numpy.ndarray`
298 Nx2 array of ra/dec positions associated with pixels.
300 sph_pts = wcs.pixelToSky(pixels)
301 return np.array([(sph.getRa().asDegrees(), sph.getDec().asDegrees())