22__all__ = [
"HealSparseInputMapTask",
"HealSparseInputMapConfig",
23 "HealSparseMapFormatter",
"HealSparsePropertyMapConnections",
24 "HealSparsePropertyMapConfig",
"HealSparsePropertyMapTask",
25 "ConsolidateHealSparsePropertyMapConnections",
26 "ConsolidateHealSparsePropertyMapConfig",
27 "ConsolidateHealSparsePropertyMapTask"]
29from collections
import defaultdict
35import healsparse
as hsp
41from lsst.daf.butler
import Formatter
43from lsst.utils.timer
import timeMethod
44from .healSparseMappingProperties
import (BasePropertyMap, BasePropertyMapConfig,
45 PropertyMapMap, compute_approx_psf_size_and_shape)
49 """Interface for reading and writing healsparse.HealSparseMap files."""
50 unsupportedParameters = frozenset()
51 supportedExtensions = frozenset({
".hsp",
".fit",
".fits"})
54 def read(self, component=None):
56 path = self.fileDescriptor.location.path
58 if component ==
'coverage':
60 data = hsp.HealSparseCoverage.read(path)
61 except (OSError, RuntimeError):
62 raise ValueError(f
"Unable to read healsparse map with URI {self.fileDescriptor.location.uri}")
66 if self.fileDescriptor.parameters
is None:
70 pixels = self.fileDescriptor.parameters.get(
'pixels',
None)
71 degrade_nside = self.fileDescriptor.parameters.get(
'degrade_nside',
None)
73 data = hsp.HealSparseMap.read(path, pixels=pixels, degrade_nside=degrade_nside)
74 except (OSError, RuntimeError):
75 raise ValueError(f
"Unable to read healsparse map with URI {self.fileDescriptor.location.uri}")
54 def read(self, component=None):
…
79 def write(self, inMemoryDataset):
82 self.fileDescriptor.location.updateExtension(self.
extension)
83 inMemoryDataset.write(self.fileDescriptor.location.path, clobber=
True)
87 """Check that value is a power of two.
96 is_power_of_two : `bool`
97 True if value is a power of two; False otherwise, or
98 if value is not an integer.
100 if not isinstance(value, numbers.Integral):
107 return (value & (value - 1) == 0)
and value != 0
111 """Configuration parameters for HealSparseInputMapTask"""
112 nside = pexConfig.Field(
113 doc=
"Mapping healpix nside. Must be power of 2.",
116 check=_is_power_of_two,
118 nside_coverage = pexConfig.Field(
119 doc=
"HealSparse coverage map nside. Must be power of 2.",
122 check=_is_power_of_two,
124 bad_mask_min_coverage = pexConfig.Field(
125 doc=(
"Minimum area fraction of a map healpixel pixel that must be "
126 "covered by bad pixels to be removed from the input map. "
127 "This is approximate."),
134 """Task for making a HealSparse input map."""
136 ConfigClass = HealSparseInputMapConfig
137 _DefaultName =
"healSparseInputMap"
140 pipeBase.Task.__init__(self, **kwargs)
145 """Build a map from ccd valid polygons or bounding boxes.
149 bbox : `lsst.geom.Box2I`
150 Bounding box for region to build input map.
151 wcs : `lsst.afw.geom.SkyWcs`
152 WCS object for region to build input map.
153 ccds : `lsst.afw.table.ExposureCatalog`
154 Exposure catalog with ccd data from coadd inputs.
156 with warnings.catch_warnings():
161 warnings.simplefilter(
"ignore")
162 self.
ccd_input_map = hsp.HealSparseMap.make_empty(nside_coverage=self.config.nside_coverage,
163 nside_sparse=self.config.nside,
165 wide_mask_maxbits=len(ccds))
170 pixel_scale = wcs.getPixelScale(bbox.getCenter()).asArcseconds()
171 hpix_area_arcsec2 = hpg.nside_to_pixel_area(self.config.nside, degrees=
True)*(3600.**2.)
172 self.
_min_bad = self.config.bad_mask_min_coverage*hpix_area_arcsec2/(pixel_scale**2.)
177 for bit, ccd_row
in enumerate(ccds):
178 metadata[f
"B{bit:04d}CCD"] = ccd_row[
"ccd"]
179 metadata[f
"B{bit:04d}VIS"] = ccd_row[
"visit"]
180 metadata[f
"B{bit:04d}WT"] = ccd_row[
"weight"]
185 ccd_poly = ccd_row.getValidPolygon()
189 ccd_poly_radec = self.
_pixels_to_radec(ccd_row.getWcs(), ccd_poly.convexHull().getVertices())
192 poly = hsp.Polygon(ra=ccd_poly_radec[: -1, 0],
193 dec=ccd_poly_radec[: -1, 1],
201 bbox_afw_poly.convexHull().getVertices())
202 bbox_poly = hsp.Polygon(ra=bbox_poly_radec[: -1, 0], dec=bbox_poly_radec[: -1, 1],
204 with warnings.catch_warnings():
205 warnings.simplefilter(
"ignore")
211 dtype = [(f
"v{visit}", np.int64)
for visit
in self.
_bits_per_visit.keys()]
213 with warnings.catch_warnings():
218 warnings.simplefilter(
"ignore")
220 nside_coverage=self.config.nside_coverage,
221 nside_sparse=self.config.nside,
235 """Mask a subregion from a visit.
236 This must be run after build_ccd_input_map initializes
241 bbox : `lsst.geom.Box2I`
242 Bounding box from region to mask.
244 Visit number corresponding to warp with mask.
245 mask : `lsst.afw.image.MaskX`
246 Mask plane from warp exposure.
247 bit_mask_value : `int`
248 Bit mask to check for bad pixels.
252 RuntimeError : Raised if build_ccd_input_map was not run first.
255 raise RuntimeError(
"Must run build_ccd_input_map before mask_warp_bbox")
262 bad_pixels = np.where(mask.array & bit_mask_value)
263 if len(bad_pixels[0]) == 0:
268 bad_ra, bad_dec = self.
_wcs.pixelToSkyArray(bad_pixels[1].astype(np.float64),
269 bad_pixels[0].astype(np.float64),
271 bad_hpix = hpg.angle_to_pixel(self.config.nside, bad_ra, bad_dec)
274 match_input, match_bad = esutil.numpy_util.match(self.
_ccd_input_pixels, bad_hpix, presorted=
True)
275 if len(match_bad) == 0:
278 bad_hpix = bad_hpix[match_bad]
286 count_map_visit.update_values_pix(bad_hpix, 1, operation=
"add")
289 """Use accumulated mask information to finalize the masking of
294 RuntimeError : Raised if build_ccd_input_map was not run first.
297 raise RuntimeError(
"Must run build_ccd_input_map before finalize_ccd_input_map_mask.")
301 to_mask, = np.where(count_map_arr[f
"v{visit}"] > self.
_min_bad)
302 if to_mask.size == 0:
311 """Convert pixels to ra/dec positions using a wcs.
315 wcs : `lsst.afw.geom.SkyWcs`
317 pixels : `list` [`lsst.geom.Point2D`]
318 List of pixels to convert.
322 radec : `numpy.ndarray`
323 Nx2 array of ra/dec positions associated with pixels.
325 sph_pts = wcs.pixelToSky(pixels)
326 return np.array([(sph.getRa().asDegrees(), sph.getDec().asDegrees())
331 dimensions=(
"tract",
"band",
"skymap",),
332 defaultTemplates={
"coaddName":
"deep",
334 input_maps = pipeBase.connectionTypes.Input(
335 doc=
"Healsparse bit-wise coadd input maps",
336 name=
"{coaddName}Coadd_inputMap",
337 storageClass=
"HealSparseMap",
338 dimensions=(
"tract",
"patch",
"skymap",
"band"),
342 coadd_exposures = pipeBase.connectionTypes.Input(
343 doc=
"Coadded exposures associated with input_maps",
344 name=
"{coaddName}Coadd",
345 storageClass=
"ExposureF",
346 dimensions=(
"tract",
"patch",
"skymap",
"band"),
350 visit_summaries = pipeBase.connectionTypes.Input(
351 doc=
"Visit summary tables with aggregated statistics",
352 name=
"finalVisitSummary",
353 storageClass=
"ExposureCatalog",
354 dimensions=(
"instrument",
"visit"),
358 sky_map = pipeBase.connectionTypes.Input(
359 doc=
"Input definition of geometry/bbox and projection/wcs for coadded exposures",
360 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
361 storageClass=
"SkyMap",
362 dimensions=(
"skymap",),
370 for name
in BasePropertyMap.registry:
371 vars()[f
"{name}_map_min"] = pipeBase.connectionTypes.Output(
372 doc=f
"Minimum-value map of {name}",
373 name=f
"{{coaddName}}Coadd_{name}_map_min",
374 storageClass=
"HealSparseMap",
375 dimensions=(
"tract",
"skymap",
"band"),
377 vars()[f
"{name}_map_max"] = pipeBase.connectionTypes.Output(
378 doc=f
"Maximum-value map of {name}",
379 name=f
"{{coaddName}}Coadd_{name}_map_max",
380 storageClass=
"HealSparseMap",
381 dimensions=(
"tract",
"skymap",
"band"),
383 vars()[f
"{name}_map_mean"] = pipeBase.connectionTypes.Output(
384 doc=f
"Mean-value map of {name}",
385 name=f
"{{coaddName}}Coadd_{name}_map_mean",
386 storageClass=
"HealSparseMap",
387 dimensions=(
"tract",
"skymap",
"band"),
389 vars()[f
"{name}_map_weighted_mean"] = pipeBase.connectionTypes.Output(
390 doc=f
"Weighted mean-value map of {name}",
391 name=f
"{{coaddName}}Coadd_{name}_map_weighted_mean",
392 storageClass=
"HealSparseMap",
393 dimensions=(
"tract",
"skymap",
"band"),
395 vars()[f
"{name}_map_sum"] = pipeBase.connectionTypes.Output(
396 doc=f
"Sum-value map of {name}",
397 name=f
"{{coaddName}}Coadd_{name}_map_sum",
398 storageClass=
"HealSparseMap",
399 dimensions=(
"tract",
"skymap",
"band"),
402 def __init__(self, *, config=None):
403 super().__init__(config=config)
407 for name
in BasePropertyMap.registry:
408 if name
not in config.property_maps:
410 prop_config.do_min =
False
411 prop_config.do_max =
False
412 prop_config.do_mean =
False
413 prop_config.do_weighted_mean =
False
414 prop_config.do_sum =
False
416 prop_config = config.property_maps[name]
418 if not prop_config.do_min:
419 self.outputs.remove(f
"{name}_map_min")
420 if not prop_config.do_max:
421 self.outputs.remove(f
"{name}_map_max")
422 if not prop_config.do_mean:
423 self.outputs.remove(f
"{name}_map_mean")
424 if not prop_config.do_weighted_mean:
425 self.outputs.remove(f
"{name}_map_weighted_mean")
426 if not prop_config.do_sum:
427 self.outputs.remove(f
"{name}_map_sum")
430class HealSparsePropertyMapConfig(pipeBase.PipelineTaskConfig,
431 pipelineConnections=HealSparsePropertyMapConnections):
432 """Configuration parameters for HealSparsePropertyMapTask"""
433 property_maps = BasePropertyMap.registry.makeField(
435 default=[
"exposure_time",
447 doc=
"Property map computation objects",
450 def setDefaults(self):
451 self.property_maps[
"exposure_time"].do_sum =
True
452 self.property_maps[
"psf_size"].do_weighted_mean =
True
453 self.property_maps[
"psf_e1"].do_weighted_mean =
True
454 self.property_maps[
"psf_e2"].do_weighted_mean =
True
455 self.property_maps[
"psf_maglim"].do_weighted_mean =
True
456 self.property_maps[
"sky_noise"].do_weighted_mean =
True
457 self.property_maps[
"sky_background"].do_weighted_mean =
True
458 self.property_maps[
"dcr_dra"].do_weighted_mean =
True
459 self.property_maps[
"dcr_ddec"].do_weighted_mean =
True
460 self.property_maps[
"dcr_e1"].do_weighted_mean =
True
461 self.property_maps[
"dcr_e2"].do_weighted_mean =
True
462 self.property_maps[
"epoch"].do_mean =
True
463 self.property_maps[
"epoch"].do_min =
True
464 self.property_maps[
"epoch"].do_max =
True
467class HealSparsePropertyMapTask(pipeBase.PipelineTask):
468 """Task to compute Healsparse property maps.
470 This task will compute individual property maps (per tract, per
471 map type, per band). These maps cover the full coadd tract, and
472 are not truncated to the inner tract region.
474 ConfigClass = HealSparsePropertyMapConfig
475 _DefaultName =
"healSparsePropertyMapTask"
477 def __init__(self, **kwargs):
478 super().__init__(**kwargs)
480 for name, config, PropertyMapClass
in self.config.property_maps.apply():
481 self.property_maps[name] = PropertyMapClass(config, name)
484 def runQuantum(self, butlerQC, inputRefs, outputRefs):
485 inputs = butlerQC.get(inputRefs)
487 sky_map = inputs.pop(
"sky_map")
489 tract = butlerQC.quantum.dataId[
"tract"]
490 band = butlerQC.quantum.dataId[
"band"]
492 input_map_dict = {ref.dataId[
"patch"]: ref
for ref
in inputs[
"input_maps"]}
493 coadd_dict = {ref.dataId[
"patch"]: ref
for ref
in inputs[
"coadd_exposures"]}
495 visit_summary_dict = {ref.dataId[
"visit"]: ref.get()
496 for ref
in inputs[
"visit_summaries"]}
498 self.run(sky_map, tract, band, coadd_dict, input_map_dict, visit_summary_dict)
501 for name, property_map
in self.property_maps.items():
502 if property_map.config.do_min:
503 butlerQC.put(property_map.min_map,
504 getattr(outputRefs, f
"{name}_map_min"))
505 if property_map.config.do_max:
506 butlerQC.put(property_map.max_map,
507 getattr(outputRefs, f
"{name}_map_max"))
508 if property_map.config.do_mean:
509 butlerQC.put(property_map.mean_map,
510 getattr(outputRefs, f
"{name}_map_mean"))
511 if property_map.config.do_weighted_mean:
512 butlerQC.put(property_map.weighted_mean_map,
513 getattr(outputRefs, f
"{name}_map_weighted_mean"))
514 if property_map.config.do_sum:
515 butlerQC.put(property_map.sum_map,
516 getattr(outputRefs, f
"{name}_map_sum"))
518 def run(self, sky_map, tract, band, coadd_dict, input_map_dict, visit_summary_dict):
519 """Run the healsparse property task.
523 sky_map : Sky map object
527 Band name for logging.
528 coadd_dict : `dict` [`int`: `lsst.daf.butler.DeferredDatasetHandle`]
529 Dictionary of coadd exposure references. Keys are patch numbers.
530 input_map_dict : `dict` [`int`: `lsst.daf.butler.DeferredDatasetHandle`]
531 Dictionary of input map references. Keys are patch numbers.
532 visit_summary_dict : `dict` [`int`: `lsst.afw.table.ExposureCatalog`]
533 Dictionary of visit summary tables. Keys are visit numbers.
537 RepeatableQuantumError
538 If visit_summary_dict is missing any visits or detectors found in an
539 input map. This leads to an inconsistency between what is in the coadd
540 (via the input map) and the visit summary tables which contain data
543 tract_info = sky_map[tract]
545 tract_maps_initialized =
False
547 for patch
in input_map_dict.keys():
548 self.log.info(
"Making maps for band %s, tract %d, patch %d.",
551 patch_info = tract_info[patch]
553 input_map = input_map_dict[patch].get()
557 if not tract_maps_initialized:
560 nside_coverage = self._compute_nside_coverage_tract(tract_info)
561 nside = input_map.nside_sparse
563 do_compute_approx_psf =
False
565 for property_map
in self.property_maps:
566 property_map.initialize_tract_maps(nside_coverage, nside)
567 if property_map.requires_psf:
568 do_compute_approx_psf =
True
570 tract_maps_initialized =
True
572 if input_map.valid_pixels.size == 0:
573 self.log.warning(
"No valid pixels for band %s, tract %d, patch %d; skipping.",
577 coadd_photo_calib = coadd_dict[patch].get(component=
"photoCalib")
578 coadd_inputs = coadd_dict[patch].get(component=
"coaddInputs")
580 coadd_zeropoint = 2.5*np.log10(coadd_photo_calib.getInstFluxAtZeroMagnitude())
583 poly_vertices = patch_info.getInnerSkyPolygon(tract_info.getWcs()).getVertices()
584 patch_radec = self._vertices_to_radec(poly_vertices)
585 patch_poly = hsp.Polygon(ra=patch_radec[:, 0], dec=patch_radec[:, 1],
586 value=np.arange(input_map.wide_mask_maxbits))
587 with warnings.catch_warnings():
592 warnings.simplefilter(
"ignore")
593 patch_poly_map = patch_poly.get_map_like(input_map)
594 input_map = hsp.and_intersection([input_map, patch_poly_map])
596 valid_pixels, vpix_ra, vpix_dec = input_map.valid_pixels_pos(return_pixels=
True)
599 if valid_pixels.size == 0:
603 for property_map
in self.property_maps:
604 property_map.initialize_values(valid_pixels.size)
605 property_map.zeropoint = coadd_zeropoint
608 total_weights = np.zeros(valid_pixels.size)
609 total_inputs = np.zeros(valid_pixels.size, dtype=np.int32)
611 for bit, ccd_row
in enumerate(coadd_inputs.ccds):
613 inmap, = np.where(input_map.check_bits_pix(valid_pixels, [bit]))
620 visit = ccd_row[
"visit"]
621 detector_id = ccd_row[
"ccd"]
622 weight = ccd_row[
"weight"]
624 x, y = ccd_row.getWcs().skyToPixelArray(vpix_ra[inmap], vpix_dec[inmap], degrees=
True)
625 scalings = self._compute_calib_scale(ccd_row, x, y)
627 if do_compute_approx_psf:
628 psf_array = compute_approx_psf_size_and_shape(ccd_row, vpix_ra[inmap], vpix_dec[inmap])
632 total_weights[inmap] += weight
633 total_inputs[inmap] += 1
636 if visit
not in visit_summary_dict:
637 msg = f
"Visit {visit} not found in visit_summaries."
638 raise pipeBase.RepeatableQuantumError(msg)
639 row = visit_summary_dict[visit].find(detector_id)
641 msg = f
"Visit {visit} / detector_id {detector_id} not found in visit_summaries."
642 raise pipeBase.RepeatableQuantumError(msg)
645 for property_map
in self.property_maps:
646 property_map.accumulate_values(inmap,
655 for property_map
in self.property_maps:
656 property_map.finalize_mean_values(total_weights, total_inputs)
657 property_map.set_map_values(valid_pixels)
659 def _compute_calib_scale(self, ccd_row, x, y):
660 """Compute calibration scaling values.
664 ccd_row : `lsst.afw.table.ExposureRecord`
665 Exposure metadata for a given detector exposure.
667 Array of x positions.
669 Array of y positions.
673 calib_scale : `np.ndarray`
674 Array of calibration scale values.
676 photo_calib = ccd_row.getPhotoCalib()
677 bf = photo_calib.computeScaledCalibration()
678 if bf.getBBox() == ccd_row.getBBox():
680 calib_scale = photo_calib.getCalibrationMean()*bf.evaluate(x, y)
683 calib_scale = photo_calib.getCalibrationMean()
687 def _vertices_to_radec(self, vertices):
688 """Convert polygon vertices to ra/dec.
692 vertices : `list` [ `lsst.sphgeom.UnitVector3d` ]
693 Vertices for bounding polygon.
697 radec : `numpy.ndarray`
698 Nx2 array of ra/dec positions (in degrees) associated with vertices.
701 radec = np.array([(x.getLon().asDegrees(), x.getLat().asDegrees())
for
705 def _compute_nside_coverage_tract(self, tract_info):
706 """Compute the optimal coverage nside for a tract.
710 tract_info : `lsst.skymap.tractInfo.ExplicitTractInfo`
711 Tract information object.
715 nside_coverage : `int`
716 Optimal coverage nside for a tract map.
718 num_patches = tract_info.getNumPatches()
721 patch_info = tract_info.getPatchInfo(0)
722 vertices = patch_info.getInnerSkyPolygon(tract_info.getWcs()).getVertices()
723 radec = self._vertices_to_radec(vertices)
724 delta_ra = np.max(radec[:, 0]) - np.min(radec[:, 0])
725 delta_dec = np.max(radec[:, 1]) - np.min(radec[:, 1])
726 patch_area = delta_ra*delta_dec*np.cos(np.deg2rad(np.mean(radec[:, 1])))
728 tract_area = num_patches[0]*num_patches[1]*patch_area
730 nside_coverage_tract = 32
731 while hpg.nside_to_pixel_area(nside_coverage_tract, degrees=
True) > tract_area:
732 nside_coverage_tract = 2*nside_coverage_tract
735 nside_coverage_tract = int(np.clip(nside_coverage_tract/2, 32, 128))
737 return nside_coverage_tract
740class ConsolidateHealSparsePropertyMapConnections(pipeBase.PipelineTaskConnections,
741 dimensions=(
"band",
"skymap",),
742 defaultTemplates={
"coaddName":
"deep"}):
743 sky_map = pipeBase.connectionTypes.Input(
744 doc=
"Input definition of geometry/bbox and projection/wcs for coadded exposures",
745 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
746 storageClass=
"SkyMap",
747 dimensions=(
"skymap",),
755 for name
in BasePropertyMap.registry:
756 vars()[f
"{name}_map_min"] = pipeBase.connectionTypes.Input(
757 doc=f
"Minimum-value map of {name}",
758 name=f
"{{coaddName}}Coadd_{name}_map_min",
759 storageClass=
"HealSparseMap",
760 dimensions=(
"tract",
"skymap",
"band"),
764 vars()[f
"{name}_consolidated_map_min"] = pipeBase.connectionTypes.Output(
765 doc=f
"Minumum-value map of {name}",
766 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_min",
767 storageClass=
"HealSparseMap",
768 dimensions=(
"skymap",
"band"),
770 vars()[f
"{name}_map_max"] = pipeBase.connectionTypes.Input(
771 doc=f
"Maximum-value map of {name}",
772 name=f
"{{coaddName}}Coadd_{name}_map_max",
773 storageClass=
"HealSparseMap",
774 dimensions=(
"tract",
"skymap",
"band"),
778 vars()[f
"{name}_consolidated_map_max"] = pipeBase.connectionTypes.Output(
779 doc=f
"Minumum-value map of {name}",
780 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_max",
781 storageClass=
"HealSparseMap",
782 dimensions=(
"skymap",
"band"),
784 vars()[f
"{name}_map_mean"] = pipeBase.connectionTypes.Input(
785 doc=f
"Mean-value map of {name}",
786 name=f
"{{coaddName}}Coadd_{name}_map_mean",
787 storageClass=
"HealSparseMap",
788 dimensions=(
"tract",
"skymap",
"band"),
792 vars()[f
"{name}_consolidated_map_mean"] = pipeBase.connectionTypes.Output(
793 doc=f
"Minumum-value map of {name}",
794 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_mean",
795 storageClass=
"HealSparseMap",
796 dimensions=(
"skymap",
"band"),
798 vars()[f
"{name}_map_weighted_mean"] = pipeBase.connectionTypes.Input(
799 doc=f
"Weighted mean-value map of {name}",
800 name=f
"{{coaddName}}Coadd_{name}_map_weighted_mean",
801 storageClass=
"HealSparseMap",
802 dimensions=(
"tract",
"skymap",
"band"),
806 vars()[f
"{name}_consolidated_map_weighted_mean"] = pipeBase.connectionTypes.Output(
807 doc=f
"Minumum-value map of {name}",
808 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_weighted_mean",
809 storageClass=
"HealSparseMap",
810 dimensions=(
"skymap",
"band"),
812 vars()[f
"{name}_map_sum"] = pipeBase.connectionTypes.Input(
813 doc=f
"Sum-value map of {name}",
814 name=f
"{{coaddName}}Coadd_{name}_map_sum",
815 storageClass=
"HealSparseMap",
816 dimensions=(
"tract",
"skymap",
"band"),
820 vars()[f
"{name}_consolidated_map_sum"] = pipeBase.connectionTypes.Output(
821 doc=f
"Minumum-value map of {name}",
822 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_sum",
823 storageClass=
"HealSparseMap",
824 dimensions=(
"skymap",
"band"),
827 def __init__(self, *, config=None):
828 super().__init__(config=config)
832 for name
in BasePropertyMap.registry:
833 if name
not in config.property_maps:
835 prop_config.do_min =
False
836 prop_config.do_max =
False
837 prop_config.do_mean =
False
838 prop_config.do_weighted_mean =
False
839 prop_config.do_sum =
False
841 prop_config = config.property_maps[name]
843 if not prop_config.do_min:
844 self.inputs.remove(f
"{name}_map_min")
845 self.outputs.remove(f
"{name}_consolidated_map_min")
846 if not prop_config.do_max:
847 self.inputs.remove(f
"{name}_map_max")
848 self.outputs.remove(f
"{name}_consolidated_map_max")
849 if not prop_config.do_mean:
850 self.inputs.remove(f
"{name}_map_mean")
851 self.outputs.remove(f
"{name}_consolidated_map_mean")
852 if not prop_config.do_weighted_mean:
853 self.inputs.remove(f
"{name}_map_weighted_mean")
854 self.outputs.remove(f
"{name}_consolidated_map_weighted_mean")
855 if not prop_config.do_sum:
856 self.inputs.remove(f
"{name}_map_sum")
857 self.outputs.remove(f
"{name}_consolidated_map_sum")
860class ConsolidateHealSparsePropertyMapConfig(pipeBase.PipelineTaskConfig,
861 pipelineConnections=ConsolidateHealSparsePropertyMapConnections):
862 """Configuration parameters for ConsolidateHealSparsePropertyMapTask"""
863 property_maps = BasePropertyMap.registry.makeField(
865 default=[
"exposure_time",
877 doc=
"Property map computation objects",
879 nside_coverage = pexConfig.Field(
880 doc=
"Consolidated HealSparse coverage map nside. Must be power of 2.",
883 check=_is_power_of_two,
886 def setDefaults(self):
887 self.property_maps[
"exposure_time"].do_sum =
True
888 self.property_maps[
"psf_size"].do_weighted_mean =
True
889 self.property_maps[
"psf_e1"].do_weighted_mean =
True
890 self.property_maps[
"psf_e2"].do_weighted_mean =
True
891 self.property_maps[
"psf_maglim"].do_weighted_mean =
True
892 self.property_maps[
"sky_noise"].do_weighted_mean =
True
893 self.property_maps[
"sky_background"].do_weighted_mean =
True
894 self.property_maps[
"dcr_dra"].do_weighted_mean =
True
895 self.property_maps[
"dcr_ddec"].do_weighted_mean =
True
896 self.property_maps[
"dcr_e1"].do_weighted_mean =
True
897 self.property_maps[
"dcr_e2"].do_weighted_mean =
True
898 self.property_maps[
"epoch"].do_mean =
True
899 self.property_maps[
"epoch"].do_min =
True
900 self.property_maps[
"epoch"].do_max =
True
903class ConsolidateHealSparsePropertyMapTask(pipeBase.PipelineTask):
904 """Task to consolidate HealSparse property maps.
906 This task will take all the individual tract-based maps (per map type,
907 per band) and consolidate them into one survey-wide map (per map type,
908 per band). Each tract map is truncated to its inner region before
911 ConfigClass = ConsolidateHealSparsePropertyMapConfig
912 _DefaultName =
"consolidateHealSparsePropertyMapTask"
914 def __init__(self, **kwargs):
915 super().__init__(**kwargs)
917 for name, config, PropertyMapClass
in self.config.property_maps.apply():
918 self.property_maps[name] = PropertyMapClass(config, name)
921 def runQuantum(self, butlerQC, inputRefs, outputRefs):
922 inputs = butlerQC.get(inputRefs)
924 sky_map = inputs.pop(
"sky_map")
927 for name
in self.config.property_maps.names:
928 for type_
in [
'min',
'max',
'mean',
'weighted_mean',
'sum']:
929 map_type = f
"{name}_map_{type_}"
930 if map_type
in inputs:
931 input_refs = {ref.dataId[
'tract']: ref
932 for ref
in inputs[map_type]}
933 consolidated_map = self.consolidate_map(sky_map, input_refs)
934 butlerQC.put(consolidated_map,
935 getattr(outputRefs, f
"{name}_consolidated_map_{type_}"))
937 def consolidate_map(self, sky_map, input_refs):
938 """Consolidate the healsparse property maps.
942 sky_map : Sky map object
943 input_refs : `dict` [`int`: `lsst.daf.butler.DeferredDatasetHandle`]
944 Dictionary of tract_id mapping to dataref.
948 consolidated_map : `healsparse.HealSparseMap`
949 Consolidated HealSparse map.
954 nside_coverage_inputs =
None
955 for tract_id
in input_refs:
956 cov = input_refs[tract_id].get(component=
'coverage')
958 cov_mask = cov.coverage_mask
959 nside_coverage_inputs = cov.nside_coverage
961 cov_mask |= cov.coverage_mask
963 cov_pix_inputs, = np.where(cov_mask)
966 if nside_coverage_inputs == self.config.nside_coverage:
967 cov_pix = cov_pix_inputs
968 elif nside_coverage_inputs > self.config.nside_coverage:
971 bit_shift = hsp.utils._compute_bitshift(self.config.nside_coverage,
972 nside_coverage_inputs)
973 cov_pix = np.right_shift(cov_pix_inputs, bit_shift)
977 bit_shift = hsp.utils._compute_bitshift(nside_coverage_inputs,
978 self.config.nside_coverage)
979 cov_pix = np.left_shift(cov_pix_inputs, bit_shift)
982 consolidated_map =
None
983 for tract_id
in input_refs:
984 input_map = input_refs[tract_id].get()
985 if consolidated_map
is None:
986 consolidated_map = hsp.HealSparseMap.make_empty(
987 self.config.nside_coverage,
988 input_map.nside_sparse,
990 sentinel=input_map._sentinel,
992 metadata=input_map.metadata,
996 vpix, ra, dec = input_map.valid_pixels_pos(return_pixels=
True)
997 vpix_tract_ids = sky_map.findTractIdArray(ra, dec, degrees=
True)
999 in_tract = (vpix_tract_ids == tract_id)
1001 consolidated_map[vpix[in_tract]] = input_map[vpix[in_tract]]
1003 return consolidated_map
A floating-point coordinate rectangle geometry.
LonLat represents a spherical coordinate (longitude/latitude angle) pair.