22 from collections
import defaultdict
26 import healsparse
as hsp
32 from lsst.daf.butler
import Formatter
34 from .healSparseMappingProperties
import (BasePropertyMap, BasePropertyMapConfig,
35 PropertyMapMap, compute_approx_psf_size_and_shape)
38 __all__ = [
"HealSparseInputMapTask",
"HealSparseInputMapConfig",
39 "HealSparseMapFormatter",
"HealSparsePropertyMapConnections",
40 "HealSparsePropertyMapConfig",
"HealSparsePropertyMapTask",
41 "ConsolidateHealSparsePropertyMapConnections",
42 "ConsolidateHealSparsePropertyMapConfig",
43 "ConsolidateHealSparsePropertyMapTask"]
47 """Interface for reading and writing healsparse.HealSparseMap files."""
48 unsupportedParameters = frozenset()
49 supportedExtensions = frozenset({
".hsp",
".fit",
".fits"})
52 def read(self, component=None):
54 path = self.fileDescriptor.location.path
56 if component ==
'coverage':
58 data = hsp.HealSparseCoverage.read(path)
59 except (OSError, RuntimeError):
60 raise ValueError(f
"Unable to read healsparse map with URI {self.fileDescriptor.location.uri}")
64 if self.fileDescriptor.parameters
is None:
68 pixels = self.fileDescriptor.parameters.get(
'pixels',
None)
69 degrade_nside = self.fileDescriptor.parameters.get(
'degrade_nside',
None)
71 data = hsp.HealSparseMap.read(path, pixels=pixels, degrade_nside=degrade_nside)
72 except (OSError, RuntimeError):
73 raise ValueError(f
"Unable to read healsparse map with URI {self.fileDescriptor.location.uri}")
77 def write(self, inMemoryDataset):
80 self.fileDescriptor.location.updateExtension(self.
extensionextension)
81 inMemoryDataset.write(self.fileDescriptor.location.path, clobber=
True)
84 def _is_power_of_two(value):
85 """Check that value is a power of two.
94 is_power_of_two : `bool`
95 True if value is a power of two; False otherwise, or
96 if value is not an integer.
98 if not isinstance(value, numbers.Integral):
105 return (value & (value - 1) == 0)
and value != 0
109 """Configuration parameters for HealSparseInputMapTask"""
110 nside = pexConfig.Field(
111 doc=
"Mapping healpix nside. Must be power of 2.",
114 check=_is_power_of_two,
116 nside_coverage = pexConfig.Field(
117 doc=
"HealSparse coverage map nside. Must be power of 2.",
120 check=_is_power_of_two,
122 bad_mask_min_coverage = pexConfig.Field(
123 doc=(
"Minimum area fraction of a map healpixel pixel that must be "
124 "covered by bad pixels to be removed from the input map. "
125 "This is approximate."),
132 """Task for making a HealSparse input map."""
134 ConfigClass = HealSparseInputMapConfig
135 _DefaultName =
"healSparseInputMap"
138 pipeBase.Task.__init__(self, **kwargs)
143 """Build a map from ccd valid polygons or bounding boxes.
147 bbox : `lsst.geom.Box2I`
148 Bounding box for region to build input map.
149 wcs : `lsst.afw.geom.SkyWcs`
150 WCS object for region to build input map.
151 ccds : `lsst.afw.table.ExposureCatalog`
152 Exposure catalog with ccd data from coadd inputs.
154 self.
ccd_input_mapccd_input_map = hsp.HealSparseMap.make_empty(nside_coverage=self.config.nside_coverage,
155 nside_sparse=self.config.nside,
157 wide_mask_maxbits=len(ccds))
159 self.
_bbox_bbox = bbox
160 self.
_ccds_ccds = ccds
162 pixel_scale = wcs.getPixelScale().asArcseconds()
163 hpix_area_arcsec2 = hp.nside2pixarea(self.config.nside, degrees=
True)*(3600.**2.)
164 self.
_min_bad_min_bad = self.config.bad_mask_min_coverage*hpix_area_arcsec2/(pixel_scale**2.)
169 for bit, ccd_row
in enumerate(ccds):
170 metadata[f
"B{bit:04d}CCD"] = ccd_row[
"ccd"]
171 metadata[f
"B{bit:04d}VIS"] = ccd_row[
"visit"]
172 metadata[f
"B{bit:04d}WT"] = ccd_row[
"weight"]
177 ccd_poly = ccd_row.getValidPolygon()
181 ccd_poly_radec = self.
_pixels_to_radec_pixels_to_radec(ccd_row.getWcs(), ccd_poly.convexHull().getVertices())
184 poly = hsp.Polygon(ra=ccd_poly_radec[: -1, 0],
185 dec=ccd_poly_radec[: -1, 1],
193 bbox_afw_poly.convexHull().getVertices())
194 bbox_poly = hsp.Polygon(ra=bbox_poly_radec[: -1, 0], dec=bbox_poly_radec[: -1, 1],
195 value=np.arange(self.
ccd_input_mapccd_input_map.wide_mask_maxbits))
196 bbox_poly_map = bbox_poly.get_map_like(self.
ccd_input_mapccd_input_map)
205 cov = self.config.nside_coverage
206 ns = self.config.nside
216 """Mask a subregion from a visit.
217 This must be run after build_ccd_input_map initializes
222 bbox : `lsst.geom.Box2I`
223 Bounding box from region to mask.
225 Visit number corresponding to warp with mask.
226 mask : `lsst.afw.image.MaskX`
227 Mask plane from warp exposure.
228 bit_mask_value : `int`
229 Bit mask to check for bad pixels.
233 RuntimeError : Raised if build_ccd_input_map was not run first.
236 raise RuntimeError(
"Must run build_ccd_input_map before mask_warp_bbox")
239 bad_pixels = np.where(mask.array & bit_mask_value)
240 if len(bad_pixels[0]) == 0:
245 bad_ra, bad_dec = self.
_wcs_wcs.pixelToSkyArray(bad_pixels[1].astype(np.float64),
246 bad_pixels[0].astype(np.float64),
248 bad_hpix = hp.ang2pix(self.config.nside, bad_ra, bad_dec,
249 lonlat=
True, nest=
True)
252 min_bad_hpix = bad_hpix.min()
253 bad_hpix_count = np.zeros(bad_hpix.max() - min_bad_hpix + 1, dtype=np.int32)
254 np.add.at(bad_hpix_count, bad_hpix - min_bad_hpix, 1)
259 pix_to_add, = np.where(bad_hpix_count > 0)
262 count_map_arr[primary] = np.clip(count_map_arr[primary], 0,
None)
264 count_map_arr[f
"v{visit}"] = np.clip(count_map_arr[f
"v{visit}"], 0,
None)
265 count_map_arr[f
"v{visit}"] += bad_hpix_count[pix_to_add]
270 """Use accumulated mask information to finalize the masking of
275 RuntimeError : Raised if build_ccd_input_map was not run first.
278 raise RuntimeError(
"Must run build_ccd_input_map before finalize_ccd_input_map_mask.")
282 to_mask, = np.where(count_map_arr[f
"v{visit}"] > self.
_min_bad_min_bad)
283 if to_mask.size == 0:
291 def _pixels_to_radec(self, wcs, pixels):
292 """Convert pixels to ra/dec positions using a wcs.
296 wcs : `lsst.afw.geom.SkyWcs`
298 pixels : `list` [`lsst.geom.Point2D`]
299 List of pixels to convert.
303 radec : `numpy.ndarray`
304 Nx2 array of ra/dec positions associated with pixels.
306 sph_pts = wcs.pixelToSky(pixels)
307 return np.array([(sph.getRa().asDegrees(), sph.getDec().asDegrees())
312 dimensions=(
"tract",
"band",
"skymap",),
313 defaultTemplates={
"coaddName":
"deep"}):
314 input_maps = pipeBase.connectionTypes.Input(
315 doc=
"Healsparse bit-wise coadd input maps",
316 name=
"{coaddName}Coadd_inputMap",
317 storageClass=
"HealSparseMap",
318 dimensions=(
"tract",
"patch",
"skymap",
"band"),
322 coadd_exposures = pipeBase.connectionTypes.Input(
323 doc=
"Coadded exposures associated with input_maps",
324 name=
"{coaddName}Coadd",
325 storageClass=
"ExposureF",
326 dimensions=(
"tract",
"patch",
"skymap",
"band"),
330 visit_summaries = pipeBase.connectionTypes.Input(
331 doc=
"Visit summary tables with aggregated statistics",
333 storageClass=
"ExposureCatalog",
334 dimensions=(
"instrument",
"visit"),
338 sky_map = pipeBase.connectionTypes.Input(
339 doc=
"Input definition of geometry/bbox and projection/wcs for coadded exposures",
340 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
341 storageClass=
"SkyMap",
342 dimensions=(
"skymap",),
350 for name
in BasePropertyMap.registry:
351 vars()[f
"{name}_map_min"] = pipeBase.connectionTypes.Output(
352 doc=f
"Minimum-value map of {name}",
353 name=f
"{{coaddName}}Coadd_{name}_map_min",
354 storageClass=
"HealSparseMap",
355 dimensions=(
"tract",
"skymap",
"band"),
357 vars()[f
"{name}_map_max"] = pipeBase.connectionTypes.Output(
358 doc=f
"Maximum-value map of {name}",
359 name=f
"{{coaddName}}Coadd_{name}_map_max",
360 storageClass=
"HealSparseMap",
361 dimensions=(
"tract",
"skymap",
"band"),
363 vars()[f
"{name}_map_mean"] = pipeBase.connectionTypes.Output(
364 doc=f
"Mean-value map of {name}",
365 name=f
"{{coaddName}}Coadd_{name}_map_mean",
366 storageClass=
"HealSparseMap",
367 dimensions=(
"tract",
"skymap",
"band"),
369 vars()[f
"{name}_map_weighted_mean"] = pipeBase.connectionTypes.Output(
370 doc=f
"Weighted mean-value map of {name}",
371 name=f
"{{coaddName}}Coadd_{name}_map_weighted_mean",
372 storageClass=
"HealSparseMap",
373 dimensions=(
"tract",
"skymap",
"band"),
375 vars()[f
"{name}_map_sum"] = pipeBase.connectionTypes.Output(
376 doc=f
"Sum-value map of {name}",
377 name=f
"{{coaddName}}Coadd_{name}_map_sum",
378 storageClass=
"HealSparseMap",
379 dimensions=(
"tract",
"skymap",
"band"),
382 def __init__(self, *, config=None):
383 super().__init__(config=config)
387 for name
in BasePropertyMap.registry:
388 if name
not in config.property_maps:
390 prop_config.do_min =
False
391 prop_config.do_max =
False
392 prop_config.do_mean =
False
393 prop_config.do_weighted_mean =
False
394 prop_config.do_sum =
False
396 prop_config = config.property_maps[name]
398 if not prop_config.do_min:
399 self.outputs.remove(f
"{name}_map_min")
400 if not prop_config.do_max:
401 self.outputs.remove(f
"{name}_map_max")
402 if not prop_config.do_mean:
403 self.outputs.remove(f
"{name}_map_mean")
404 if not prop_config.do_weighted_mean:
405 self.outputs.remove(f
"{name}_map_weighted_mean")
406 if not prop_config.do_sum:
407 self.outputs.remove(f
"{name}_map_sum")
410 class HealSparsePropertyMapConfig(pipeBase.PipelineTaskConfig,
411 pipelineConnections=HealSparsePropertyMapConnections):
412 """Configuration parameters for HealSparsePropertyMapTask"""
413 property_maps = BasePropertyMap.registry.makeField(
415 default=[
"exposure_time",
426 doc=
"Property map computation objects",
430 self.property_maps[
"exposure_time"].do_sum =
True
431 self.property_maps[
"psf_size"].do_weighted_mean =
True
432 self.property_maps[
"psf_e1"].do_weighted_mean =
True
433 self.property_maps[
"psf_e2"].do_weighted_mean =
True
434 self.property_maps[
"psf_maglim"].do_weighted_mean =
True
435 self.property_maps[
"sky_noise"].do_weighted_mean =
True
436 self.property_maps[
"sky_background"].do_weighted_mean =
True
437 self.property_maps[
"dcr_dra"].do_weighted_mean =
True
438 self.property_maps[
"dcr_ddec"].do_weighted_mean =
True
439 self.property_maps[
"dcr_e1"].do_weighted_mean =
True
440 self.property_maps[
"dcr_e2"].do_weighted_mean =
True
443 class HealSparsePropertyMapTask(pipeBase.PipelineTask):
444 """Task to compute Healsparse property maps.
446 This task will compute individual property maps (per tract, per
447 map type, per band). These maps cover the full coadd tract, and
448 are not truncated to the inner tract region.
450 ConfigClass = HealSparsePropertyMapConfig
451 _DefaultName =
"healSparsePropertyMapTask"
453 def __init__(self, **kwargs):
454 super().__init__(**kwargs)
456 for name, config, PropertyMapClass
in self.config.property_maps.apply():
457 self.property_maps[name] = PropertyMapClass(config, name)
460 def runQuantum(self, butlerQC, inputRefs, outputRefs):
461 inputs = butlerQC.get(inputRefs)
463 sky_map = inputs.pop(
"sky_map")
465 tract = butlerQC.quantum.dataId[
"tract"]
466 band = butlerQC.quantum.dataId[
"band"]
468 input_map_dict = {ref.dataId[
"patch"]: ref
for ref
in inputs[
"input_maps"]}
469 coadd_dict = {ref.dataId[
"patch"]: ref
for ref
in inputs[
"coadd_exposures"]}
471 visit_summary_dict = {ref.dataId[
"visit"]: ref.get()
472 for ref
in inputs[
"visit_summaries"]}
474 self.run(sky_map, tract, band, coadd_dict, input_map_dict, visit_summary_dict)
477 for name, property_map
in self.property_maps.
items():
478 if property_map.config.do_min:
479 butlerQC.put(property_map.min_map,
480 getattr(outputRefs, f
"{name}_map_min"))
481 if property_map.config.do_max:
482 butlerQC.put(property_map.max_map,
483 getattr(outputRefs, f
"{name}_map_max"))
484 if property_map.config.do_mean:
485 butlerQC.put(property_map.mean_map,
486 getattr(outputRefs, f
"{name}_map_mean"))
487 if property_map.config.do_weighted_mean:
488 butlerQC.put(property_map.weighted_mean_map,
489 getattr(outputRefs, f
"{name}_map_weighted_mean"))
490 if property_map.config.do_sum:
491 butlerQC.put(property_map.sum_map,
492 getattr(outputRefs, f
"{name}_map_sum"))
494 def run(self, sky_map, tract, band, coadd_dict, input_map_dict, visit_summary_dict):
495 """Run the healsparse property task.
499 sky_map : Sky map object
503 Band name for logging.
504 coadd_dict : `dict` [`int`: `lsst.daf.butler.DeferredDatasetHandle`]
505 Dictionary of coadd exposure references. Keys are patch numbers.
506 input_map_dict : `dict` [`int`: `lsst.daf.butler.DeferredDatasetHandle`]
507 Dictionary of input map references. Keys are patch numbers.
508 visit_summary_dict : `dict` [`int`: `lsst.afw.table.ExposureCatalog`]
509 Dictionary of visit summary tables. Keys are visit numbers.
513 RepeatableQuantumError
514 If visit_summary_dict is missing any visits or detectors found in an
515 input map. This leads to an inconsistency between what is in the coadd
516 (via the input map) and the visit summary tables which contain data
519 tract_info = sky_map[tract]
521 tract_maps_initialized =
False
523 for patch
in input_map_dict.keys():
524 self.log.
info(
"Making maps for band %s, tract %d, patch %d.",
527 patch_info = tract_info[patch]
529 input_map = input_map_dict[patch].get()
530 coadd_photo_calib = coadd_dict[patch].get(component=
"photoCalib")
531 coadd_inputs = coadd_dict[patch].get(component=
"coaddInputs")
533 coadd_zeropoint = 2.5*np.log10(coadd_photo_calib.getInstFluxAtZeroMagnitude())
536 poly_vertices = patch_info.getInnerSkyPolygon(tract_info.getWcs()).getVertices()
537 patch_radec = self._vertices_to_radec(poly_vertices)
538 patch_poly = hsp.Polygon(ra=patch_radec[:, 0], dec=patch_radec[:, 1],
539 value=np.arange(input_map.wide_mask_maxbits))
540 patch_poly_map = patch_poly.get_map_like(input_map)
541 input_map = hsp.and_intersection([input_map, patch_poly_map])
543 if not tract_maps_initialized:
546 nside_coverage = self._compute_nside_coverage_tract(tract_info)
547 nside = input_map.nside_sparse
549 do_compute_approx_psf =
False
551 for property_map
in self.property_maps:
552 property_map.initialize_tract_maps(nside_coverage, nside)
553 if property_map.requires_psf:
554 do_compute_approx_psf =
True
556 tract_maps_initialized =
True
558 valid_pixels, vpix_ra, vpix_dec = input_map.valid_pixels_pos(return_pixels=
True)
561 if valid_pixels.size == 0:
565 for property_map
in self.property_maps:
566 property_map.initialize_values(valid_pixels.size)
567 property_map.zeropoint = coadd_zeropoint
570 total_weights = np.zeros(valid_pixels.size)
571 total_inputs = np.zeros(valid_pixels.size, dtype=np.int32)
573 for bit, ccd_row
in enumerate(coadd_inputs.ccds):
575 inmap, = np.where(input_map.check_bits_pix(valid_pixels, [bit]))
582 visit = ccd_row[
"visit"]
583 detector_id = ccd_row[
"ccd"]
584 weight = ccd_row[
"weight"]
586 x, y = ccd_row.getWcs().skyToPixelArray(vpix_ra[inmap], vpix_dec[inmap], degrees=
True)
587 scalings = self._compute_calib_scale(ccd_row, x, y)
589 if do_compute_approx_psf:
594 total_weights[inmap] += weight
595 total_inputs[inmap] += 1
598 if visit
not in visit_summary_dict:
599 msg = f
"Visit {visit} not found in visit_summaries."
600 raise pipeBase.RepeatableQuantumError(msg)
601 row = visit_summary_dict[visit].find(detector_id)
603 msg = f
"Visit {visit} / detector_id {detector_id} not found in visit_summaries."
604 raise pipeBase.RepeatableQuantumError(msg)
607 for property_map
in self.property_maps:
608 property_map.accumulate_values(inmap,
617 for property_map
in self.property_maps:
618 property_map.finalize_mean_values(total_weights, total_inputs)
619 property_map.set_map_values(valid_pixels)
621 def _compute_calib_scale(self, ccd_row, x, y):
622 """Compute calibration scaling values.
626 ccd_row : `lsst.afw.table.ExposureRecord`
627 Exposure metadata for a given detector exposure.
629 Array of x positions.
631 Array of y positions.
635 calib_scale : `np.ndarray`
636 Array of calibration scale values.
638 photo_calib = ccd_row.getPhotoCalib()
639 bf = photo_calib.computeScaledCalibration()
640 if bf.getBBox() == ccd_row.getBBox():
642 calib_scale = photo_calib.getCalibrationMean()*bf.evaluate(x, y)
645 calib_scale = photo_calib.getCalibrationMean()
649 def _vertices_to_radec(self, vertices):
650 """Convert polygon vertices to ra/dec.
654 vertices : `list` [ `lsst.sphgeom.UnitVector3d` ]
655 Vertices for bounding polygon.
659 radec : `numpy.ndarray`
660 Nx2 array of ra/dec positions (in degrees) associated with vertices.
663 radec = np.array([(x.getLon().asDegrees(), x.getLat().asDegrees())
for
667 def _compute_nside_coverage_tract(self, tract_info):
668 """Compute the optimal coverage nside for a tract.
672 tract_info : `lsst.skymap.tractInfo.ExplicitTractInfo`
673 Tract information object.
677 nside_coverage : `int`
678 Optimal coverage nside for a tract map.
680 num_patches = tract_info.getNumPatches()
683 patch_info = tract_info.getPatchInfo(0)
684 vertices = patch_info.getInnerSkyPolygon(tract_info.getWcs()).getVertices()
685 radec = self._vertices_to_radec(vertices)
686 delta_ra = np.max(radec[:, 0]) - np.min(radec[:, 0])
687 delta_dec = np.max(radec[:, 1]) - np.min(radec[:, 1])
688 patch_area = delta_ra*delta_dec*np.cos(np.deg2rad(np.mean(radec[:, 1])))
690 tract_area = num_patches[0]*num_patches[1]*patch_area
692 nside_coverage_tract = 32
693 while hp.nside2pixarea(nside_coverage_tract, degrees=
True) > tract_area:
694 nside_coverage_tract = 2*nside_coverage_tract
696 nside_coverage_tract = int(np.clip(nside_coverage_tract/2, 32,
None))
698 return nside_coverage_tract
701 class ConsolidateHealSparsePropertyMapConnections(pipeBase.PipelineTaskConnections,
702 dimensions=(
"band",
"skymap",),
703 defaultTemplates={
"coaddName":
"deep"}):
704 sky_map = pipeBase.connectionTypes.Input(
705 doc=
"Input definition of geometry/bbox and projection/wcs for coadded exposures",
706 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
707 storageClass=
"SkyMap",
708 dimensions=(
"skymap",),
716 for name
in BasePropertyMap.registry:
717 vars()[f
"{name}_map_min"] = pipeBase.connectionTypes.Input(
718 doc=f
"Minimum-value map of {name}",
719 name=f
"{{coaddName}}Coadd_{name}_map_min",
720 storageClass=
"HealSparseMap",
721 dimensions=(
"tract",
"skymap",
"band"),
725 vars()[f
"{name}_consolidated_map_min"] = pipeBase.connectionTypes.Output(
726 doc=f
"Minumum-value map of {name}",
727 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_min",
728 storageClass=
"HealSparseMap",
729 dimensions=(
"skymap",
"band"),
731 vars()[f
"{name}_map_max"] = pipeBase.connectionTypes.Input(
732 doc=f
"Maximum-value map of {name}",
733 name=f
"{{coaddName}}Coadd_{name}_map_max",
734 storageClass=
"HealSparseMap",
735 dimensions=(
"tract",
"skymap",
"band"),
739 vars()[f
"{name}_consolidated_map_max"] = pipeBase.connectionTypes.Output(
740 doc=f
"Minumum-value map of {name}",
741 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_max",
742 storageClass=
"HealSparseMap",
743 dimensions=(
"skymap",
"band"),
745 vars()[f
"{name}_map_mean"] = pipeBase.connectionTypes.Input(
746 doc=f
"Mean-value map of {name}",
747 name=f
"{{coaddName}}Coadd_{name}_map_mean",
748 storageClass=
"HealSparseMap",
749 dimensions=(
"tract",
"skymap",
"band"),
753 vars()[f
"{name}_consolidated_map_mean"] = pipeBase.connectionTypes.Output(
754 doc=f
"Minumum-value map of {name}",
755 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_mean",
756 storageClass=
"HealSparseMap",
757 dimensions=(
"skymap",
"band"),
759 vars()[f
"{name}_map_weighted_mean"] = pipeBase.connectionTypes.Input(
760 doc=f
"Weighted mean-value map of {name}",
761 name=f
"{{coaddName}}Coadd_{name}_map_weighted_mean",
762 storageClass=
"HealSparseMap",
763 dimensions=(
"tract",
"skymap",
"band"),
767 vars()[f
"{name}_consolidated_map_weighted_mean"] = pipeBase.connectionTypes.Output(
768 doc=f
"Minumum-value map of {name}",
769 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_weighted_mean",
770 storageClass=
"HealSparseMap",
771 dimensions=(
"skymap",
"band"),
773 vars()[f
"{name}_map_sum"] = pipeBase.connectionTypes.Input(
774 doc=f
"Sum-value map of {name}",
775 name=f
"{{coaddName}}Coadd_{name}_map_sum",
776 storageClass=
"HealSparseMap",
777 dimensions=(
"tract",
"skymap",
"band"),
781 vars()[f
"{name}_consolidated_map_sum"] = pipeBase.connectionTypes.Output(
782 doc=f
"Minumum-value map of {name}",
783 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_sum",
784 storageClass=
"HealSparseMap",
785 dimensions=(
"skymap",
"band"),
788 def __init__(self, *, config=None):
789 super().__init__(config=config)
793 for name
in BasePropertyMap.registry:
794 if name
not in config.property_maps:
796 prop_config.do_min =
False
797 prop_config.do_max =
False
798 prop_config.do_mean =
False
799 prop_config.do_weighted_mean =
False
800 prop_config.do_sum =
False
802 prop_config = config.property_maps[name]
804 if not prop_config.do_min:
805 self.inputs.remove(f
"{name}_map_min")
806 self.outputs.remove(f
"{name}_consolidated_map_min")
807 if not prop_config.do_max:
808 self.inputs.remove(f
"{name}_map_max")
809 self.outputs.remove(f
"{name}_consolidated_map_max")
810 if not prop_config.do_mean:
811 self.inputs.remove(f
"{name}_map_mean")
812 self.outputs.remove(f
"{name}_consolidated_map_mean")
813 if not prop_config.do_weighted_mean:
814 self.inputs.remove(f
"{name}_map_weighted_mean")
815 self.outputs.remove(f
"{name}_consolidated_map_weighted_mean")
816 if not prop_config.do_sum:
817 self.inputs.remove(f
"{name}_map_sum")
818 self.outputs.remove(f
"{name}_consolidated_map_sum")
821 class ConsolidateHealSparsePropertyMapConfig(pipeBase.PipelineTaskConfig,
822 pipelineConnections=ConsolidateHealSparsePropertyMapConnections):
823 """Configuration parameters for ConsolidateHealSparsePropertyMapTask"""
824 property_maps = BasePropertyMap.registry.makeField(
826 default=[
"exposure_time",
837 doc=
"Property map computation objects",
841 self.property_maps[
"exposure_time"].do_sum =
True
842 self.property_maps[
"psf_size"].do_weighted_mean =
True
843 self.property_maps[
"psf_e1"].do_weighted_mean =
True
844 self.property_maps[
"psf_e2"].do_weighted_mean =
True
845 self.property_maps[
"psf_maglim"].do_weighted_mean =
True
846 self.property_maps[
"sky_noise"].do_weighted_mean =
True
847 self.property_maps[
"sky_background"].do_weighted_mean =
True
848 self.property_maps[
"dcr_dra"].do_weighted_mean =
True
849 self.property_maps[
"dcr_ddec"].do_weighted_mean =
True
850 self.property_maps[
"dcr_e1"].do_weighted_mean =
True
851 self.property_maps[
"dcr_e2"].do_weighted_mean =
True
854 class ConsolidateHealSparsePropertyMapTask(pipeBase.PipelineTask):
855 """Task to consolidate HealSparse property maps.
857 This task will take all the individual tract-based maps (per map type,
858 per band) and consolidate them into one survey-wide map (per map type,
859 per band). Each tract map is truncated to its inner region before
862 ConfigClass = ConsolidateHealSparsePropertyMapConfig
863 _DefaultName =
"consolidateHealSparsePropertyMapTask"
865 def __init__(self, **kwargs):
866 super().__init__(**kwargs)
868 for name, config, PropertyMapClass
in self.config.property_maps.apply():
869 self.property_maps[name] = PropertyMapClass(config, name)
872 def runQuantum(self, butlerQC, inputRefs, outputRefs):
873 inputs = butlerQC.get(inputRefs)
875 sky_map = inputs.pop(
"sky_map")
878 for name
in self.config.property_maps.names:
879 for type_
in [
'min',
'max',
'mean',
'weighted_mean',
'sum']:
880 map_type = f
"{name}_map_{type_}"
881 if map_type
in inputs:
882 input_refs = {ref.dataId[
'tract']: ref
883 for ref
in inputs[map_type]}
884 consolidated_map = self.consolidate_map(sky_map, input_refs)
885 butlerQC.put(consolidated_map,
886 getattr(outputRefs, f
"{name}_consolidated_map_{type_}"))
888 def consolidate_map(self, sky_map, input_refs):
889 """Consolidate the healsparse property maps.
893 sky_map : Sky map object
894 input_refs : `dict` [`int`: `lsst.daf.butler.DeferredDatasetHandle`]
895 Dictionary of tract_id mapping to dataref.
899 consolidated_map : `healsparse.HealSparseMap`
900 Consolidated HealSparse map.
905 for tract_id
in input_refs:
906 cov = input_refs[tract_id].get(component=
'coverage')
908 cov_mask = cov.coverage_mask
910 cov_mask |= cov.coverage_mask
912 cov_pix, = np.where(cov_mask)
915 consolidated_map =
None
916 for tract_id
in input_refs:
917 input_map = input_refs[tract_id].get()
918 if consolidated_map
is None:
919 dtype = input_map.dtype
920 sentinel = input_map._sentinel
921 nside_coverage = input_map.nside_coverage
922 nside_sparse = input_map.nside_sparse
923 consolidated_map = hsp.HealSparseMap.make_empty(nside_coverage,
927 consolidated_map._reserve_cov_pix(cov_pix)
930 vpix, ra, dec = input_map.valid_pixels_pos(return_pixels=
True)
931 vpix_tract_ids = sky_map.findTractIdArray(ra, dec, degrees=
True)
933 in_tract = (vpix_tract_ids == tract_id)
935 consolidated_map[vpix[in_tract]] = input_map[vpix[in_tract]]
937 return consolidated_map
std::vector< SchemaItem< Flag > > * items
A floating-point coordinate rectangle geometry.
LonLat represents a spherical coordinate (longitude/latitude angle) pair.
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
def run(self, coaddExposures, bbox, wcs)
def compute_approx_psf_size_and_shape(ccd_row, ra, dec, nx=20, ny=20, orderx=2, ordery=2)