37from lsst.utils.introspection
import find_outside_stacklevel
39import lsst.pipe.base.connectionTypes
as cT
42from lsst.skymap
import BaseSkyMap
44from .forcedMeasurement
import ForcedMeasurementTask
45from .applyApCorr
import ApplyApCorrTask
46from .catalogCalculation
import CatalogCalculationTask
47from ._id_generator
import DetectorVisitIdGeneratorConfig
49__all__ = (
"ForcedPhotCcdConfig",
"ForcedPhotCcdTask",
50 "ForcedPhotCcdFromDataFrameTask",
"ForcedPhotCcdFromDataFrameConfig")
54 dimensions=(
"instrument",
"visit",
"detector",
"skymap",
"tract"),
55 defaultTemplates={
"inputCoaddName":
"deep",
56 "inputName":
"calexp",
57 "skyWcsName":
"gbdesAstrometricFit",
58 "photoCalibName":
"fgcm"},
60 deprecatedTemplates={
"skyWcsName":
"Deprecated; will be removed after v26.",
61 "photoCalibName":
"Deprecated; will be removed after v26."
63 inputSchema = cT.InitInput(
64 doc=
"Schema for the input measurement catalogs.",
65 name=
"{inputCoaddName}Coadd_ref_schema",
66 storageClass=
"SourceCatalog",
68 outputSchema = cT.InitOutput(
69 doc=
"Schema for the output forced measurement catalogs.",
70 name=
"forced_src_schema",
71 storageClass=
"SourceCatalog",
74 doc=
"Input exposure to perform photometry on.",
76 storageClass=
"ExposureF",
77 dimensions=[
"instrument",
"visit",
"detector"],
80 doc=
"Catalog of shapes and positions at which to force photometry.",
81 name=
"{inputCoaddName}Coadd_ref",
82 storageClass=
"SourceCatalog",
83 dimensions=[
"skymap",
"tract",
"patch"],
88 doc=
"SkyMap dataset that defines the coordinate system of the reference catalog.",
89 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
90 storageClass=
"SkyMap",
91 dimensions=[
"skymap"],
94 doc=
"Input Sky Correction to be subtracted from the calexp if doApplySkyCorr=True",
96 storageClass=
"Background",
97 dimensions=(
"instrument",
"visit",
"detector"),
99 visitSummary = cT.Input(
100 doc=
"Input visit-summary catalog with updated calibration objects.",
101 name=
"finalVisitSummary",
102 storageClass=
"ExposureCatalog",
103 dimensions=(
"instrument",
"visit"),
105 externalSkyWcsTractCatalog = cT.Input(
106 doc=(
"Per-tract, per-visit wcs calibrations. These catalogs use the detector "
107 "id for the catalog id, sorted on id for fast lookup."),
108 name=
"{skyWcsName}SkyWcsCatalog",
109 storageClass=
"ExposureCatalog",
110 dimensions=[
"instrument",
"visit",
"tract"],
112 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
114 externalSkyWcsGlobalCatalog = cT.Input(
115 doc=(
"Per-visit wcs calibrations computed globally (with no tract information). "
116 "These catalogs use the detector id for the catalog id, sorted on id for "
118 name=
"finalVisitSummary",
119 storageClass=
"ExposureCatalog",
120 dimensions=[
"instrument",
"visit"],
122 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
124 externalPhotoCalibTractCatalog = cT.Input(
125 doc=(
"Per-tract, per-visit photometric calibrations. These catalogs use the "
126 "detector id for the catalog id, sorted on id for fast lookup."),
127 name=
"{photoCalibName}PhotoCalibCatalog",
128 storageClass=
"ExposureCatalog",
129 dimensions=[
"instrument",
"visit",
"tract"],
131 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
133 externalPhotoCalibGlobalCatalog = cT.Input(
134 doc=(
"Per-visit photometric calibrations computed globally (with no tract "
135 "information). These catalogs use the detector id for the catalog id, "
136 "sorted on id for fast lookup."),
137 name=
"finalVisitSummary",
138 storageClass=
"ExposureCatalog",
139 dimensions=[
"instrument",
"visit"],
141 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
143 finalizedPsfApCorrCatalog = cT.Input(
144 doc=(
"Per-visit finalized psf models and aperture correction maps. "
145 "These catalogs use the detector id for the catalog id, "
146 "sorted on id for fast lookup."),
147 name=
"finalized_psf_ap_corr_catalog",
148 storageClass=
"ExposureCatalog",
149 dimensions=[
"instrument",
"visit"],
151 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
154 doc=
"Output forced photometry catalog.",
156 storageClass=
"SourceCatalog",
157 dimensions=[
"instrument",
"visit",
"detector",
"skymap",
"tract"],
160 def __init__(self, *, config=None):
161 super().__init__(config=config)
162 if not config.doApplySkyCorr:
163 self.inputs.remove(
"skyCorr")
164 if config.doApplyExternalSkyWcs:
165 if config.useGlobalExternalSkyWcs:
166 self.inputs.remove(
"externalSkyWcsTractCatalog")
168 self.inputs.remove(
"externalSkyWcsGlobalCatalog")
170 self.inputs.remove(
"externalSkyWcsTractCatalog")
171 self.inputs.remove(
"externalSkyWcsGlobalCatalog")
172 if config.doApplyExternalPhotoCalib:
173 if config.useGlobalExternalPhotoCalib:
174 self.inputs.remove(
"externalPhotoCalibTractCatalog")
176 self.inputs.remove(
"externalPhotoCalibGlobalCatalog")
178 self.inputs.remove(
"externalPhotoCalibTractCatalog")
179 self.inputs.remove(
"externalPhotoCalibGlobalCatalog")
180 if not config.doApplyFinalizedPsf:
181 self.inputs.remove(
"finalizedPsfApCorrCatalog")
185 pipelineConnections=ForcedPhotCcdConnections):
186 """Config class for forced measurement driver task."""
187 measurement = lsst.pex.config.ConfigurableField(
188 target=ForcedMeasurementTask,
189 doc=
"subtask to do forced measurement"
191 coaddName = lsst.pex.config.Field(
192 doc=
"coadd name: typically one of deep or goodSeeing",
196 doApCorr = lsst.pex.config.Field(
199 doc=
"Run subtask to apply aperture corrections"
201 applyApCorr = lsst.pex.config.ConfigurableField(
202 target=ApplyApCorrTask,
203 doc=
"Subtask to apply aperture corrections"
205 catalogCalculation = lsst.pex.config.ConfigurableField(
206 target=CatalogCalculationTask,
207 doc=
"Subtask to run catalogCalculation plugins on catalog"
209 doApplyExternalPhotoCalib = lsst.pex.config.Field(
212 doc=(
"Whether to apply external photometric calibration via an "
213 "`lsst.afw.image.PhotoCalib` object."),
215 deprecated=
"Removed in favor of the 'visitSummary' connection; will be removed after v26.",
217 useGlobalExternalPhotoCalib = lsst.pex.config.Field(
220 doc=(
"When using doApplyExternalPhotoCalib, use 'global' calibrations "
221 "that are not run per-tract. When False, use per-tract photometric "
222 "calibration files."),
224 deprecated=
"Removed in favor of the 'visitSummary' connection; will be removed after v26.",
226 doApplyExternalSkyWcs = lsst.pex.config.Field(
229 doc=(
"Whether to apply external astrometric calibration via an "
230 "`lsst.afw.geom.SkyWcs` object."),
232 deprecated=
"Removed in favor of the 'visitSummary' connection; will be removed after v26.",
234 useGlobalExternalSkyWcs = lsst.pex.config.Field(
237 doc=(
"When using doApplyExternalSkyWcs, use 'global' calibrations "
238 "that are not run per-tract. When False, use per-tract wcs "
241 deprecated=
"Removed in favor of the 'visitSummary' connection; will be removed after v26.",
243 doApplyFinalizedPsf = lsst.pex.config.Field(
246 doc=
"Whether to apply finalized psf models and aperture correction map.",
248 deprecated=
"Removed in favor of the 'visitSummary' connection; will be removed after v26.",
250 doApplySkyCorr = lsst.pex.config.Field(
253 doc=
"Apply sky correction?",
255 includePhotoCalibVar = lsst.pex.config.Field(
258 doc=
"Add photometric calibration variance to warp variance plane?",
260 footprintSource = lsst.pex.config.ChoiceField(
262 doc=
"Where to obtain footprints to install in the measurement catalog, prior to measurement.",
264 "transformed":
"Transform footprints from the reference catalog (downgrades HeavyFootprints).",
265 "psf": (
"Use the scaled shape of the PSF at the position of each source (does not generate "
266 "HeavyFootprints)."),
269 default=
"transformed",
271 psfFootprintScaling = lsst.pex.config.Field(
273 doc=
"Scaling factor to apply to the PSF shape when footprintSource='psf' (ignored otherwise).",
276 idGenerator = DetectorVisitIdGeneratorConfig.make_field()
278 def setDefaults(self):
280 super().setDefaults()
283 self.measurement.doReplaceWithNoise =
False
286 self.measurement.plugins.names = [
"base_PixelFlags",
287 "base_TransformedCentroid",
289 "base_LocalBackground",
290 "base_LocalPhotoCalib",
293 self.measurement.slots.shape =
None
296 self.catalogCalculation.plugins.names = []
300 """A pipeline task for performing forced measurement on CCD images.
304 refSchema : `lsst.afw.table.Schema`, optional
305 The schema of the reference catalog, passed to the constructor of the
306 references subtask. Optional, but must be specified if ``initInputs``
307 is not; if both are specified, ``initInputs`` takes precedence.
309 Dictionary that can contain a key ``inputSchema`` containing the
310 schema. If present will override the value of ``refSchema``.
312 Keyword arguments are passed to the supertask constructor.
315 ConfigClass = ForcedPhotCcdConfig
316 _DefaultName =
"forcedPhotCcd"
319 def __init__(self, refSchema=None, initInputs=None, **kwargs):
320 super().__init__(**kwargs)
322 if initInputs
is not None:
323 refSchema = initInputs[
'inputSchema'].schema
325 if refSchema
is None:
326 raise ValueError(
"No reference schema provided.")
328 self.makeSubtask(
"measurement", refSchema=refSchema)
332 if self.config.doApCorr:
333 self.makeSubtask(
"applyApCorr", schema=self.measurement.schema)
334 self.makeSubtask(
'catalogCalculation', schema=self.measurement.schema)
337 def runQuantum(self, butlerQC, inputRefs, outputRefs):
338 inputs = butlerQC.get(inputRefs)
340 tract = butlerQC.quantum.dataId[
'tract']
341 skyMap = inputs.pop(
'skyMap')
342 inputs[
'refWcs'] = skyMap[tract].getWcs()
345 skyCorr = inputs.pop(
'skyCorr',
None)
346 if self.config.useGlobalExternalSkyWcs:
347 externalSkyWcsCatalog = inputs.pop(
'externalSkyWcsGlobalCatalog',
None)
349 externalSkyWcsCatalog = inputs.pop(
'externalSkyWcsTractCatalog',
None)
350 if self.config.useGlobalExternalPhotoCalib:
351 externalPhotoCalibCatalog = inputs.pop(
'externalPhotoCalibGlobalCatalog',
None)
353 externalPhotoCalibCatalog = inputs.pop(
'externalPhotoCalibTractCatalog',
None)
354 finalizedPsfApCorrCatalog = inputs.pop(
'finalizedPsfApCorrCatalog',
None)
356 inputs[
'exposure'] = self.prepareCalibratedExposure(
359 externalSkyWcsCatalog=externalSkyWcsCatalog,
360 externalPhotoCalibCatalog=externalPhotoCalibCatalog,
361 finalizedPsfApCorrCatalog=finalizedPsfApCorrCatalog,
362 visitSummary=inputs.pop(
"visitSummary"),
365 inputs[
'refCat'] = self.mergeAndFilterReferences(inputs[
'exposure'], inputs[
'refCat'],
368 if inputs[
'refCat']
is None:
369 self.log.
info(
"No WCS for exposure %s. No %s catalog will be written.",
370 butlerQC.quantum.dataId, outputRefs.measCat.datasetType.name)
372 inputs[
'measCat'], inputs[
'exposureId'] = self.generateMeasCat(inputRefs.exposure.dataId,
374 inputs[
'refCat'], inputs[
'refWcs'])
375 self.attachFootprints(inputs[
'measCat'], inputs[
'refCat'], inputs[
'exposure'], inputs[
'refWcs'])
376 outputs = self.run(**inputs)
377 butlerQC.put(outputs, outputRefs)
380 externalPhotoCalibCatalog=None, finalizedPsfApCorrCatalog=None,
382 """Prepare a calibrated exposure and apply external calibrations
383 and sky corrections if so configured.
387 exposure : `lsst.afw.image.exposure.Exposure`
388 Input exposure to adjust calibrations.
389 skyCorr : `lsst.afw.math.backgroundList`, optional
390 Sky correction frame to apply if doApplySkyCorr=True.
391 externalSkyWcsCatalog : `lsst.afw.table.ExposureCatalog`, optional
392 Exposure catalog with external skyWcs to be applied if
393 config.doApplyExternalSkyWcs=True. Catalog uses the detector id
394 for the catalog id, sorted on id for fast lookup. Deprecated in
395 favor of ``visitSummary`` and will be removed after v26.
396 externalPhotoCalibCatalog : `lsst.afw.table.ExposureCatalog`, optional
397 Exposure catalog with external photoCalib to be applied if
398 config.doApplyExternalPhotoCalib=True. Catalog uses the detector
399 id for the catalog id, sorted on id for fast lookup. Deprecated in
400 favor of ``visitSummary`` and will be removed after v26.
401 finalizedPsfApCorrCatalog : `lsst.afw.table.ExposureCatalog`, optional
402 Exposure catalog with finalized psf models and aperture correction
403 maps to be applied if config.doApplyFinalizedPsf=True. Catalog
404 uses the detector id for the catalog id, sorted on id for fast
405 lookup. Deprecated in favor of ``visitSummary`` and will be removed
407 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
408 Exposure catalog with update calibrations; any not-None calibration
409 objects attached will be used. These are applied first and may be
410 overridden by other arguments.
414 exposure : `lsst.afw.image.exposure.Exposure`
415 Exposure with adjusted calibrations.
417 detectorId = exposure.getInfo().getDetector().getId()
419 if visitSummary
is not None:
420 row = visitSummary.find(detectorId)
422 raise RuntimeError(f
"Detector id {detectorId} not found in visitSummary.")
423 if (photoCalib := row.getPhotoCalib())
is not None:
424 exposure.setPhotoCalib(photoCalib)
425 if (skyWcs := row.getWcs())
is not None:
426 exposure.setWcs(skyWcs)
427 if (psf := row.getPsf())
is not None:
429 if (apCorrMap := row.getApCorrMap())
is not None:
430 exposure.info.setApCorrMap(apCorrMap)
432 if externalPhotoCalibCatalog
is not None:
435 "The 'externalPhotoCalibCatalog' argument is deprecated in favor of 'visitSummary' and will "
436 "be removed after v26.",
438 stacklevel=find_outside_stacklevel(
"lsst.meas.base"),
440 row = externalPhotoCalibCatalog.find(detectorId)
442 self.log.
warning(
"Detector id %s not found in externalPhotoCalibCatalog; "
443 "Using original photoCalib.", detectorId)
445 photoCalib = row.getPhotoCalib()
446 if photoCalib
is None:
447 self.log.
warning(
"Detector id %s has None for photoCalib in externalPhotoCalibCatalog; "
448 "Using original photoCalib.", detectorId)
450 exposure.setPhotoCalib(photoCalib)
452 if externalSkyWcsCatalog
is not None:
455 "The 'externalSkyWcsCatalog' argument is deprecated in favor of 'visitSummary' and will "
456 "be removed after v26.",
458 stacklevel=find_outside_stacklevel(
"lsst.meas.base"),
460 row = externalSkyWcsCatalog.find(detectorId)
462 self.log.
warning(
"Detector id %s not found in externalSkyWcsCatalog; "
463 "Using original skyWcs.", detectorId)
465 skyWcs = row.getWcs()
467 self.log.
warning(
"Detector id %s has None for skyWcs in externalSkyWcsCatalog; "
468 "Using original skyWcs.", detectorId)
470 exposure.setWcs(skyWcs)
472 if finalizedPsfApCorrCatalog
is not None:
475 "The 'finalizedPsfApCorrCatalog' argument is deprecated in favor of 'visitSummary' and will "
476 "be removed after v26.",
478 stacklevel=find_outside_stacklevel(
"lsst.meas.base"),
480 row = finalizedPsfApCorrCatalog.find(detectorId)
482 self.log.
warning(
"Detector id %s not found in finalizedPsfApCorrCatalog; "
483 "Using original psf.", detectorId)
486 apCorrMap = row.getApCorrMap()
487 if psf
is None or apCorrMap
is None:
488 self.log.
warning(
"Detector id %s has None for psf/apCorrMap in "
489 "finalizedPsfApCorrCatalog; Using original psf.", detectorId)
492 exposure.setApCorrMap(apCorrMap)
494 if skyCorr
is not None:
495 exposure.maskedImage -= skyCorr.getImage()
500 """Filter reference catalog so that all sources are within the
501 boundaries of the exposure.
505 exposure : `lsst.afw.image.exposure.Exposure`
506 Exposure to generate the catalog for.
507 refCats : sequence of `lsst.daf.butler.DeferredDatasetHandle`
508 Handles for catalogs of shapes and positions at which to force
510 refWcs : `lsst.afw.image.SkyWcs`
511 Reference world coordinate system.
515 refSources : `lsst.afw.table.SourceCatalog`
516 Filtered catalog of forced sources to measure.
520 The majority of this code is based on the methods of
521 lsst.meas.algorithms.loadReferenceObjects.ReferenceObjectLoader
528 expWcs = exposure.getWcs()
530 self.log.
info(
"Exposure has no WCS. Returning None for mergedRefCat.")
532 expRegion = exposure.getBBox(lsst.afw.image.PARENT)
534 expBoxCorners = expBBox.getCorners()
535 expSkyCorners = [expWcs.pixelToSky(corner).getVector()
for
536 corner
in expBoxCorners]
544 for refCat
in refCats:
545 refCat = refCat.get()
546 if mergedRefCat
is None:
549 for record
in refCat:
550 if (expPolygon.contains(record.getCoord().getVector())
and record.getParent()
552 record.setFootprint(record.getFootprint())
553 mergedRefCat.append(record)
554 containedIds.add(record.getId())
555 if mergedRefCat
is None:
556 raise RuntimeError(
"No reference objects for forced photometry.")
560 def generateMeasCat(self, dataId, exposure, refCat, refWcs):
561 """Generate a measurement catalog.
565 dataId : `lsst.daf.butler.DataCoordinate`
566 Butler data ID for this image, with ``{visit, detector}`` keys.
567 exposure : `lsst.afw.image.exposure.Exposure`
568 Exposure to generate the catalog for.
569 refCat : `lsst.afw.table.SourceCatalog`
570 Catalog of shapes and positions at which to force photometry.
571 refWcs : `lsst.afw.image.SkyWcs`
572 Reference world coordinate system.
573 This parameter is not currently used.
577 measCat : `lsst.afw.table.SourceCatalog`
578 Catalog of forced sources to measure.
580 Unique binary id associated with the input exposure
582 id_generator = self.config.idGenerator.apply(dataId)
583 measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
584 idFactory=id_generator.make_table_id_factory())
585 return measCat, id_generator.catalog_id
587 def run(self, measCat, exposure, refCat, refWcs, exposureId=None):
588 """Perform forced measurement on a single exposure.
592 measCat : `lsst.afw.table.SourceCatalog`
593 The measurement catalog, based on the sources listed in the
595 exposure : `lsst.afw.image.Exposure`
596 The measurement image upon which to perform forced detection.
597 refCat : `lsst.afw.table.SourceCatalog`
598 The reference catalog of sources to measure.
599 refWcs : `lsst.afw.image.SkyWcs`
600 The WCS for the references.
602 Optional unique exposureId used for random seed in measurement
607 result : `lsst.pipe.base.Struct`
608 Structure with fields:
611 Catalog of forced measurement results
612 (`lsst.afw.table.SourceCatalog`).
614 self.measurement.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
615 if self.config.doApCorr:
616 apCorrMap = exposure.getInfo().getApCorrMap()
617 if apCorrMap
is None:
618 self.log.
warning(
"Forced exposure image does not have valid aperture correction; skipping.")
620 self.applyApCorr.run(
624 self.catalogCalculation.run(measCat)
626 return pipeBase.Struct(measCat=measCat)
629 """Attach footprints to blank sources prior to measurements.
633 `~lsst.afw.detection.Footprint` objects for forced photometry must
634 be in the pixel coordinate system of the image being measured, while
635 the actual detections may start out in a different coordinate system.
637 Subclasses of this class may implement this method to define how
638 those `~lsst.afw.detection.Footprint` objects should be generated.
640 This default implementation transforms depends on the
641 ``footprintSource`` configuration parameter.
643 if self.
config.footprintSource ==
"transformed":
644 return self.measurement.attachTransformedFootprints(sources, refCat, exposure, refWcs)
645 elif self.
config.footprintSource ==
"psf":
646 return self.measurement.attachPsfShapeFootprints(sources, exposure,
647 scaling=self.
config.psfFootprintScaling)
651 dimensions=(
"instrument",
"visit",
"detector",
"skymap",
"tract"),
652 defaultTemplates={
"inputCoaddName":
"goodSeeing",
653 "inputName":
"calexp",
654 "skyWcsName":
"gbdesAstrometricFit",
655 "photoCalibName":
"fgcm"},
657 deprecatedTemplates={
658 "skyWcsName":
"Deprecated; will be removed after v26.",
659 "photoCalibName":
"Deprecated; will be removed after v26."
662 doc=
"Catalog of positions at which to force photometry.",
663 name=
"{inputCoaddName}Diff_fullDiaObjTable",
664 storageClass=
"DataFrame",
665 dimensions=[
"skymap",
"tract",
"patch"],
670 doc=
"Input exposure to perform photometry on.",
672 storageClass=
"ExposureF",
673 dimensions=[
"instrument",
"visit",
"detector"],
676 doc=
"Input Sky Correction to be subtracted from the calexp if doApplySkyCorr=True",
678 storageClass=
"Background",
679 dimensions=(
"instrument",
"visit",
"detector"),
681 visitSummary = cT.Input(
682 doc=
"Input visit-summary catalog with updated calibration objects.",
683 name=
"finalVisitSummary",
684 storageClass=
"ExposureCatalog",
685 dimensions=(
"instrument",
"visit"),
687 externalSkyWcsTractCatalog = cT.Input(
688 doc=(
"Per-tract, per-visit wcs calibrations. These catalogs use the detector "
689 "id for the catalog id, sorted on id for fast lookup."),
690 name=
"{skyWcsName}SkyWcsCatalog",
691 storageClass=
"ExposureCatalog",
692 dimensions=[
"instrument",
"visit",
"tract"],
694 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
696 externalSkyWcsGlobalCatalog = cT.Input(
697 doc=(
"Per-visit wcs calibrations computed globally (with no tract information). "
698 "These catalogs use the detector id for the catalog id, sorted on id for "
700 name=
"{skyWcsName}SkyWcsCatalog",
701 storageClass=
"ExposureCatalog",
702 dimensions=[
"instrument",
"visit"],
704 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
706 externalPhotoCalibTractCatalog = cT.Input(
707 doc=(
"Per-tract, per-visit photometric calibrations. These catalogs use the "
708 "detector id for the catalog id, sorted on id for fast lookup."),
709 name=
"{photoCalibName}PhotoCalibCatalog",
710 storageClass=
"ExposureCatalog",
711 dimensions=[
"instrument",
"visit",
"tract"],
713 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
715 externalPhotoCalibGlobalCatalog = cT.Input(
716 doc=(
"Per-visit photometric calibrations computed globally (with no tract "
717 "information). These catalogs use the detector id for the catalog id, "
718 "sorted on id for fast lookup."),
719 name=
"{photoCalibName}PhotoCalibCatalog",
720 storageClass=
"ExposureCatalog",
721 dimensions=[
"instrument",
"visit"],
723 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
725 finalizedPsfApCorrCatalog = cT.Input(
726 doc=(
"Per-visit finalized psf models and aperture correction maps. "
727 "These catalogs use the detector id for the catalog id, "
728 "sorted on id for fast lookup."),
729 name=
"finalized_psf_ap_corr_catalog",
730 storageClass=
"ExposureCatalog",
731 dimensions=[
"instrument",
"visit"],
733 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
736 doc=
"Output forced photometry catalog.",
737 name=
"forced_src_diaObject",
738 storageClass=
"SourceCatalog",
739 dimensions=[
"instrument",
"visit",
"detector",
"skymap",
"tract"],
741 outputSchema = cT.InitOutput(
742 doc=
"Schema for the output forced measurement catalogs.",
743 name=
"forced_src_diaObject_schema",
744 storageClass=
"SourceCatalog",
747 def __init__(self, *, config=None):
748 super().__init__(config=config)
749 if not config.doApplySkyCorr:
750 self.inputs.remove(
"skyCorr")
751 if config.doApplyExternalSkyWcs:
752 if config.useGlobalExternalSkyWcs:
753 self.inputs.remove(
"externalSkyWcsTractCatalog")
755 self.inputs.remove(
"externalSkyWcsGlobalCatalog")
757 self.inputs.remove(
"externalSkyWcsTractCatalog")
758 self.inputs.remove(
"externalSkyWcsGlobalCatalog")
759 if config.doApplyExternalPhotoCalib:
760 if config.useGlobalExternalPhotoCalib:
761 self.inputs.remove(
"externalPhotoCalibTractCatalog")
763 self.inputs.remove(
"externalPhotoCalibGlobalCatalog")
765 self.inputs.remove(
"externalPhotoCalibTractCatalog")
766 self.inputs.remove(
"externalPhotoCalibGlobalCatalog")
767 if not config.doApplyFinalizedPsf:
768 self.inputs.remove(
"finalizedPsfApCorrCatalog")
772 pipelineConnections=ForcedPhotCcdFromDataFrameConnections):
773 def setDefaults(self):
774 super().setDefaults()
775 self.footprintSource =
"psf"
776 self.measurement.doReplaceWithNoise =
False
779 self.measurement.plugins.names = [
"base_PixelFlags",
780 "base_TransformedCentroidFromCoord",
782 "base_LocalBackground",
783 "base_LocalPhotoCalib",
786 self.measurement.slots.shape =
None
789 self.catalogCalculation.plugins.names = []
791 self.measurement.copyColumns = {
'id':
'diaObjectId',
'coord_ra':
'coord_ra',
'coord_dec':
'coord_dec'}
792 self.measurement.slots.centroid =
"base_TransformedCentroidFromCoord"
793 self.measurement.slots.psfFlux =
"base_PsfFlux"
797 if self.footprintSource ==
"transformed":
798 raise ValueError(
"Cannot transform footprints from reference catalog, "
799 "because DataFrames can't hold footprints.")
803 """Force Photometry on a per-detector exposure with coords from a DataFrame
805 Uses input from a DataFrame instead of SourceCatalog
806 like the base class ForcedPhotCcd does.
807 Writes out a SourceCatalog so that the downstream
808 WriteForcedSourceTableTask can be reused with output from this Task.
810 _DefaultName =
"forcedPhotCcdFromDataFrame"
811 ConfigClass = ForcedPhotCcdFromDataFrameConfig
813 def __init__(self, refSchema=None, initInputs=None, **kwargs):
815 pipeBase.PipelineTask.__init__(self, **kwargs)
821 self.makeSubtask(
'catalogCalculation', schema=self.measurement.schema)
825 inputs = butlerQC.get(inputRefs)
828 inputs[
'refWcs'] =
None
831 skyCorr = inputs.pop(
'skyCorr',
None)
832 if self.config.useGlobalExternalSkyWcs:
833 externalSkyWcsCatalog = inputs.pop(
'externalSkyWcsGlobalCatalog',
None)
835 externalSkyWcsCatalog = inputs.pop(
'externalSkyWcsTractCatalog',
None)
836 if self.config.useGlobalExternalPhotoCalib:
837 externalPhotoCalibCatalog = inputs.pop(
'externalPhotoCalibGlobalCatalog',
None)
839 externalPhotoCalibCatalog = inputs.pop(
'externalPhotoCalibTractCatalog',
None)
840 finalizedPsfApCorrCatalog = inputs.pop(
'finalizedPsfApCorrCatalog',
None)
842 inputs[
'exposure'] = self.prepareCalibratedExposure(
845 externalSkyWcsCatalog=externalSkyWcsCatalog,
846 externalPhotoCalibCatalog=externalPhotoCalibCatalog,
847 finalizedPsfApCorrCatalog=finalizedPsfApCorrCatalog,
848 visitSummary=inputs.pop(
"visitSummary"),
851 self.log.
info(
"Filtering ref cats: %s",
','.join([str(i.dataId)
for i
in inputs[
'refCat']]))
852 if inputs[
"exposure"].getWcs()
is not None:
853 refCat = self.df2RefCat([i.get(parameters={
"columns": [
'diaObjectId',
'ra',
'dec']})
854 for i
in inputs[
'refCat']],
855 inputs[
'exposure'].getBBox(), inputs[
'exposure'].getWcs())
856 inputs[
'refCat'] = refCat
858 inputs[
'measCat'], inputs[
'exposureId'] = self.generateMeasCat(
859 inputRefs.exposure.dataId, inputs[
'exposure'], inputs[
'refCat'], inputs[
'refWcs']
863 self.attachFootprints(inputs[
"measCat"], inputs[
"refCat"], inputs[
"exposure"], inputs[
"refWcs"])
864 outputs = self.run(**inputs)
866 butlerQC.put(outputs, outputRefs)
868 self.log.
info(
"No WCS for %s. Skipping and no %s catalog will be written.",
869 butlerQC.quantum.dataId, outputRefs.measCat.datasetType.name)
872 """Convert list of DataFrames to reference catalog
874 Concatenate list of DataFrames presumably from multiple patches and
875 downselect rows that overlap the exposureBBox using the exposureWcs.
879 dfList : `list` of `pandas.DataFrame`
880 Each element containst diaObjects with ra/dec position in degrees
881 Columns 'diaObjectId', 'ra', 'dec' are expected
882 exposureBBox : `lsst.geom.Box2I`
883 Bounding box on which to select rows that overlap
884 exposureWcs : `lsst.afw.geom.SkyWcs`
885 World coordinate system to convert sky coords in ref cat to
886 pixel coords with which to compare with exposureBBox
890 refCat : `lsst.afw.table.SourceTable`
891 Source Catalog with minimal schema that overlaps exposureBBox
893 df = pd.concat(dfList)
896 mapping = exposureWcs.getTransform().getMapping()
897 x, y = mapping.applyInverse(np.array(df[[
'ra',
'dec']].values*2*np.pi/360).T)
899 refCat = self.df2SourceCat(df[inBBox])
903 """Create minimal schema SourceCatalog from a pandas DataFrame.
905 The forced measurement subtask expects this as input.
909 df : `pandas.DataFrame`
910 DiaObjects with locations and ids.
914 outputCatalog : `lsst.afw.table.SourceTable`
915 Output catalog with minimal schema.
919 outputCatalog.reserve(
len(df))
921 for diaObjectId, ra, dec
in df[[
'ra',
'dec']].
itertuples():
922 outputRecord = outputCatalog.addNew()
923 outputRecord.setId(diaObjectId)
static Schema makeMinimalSchema()
static Key< RecordId > getParentKey()
df2RefCat(self, dfList, exposureBBox, exposureWcs)
runQuantum(self, butlerQC, inputRefs, outputRefs)