22 from collections
import defaultdict
26 import healsparse
as hsp
32 from lsst.daf.butler
import Formatter
34 from lsst.utils.timer
import timeMethod
35 from .healSparseMappingProperties
import (BasePropertyMap, BasePropertyMapConfig,
36 PropertyMapMap, compute_approx_psf_size_and_shape)
39 __all__ = [
"HealSparseInputMapTask",
"HealSparseInputMapConfig",
40 "HealSparseMapFormatter",
"HealSparsePropertyMapConnections",
41 "HealSparsePropertyMapConfig",
"HealSparsePropertyMapTask",
42 "ConsolidateHealSparsePropertyMapConnections",
43 "ConsolidateHealSparsePropertyMapConfig",
44 "ConsolidateHealSparsePropertyMapTask"]
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.
extensionextension)
82 inMemoryDataset.write(self.fileDescriptor.location.path, clobber=
True)
85 def _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.
148 bbox : `lsst.geom.Box2I`
149 Bounding box for region to build input map.
150 wcs : `lsst.afw.geom.SkyWcs`
151 WCS object for region to build input map.
152 ccds : `lsst.afw.table.ExposureCatalog`
153 Exposure catalog with ccd data from coadd inputs.
155 self.
ccd_input_mapccd_input_map = hsp.HealSparseMap.make_empty(nside_coverage=self.config.nside_coverage,
156 nside_sparse=self.config.nside,
158 wide_mask_maxbits=len(ccds))
160 self.
_bbox_bbox = bbox
161 self.
_ccds_ccds = ccds
163 pixel_scale = wcs.getPixelScale().asArcseconds()
164 hpix_area_arcsec2 = hp.nside2pixarea(self.config.nside, degrees=
True)*(3600.**2.)
165 self.
_min_bad_min_bad = self.config.bad_mask_min_coverage*hpix_area_arcsec2/(pixel_scale**2.)
170 for bit, ccd_row
in enumerate(ccds):
171 metadata[f
"B{bit:04d}CCD"] = ccd_row[
"ccd"]
172 metadata[f
"B{bit:04d}VIS"] = ccd_row[
"visit"]
173 metadata[f
"B{bit:04d}WT"] = ccd_row[
"weight"]
178 ccd_poly = ccd_row.getValidPolygon()
182 ccd_poly_radec = self.
_pixels_to_radec_pixels_to_radec(ccd_row.getWcs(), ccd_poly.convexHull().getVertices())
185 poly = hsp.Polygon(ra=ccd_poly_radec[: -1, 0],
186 dec=ccd_poly_radec[: -1, 1],
194 bbox_afw_poly.convexHull().getVertices())
195 bbox_poly = hsp.Polygon(ra=bbox_poly_radec[: -1, 0], dec=bbox_poly_radec[: -1, 1],
196 value=np.arange(self.
ccd_input_mapccd_input_map.wide_mask_maxbits))
197 bbox_poly_map = bbox_poly.get_map_like(self.
ccd_input_mapccd_input_map)
204 dtype = [(f
"v{visit}",
"i4")
for visit
in self.
_bits_per_visit_bits_per_visit.keys()]
206 cov = self.config.nside_coverage
207 ns = self.config.nside
217 """Mask a subregion from a visit.
218 This must be run after build_ccd_input_map initializes
223 bbox : `lsst.geom.Box2I`
224 Bounding box from region to mask.
226 Visit number corresponding to warp with mask.
227 mask : `lsst.afw.image.MaskX`
228 Mask plane from warp exposure.
229 bit_mask_value : `int`
230 Bit mask to check for bad pixels.
234 RuntimeError : Raised if build_ccd_input_map was not run first.
237 raise RuntimeError(
"Must run build_ccd_input_map before mask_warp_bbox")
240 bad_pixels = np.where(mask.array & bit_mask_value)
241 if len(bad_pixels[0]) == 0:
246 bad_ra, bad_dec = self.
_wcs_wcs.pixelToSkyArray(bad_pixels[1].astype(np.float64),
247 bad_pixels[0].astype(np.float64),
249 bad_hpix = hp.ang2pix(self.config.nside, bad_ra, bad_dec,
250 lonlat=
True, nest=
True)
253 min_bad_hpix = bad_hpix.min()
254 bad_hpix_count = np.zeros(bad_hpix.max() - min_bad_hpix + 1, dtype=np.int32)
255 np.add.at(bad_hpix_count, bad_hpix - min_bad_hpix, 1)
260 pix_to_add, = np.where(bad_hpix_count > 0)
263 count_map_arr[primary] = np.clip(count_map_arr[primary], 0,
None)
265 count_map_arr[f
"v{visit}"] = np.clip(count_map_arr[f
"v{visit}"], 0,
None)
266 count_map_arr[f
"v{visit}"] += bad_hpix_count[pix_to_add]
271 """Use accumulated mask information to finalize the masking of
276 RuntimeError : Raised if build_ccd_input_map was not run first.
279 raise RuntimeError(
"Must run build_ccd_input_map before finalize_ccd_input_map_mask.")
283 to_mask, = np.where(count_map_arr[f
"v{visit}"] > self.
_min_bad_min_bad)
284 if to_mask.size == 0:
292 def _pixels_to_radec(self, wcs, pixels):
293 """Convert pixels to ra/dec positions using a wcs.
297 wcs : `lsst.afw.geom.SkyWcs`
299 pixels : `list` [`lsst.geom.Point2D`]
300 List of pixels to convert.
304 radec : `numpy.ndarray`
305 Nx2 array of ra/dec positions associated with pixels.
307 sph_pts = wcs.pixelToSky(pixels)
308 return np.array([(sph.getRa().asDegrees(), sph.getDec().asDegrees())
313 dimensions=(
"tract",
"band",
"skymap",),
314 defaultTemplates={
"coaddName":
"deep"}):
315 input_maps = pipeBase.connectionTypes.Input(
316 doc=
"Healsparse bit-wise coadd input maps",
317 name=
"{coaddName}Coadd_inputMap",
318 storageClass=
"HealSparseMap",
319 dimensions=(
"tract",
"patch",
"skymap",
"band"),
323 coadd_exposures = pipeBase.connectionTypes.Input(
324 doc=
"Coadded exposures associated with input_maps",
325 name=
"{coaddName}Coadd",
326 storageClass=
"ExposureF",
327 dimensions=(
"tract",
"patch",
"skymap",
"band"),
331 visit_summaries = pipeBase.connectionTypes.Input(
332 doc=
"Visit summary tables with aggregated statistics",
334 storageClass=
"ExposureCatalog",
335 dimensions=(
"instrument",
"visit"),
339 sky_map = pipeBase.connectionTypes.Input(
340 doc=
"Input definition of geometry/bbox and projection/wcs for coadded exposures",
341 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
342 storageClass=
"SkyMap",
343 dimensions=(
"skymap",),
351 for name
in BasePropertyMap.registry:
352 vars()[f
"{name}_map_min"] = pipeBase.connectionTypes.Output(
353 doc=f
"Minimum-value map of {name}",
354 name=f
"{{coaddName}}Coadd_{name}_map_min",
355 storageClass=
"HealSparseMap",
356 dimensions=(
"tract",
"skymap",
"band"),
358 vars()[f
"{name}_map_max"] = pipeBase.connectionTypes.Output(
359 doc=f
"Maximum-value map of {name}",
360 name=f
"{{coaddName}}Coadd_{name}_map_max",
361 storageClass=
"HealSparseMap",
362 dimensions=(
"tract",
"skymap",
"band"),
364 vars()[f
"{name}_map_mean"] = pipeBase.connectionTypes.Output(
365 doc=f
"Mean-value map of {name}",
366 name=f
"{{coaddName}}Coadd_{name}_map_mean",
367 storageClass=
"HealSparseMap",
368 dimensions=(
"tract",
"skymap",
"band"),
370 vars()[f
"{name}_map_weighted_mean"] = pipeBase.connectionTypes.Output(
371 doc=f
"Weighted mean-value map of {name}",
372 name=f
"{{coaddName}}Coadd_{name}_map_weighted_mean",
373 storageClass=
"HealSparseMap",
374 dimensions=(
"tract",
"skymap",
"band"),
376 vars()[f
"{name}_map_sum"] = pipeBase.connectionTypes.Output(
377 doc=f
"Sum-value map of {name}",
378 name=f
"{{coaddName}}Coadd_{name}_map_sum",
379 storageClass=
"HealSparseMap",
380 dimensions=(
"tract",
"skymap",
"band"),
383 def __init__(self, *, config=None):
384 super().__init__(config=config)
388 for name
in BasePropertyMap.registry:
389 if name
not in config.property_maps:
391 prop_config.do_min =
False
392 prop_config.do_max =
False
393 prop_config.do_mean =
False
394 prop_config.do_weighted_mean =
False
395 prop_config.do_sum =
False
397 prop_config = config.property_maps[name]
399 if not prop_config.do_min:
400 self.outputs.remove(f
"{name}_map_min")
401 if not prop_config.do_max:
402 self.outputs.remove(f
"{name}_map_max")
403 if not prop_config.do_mean:
404 self.outputs.remove(f
"{name}_map_mean")
405 if not prop_config.do_weighted_mean:
406 self.outputs.remove(f
"{name}_map_weighted_mean")
407 if not prop_config.do_sum:
408 self.outputs.remove(f
"{name}_map_sum")
411 class HealSparsePropertyMapConfig(pipeBase.PipelineTaskConfig,
412 pipelineConnections=HealSparsePropertyMapConnections):
413 """Configuration parameters for HealSparsePropertyMapTask"""
414 property_maps = BasePropertyMap.registry.makeField(
416 default=[
"exposure_time",
427 doc=
"Property map computation objects",
430 def setDefaults(self):
431 self.property_maps[
"exposure_time"].do_sum =
True
432 self.property_maps[
"psf_size"].do_weighted_mean =
True
433 self.property_maps[
"psf_e1"].do_weighted_mean =
True
434 self.property_maps[
"psf_e2"].do_weighted_mean =
True
435 self.property_maps[
"psf_maglim"].do_weighted_mean =
True
436 self.property_maps[
"sky_noise"].do_weighted_mean =
True
437 self.property_maps[
"sky_background"].do_weighted_mean =
True
438 self.property_maps[
"dcr_dra"].do_weighted_mean =
True
439 self.property_maps[
"dcr_ddec"].do_weighted_mean =
True
440 self.property_maps[
"dcr_e1"].do_weighted_mean =
True
441 self.property_maps[
"dcr_e2"].do_weighted_mean =
True
444 class HealSparsePropertyMapTask(pipeBase.PipelineTask):
445 """Task to compute Healsparse property maps.
447 This task will compute individual property maps (per tract, per
448 map type, per band). These maps cover the full coadd tract, and
449 are not truncated to the inner tract region.
451 ConfigClass = HealSparsePropertyMapConfig
452 _DefaultName =
"healSparsePropertyMapTask"
454 def __init__(self, **kwargs):
455 super().__init__(**kwargs)
457 for name, config, PropertyMapClass
in self.config.property_maps.apply():
458 self.property_maps[name] = PropertyMapClass(config, name)
461 def runQuantum(self, butlerQC, inputRefs, outputRefs):
462 inputs = butlerQC.get(inputRefs)
464 sky_map = inputs.pop(
"sky_map")
466 tract = butlerQC.quantum.dataId[
"tract"]
467 band = butlerQC.quantum.dataId[
"band"]
469 input_map_dict = {ref.dataId[
"patch"]: ref
for ref
in inputs[
"input_maps"]}
470 coadd_dict = {ref.dataId[
"patch"]: ref
for ref
in inputs[
"coadd_exposures"]}
472 visit_summary_dict = {ref.dataId[
"visit"]: ref.get()
473 for ref
in inputs[
"visit_summaries"]}
475 self.run(sky_map, tract, band, coadd_dict, input_map_dict, visit_summary_dict)
478 for name, property_map
in self.property_maps.items():
479 if property_map.config.do_min:
480 butlerQC.put(property_map.min_map,
481 getattr(outputRefs, f
"{name}_map_min"))
482 if property_map.config.do_max:
483 butlerQC.put(property_map.max_map,
484 getattr(outputRefs, f
"{name}_map_max"))
485 if property_map.config.do_mean:
486 butlerQC.put(property_map.mean_map,
487 getattr(outputRefs, f
"{name}_map_mean"))
488 if property_map.config.do_weighted_mean:
489 butlerQC.put(property_map.weighted_mean_map,
490 getattr(outputRefs, f
"{name}_map_weighted_mean"))
491 if property_map.config.do_sum:
492 butlerQC.put(property_map.sum_map,
493 getattr(outputRefs, f
"{name}_map_sum"))
495 def run(self, sky_map, tract, band, coadd_dict, input_map_dict, visit_summary_dict):
496 """Run the healsparse property task.
500 sky_map : Sky map object
504 Band name for logging.
505 coadd_dict : `dict` [`int`: `lsst.daf.butler.DeferredDatasetHandle`]
506 Dictionary of coadd exposure references. Keys are patch numbers.
507 input_map_dict : `dict` [`int`: `lsst.daf.butler.DeferredDatasetHandle`]
508 Dictionary of input map references. Keys are patch numbers.
509 visit_summary_dict : `dict` [`int`: `lsst.afw.table.ExposureCatalog`]
510 Dictionary of visit summary tables. Keys are visit numbers.
514 RepeatableQuantumError
515 If visit_summary_dict is missing any visits or detectors found in an
516 input map. This leads to an inconsistency between what is in the coadd
517 (via the input map) and the visit summary tables which contain data
520 tract_info = sky_map[tract]
522 tract_maps_initialized =
False
524 for patch
in input_map_dict.keys():
525 self.log.info(
"Making maps for band %s, tract %d, patch %d.",
528 patch_info = tract_info[patch]
530 input_map = input_map_dict[patch].get()
531 coadd_photo_calib = coadd_dict[patch].get(component=
"photoCalib")
532 coadd_inputs = coadd_dict[patch].get(component=
"coaddInputs")
534 coadd_zeropoint = 2.5*np.log10(coadd_photo_calib.getInstFluxAtZeroMagnitude())
537 poly_vertices = patch_info.getInnerSkyPolygon(tract_info.getWcs()).getVertices()
538 patch_radec = self._vertices_to_radec(poly_vertices)
539 patch_poly = hsp.Polygon(ra=patch_radec[:, 0], dec=patch_radec[:, 1],
540 value=np.arange(input_map.wide_mask_maxbits))
541 patch_poly_map = patch_poly.get_map_like(input_map)
542 input_map = hsp.and_intersection([input_map, patch_poly_map])
544 if not tract_maps_initialized:
547 nside_coverage = self._compute_nside_coverage_tract(tract_info)
548 nside = input_map.nside_sparse
550 do_compute_approx_psf =
False
552 for property_map
in self.property_maps:
553 property_map.initialize_tract_maps(nside_coverage, nside)
554 if property_map.requires_psf:
555 do_compute_approx_psf =
True
557 tract_maps_initialized =
True
559 valid_pixels, vpix_ra, vpix_dec = input_map.valid_pixels_pos(return_pixels=
True)
562 if valid_pixels.size == 0:
566 for property_map
in self.property_maps:
567 property_map.initialize_values(valid_pixels.size)
568 property_map.zeropoint = coadd_zeropoint
571 total_weights = np.zeros(valid_pixels.size)
572 total_inputs = np.zeros(valid_pixels.size, dtype=np.int32)
574 for bit, ccd_row
in enumerate(coadd_inputs.ccds):
576 inmap, = np.where(input_map.check_bits_pix(valid_pixels, [bit]))
583 visit = ccd_row[
"visit"]
584 detector_id = ccd_row[
"ccd"]
585 weight = ccd_row[
"weight"]
587 x, y = ccd_row.getWcs().skyToPixelArray(vpix_ra[inmap], vpix_dec[inmap], degrees=
True)
588 scalings = self._compute_calib_scale(ccd_row, x, y)
590 if do_compute_approx_psf:
595 total_weights[inmap] += weight
596 total_inputs[inmap] += 1
599 if visit
not in visit_summary_dict:
600 msg = f
"Visit {visit} not found in visit_summaries."
601 raise pipeBase.RepeatableQuantumError(msg)
602 row = visit_summary_dict[visit].find(detector_id)
604 msg = f
"Visit {visit} / detector_id {detector_id} not found in visit_summaries."
605 raise pipeBase.RepeatableQuantumError(msg)
608 for property_map
in self.property_maps:
609 property_map.accumulate_values(inmap,
618 for property_map
in self.property_maps:
619 property_map.finalize_mean_values(total_weights, total_inputs)
620 property_map.set_map_values(valid_pixels)
622 def _compute_calib_scale(self, ccd_row, x, y):
623 """Compute calibration scaling values.
627 ccd_row : `lsst.afw.table.ExposureRecord`
628 Exposure metadata for a given detector exposure.
630 Array of x positions.
632 Array of y positions.
636 calib_scale : `np.ndarray`
637 Array of calibration scale values.
639 photo_calib = ccd_row.getPhotoCalib()
640 bf = photo_calib.computeScaledCalibration()
641 if bf.getBBox() == ccd_row.getBBox():
643 calib_scale = photo_calib.getCalibrationMean()*bf.evaluate(x, y)
646 calib_scale = photo_calib.getCalibrationMean()
650 def _vertices_to_radec(self, vertices):
651 """Convert polygon vertices to ra/dec.
655 vertices : `list` [ `lsst.sphgeom.UnitVector3d` ]
656 Vertices for bounding polygon.
660 radec : `numpy.ndarray`
661 Nx2 array of ra/dec positions (in degrees) associated with vertices.
664 radec = np.array([(x.getLon().asDegrees(), x.getLat().asDegrees())
for
668 def _compute_nside_coverage_tract(self, tract_info):
669 """Compute the optimal coverage nside for a tract.
673 tract_info : `lsst.skymap.tractInfo.ExplicitTractInfo`
674 Tract information object.
678 nside_coverage : `int`
679 Optimal coverage nside for a tract map.
681 num_patches = tract_info.getNumPatches()
684 patch_info = tract_info.getPatchInfo(0)
685 vertices = patch_info.getInnerSkyPolygon(tract_info.getWcs()).getVertices()
686 radec = self._vertices_to_radec(vertices)
687 delta_ra = np.max(radec[:, 0]) - np.min(radec[:, 0])
688 delta_dec = np.max(radec[:, 1]) - np.min(radec[:, 1])
689 patch_area = delta_ra*delta_dec*np.cos(np.deg2rad(np.mean(radec[:, 1])))
691 tract_area = num_patches[0]*num_patches[1]*patch_area
693 nside_coverage_tract = 32
694 while hp.nside2pixarea(nside_coverage_tract, degrees=
True) > tract_area:
695 nside_coverage_tract = 2*nside_coverage_tract
697 nside_coverage_tract = int(np.clip(nside_coverage_tract/2, 32,
None))
699 return nside_coverage_tract
702 class ConsolidateHealSparsePropertyMapConnections(pipeBase.PipelineTaskConnections,
703 dimensions=(
"band",
"skymap",),
704 defaultTemplates={
"coaddName":
"deep"}):
705 sky_map = pipeBase.connectionTypes.Input(
706 doc=
"Input definition of geometry/bbox and projection/wcs for coadded exposures",
707 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
708 storageClass=
"SkyMap",
709 dimensions=(
"skymap",),
717 for name
in BasePropertyMap.registry:
718 vars()[f
"{name}_map_min"] = pipeBase.connectionTypes.Input(
719 doc=f
"Minimum-value map of {name}",
720 name=f
"{{coaddName}}Coadd_{name}_map_min",
721 storageClass=
"HealSparseMap",
722 dimensions=(
"tract",
"skymap",
"band"),
726 vars()[f
"{name}_consolidated_map_min"] = pipeBase.connectionTypes.Output(
727 doc=f
"Minumum-value map of {name}",
728 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_min",
729 storageClass=
"HealSparseMap",
730 dimensions=(
"skymap",
"band"),
732 vars()[f
"{name}_map_max"] = pipeBase.connectionTypes.Input(
733 doc=f
"Maximum-value map of {name}",
734 name=f
"{{coaddName}}Coadd_{name}_map_max",
735 storageClass=
"HealSparseMap",
736 dimensions=(
"tract",
"skymap",
"band"),
740 vars()[f
"{name}_consolidated_map_max"] = pipeBase.connectionTypes.Output(
741 doc=f
"Minumum-value map of {name}",
742 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_max",
743 storageClass=
"HealSparseMap",
744 dimensions=(
"skymap",
"band"),
746 vars()[f
"{name}_map_mean"] = pipeBase.connectionTypes.Input(
747 doc=f
"Mean-value map of {name}",
748 name=f
"{{coaddName}}Coadd_{name}_map_mean",
749 storageClass=
"HealSparseMap",
750 dimensions=(
"tract",
"skymap",
"band"),
754 vars()[f
"{name}_consolidated_map_mean"] = pipeBase.connectionTypes.Output(
755 doc=f
"Minumum-value map of {name}",
756 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_mean",
757 storageClass=
"HealSparseMap",
758 dimensions=(
"skymap",
"band"),
760 vars()[f
"{name}_map_weighted_mean"] = pipeBase.connectionTypes.Input(
761 doc=f
"Weighted mean-value map of {name}",
762 name=f
"{{coaddName}}Coadd_{name}_map_weighted_mean",
763 storageClass=
"HealSparseMap",
764 dimensions=(
"tract",
"skymap",
"band"),
768 vars()[f
"{name}_consolidated_map_weighted_mean"] = pipeBase.connectionTypes.Output(
769 doc=f
"Minumum-value map of {name}",
770 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_weighted_mean",
771 storageClass=
"HealSparseMap",
772 dimensions=(
"skymap",
"band"),
774 vars()[f
"{name}_map_sum"] = pipeBase.connectionTypes.Input(
775 doc=f
"Sum-value map of {name}",
776 name=f
"{{coaddName}}Coadd_{name}_map_sum",
777 storageClass=
"HealSparseMap",
778 dimensions=(
"tract",
"skymap",
"band"),
782 vars()[f
"{name}_consolidated_map_sum"] = pipeBase.connectionTypes.Output(
783 doc=f
"Minumum-value map of {name}",
784 name=f
"{{coaddName}}Coadd_{name}_consolidated_map_sum",
785 storageClass=
"HealSparseMap",
786 dimensions=(
"skymap",
"band"),
789 def __init__(self, *, config=None):
790 super().__init__(config=config)
794 for name
in BasePropertyMap.registry:
795 if name
not in config.property_maps:
797 prop_config.do_min =
False
798 prop_config.do_max =
False
799 prop_config.do_mean =
False
800 prop_config.do_weighted_mean =
False
801 prop_config.do_sum =
False
803 prop_config = config.property_maps[name]
805 if not prop_config.do_min:
806 self.inputs.remove(f
"{name}_map_min")
807 self.outputs.remove(f
"{name}_consolidated_map_min")
808 if not prop_config.do_max:
809 self.inputs.remove(f
"{name}_map_max")
810 self.outputs.remove(f
"{name}_consolidated_map_max")
811 if not prop_config.do_mean:
812 self.inputs.remove(f
"{name}_map_mean")
813 self.outputs.remove(f
"{name}_consolidated_map_mean")
814 if not prop_config.do_weighted_mean:
815 self.inputs.remove(f
"{name}_map_weighted_mean")
816 self.outputs.remove(f
"{name}_consolidated_map_weighted_mean")
817 if not prop_config.do_sum:
818 self.inputs.remove(f
"{name}_map_sum")
819 self.outputs.remove(f
"{name}_consolidated_map_sum")
822 class ConsolidateHealSparsePropertyMapConfig(pipeBase.PipelineTaskConfig,
823 pipelineConnections=ConsolidateHealSparsePropertyMapConnections):
824 """Configuration parameters for ConsolidateHealSparsePropertyMapTask"""
825 property_maps = BasePropertyMap.registry.makeField(
827 default=[
"exposure_time",
838 doc=
"Property map computation objects",
841 def setDefaults(self):
842 self.property_maps[
"exposure_time"].do_sum =
True
843 self.property_maps[
"psf_size"].do_weighted_mean =
True
844 self.property_maps[
"psf_e1"].do_weighted_mean =
True
845 self.property_maps[
"psf_e2"].do_weighted_mean =
True
846 self.property_maps[
"psf_maglim"].do_weighted_mean =
True
847 self.property_maps[
"sky_noise"].do_weighted_mean =
True
848 self.property_maps[
"sky_background"].do_weighted_mean =
True
849 self.property_maps[
"dcr_dra"].do_weighted_mean =
True
850 self.property_maps[
"dcr_ddec"].do_weighted_mean =
True
851 self.property_maps[
"dcr_e1"].do_weighted_mean =
True
852 self.property_maps[
"dcr_e2"].do_weighted_mean =
True
855 class ConsolidateHealSparsePropertyMapTask(pipeBase.PipelineTask):
856 """Task to consolidate HealSparse property maps.
858 This task will take all the individual tract-based maps (per map type,
859 per band) and consolidate them into one survey-wide map (per map type,
860 per band). Each tract map is truncated to its inner region before
863 ConfigClass = ConsolidateHealSparsePropertyMapConfig
864 _DefaultName =
"consolidateHealSparsePropertyMapTask"
866 def __init__(self, **kwargs):
867 super().__init__(**kwargs)
869 for name, config, PropertyMapClass
in self.config.property_maps.apply():
870 self.property_maps[name] = PropertyMapClass(config, name)
873 def runQuantum(self, butlerQC, inputRefs, outputRefs):
874 inputs = butlerQC.get(inputRefs)
876 sky_map = inputs.pop(
"sky_map")
879 for name
in self.config.property_maps.names:
880 for type_
in [
'min',
'max',
'mean',
'weighted_mean',
'sum']:
881 map_type = f
"{name}_map_{type_}"
882 if map_type
in inputs:
883 input_refs = {ref.dataId[
'tract']: ref
884 for ref
in inputs[map_type]}
885 consolidated_map = self.consolidate_map(sky_map, input_refs)
886 butlerQC.put(consolidated_map,
887 getattr(outputRefs, f
"{name}_consolidated_map_{type_}"))
889 def consolidate_map(self, sky_map, input_refs):
890 """Consolidate the healsparse property maps.
894 sky_map : Sky map object
895 input_refs : `dict` [`int`: `lsst.daf.butler.DeferredDatasetHandle`]
896 Dictionary of tract_id mapping to dataref.
900 consolidated_map : `healsparse.HealSparseMap`
901 Consolidated HealSparse map.
906 for tract_id
in input_refs:
907 cov = input_refs[tract_id].get(component=
'coverage')
909 cov_mask = cov.coverage_mask
911 cov_mask |= cov.coverage_mask
913 cov_pix, = np.where(cov_mask)
916 consolidated_map =
None
917 for tract_id
in input_refs:
918 input_map = input_refs[tract_id].get()
919 if consolidated_map
is None:
920 dtype = input_map.dtype
921 sentinel = input_map._sentinel
922 nside_coverage = input_map.nside_coverage
923 nside_sparse = input_map.nside_sparse
924 consolidated_map = hsp.HealSparseMap.make_empty(nside_coverage,
928 consolidated_map._reserve_cov_pix(cov_pix)
931 vpix, ra, dec = input_map.valid_pixels_pos(return_pixels=
True)
932 vpix_tract_ids = sky_map.findTractIdArray(ra, dec, degrees=
True)
934 in_tract = (vpix_tract_ids == tract_id)
936 consolidated_map[vpix[in_tract]] = input_map[vpix[in_tract]]
938 return consolidated_map
def compute_approx_psf_size_and_shape(ccd_row, ra, dec, nx=20, ny=20, orderx=2, ordery=2)