22__all__ = [
"HealSparseInputMapTask",
"HealSparseInputMapConfig",
23 "HealSparseMapFormatter",
"HealSparsePropertyMapConnections",
24 "HealSparsePropertyMapConfig",
"HealSparsePropertyMapTask",
25 "ConsolidateHealSparsePropertyMapConnections",
26 "ConsolidateHealSparsePropertyMapConfig",
27 "ConsolidateHealSparsePropertyMapTask"]
29from collections
import defaultdict
34import healsparse
as hsp
40from lsst.daf.butler
import Formatter
42from lsst.utils.timer
import timeMethod
43from .healSparseMappingProperties
import (BasePropertyMap, BasePropertyMapConfig,
44 PropertyMapMap, compute_approx_psf_size_and_shape)
48 """Interface for reading and writing healsparse.HealSparseMap files."""
49 unsupportedParameters = frozenset()
50 supportedExtensions = frozenset({
".hsp",
".fit",
".fits"})
53 def read(self, component=None):
55 path = self.fileDescriptor.location.path
57 if component ==
'coverage':
59 data = hsp.HealSparseCoverage.read(path)
60 except (OSError, RuntimeError):
61 raise ValueError(f
"Unable to read healsparse map with URI {self.fileDescriptor.location.uri}")
65 if self.fileDescriptor.parameters
is None:
69 pixels = self.fileDescriptor.parameters.get(
'pixels',
None)
70 degrade_nside = self.fileDescriptor.parameters.get(
'degrade_nside',
None)
72 data = hsp.HealSparseMap.read(path, pixels=pixels, degrade_nside=degrade_nside)
73 except (OSError, RuntimeError):
74 raise ValueError(f
"Unable to read healsparse map with URI {self.fileDescriptor.location.uri}")
78 def write(self, inMemoryDataset):
81 self.fileDescriptor.location.updateExtension(self.
extension)
82 inMemoryDataset.write(self.fileDescriptor.location.path, clobber=
True)
85def _is_power_of_two(value):
86 """Check that value is a power of two.
95 is_power_of_two : `bool`
96 True if value
is a power of two;
False otherwise,
or
97 if value
is not an integer.
99 if not isinstance(value, numbers.Integral):
106 return (value & (value - 1) == 0)
and value != 0
110 """Configuration parameters for HealSparseInputMapTask"""
111 nside = pexConfig.Field(
112 doc=
"Mapping healpix nside. Must be power of 2.",
115 check=_is_power_of_two,
117 nside_coverage = pexConfig.Field(
118 doc=
"HealSparse coverage map nside. Must be power of 2.",
121 check=_is_power_of_two,
123 bad_mask_min_coverage = pexConfig.Field(
124 doc=(
"Minimum area fraction of a map healpixel pixel that must be "
125 "covered by bad pixels to be removed from the input map. "
126 "This is approximate."),
133 """Task for making a HealSparse input map."""
135 ConfigClass = HealSparseInputMapConfig
136 _DefaultName =
"healSparseInputMap"
139 pipeBase.Task.__init__(self, **kwargs)
144 """Build a map from ccd valid polygons or bounding boxes.
149 Bounding box for region to build input map.
151 WCS object
for region to build input map.
153 Exposure catalog
with ccd data
from coadd inputs.
155 with warnings.catch_warnings():
160 warnings.simplefilter(
"ignore")
161 self.
ccd_input_map = hsp.HealSparseMap.make_empty(nside_coverage=self.config.nside_coverage,
162 nside_sparse=self.config.nside,
164 wide_mask_maxbits=len(ccds))
169 pixel_scale = wcs.getPixelScale().asArcseconds()
170 hpix_area_arcsec2 = hpg.nside_to_pixel_area(self.config.nside, degrees=
True)*(3600.**2.)
171 self.
_min_bad = self.config.bad_mask_min_coverage*hpix_area_arcsec2/(pixel_scale**2.)
176 for bit, ccd_row
in enumerate(ccds):
177 metadata[f
"B{bit:04d}CCD"] = ccd_row[
"ccd"]
178 metadata[f
"B{bit:04d}VIS"] = ccd_row[
"visit"]
179 metadata[f
"B{bit:04d}WT"] = ccd_row[
"weight"]
184 ccd_poly = ccd_row.getValidPolygon()
188 ccd_poly_radec = self.
_pixels_to_radec(ccd_row.getWcs(), ccd_poly.convexHull().getVertices())
191 poly = hsp.Polygon(ra=ccd_poly_radec[: -1, 0],
192 dec=ccd_poly_radec[: -1, 1],
200 bbox_afw_poly.convexHull().getVertices())
201 bbox_poly = hsp.Polygon(ra=bbox_poly_radec[: -1, 0], dec=bbox_poly_radec[: -1, 1],
210 dtype = [(f
"v{visit}", np.int64)
for visit
in self.
_bits_per_visit.keys()]
212 with warnings.catch_warnings():
217 warnings.simplefilter(
"ignore")
219 nside_coverage=self.config.nside_coverage,
220 nside_sparse=self.config.nside,
229 """Mask a subregion from a visit.
230 This must be run after build_ccd_input_map initializes
236 Bounding box from region to mask.
238 Visit number corresponding to warp
with mask.
239 mask : `lsst.afw.image.MaskX`
240 Mask plane
from warp exposure.
241 bit_mask_value : `int`
242 Bit mask to check
for bad pixels.
246 RuntimeError : Raised
if build_ccd_input_map was
not run first.
249 raise RuntimeError(
"Must run build_ccd_input_map before mask_warp_bbox")
252 bad_pixels = np.where(mask.array & bit_mask_value)
253 if len(bad_pixels[0]) == 0:
258 bad_ra, bad_dec = self.
_wcs.pixelToSkyArray(bad_pixels[1].astype(np.float64),
259 bad_pixels[0].astype(np.float64),
261 bad_hpix = hpg.angle_to_pixel(self.config.nside, bad_ra, bad_dec)
264 min_bad_hpix = bad_hpix.min()
265 bad_hpix_count = np.zeros(bad_hpix.max() - min_bad_hpix + 1, dtype=np.int32)
266 np.add.at(bad_hpix_count, bad_hpix - min_bad_hpix, 1)
271 pix_to_add, = np.where(bad_hpix_count > 0)
274 count_map_arr[primary] = np.clip(count_map_arr[primary], 0,
None)
276 count_map_arr[f
"v{visit}"] = np.clip(count_map_arr[f
"v{visit}"], 0,
None)
277 count_map_arr[f
"v{visit}"] += bad_hpix_count[pix_to_add]
282 """Use accumulated mask information to finalize the masking of
287 RuntimeError : Raised if build_ccd_input_map was
not run first.
290 raise RuntimeError(
"Must run build_ccd_input_map before finalize_ccd_input_map_mask.")
294 to_mask, = np.where(count_map_arr[f
"v{visit}"] > self.
_min_bad)
295 if to_mask.size == 0:
303 def _pixels_to_radec(self, wcs, pixels):
304 """Convert pixels to ra/dec positions using a wcs.
311 List of pixels to convert.
315 radec : `numpy.ndarray`
316 Nx2 array of ra/dec positions associated with pixels.
318 sph_pts = wcs.pixelToSky(pixels)
319 return np.array([(sph.getRa().asDegrees(), sph.getDec().asDegrees())
324 dimensions=(
"tract",
"band",
"skymap",),
325 defaultTemplates={
"coaddName":
"deep",
327 input_maps = pipeBase.connectionTypes.Input(
328 doc=
"Healsparse bit-wise coadd input maps",
329 name=
"{coaddName}Coadd_inputMap",
330 storageClass=
"HealSparseMap",
331 dimensions=(
"tract",
"patch",
"skymap",
"band"),
335 coadd_exposures = pipeBase.connectionTypes.Input(
336 doc=
"Coadded exposures associated with input_maps",
337 name=
"{coaddName}Coadd",
338 storageClass=
"ExposureF",
339 dimensions=(
"tract",
"patch",
"skymap",
"band"),
343 visit_summaries = pipeBase.connectionTypes.Input(
344 doc=
"Visit summary tables with aggregated statistics",
345 name=
"{calexpType}visitSummary",
346 storageClass=
"ExposureCatalog",
347 dimensions=(
"instrument",
"visit"),
351 sky_map = pipeBase.connectionTypes.Input(
352 doc=
"Input definition of geometry/bbox and projection/wcs for coadded exposures",
353 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
354 storageClass=
"SkyMap",
355 dimensions=(
"skymap",),
363 for name
in BasePropertyMap.registry:
364 vars()[f
"{name}_map_min"] = pipeBase.connectionTypes.Output(
365 doc=f
"Minimum-value map of {name}",
366 name=f
"{{coaddName}}Coadd_{name}_map_min",
367 storageClass=
"HealSparseMap",
368 dimensions=(
"tract",
"skymap",
"band"),
370 vars()[f
"{name}_map_max"] = pipeBase.connectionTypes.Output(
371 doc=f
"Maximum-value map of {name}",
372 name=f
"{{coaddName}}Coadd_{name}_map_max",
373 storageClass=
"HealSparseMap",
374 dimensions=(
"tract",
"skymap",
"band"),
376 vars()[f
"{name}_map_mean"] = pipeBase.connectionTypes.Output(
377 doc=f
"Mean-value map of {name}",
378 name=f
"{{coaddName}}Coadd_{name}_map_mean",
379 storageClass=
"HealSparseMap",
380 dimensions=(
"tract",
"skymap",
"band"),
382 vars()[f
"{name}_map_weighted_mean"] = pipeBase.connectionTypes.Output(
383 doc=f
"Weighted mean-value map of {name}",
384 name=f
"{{coaddName}}Coadd_{name}_map_weighted_mean",
385 storageClass=
"HealSparseMap",
386 dimensions=(
"tract",
"skymap",
"band"),
388 vars()[f
"{name}_map_sum"] = pipeBase.connectionTypes.Output(
389 doc=f
"Sum-value map of {name}",
390 name=f
"{{coaddName}}Coadd_{name}_map_sum",
391 storageClass=
"HealSparseMap",
392 dimensions=(
"tract",
"skymap",
"band"),
395 def __init__(self, *, config=None):
396 super().__init__(config=config)
400 for name
in BasePropertyMap.registry:
401 if name
not in config.property_maps:
403 prop_config.do_min =
False
404 prop_config.do_max =
False
405 prop_config.do_mean =
False
406 prop_config.do_weighted_mean =
False
407 prop_config.do_sum =
False
409 prop_config = config.property_maps[name]
411 if not prop_config.do_min:
412 self.outputs.remove(f
"{name}_map_min")
413 if not prop_config.do_max:
414 self.outputs.remove(f
"{name}_map_max")
415 if not prop_config.do_mean:
416 self.outputs.remove(f
"{name}_map_mean")
417 if not prop_config.do_weighted_mean:
418 self.outputs.remove(f
"{name}_map_weighted_mean")
419 if not prop_config.do_sum:
420 self.outputs.remove(f
"{name}_map_sum")
423class HealSparsePropertyMapConfig(pipeBase.PipelineTaskConfig,
424 pipelineConnections=HealSparsePropertyMapConnections):
425 """Configuration parameters for HealSparsePropertyMapTask"""
426 property_maps = BasePropertyMap.registry.makeField(
428 default=[
"exposure_time",
440 doc=
"Property map computation objects",
443 def setDefaults(self):
444 self.property_maps[
"exposure_time"].do_sum =
True
445 self.property_maps[
"psf_size"].do_weighted_mean =
True
446 self.property_maps[
"psf_e1"].do_weighted_mean =
True
447 self.property_maps[
"psf_e2"].do_weighted_mean =
True
448 self.property_maps[
"psf_maglim"].do_weighted_mean =
True
449 self.property_maps[
"sky_noise"].do_weighted_mean =
True
450 self.property_maps[
"sky_background"].do_weighted_mean =
True
451 self.property_maps[
"dcr_dra"].do_weighted_mean =
True
452 self.property_maps[
"dcr_ddec"].do_weighted_mean =
True
453 self.property_maps[
"dcr_e1"].do_weighted_mean =
True
454 self.property_maps[
"dcr_e2"].do_weighted_mean =
True
455 self.property_maps[
"epoch"].do_mean =
True
456 self.property_maps[
"epoch"].do_min =
True
457 self.property_maps[
"epoch"].do_max =
True
460class HealSparsePropertyMapTask(pipeBase.PipelineTask):
461 """Task to compute Healsparse property maps.
463 This task will compute individual property maps (per tract, per
464 map type, per band). These maps cover the full coadd tract, and
465 are
not truncated to the inner tract region.
467 ConfigClass = HealSparsePropertyMapConfig
468 _DefaultName = "healSparsePropertyMapTask"
470 def __init__(self, **kwargs):
471 super().__init__(**kwargs)
473 for name, config, PropertyMapClass
in self.config.property_maps.apply():
474 self.property_maps[name] = PropertyMapClass(config, name)
477 def runQuantum(self, butlerQC, inputRefs, outputRefs):
478 inputs = butlerQC.get(inputRefs)
480 sky_map = inputs.pop(
"sky_map")
482 tract = butlerQC.quantum.dataId[
"tract"]
483 band = butlerQC.quantum.dataId[
"band"]
485 input_map_dict = {ref.dataId[
"patch"]: ref
for ref
in inputs[
"input_maps"]}
486 coadd_dict = {ref.dataId[
"patch"]: ref
for ref
in inputs[
"coadd_exposures"]}
488 visit_summary_dict = {ref.dataId[
"visit"]: ref.get()
489 for ref
in inputs[
"visit_summaries"]}
491 self.run(sky_map, tract, band, coadd_dict, input_map_dict, visit_summary_dict)
494 for name, property_map
in self.property_maps.
items():
495 if property_map.config.do_min:
496 butlerQC.put(property_map.min_map,
497 getattr(outputRefs, f
"{name}_map_min"))
498 if property_map.config.do_max:
499 butlerQC.put(property_map.max_map,
500 getattr(outputRefs, f
"{name}_map_max"))
501 if property_map.config.do_mean:
502 butlerQC.put(property_map.mean_map,
503 getattr(outputRefs, f
"{name}_map_mean"))
504 if property_map.config.do_weighted_mean:
505 butlerQC.put(property_map.weighted_mean_map,
506 getattr(outputRefs, f
"{name}_map_weighted_mean"))
507 if property_map.config.do_sum:
508 butlerQC.put(property_map.sum_map,
509 getattr(outputRefs, f
"{name}_map_sum"))
511 def run(self, sky_map, tract, band, coadd_dict, input_map_dict, visit_summary_dict):
512 """Run the healsparse property task.
516 sky_map : Sky map object
520 Band name for logging.
521 coadd_dict : `dict` [`int`: `lsst.daf.butler.DeferredDatasetHandle`]
522 Dictionary of coadd exposure references. Keys are patch numbers.
523 input_map_dict : `dict` [`int`: `lsst.daf.butler.DeferredDatasetHandle`]
524 Dictionary of input map references. Keys are patch numbers.
526 Dictionary of visit summary tables. Keys are visit numbers.
530 RepeatableQuantumError
531 If visit_summary_dict
is missing any visits
or detectors found
in an
532 input map. This leads to an inconsistency between what
is in the coadd
533 (via the input map)
and the visit summary tables which contain data
536 tract_info = sky_map[tract]
538 tract_maps_initialized = False
540 for patch
in input_map_dict.keys():
541 self.log.info(
"Making maps for band %s, tract %d, patch %d.",
544 patch_info = tract_info[patch]
546 input_map = input_map_dict[patch].get()
547 coadd_photo_calib = coadd_dict[patch].get(component=
"photoCalib")
548 coadd_inputs = coadd_dict[patch].get(component=
"coaddInputs")
550 coadd_zeropoint = 2.5*np.log10(coadd_photo_calib.getInstFluxAtZeroMagnitude())
553 poly_vertices = patch_info.getInnerSkyPolygon(tract_info.getWcs()).getVertices()
554 patch_radec = self._vertices_to_radec(poly_vertices)
555 patch_poly = hsp.Polygon(ra=patch_radec[:, 0], dec=patch_radec[:, 1],
556 value=np.arange(input_map.wide_mask_maxbits))
557 with warnings.catch_warnings():
562 warnings.simplefilter(
"ignore")
563 patch_poly_map = patch_poly.get_map_like(input_map)
564 input_map = hsp.and_intersection([input_map, patch_poly_map])
566 if not tract_maps_initialized:
569 nside_coverage = self._compute_nside_coverage_tract(tract_info)
570 nside = input_map.nside_sparse
572 do_compute_approx_psf =
False
574 for property_map
in self.property_maps:
575 property_map.initialize_tract_maps(nside_coverage, nside)
576 if property_map.requires_psf:
577 do_compute_approx_psf =
True
579 tract_maps_initialized =
True
581 valid_pixels, vpix_ra, vpix_dec = input_map.valid_pixels_pos(return_pixels=
True)
584 if valid_pixels.size == 0:
588 for property_map
in self.property_maps:
589 property_map.initialize_values(valid_pixels.size)
590 property_map.zeropoint = coadd_zeropoint
593 total_weights = np.zeros(valid_pixels.size)
594 total_inputs = np.zeros(valid_pixels.size, dtype=np.int32)
596 for bit, ccd_row
in enumerate(coadd_inputs.ccds):
598 inmap, = np.where(input_map.check_bits_pix(valid_pixels, [bit]))
605 visit = ccd_row[
"visit"]
606 detector_id = ccd_row[
"ccd"]
607 weight = ccd_row[
"weight"]
609 x, y = ccd_row.getWcs().skyToPixelArray(vpix_ra[inmap], vpix_dec[inmap], degrees=
True)
610 scalings = self._compute_calib_scale(ccd_row, x, y)
612 if do_compute_approx_psf:
613 psf_array = compute_approx_psf_size_and_shape(ccd_row, vpix_ra[inmap], vpix_dec[inmap])
617 total_weights[inmap] += weight
618 total_inputs[inmap] += 1
621 if visit
not in visit_summary_dict:
622 msg = f
"Visit {visit} not found in visit_summaries."
623 raise pipeBase.RepeatableQuantumError(msg)
624 row = visit_summary_dict[visit].find(detector_id)
626 msg = f
"Visit {visit} / detector_id {detector_id} not found in visit_summaries."
627 raise pipeBase.RepeatableQuantumError(msg)
630 for property_map
in self.property_maps:
631 property_map.accumulate_values(inmap,
640 for property_map
in self.property_maps:
641 property_map.finalize_mean_values(total_weights, total_inputs)
642 property_map.set_map_values(valid_pixels)
644 def _compute_calib_scale(self, ccd_row, x, y):
645 """Compute calibration scaling values.
650 Exposure metadata for a given detector exposure.
652 Array of x positions.
654 Array of y positions.
658 calib_scale : `np.ndarray`
659 Array of calibration scale values.
661 photo_calib = ccd_row.getPhotoCalib()
662 bf = photo_calib.computeScaledCalibration()
663 if bf.getBBox() == ccd_row.getBBox():
665 calib_scale = photo_calib.getCalibrationMean()*bf.evaluate(x, y)
668 calib_scale = photo_calib.getCalibrationMean()
672 def _vertices_to_radec(self, vertices):
673 """Convert polygon vertices to ra/dec.
678 Vertices for bounding polygon.
682 radec : `numpy.ndarray`
683 Nx2 array of ra/dec positions (
in degrees) associated
with vertices.
686 radec = np.array([(x.getLon().asDegrees(), x.getLat().asDegrees())
for
690 def _compute_nside_coverage_tract(self, tract_info):
691 """Compute the optimal coverage nside for a tract.
696 Tract information object.
700 nside_coverage : `int`
701 Optimal coverage nside for a tract map.
703 num_patches = tract_info.getNumPatches()
706 patch_info = tract_info.getPatchInfo(0)
707 vertices = patch_info.getInnerSkyPolygon(tract_info.getWcs()).getVertices()
708 radec = self._vertices_to_radec(vertices)
709 delta_ra = np.max(radec[:, 0]) - np.min(radec[:, 0])
710 delta_dec = np.max(radec[:, 1]) - np.min(radec[:, 1])
711 patch_area = delta_ra*delta_dec*np.cos(np.deg2rad(np.mean(radec[:, 1])))
713 tract_area = num_patches[0]*num_patches[1]*patch_area
715 nside_coverage_tract = 32
716 while hpg.nside_to_pixel_area(nside_coverage_tract, degrees=
True) > tract_area:
717 nside_coverage_tract = 2*nside_coverage_tract
720 nside_coverage_tract = int(np.clip(nside_coverage_tract/2, 32, 128))
722 return nside_coverage_tract
725class ConsolidateHealSparsePropertyMapConnections(pipeBase.PipelineTaskConnections,
726 dimensions=(
"band",
"skymap",),
727 defaultTemplates={
"coaddName":
"deep"}):
728 sky_map = pipeBase.connectionTypes.Input(
729 doc=
"Input definition of geometry/bbox and projection/wcs for coadded exposures",
730 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
731 storageClass=
"SkyMap",
732 dimensions=(
"skymap",),
740 for name
in BasePropertyMap.registry:
741 vars()[f
"{name}_map_min"] = pipeBase.connectionTypes.Input(
742 doc=f
"Minimum-value map of {name}",
743 name=f
"{{coaddName}}Coadd_{name}_map_min",
744 storageClass=
"HealSparseMap",
745 dimensions=(
"tract",
"skymap",
"band"),
749 vars()[f
"{name}_consolidated_map_min"] = pipeBase.connectionTypes.Output(
750 doc=f
"Minumum-value map of {name}",
751 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_min",
752 storageClass=
"HealSparseMap",
753 dimensions=(
"skymap",
"band"),
755 vars()[f
"{name}_map_max"] = pipeBase.connectionTypes.Input(
756 doc=f
"Maximum-value map of {name}",
757 name=f
"{{coaddName}}Coadd_{name}_map_max",
758 storageClass=
"HealSparseMap",
759 dimensions=(
"tract",
"skymap",
"band"),
763 vars()[f
"{name}_consolidated_map_max"] = pipeBase.connectionTypes.Output(
764 doc=f
"Minumum-value map of {name}",
765 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_max",
766 storageClass=
"HealSparseMap",
767 dimensions=(
"skymap",
"band"),
769 vars()[f
"{name}_map_mean"] = pipeBase.connectionTypes.Input(
770 doc=f
"Mean-value map of {name}",
771 name=f
"{{coaddName}}Coadd_{name}_map_mean",
772 storageClass=
"HealSparseMap",
773 dimensions=(
"tract",
"skymap",
"band"),
777 vars()[f
"{name}_consolidated_map_mean"] = pipeBase.connectionTypes.Output(
778 doc=f
"Minumum-value map of {name}",
779 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_mean",
780 storageClass=
"HealSparseMap",
781 dimensions=(
"skymap",
"band"),
783 vars()[f
"{name}_map_weighted_mean"] = pipeBase.connectionTypes.Input(
784 doc=f
"Weighted mean-value map of {name}",
785 name=f
"{{coaddName}}Coadd_{name}_map_weighted_mean",
786 storageClass=
"HealSparseMap",
787 dimensions=(
"tract",
"skymap",
"band"),
791 vars()[f
"{name}_consolidated_map_weighted_mean"] = pipeBase.connectionTypes.Output(
792 doc=f
"Minumum-value map of {name}",
793 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_weighted_mean",
794 storageClass=
"HealSparseMap",
795 dimensions=(
"skymap",
"band"),
797 vars()[f
"{name}_map_sum"] = pipeBase.connectionTypes.Input(
798 doc=f
"Sum-value map of {name}",
799 name=f
"{{coaddName}}Coadd_{name}_map_sum",
800 storageClass=
"HealSparseMap",
801 dimensions=(
"tract",
"skymap",
"band"),
805 vars()[f
"{name}_consolidated_map_sum"] = pipeBase.connectionTypes.Output(
806 doc=f
"Minumum-value map of {name}",
807 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_sum",
808 storageClass=
"HealSparseMap",
809 dimensions=(
"skymap",
"band"),
812 def __init__(self, *, config=None):
813 super().__init__(config=config)
817 for name
in BasePropertyMap.registry:
818 if name
not in config.property_maps:
820 prop_config.do_min =
False
821 prop_config.do_max =
False
822 prop_config.do_mean =
False
823 prop_config.do_weighted_mean =
False
824 prop_config.do_sum =
False
826 prop_config = config.property_maps[name]
828 if not prop_config.do_min:
829 self.inputs.remove(f
"{name}_map_min")
830 self.outputs.remove(f
"{name}_consolidated_map_min")
831 if not prop_config.do_max:
832 self.inputs.remove(f
"{name}_map_max")
833 self.outputs.remove(f
"{name}_consolidated_map_max")
834 if not prop_config.do_mean:
835 self.inputs.remove(f
"{name}_map_mean")
836 self.outputs.remove(f
"{name}_consolidated_map_mean")
837 if not prop_config.do_weighted_mean:
838 self.inputs.remove(f
"{name}_map_weighted_mean")
839 self.outputs.remove(f
"{name}_consolidated_map_weighted_mean")
840 if not prop_config.do_sum:
841 self.inputs.remove(f
"{name}_map_sum")
842 self.outputs.remove(f
"{name}_consolidated_map_sum")
845class ConsolidateHealSparsePropertyMapConfig(pipeBase.PipelineTaskConfig,
846 pipelineConnections=ConsolidateHealSparsePropertyMapConnections):
847 """Configuration parameters for ConsolidateHealSparsePropertyMapTask"""
848 property_maps = BasePropertyMap.registry.makeField(
850 default=[
"exposure_time",
862 doc=
"Property map computation objects",
864 nside_coverage = pexConfig.Field(
865 doc=
"Consolidated HealSparse coverage map nside. Must be power of 2.",
868 check=_is_power_of_two,
871 def setDefaults(self):
872 self.property_maps[
"exposure_time"].do_sum =
True
873 self.property_maps[
"psf_size"].do_weighted_mean =
True
874 self.property_maps[
"psf_e1"].do_weighted_mean =
True
875 self.property_maps[
"psf_e2"].do_weighted_mean =
True
876 self.property_maps[
"psf_maglim"].do_weighted_mean =
True
877 self.property_maps[
"sky_noise"].do_weighted_mean =
True
878 self.property_maps[
"sky_background"].do_weighted_mean =
True
879 self.property_maps[
"dcr_dra"].do_weighted_mean =
True
880 self.property_maps[
"dcr_ddec"].do_weighted_mean =
True
881 self.property_maps[
"dcr_e1"].do_weighted_mean =
True
882 self.property_maps[
"dcr_e2"].do_weighted_mean =
True
883 self.property_maps[
"epoch"].do_mean =
True
884 self.property_maps[
"epoch"].do_min =
True
885 self.property_maps[
"epoch"].do_max =
True
888class ConsolidateHealSparsePropertyMapTask(pipeBase.PipelineTask):
889 """Task to consolidate HealSparse property maps.
891 This task will take all the individual tract-based maps (per map type,
892 per band) and consolidate them into one survey-wide map (per map type,
893 per band). Each tract map
is truncated to its inner region before
896 ConfigClass = ConsolidateHealSparsePropertyMapConfig
897 _DefaultName = "consolidateHealSparsePropertyMapTask"
899 def __init__(self, **kwargs):
900 super().__init__(**kwargs)
902 for name, config, PropertyMapClass
in self.config.property_maps.apply():
903 self.property_maps[name] = PropertyMapClass(config, name)
906 def runQuantum(self, butlerQC, inputRefs, outputRefs):
907 inputs = butlerQC.get(inputRefs)
909 sky_map = inputs.pop(
"sky_map")
912 for name
in self.config.property_maps.names:
913 for type_
in [
'min',
'max',
'mean',
'weighted_mean',
'sum']:
914 map_type = f
"{name}_map_{type_}"
915 if map_type
in inputs:
916 input_refs = {ref.dataId[
'tract']: ref
917 for ref
in inputs[map_type]}
918 consolidated_map = self.consolidate_map(sky_map, input_refs)
919 butlerQC.put(consolidated_map,
920 getattr(outputRefs, f
"{name}_consolidated_map_{type_}"))
922 def consolidate_map(self, sky_map, input_refs):
923 """Consolidate the healsparse property maps.
927 sky_map : Sky map object
928 input_refs : `dict` [`int`: `lsst.daf.butler.DeferredDatasetHandle`]
929 Dictionary of tract_id mapping to dataref.
933 consolidated_map : `healsparse.HealSparseMap`
934 Consolidated HealSparse map.
939 nside_coverage_inputs =
None
940 for tract_id
in input_refs:
941 cov = input_refs[tract_id].get(component=
'coverage')
943 cov_mask = cov.coverage_mask
944 nside_coverage_inputs = cov.nside_coverage
946 cov_mask |= cov.coverage_mask
948 cov_pix_inputs, = np.where(cov_mask)
951 if nside_coverage_inputs == self.config.nside_coverage:
952 cov_pix = cov_pix_inputs
953 elif nside_coverage_inputs > self.config.nside_coverage:
956 bit_shift = hsp.utils._compute_bitshift(self.config.nside_coverage,
957 nside_coverage_inputs)
958 cov_pix = np.right_shift(cov_pix_inputs, bit_shift)
962 bit_shift = hsp.utils._compute_bitshift(nside_coverage_inputs,
963 self.config.nside_coverage)
964 cov_pix = np.left_shift(cov_pix_inputs, bit_shift)
967 consolidated_map =
None
968 for tract_id
in input_refs:
969 input_map = input_refs[tract_id].get()
970 if consolidated_map
is None:
971 consolidated_map = hsp.HealSparseMap.make_empty(
972 self.config.nside_coverage,
973 input_map.nside_sparse,
975 sentinel=input_map._sentinel,
979 vpix, ra, dec = input_map.valid_pixels_pos(return_pixels=
True)
980 vpix_tract_ids = sky_map.findTractIdArray(ra, dec, degrees=
True)
982 in_tract = (vpix_tract_ids == tract_id)
984 consolidated_map[vpix[in_tract]] = input_map[vpix[in_tract]]
986 return consolidated_map
std::vector< SchemaItem< Flag > > * items
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
Custom catalog class for ExposureRecord/Table.
Record class used to store exposure metadata.
A floating-point coordinate rectangle geometry.
An integer coordinate rectangle.
LonLat represents a spherical coordinate (longitude/latitude angle) pair.
UnitVector3d is a unit vector in ℝ³ with components stored in double precision.