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")
184class ForcedPhotCcdConfig(pipeBase.PipelineTaskConfig,
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.
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)
379 def prepareCalibratedExposure(self, exposure, skyCorr=None, externalSkyWcsCatalog=None,
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.
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.
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.
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
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()
499 def mergeAndFilterReferences(self, exposure, refCats, refWcs):
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.
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.
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.
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.
593 The measurement catalog, based on the sources listed in the
596 The measurement image upon which to perform forced detection.
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
614 self.measurement.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
615 if self.config.doApCorr:
616 self.applyApCorr.run(
618 apCorrMap=exposure.getInfo().getApCorrMap()
620 self.catalogCalculation.run(measCat)
622 return pipeBase.Struct(measCat=measCat)
624 def attachFootprints(self, sources, refCat, exposure, refWcs):
625 """Attach footprints to blank sources prior to measurements.
630 be
in the pixel coordinate system of the image being measured,
while
631 the actual detections may start out
in a different coordinate system.
633 Subclasses of this
class may implement this method to define how
636 This default implementation transforms depends on the
637 ``footprintSource`` configuration parameter.
639 if self.
config.footprintSource ==
"transformed":
640 return self.measurement.attachTransformedFootprints(sources, refCat, exposure, refWcs)
641 elif self.
config.footprintSource ==
"psf":
642 return self.measurement.attachPsfShapeFootprints(sources, exposure,
643 scaling=self.
config.psfFootprintScaling)
646class ForcedPhotCcdFromDataFrameConnections(PipelineTaskConnections,
647 dimensions=(
"instrument",
"visit",
"detector",
"skymap",
"tract"),
648 defaultTemplates={
"inputCoaddName":
"goodSeeing",
649 "inputName":
"calexp",
650 "skyWcsName":
"gbdesAstrometricFit",
651 "photoCalibName":
"fgcm"},
653 deprecatedTemplates={
654 "skyWcsName":
"Deprecated; will be removed after v26.",
655 "photoCalibName":
"Deprecated; will be removed after v26."
658 doc=
"Catalog of positions at which to force photometry.",
659 name=
"{inputCoaddName}Diff_fullDiaObjTable",
660 storageClass=
"DataFrame",
661 dimensions=[
"skymap",
"tract",
"patch"],
666 doc=
"Input exposure to perform photometry on.",
668 storageClass=
"ExposureF",
669 dimensions=[
"instrument",
"visit",
"detector"],
672 doc=
"Input Sky Correction to be subtracted from the calexp if doApplySkyCorr=True",
674 storageClass=
"Background",
675 dimensions=(
"instrument",
"visit",
"detector"),
677 visitSummary = cT.Input(
678 doc=
"Input visit-summary catalog with updated calibration objects.",
679 name=
"finalVisitSummary",
680 storageClass=
"ExposureCatalog",
681 dimensions=(
"instrument",
"visit"),
683 externalSkyWcsTractCatalog = cT.Input(
684 doc=(
"Per-tract, per-visit wcs calibrations. These catalogs use the detector "
685 "id for the catalog id, sorted on id for fast lookup."),
686 name=
"{skyWcsName}SkyWcsCatalog",
687 storageClass=
"ExposureCatalog",
688 dimensions=[
"instrument",
"visit",
"tract"],
690 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
692 externalSkyWcsGlobalCatalog = cT.Input(
693 doc=(
"Per-visit wcs calibrations computed globally (with no tract information). "
694 "These catalogs use the detector id for the catalog id, sorted on id for "
696 name=
"{skyWcsName}SkyWcsCatalog",
697 storageClass=
"ExposureCatalog",
698 dimensions=[
"instrument",
"visit"],
700 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
702 externalPhotoCalibTractCatalog = cT.Input(
703 doc=(
"Per-tract, per-visit photometric calibrations. These catalogs use the "
704 "detector id for the catalog id, sorted on id for fast lookup."),
705 name=
"{photoCalibName}PhotoCalibCatalog",
706 storageClass=
"ExposureCatalog",
707 dimensions=[
"instrument",
"visit",
"tract"],
709 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
711 externalPhotoCalibGlobalCatalog = cT.Input(
712 doc=(
"Per-visit photometric calibrations computed globally (with no tract "
713 "information). These catalogs use the detector id for the catalog id, "
714 "sorted on id for fast lookup."),
715 name=
"{photoCalibName}PhotoCalibCatalog",
716 storageClass=
"ExposureCatalog",
717 dimensions=[
"instrument",
"visit"],
719 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
721 finalizedPsfApCorrCatalog = cT.Input(
722 doc=(
"Per-visit finalized psf models and aperture correction maps. "
723 "These catalogs use the detector id for the catalog id, "
724 "sorted on id for fast lookup."),
725 name=
"finalized_psf_ap_corr_catalog",
726 storageClass=
"ExposureCatalog",
727 dimensions=[
"instrument",
"visit"],
729 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
732 doc=
"Output forced photometry catalog.",
733 name=
"forced_src_diaObject",
734 storageClass=
"SourceCatalog",
735 dimensions=[
"instrument",
"visit",
"detector",
"skymap",
"tract"],
737 outputSchema = cT.InitOutput(
738 doc=
"Schema for the output forced measurement catalogs.",
739 name=
"forced_src_diaObject_schema",
740 storageClass=
"SourceCatalog",
743 def __init__(self, *, config=None):
744 super().__init__(config=config)
745 if not config.doApplySkyCorr:
746 self.inputs.remove(
"skyCorr")
747 if config.doApplyExternalSkyWcs:
748 if config.useGlobalExternalSkyWcs:
749 self.inputs.remove(
"externalSkyWcsTractCatalog")
751 self.inputs.remove(
"externalSkyWcsGlobalCatalog")
753 self.inputs.remove(
"externalSkyWcsTractCatalog")
754 self.inputs.remove(
"externalSkyWcsGlobalCatalog")
755 if config.doApplyExternalPhotoCalib:
756 if config.useGlobalExternalPhotoCalib:
757 self.inputs.remove(
"externalPhotoCalibTractCatalog")
759 self.inputs.remove(
"externalPhotoCalibGlobalCatalog")
761 self.inputs.remove(
"externalPhotoCalibTractCatalog")
762 self.inputs.remove(
"externalPhotoCalibGlobalCatalog")
763 if not config.doApplyFinalizedPsf:
764 self.inputs.remove(
"finalizedPsfApCorrCatalog")
767class ForcedPhotCcdFromDataFrameConfig(ForcedPhotCcdConfig,
768 pipelineConnections=ForcedPhotCcdFromDataFrameConnections):
769 def setDefaults(self):
770 super().setDefaults()
771 self.footprintSource =
"psf"
772 self.measurement.doReplaceWithNoise =
False
775 self.measurement.plugins.names = [
"base_PixelFlags",
776 "base_TransformedCentroidFromCoord",
778 "base_LocalBackground",
779 "base_LocalPhotoCalib",
782 self.measurement.slots.shape =
None
785 self.catalogCalculation.plugins.names = []
787 self.measurement.copyColumns = {
'id':
'diaObjectId',
'coord_ra':
'coord_ra',
'coord_dec':
'coord_dec'}
788 self.measurement.slots.centroid =
"base_TransformedCentroidFromCoord"
789 self.measurement.slots.psfFlux =
"base_PsfFlux"
793 if self.footprintSource ==
"transformed":
794 raise ValueError(
"Cannot transform footprints from reference catalog, "
795 "because DataFrames can't hold footprints.")
798class ForcedPhotCcdFromDataFrameTask(ForcedPhotCcdTask):
799 """Force Photometry on a per-detector exposure with coords from a DataFrame
801 Uses input from a DataFrame instead of SourceCatalog
802 like the base
class ForcedPhotCcd does.
803 Writes out a SourceCatalog so that the downstream
804 WriteForcedSourceTableTask can be reused
with output
from this Task.
806 _DefaultName = "forcedPhotCcdFromDataFrame"
807 ConfigClass = ForcedPhotCcdFromDataFrameConfig
809 def __init__(self, refSchema=None, initInputs=None, **kwargs):
811 pipeBase.PipelineTask.__init__(self, **kwargs)
817 self.makeSubtask(
'catalogCalculation', schema=self.measurement.schema)
821 inputs = butlerQC.get(inputRefs)
824 inputs[
'refWcs'] =
None
827 skyCorr = inputs.pop(
'skyCorr',
None)
828 if self.config.useGlobalExternalSkyWcs:
829 externalSkyWcsCatalog = inputs.pop(
'externalSkyWcsGlobalCatalog',
None)
831 externalSkyWcsCatalog = inputs.pop(
'externalSkyWcsTractCatalog',
None)
832 if self.config.useGlobalExternalPhotoCalib:
833 externalPhotoCalibCatalog = inputs.pop(
'externalPhotoCalibGlobalCatalog',
None)
835 externalPhotoCalibCatalog = inputs.pop(
'externalPhotoCalibTractCatalog',
None)
836 finalizedPsfApCorrCatalog = inputs.pop(
'finalizedPsfApCorrCatalog',
None)
838 inputs[
'exposure'] = self.prepareCalibratedExposure(
841 externalSkyWcsCatalog=externalSkyWcsCatalog,
842 externalPhotoCalibCatalog=externalPhotoCalibCatalog,
843 finalizedPsfApCorrCatalog=finalizedPsfApCorrCatalog,
844 visitSummary=inputs.pop(
"visitSummary"),
847 self.log.info(
"Filtering ref cats: %s",
','.join([str(i.dataId)
for i
in inputs[
'refCat']]))
848 if inputs[
"exposure"].getWcs()
is not None:
849 refCat = self.df2RefCat([i.get(parameters={
"columns": [
'diaObjectId',
'ra',
'dec']})
850 for i
in inputs[
'refCat']],
851 inputs[
'exposure'].getBBox(), inputs[
'exposure'].getWcs())
852 inputs[
'refCat'] = refCat
854 inputs[
'measCat'], inputs[
'exposureId'] = self.generateMeasCat(
855 inputRefs.exposure.dataId, inputs[
'exposure'], inputs[
'refCat'], inputs[
'refWcs']
859 self.attachFootprints(inputs[
"measCat"], inputs[
"refCat"], inputs[
"exposure"], inputs[
"refWcs"])
860 outputs = self.run(**inputs)
862 butlerQC.put(outputs, outputRefs)
864 self.log.info(
"No WCS for %s. Skipping and no %s catalog will be written.",
865 butlerQC.quantum.dataId, outputRefs.measCat.datasetType.name)
868 """Convert list of DataFrames to reference catalog
870 Concatenate list of DataFrames presumably from multiple patches
and
871 downselect rows that overlap the exposureBBox using the exposureWcs.
875 dfList : `list` of `pandas.DataFrame`
876 Each element containst diaObjects
with ra/dec position
in degrees
877 Columns
'diaObjectId',
'ra',
'dec' are expected
879 Bounding box on which to select rows that overlap
881 World coordinate system to convert sky coords
in ref cat to
882 pixel coords
with which to compare
with exposureBBox
887 Source Catalog
with minimal schema that overlaps exposureBBox
889 df = pd.concat(dfList)
892 mapping = exposureWcs.getTransform().getMapping()
893 x, y = mapping.applyInverse(np.array(df[[
'ra',
'dec']].values*2*np.pi/360).T)
895 refCat = self.df2SourceCat(df[inBBox])
899 """Create minimal schema SourceCatalog from a pandas DataFrame.
901 The forced measurement subtask expects this as input.
905 df : `pandas.DataFrame`
906 DiaObjects
with locations
and ids.
911 Output catalog
with minimal schema.
915 outputCatalog.reserve(len(df))
917 for diaObjectId, ra, dec
in df[[
'ra',
'dec']].itertuples():
918 outputRecord = outputCatalog.addNew()
919 outputRecord.setId(diaObjectId)
static Schema makeMinimalSchema()
static Key< RecordId > getParentKey()
df2RefCat(self, dfList, exposureBBox, exposureWcs)
runQuantum(self, butlerQC, inputRefs, outputRefs)