22__all__ = [
"DetectCoaddSourcesConfig",
"DetectCoaddSourcesTask",
23 "MeasureMergedCoaddSourcesConfig",
"MeasureMergedCoaddSourcesTask",
29from lsst.pipe.base
import (
30 AnnotatedPartialOutputsError,
34 PipelineTaskConnections
36import lsst.pipe.base.connectionTypes
as cT
40 ExceedsMaxVarianceScaleError,
41 InsufficientSourcesError,
45 TooManyMaskedPixelsError,
49 SingleFrameMeasurementTask,
51 CatalogCalculationTask,
52 SkyMapIdGeneratorConfig,
54from lsst.meas.extensions.scarlet.io
import updateCatalogFootprints
63from .mergeDetections
import MergeDetectionsConfig, MergeDetectionsTask
64from .mergeMeasurements
import MergeMeasurementsConfig, MergeMeasurementsTask
65from .multiBandUtils
import CullPeaksConfig
66from .deblendCoaddSourcesPipeline
import DeblendCoaddSourcesMultiConfig
67from .deblendCoaddSourcesPipeline
import DeblendCoaddSourcesMultiTask
72* deepCoadd_det: detections from what used to be processCoadd (tract, patch, filter)
73* deepCoadd_mergeDet: merged detections (tract, patch)
74* deepCoadd_meas: measurements of merged detections (tract, patch, filter)
75* deepCoadd_ref: reference sources (tract, patch)
76All of these have associated *_schema catalogs that require no data ID and hold no records.
78In addition, we have a schema-only dataset, which saves the schema for the PeakRecords in
79the mergeDet, meas, and ref dataset Footprints:
80* deepCoadd_peak_schema
86 dimensions=(
"tract",
"patch",
"band",
"skymap"),
87 defaultTemplates={
"inputCoaddName":
"deep",
"outputCoaddName":
"deep"}):
88 detectionSchema = cT.InitOutput(
89 doc=
"Schema of the detection catalog",
90 name=
"{outputCoaddName}Coadd_det_schema",
91 storageClass=
"SourceCatalog",
94 doc=
"Exposure on which detections are to be performed. ",
95 name=
"{inputCoaddName}Coadd",
96 storageClass=
"ExposureF",
97 dimensions=(
"tract",
"patch",
"band",
"skymap")
99 exposure_cells = cT.Input(
100 doc=
"Exposure on which detections are to be performed. ",
101 name=
"{inputCoaddName}CoaddCell",
102 storageClass=
"MultipleCellCoadd",
103 dimensions=(
"tract",
"patch",
"band",
"skymap"),
106 doc=
"Description of the skymap's tracts and patches.",
107 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
108 storageClass=
"SkyMap",
109 dimensions=(
"skymap",),
111 outputBackgrounds = cT.Output(
112 doc=
"Output Backgrounds used in detection",
113 name=
"{outputCoaddName}Coadd_calexp_background",
114 storageClass=
"Background",
115 dimensions=(
"tract",
"patch",
"band",
"skymap")
117 outputSources = cT.Output(
118 doc=
"Detected sources catalog",
119 name=
"{outputCoaddName}Coadd_det",
120 storageClass=
"SourceCatalog",
121 dimensions=(
"tract",
"patch",
"band",
"skymap")
123 outputExposure = cT.Output(
124 doc=
"Exposure post detection",
125 name=
"{outputCoaddName}Coadd_calexp",
126 storageClass=
"ExposureF",
127 dimensions=(
"tract",
"patch",
"band",
"skymap")
130 def __init__(self, *, config=None):
131 super().__init__(config=config)
132 assert isinstance(config, DetectCoaddSourcesConfig)
134 if config.useCellCoadds:
137 del self.exposure_cells
139 if not self.config.forceExactBinning:
141 if self.config.writeOnlyBackgrounds:
142 del self.outputExposure
143 del self.outputSources
144 del self.detectionSchema
147class DetectCoaddSourcesConfig(PipelineTaskConfig, pipelineConnections=DetectCoaddSourcesConnections):
148 """Configuration parameters for the DetectCoaddSourcesTask
151 doScaleVariance = Field(dtype=bool, default=
True, doc=
"Scale variance plane using empirical noise?")
152 scaleVariance = ConfigurableField(target=ScaleVarianceTask, doc=
"Variance rescaling")
153 detection = ConfigurableField(target=DynamicDetectionTask, doc=
"Source detection")
154 coaddName = Field(dtype=str, default=
"deep", doc=
"Name of coadd")
155 useCellCoadds = Field(dtype=bool, default=
False, doc=
"Whether to use cell coadds?")
159 doc=
"Should be set to True if fake sources have been inserted into the input data.",
161 idGenerator = SkyMapIdGeneratorConfig.make_field()
162 forceExactBinning = Field(
166 "Check that the background bin size evenly divides the patch inner region, and "
167 "crop the outer region to an integer number of bins."
170 writeOnlyBackgrounds = Field(dtype=bool, default=
False, doc=
"If true, only save the background models.")
171 writeEmptyBackgrounds = Field(
175 "If true, save a placeholder background with NaNs in all bins (but the right geometry) when "
176 "there are no pixels to compute a background from. This can be useful if a later task combines "
177 "backgrounds from multiple patches as input."
181 def setDefaults(self):
182 super().setDefaults()
183 self.detection.thresholdType =
"pixel_stdev"
184 self.detection.isotropicGrow =
True
186 self.detection.reEstimateBackground =
False
187 self.detection.background.useApprox =
False
188 self.detection.background.binSize = 4096
189 self.detection.background.undersampleStyle =
'REDUCE_INTERP_ORDER'
190 self.detection.doTempWideBackground =
True
193 self.idGenerator.packer.n_bands =
None
196class DetectCoaddSourcesTask(PipelineTask):
197 """Detect sources on a single filter coadd.
199 Coadding individual visits requires each exposure to be warped. This
200 introduces covariance in the noise properties across pixels. Before
201 detection, we correct the coadd variance by scaling the variance plane in
202 the coadd to match the observed variance. This is an approximate
203 approach -- strictly, we should propagate the full covariance matrix --
204 but it is simple and works well in practice.
206 After scaling the variance plane, we detect sources and generate footprints
207 by delegating to the @ref SourceDetectionTask_ "detection" subtask.
209 DetectCoaddSourcesTask is meant to be run after assembling a coadded image
210 in a given band. The purpose of the task is to update the background,
211 detect all sources in a single band and generate a set of parent
212 footprints. Subsequent tasks in the multi-band processing procedure will
213 merge sources across bands and, eventually, perform forced photometry.
217 schema : `lsst.afw.table.Schema`, optional
218 Initial schema for the output catalog, modified-in place to include all
219 fields set by this task. If None, the source minimal schema will be used.
221 Additional keyword arguments.
224 _DefaultName =
"detectCoaddSources"
225 ConfigClass = DetectCoaddSourcesConfig
227 def __init__(self, schema=None, **kwargs):
230 super().__init__(**kwargs)
232 schema = afwTable.SourceTable.makeMinimalSchema()
234 self.makeSubtask(
"detection", schema=self.schema)
235 if self.config.doScaleVariance:
236 self.makeSubtask(
"scaleVariance")
238 self.detectionSchema = afwTable.SourceCatalog(self.schema)
240 def runQuantum(self, butlerQC, inputRefs, outputRefs):
241 inputs = butlerQC.get(inputRefs)
242 idGenerator = self.config.idGenerator.apply(butlerQC.quantum.dataId)
244 if self.config.useCellCoadds:
245 multiple_cell_coadd = inputs.pop(
"exposure_cells")
246 exposure = multiple_cell_coadd.stitch().asExposure()
248 exposure = inputs.pop(
"exposure")
250 skyMap = inputs.pop(
"skyMap",
None)
251 if skyMap
is not None:
252 patchInfo = skyMap[butlerQC.quantum.dataId[
"tract"]][butlerQC.quantum.dataId[
"patch"]]
256 assert not inputs,
"runQuantum got more inputs than expected."
260 idFactory=idGenerator.make_table_id_factory(),
261 expId=idGenerator.catalog_id,
265 TooManyMaskedPixelsError,
266 ExceedsMaxVarianceScaleError,
267 InsufficientSourcesError,
271 if self.config.writeEmptyBackgrounds:
272 butlerQC.put(self._makeEmptyBackground(exposure, patchInfo), outputRefs.outputBackgrounds)
274 for maskName
in [
"DETECTED",
"DETECTED_NEGATIVE"]:
275 if maskName
in exposure.mask.getMaskPlaneDict().keys():
276 detectedMask = exposure.mask.getMaskPlane(maskName)
277 exposure.mask.clearMaskPlane(detectedMask)
278 butlerQC.put(exposure, outputRefs.outputExposure)
279 error = AnnotatedPartialOutputsError.annotate(
287 butlerQC.put(outputs, outputRefs)
289 def run(self, exposure, idFactory, expId, patchInfo=None):
290 """Run detection on an exposure.
292 First scale the variance plane to match the observed variance
293 using ``ScaleVarianceTask``. Then invoke the ``SourceDetectionTask_`` "detection" subtask to
298 exposure : `lsst.afw.image.Exposure`
299 Exposure on which to detect (may be background-subtracted and scaled,
300 depending on configuration).
301 idFactory : `lsst.afw.table.IdFactory`
302 IdFactory to set source identifiers.
304 Exposure identifier (integer) for RNG seed.
305 patchInfo : `lsst.skymap.PatchInfo`, optional
306 Description of the patch geometry. Only needed if
307 `~DetectCoaddSourceConfig.forceExactBinning` is `True`.
311 result : `lsst.pipe.base.Struct`
312 Results as a struct with attributes:
315 Catalog of detections (`lsst.afw.table.SourceCatalog`).
317 List of backgrounds (`list`).
319 if self.config.forceExactBinning:
320 exposure = self._cropToExactBinning(exposure, patchInfo)
321 if self.config.doScaleVariance:
322 varScale = self.scaleVariance.run(exposure.maskedImage)
323 exposure.getMetadata().add(
"VARIANCE_SCALE", varScale)
324 backgrounds = afwMath.BackgroundList()
325 table = afwTable.SourceTable.make(self.schema, idFactory)
326 detections = self.detection.run(table, exposure, expId=expId)
327 sources = detections.sources
328 if hasattr(detections,
"background")
and detections.background:
329 for bg
in detections.background:
330 backgrounds.append(bg)
331 if len(backgrounds) == 0:
334 emptyBg = self._makeEmptyBackground(exposure, patchInfo)
335 backgrounds.append(emptyBg)
337 return Struct(outputSources=sources, outputBackgrounds=backgrounds, outputExposure=exposure)
339 def _cropToExactBinning(self, exposure, patchInfo):
340 """Crop a coadd `~lsst.afw.image.Exposure` instance to ensure exact
345 exposure : `lsst.afw.image.Exposure`
346 Exposure to crop, assumed to cover the patch outer bounding box.
347 patchInfo : `lsst.skymap.PatchInfo`
348 Description of the patch geometry.
352 cropped : `lsst.afw.image.Exposure`
353 View of ``exposure`` with background bins that evenly divide both
354 the full cropped image and the patch inner region. The bounding
355 box is guaranteed to contain the patch inner bounding box and be
356 contained by the patch outer bounding box.
361 Raised if the patch inner region width or height is not a multiple
362 of the background bin size.
364 bbox = patchInfo.getInnerBBox()
365 if bbox.width % self.detection.background.binSizeX:
367 f
"Patch inner width {bbox.width} does not evenly "
368 f
"divide bin width {self.detection.background.binSizeX}."
370 if bbox.height % self.detection.background.binSizeY:
372 f
"Patch inner height {bbox.height} does not evenly "
373 f
"divide bin height {self.detection.background.binSizeY}."
375 outer_bbox = patchInfo.getOuterBBox()
376 n_bins_grow_x = (bbox.x.begin - outer_bbox.x.begin) // self.detection.background.binSizeX
377 n_bins_grow_y = (bbox.y.begin - outer_bbox.y.begin) // self.detection.background.binSizeY
380 n_bins_grow_x*self.detection.background.binSizeX,
381 n_bins_grow_y*self.detection.background.binSizeY,
384 assert outer_bbox.contains(bbox)
385 assert bbox.contains(patchInfo.getInnerBBox())
386 assert bbox.width % self.detection.background.binSizeX == 0
387 assert bbox.height % self.detection.background.binSizeY == 0
388 return exposure[bbox]
390 def _makeEmptyBackground(self, exposure, patchInfo=None):
391 """Construct an empty `lsst.afw.math.BackgroundList` with NaN values.
395 exposure : `lsst.afw.image.Exposure`
396 Exposure that the background should correspond to.
397 patchInfo : `lsst.skymap.PatchInfo`, optional
398 Description of the patch geometry. Only needed if
399 `~DetectCoaddSourceConfig.forceExactBinning` is `True`.
403 background : `lsst.afw.math.BackgroundList`
404 A background object with a single layer and the same bin geometry
405 that a background for that exposure would have had if it had enough
406 usable pixels. This object cannot actually be used for background
411 if self.config.forceExactBinning:
412 exposure = self._cropToExactBinning(exposure, patchInfo).clone()
415 bgStats = afwImage.MaskedImageF(1, 1)
416 bgStats.set(bgLevel, 0, bgLevel)
417 bg = afwMath.BackgroundMI(exposure.getBBox(), bgStats)
418 bgData = (bg, afwMath.Interpolate.LINEAR, afwMath.REDUCE_INTERP_ORDER,
419 afwMath.ApproximateControl.UNKNOWN, 0, 0,
False)
420 background = afwMath.BackgroundList()
421 background.append(bgData)
422 for bg, *_
in background:
423 stats = bg.getStatsImage()
424 stats.mask.array[:, :] = stats.mask.getPlaneBitMask(
"NO_DATA")
425 stats.variance.array[:, :] = 0.0
429class MeasureMergedCoaddSourcesConnections(
430 PipelineTaskConnections,
431 dimensions=(
"tract",
"patch",
"band",
"skymap"),
433 "inputCoaddName":
"deep",
434 "outputCoaddName":
"deep",
437 inputSchema = cT.InitInput(
438 doc=
"Input schema for measure merged task produced by a deblender or detection task",
439 name=
"{inputCoaddName}Coadd_deblendedFlux_schema",
440 storageClass=
"SourceCatalog"
442 outputSchema = cT.InitOutput(
443 doc=
"Output schema after all new fields are added by task",
444 name=
"{inputCoaddName}Coadd_meas_schema",
445 storageClass=
"SourceCatalog"
448 doc=
"Input non-cell-based coadd image",
449 name=
"{inputCoaddName}Coadd_calexp",
450 storageClass=
"ExposureF",
451 dimensions=(
"tract",
"patch",
"band",
"skymap")
453 exposure_cells = cT.Input(
454 doc=
"Input cell-based coadd image",
455 name=
"{inputCoaddName}CoaddCell",
456 storageClass=
"MultipleCellCoadd",
457 dimensions=(
"tract",
"patch",
"band",
"skymap"),
459 background = cT.Input(
460 doc=
"Background to subtract from cell-based coadd image",
461 name=
"{inputCoaddName}Coadd_calexp_background",
462 storageClass=
"Background",
463 dimensions=(
"tract",
"patch",
"band",
"skymap")
466 doc=
"SkyMap to use in processing",
467 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
468 storageClass=
"SkyMap",
469 dimensions=(
"skymap",),
471 sourceTableHandles = cT.Input(
472 doc=(
"Source tables that are derived from the ``CalibrateTask`` sources. "
473 "These tables contain astrometry and photometry flags, and optionally "
475 name=
"sourceTable_visit",
476 storageClass=
"ArrowAstropy",
477 dimensions=(
"instrument",
"visit"),
481 finalizedSourceTableHandles = cT.Input(
482 doc=(
"Finalized source tables from ``FinalizeCalibrationTask``. These "
483 "tables contain PSF flags from the finalized PSF estimation."),
484 name=
"finalized_src_table",
485 storageClass=
"ArrowAstropy",
486 dimensions=(
"instrument",
"visit"),
490 finalVisitSummaryHandles = cT.Input(
491 doc=
"Final visit summary table",
492 name=
"finalVisitSummary",
493 storageClass=
"ExposureCatalog",
494 dimensions=(
"instrument",
"visit"),
498 scarletCatalog = cT.Input(
499 doc=
"Catalogs produced by multiband deblending",
500 name=
"{inputCoaddName}Coadd_deblendedCatalog",
501 storageClass=
"SourceCatalog",
502 dimensions=(
"tract",
"patch",
"skymap"),
504 scarletModels = cT.Input(
505 doc=
"Multiband scarlet models produced by the deblender",
506 name=
"{inputCoaddName}Coadd_scarletModelData",
507 storageClass=
"LsstScarletModelData",
508 dimensions=(
"tract",
"patch",
"skymap"),
510 outputSources = cT.Output(
511 doc=
"Source catalog containing all the measurement information generated in this task",
512 name=
"{outputCoaddName}Coadd_meas",
513 dimensions=(
"tract",
"patch",
"band",
"skymap"),
514 storageClass=
"SourceCatalog",
517 def __init__(self, *, config=None):
518 super().__init__(config=config)
519 if not config.doPropagateFlags:
520 del self.sourceTableHandles
521 del self.finalizedSourceTableHandles
522 del self.finalVisitSummaryHandles
525 if not config.propagateFlags.source_flags:
526 del self.sourceTableHandles
527 if not config.propagateFlags.finalized_source_flags:
528 del self.finalizedSourceTableHandles
529 if not config.doAddFootprints:
530 del self.scarletModels
532 if config.useCellCoadds:
535 del self.exposure_cells
539class MeasureMergedCoaddSourcesConfig(PipelineTaskConfig,
540 pipelineConnections=MeasureMergedCoaddSourcesConnections):
541 """Configuration parameters for the MeasureMergedCoaddSourcesTask
543 doAddFootprints = Field(dtype=bool,
545 doc=
"Whether or not to add footprints to the input catalog from scarlet models. "
546 "This should be true whenever using the multi-band deblender, "
547 "otherwise this should be False.")
548 doConserveFlux = Field(dtype=bool, default=
True,
549 doc=
"Whether to use the deblender models as templates to re-distribute the flux "
550 "from the 'exposure' (True), or to perform measurements on the deblender "
552 doStripFootprints = Field(dtype=bool, default=
True,
553 doc=
"Whether to strip footprints from the output catalog before "
555 "This is usually done when using scarlet models to save disk space.")
556 useCellCoadds = Field(dtype=bool, default=
False, doc=
"Whether to use cell coadds?")
557 measurement = ConfigurableField(target=SingleFrameMeasurementTask, doc=
"Source measurement")
558 setPrimaryFlags = ConfigurableField(target=SetPrimaryFlagsTask, doc=
"Set flags for primary tract/patch")
559 doPropagateFlags = Field(
560 dtype=bool, default=
True,
561 doc=
"Whether to match sources to CCD catalogs to propagate flags (to e.g. identify PSF stars)"
563 propagateFlags = ConfigurableField(target=PropagateSourceFlagsTask, doc=
"Propagate source flags to coadd")
564 coaddName = Field(dtype=str, default=
"deep", doc=
"Name of coadd")
565 psfCache = Field(dtype=int, default=100, doc=
"Size of psfCache")
566 checkUnitsParseStrict = Field(
567 doc=
"Strictness of Astropy unit compatibility check, can be 'raise', 'warn' or 'silent'",
574 doc=
"Apply aperture corrections"
576 applyApCorr = ConfigurableField(
577 target=ApplyApCorrTask,
578 doc=
"Subtask to apply aperture corrections"
580 doRunCatalogCalculation = Field(
583 doc=
'Run catalogCalculation task'
585 catalogCalculation = ConfigurableField(
586 target=CatalogCalculationTask,
587 doc=
"Subtask to run catalogCalculation plugins on catalog"
593 doc=
"Should be set to True if fake sources have been inserted into the input data."
595 idGenerator = SkyMapIdGeneratorConfig.make_field()
597 def setDefaults(self):
598 super().setDefaults()
599 self.measurement.plugins.names |= [
'base_InputCount',
601 'base_LocalPhotoCalib',
607 self.measurement.plugins[
'base_PixelFlags'].masksFpAnywhere = [
'CLIPPED',
'SENSOR_EDGE',
609 self.measurement.plugins[
'base_PixelFlags'].masksFpCenter = [
'CLIPPED',
'SENSOR_EDGE',
613class MeasureMergedCoaddSourcesTask(PipelineTask):
614 """Deblend sources from main catalog in each coadd seperately and measure.
616 Use peaks and footprints from a master catalog to perform deblending and
617 measurement in each coadd.
619 Given a master input catalog of sources (peaks and footprints) or deblender
620 outputs(including a HeavyFootprint in each band), measure each source on
621 the coadd. Repeating this procedure with the same master catalog across
622 multiple coadds will generate a consistent set of child sources.
624 The deblender retains all peaks and deblends any missing peaks (dropouts in
625 that band) as PSFs. Source properties are measured and the @c is-primary
626 flag (indicating sources with no children) is set. Visit flags are
627 propagated to the coadd sources.
629 After MeasureMergedCoaddSourcesTask has been run on multiple coadds, we
630 have a set of per-band catalogs. The next stage in the multi-band
631 processing procedure will merge these measurements into a suitable catalog
632 for driving forced photometry.
636 schema : ``lsst.afw.table.Schema`, optional
637 The schema of the merged detection catalog used as input to this one.
638 peakSchema : ``lsst.afw.table.Schema`, optional
639 The schema of the PeakRecords in the Footprints in the merged detection catalog.
640 initInputs : `dict`, optional
641 Dictionary that can contain a key ``inputSchema`` containing the
642 input schema. If present will override the value of ``schema``.
644 Additional keyword arguments.
647 _DefaultName =
"measureCoaddSources"
648 ConfigClass = MeasureMergedCoaddSourcesConfig
650 def __init__(self, schema=None, peakSchema=None, initInputs=None, **kwargs):
651 super().__init__(**kwargs)
652 if initInputs
is not None:
653 schema = initInputs[
'inputSchema'].schema
655 raise ValueError(
"Schema must be defined.")
656 self.schemaMapper = afwTable.SchemaMapper(schema)
657 self.schemaMapper.addMinimalSchema(schema)
658 self.schema = self.schemaMapper.getOutputSchema()
660 self.makeSubtask(
"measurement", schema=self.schema, algMetadata=self.algMetadata)
661 self.makeSubtask(
"setPrimaryFlags", schema=self.schema)
662 if self.config.doPropagateFlags:
663 self.makeSubtask(
"propagateFlags", schema=self.schema)
664 self.schema.checkUnits(parse_strict=self.config.checkUnitsParseStrict)
665 if self.config.doApCorr:
666 self.makeSubtask(
"applyApCorr", schema=self.schema)
667 if self.config.doRunCatalogCalculation:
668 self.makeSubtask(
"catalogCalculation", schema=self.schema)
670 self.outputSchema = afwTable.SourceCatalog(self.schema)
672 def runQuantum(self, butlerQC, inputRefs, outputRefs):
673 inputs = butlerQC.get(inputRefs)
675 if self.config.useCellCoadds:
676 multiple_cell_coadd = inputs.pop(
"exposure_cells")
677 stitched_coadd = multiple_cell_coadd.stitch()
678 exposure = stitched_coadd.asExposure()
679 background = inputs.pop(
"background")
680 exposure.image -= background.getImage()
682 ccdInputs = stitched_coadd.ccds
683 apCorrMap = stitched_coadd.ap_corr_map
684 band = inputRefs.exposure_cells.dataId[
"band"]
686 exposure = inputs.pop(
"exposure")
689 exposure.getPsf().setCacheCapacity(self.config.psfCache)
691 ccdInputs = exposure.getInfo().getCoaddInputs().ccds
692 apCorrMap = exposure.getInfo().getApCorrMap()
693 band = inputRefs.exposure.dataId[
"band"]
697 idGenerator = self.config.idGenerator.apply(butlerQC.quantum.dataId)
700 table = afwTable.SourceTable.make(self.schema, idGenerator.make_table_id_factory())
701 sources = afwTable.SourceCatalog(table)
703 inputCatalog = inputs.pop(
"scarletCatalog")
704 catalogRef = inputRefs.scarletCatalog
705 sources.extend(inputCatalog, self.schemaMapper)
708 if self.config.doAddFootprints:
709 modelData = inputs.pop(
'scarletModels')
710 if self.config.doConserveFlux:
711 imageForRedistribution = exposure
713 imageForRedistribution =
None
714 updateCatalogFootprints(
718 imageForRedistribution=imageForRedistribution,
719 removeScarletData=
True,
720 updateFluxColumns=
True,
722 table = sources.getTable()
723 table.setMetadata(self.algMetadata)
725 skyMap = inputs.pop(
'skyMap')
726 tractNumber = catalogRef.dataId[
'tract']
727 tractInfo = skyMap[tractNumber]
728 patchInfo = tractInfo.getPatchInfo(catalogRef.dataId[
'patch'])
733 wcs=tractInfo.getWcs(),
734 bbox=patchInfo.getOuterBBox()
737 sourceTableHandleDict =
None
738 finalizedSourceTableHandleDict =
None
739 finalVisitSummaryHandleDict =
None
740 if self.config.doPropagateFlags:
741 if "sourceTableHandles" in inputs:
742 sourceTableHandles = inputs.pop(
"sourceTableHandles")
743 sourceTableHandleDict = {handle.dataId[
"visit"]: handle
for handle
in sourceTableHandles}
744 if "finalizedSourceTableHandles" in inputs:
745 finalizedSourceTableHandles = inputs.pop(
"finalizedSourceTableHandles")
746 finalizedSourceTableHandleDict = {handle.dataId[
"visit"]: handle
747 for handle
in finalizedSourceTableHandles}
748 if "finalVisitSummaryHandles" in inputs:
749 finalVisitSummaryHandles = inputs.pop(
"finalVisitSummaryHandles")
750 finalVisitSummaryHandleDict = {handle.dataId[
"visit"]: handle
751 for handle
in finalVisitSummaryHandles}
753 assert not inputs,
"runQuantum got more inputs than expected."
758 exposureId=idGenerator.catalog_id,
760 sourceTableHandleDict=sourceTableHandleDict,
761 finalizedSourceTableHandleDict=finalizedSourceTableHandleDict,
762 finalVisitSummaryHandleDict=finalVisitSummaryHandleDict,
766 if self.config.doStripFootprints:
767 sources = outputs.outputSources
768 for source
in sources[sources[
"parent"] != 0]:
769 source.setFootprint(
None)
770 butlerQC.put(outputs, outputRefs)
772 def run(self, exposure, sources, skyInfo, exposureId, ccdInputs=None,
773 sourceTableHandleDict=None, finalizedSourceTableHandleDict=None, finalVisitSummaryHandleDict=None,
775 """Run measurement algorithms on the input exposure, and optionally populate the
776 resulting catalog with extra information.
780 exposure : `lsst.afw.exposure.Exposure`
781 The input exposure on which measurements are to be performed.
782 sources : `lsst.afw.table.SourceCatalog`
783 A catalog built from the results of merged detections, or
785 parentCatalog : `lsst.afw.table.SourceCatalog`
786 Catalog of parent sources corresponding to sources.
787 skyInfo : `lsst.pipe.base.Struct`
788 A struct containing information about the position of the input exposure within
789 a `SkyMap`, the `SkyMap`, its `Wcs`, and its bounding box.
790 exposureId : `int` or `bytes`
791 Packed unique number or bytes unique to the input exposure.
792 ccdInputs : `lsst.afw.table.ExposureCatalog`, optional
793 Catalog containing information on the individual visits which went into making
795 sourceTableHandleDict : `dict` [`int`, `lsst.daf.butler.DeferredDatasetHandle`], optional
796 Dict for sourceTable_visit handles (key is visit) for propagating flags.
797 These tables contain astrometry and photometry flags, and optionally PSF flags.
798 finalizedSourceTableHandleDict : `dict` [`int`, `lsst.daf.butler.DeferredDatasetHandle`], optional
799 Dict for finalized_src_table handles (key is visit) for propagating flags.
800 These tables contain PSF flags from the finalized PSF estimation.
801 finalVisitSummaryHandleDict : `dict` [`int`, `lsst.daf.butler.DeferredDatasetHandle`], optional
802 Dict for visit_summary handles (key is visit) for visit-level information.
803 These tables contain the WCS information of the single-visit input images.
804 apCorrMap : `lsst.afw.image.ApCorrMap`, optional
805 Aperture correction map attached to the ``exposure``. If None, it
806 will be read from the ``exposure``.
810 results : `lsst.pipe.base.Struct`
811 Results of running measurement task. Will contain the catalog in the
814 if self.config.doPropagateFlags:
817 for maskPlane
in self.config.measurement.plugins[
"base_PixelFlags"].masksFpAnywhere:
818 exposure.mask.addMaskPlane(maskPlane)
819 for maskPlane
in self.config.measurement.plugins[
"base_PixelFlags"].masksFpCenter:
820 exposure.mask.addMaskPlane(maskPlane)
822 self.measurement.run(sources, exposure, exposureId=exposureId)
824 if self.config.doApCorr:
825 if apCorrMap
is None:
826 apCorrMap = exposure.getInfo().getApCorrMap()
827 self.applyApCorr.run(
836 if not sources.isContiguous():
837 sources = sources.copy(deep=
True)
839 if self.config.doRunCatalogCalculation:
840 self.catalogCalculation.run(sources)
842 self.setPrimaryFlags.run(sources, skyMap=skyInfo.skyMap, tractInfo=skyInfo.tractInfo,
843 patchInfo=skyInfo.patchInfo)
844 if self.config.doPropagateFlags:
845 self.propagateFlags.run(
848 sourceTableHandleDict,
849 finalizedSourceTableHandleDict,
850 finalVisitSummaryHandleDict,
854 results.outputSources = sources