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
295 self.measurement.plugins[
'base_PixelFlags'].masksFpAnywhere = [
'STREAK']
296 self.measurement.plugins[
'base_PixelFlags'].masksFpCenter = [
'STREAK']
299 self.catalogCalculation.plugins.names = []
303 """A pipeline task for performing forced measurement on CCD images.
307 refSchema : `lsst.afw.table.Schema`, optional
308 The schema of the reference catalog, passed to the constructor of the
309 references subtask. Optional, but must be specified if ``initInputs``
310 is not; if both are specified, ``initInputs`` takes precedence.
312 Dictionary that can contain a key ``inputSchema`` containing the
313 schema. If present will override the value of ``refSchema``.
315 Keyword arguments are passed to the supertask constructor.
318 ConfigClass = ForcedPhotCcdConfig
319 _DefaultName =
"forcedPhotCcd"
322 def __init__(self, refSchema=None, initInputs=None, **kwargs):
323 super().__init__(**kwargs)
325 if initInputs
is not None:
326 refSchema = initInputs[
'inputSchema'].schema
328 if refSchema
is None:
329 raise ValueError(
"No reference schema provided.")
331 self.makeSubtask(
"measurement", refSchema=refSchema)
335 if self.config.doApCorr:
336 self.makeSubtask(
"applyApCorr", schema=self.measurement.schema)
337 self.makeSubtask(
'catalogCalculation', schema=self.measurement.schema)
340 def runQuantum(self, butlerQC, inputRefs, outputRefs):
341 inputs = butlerQC.get(inputRefs)
343 tract = butlerQC.quantum.dataId[
'tract']
344 skyMap = inputs.pop(
'skyMap')
345 inputs[
'refWcs'] = skyMap[tract].getWcs()
348 skyCorr = inputs.pop(
'skyCorr',
None)
349 if self.config.useGlobalExternalSkyWcs:
350 externalSkyWcsCatalog = inputs.pop(
'externalSkyWcsGlobalCatalog',
None)
352 externalSkyWcsCatalog = inputs.pop(
'externalSkyWcsTractCatalog',
None)
353 if self.config.useGlobalExternalPhotoCalib:
354 externalPhotoCalibCatalog = inputs.pop(
'externalPhotoCalibGlobalCatalog',
None)
356 externalPhotoCalibCatalog = inputs.pop(
'externalPhotoCalibTractCatalog',
None)
357 finalizedPsfApCorrCatalog = inputs.pop(
'finalizedPsfApCorrCatalog',
None)
359 inputs[
'exposure'] = self.prepareCalibratedExposure(
362 externalSkyWcsCatalog=externalSkyWcsCatalog,
363 externalPhotoCalibCatalog=externalPhotoCalibCatalog,
364 finalizedPsfApCorrCatalog=finalizedPsfApCorrCatalog,
365 visitSummary=inputs.pop(
"visitSummary"),
368 inputs[
'refCat'] = self.mergeAndFilterReferences(inputs[
'exposure'], inputs[
'refCat'],
371 if inputs[
'refCat']
is None:
372 self.log.
info(
"No WCS for exposure %s. No %s catalog will be written.",
373 butlerQC.quantum.dataId, outputRefs.measCat.datasetType.name)
375 inputs[
'measCat'], inputs[
'exposureId'] = self.generateMeasCat(inputRefs.exposure.dataId,
377 inputs[
'refCat'], inputs[
'refWcs'])
378 self.attachFootprints(inputs[
'measCat'], inputs[
'refCat'], inputs[
'exposure'], inputs[
'refWcs'])
379 outputs = self.run(**inputs)
380 butlerQC.put(outputs, outputRefs)
383 externalPhotoCalibCatalog=None, finalizedPsfApCorrCatalog=None,
385 """Prepare a calibrated exposure and apply external calibrations
386 and sky corrections if so configured.
390 exposure : `lsst.afw.image.exposure.Exposure`
391 Input exposure to adjust calibrations.
392 skyCorr : `lsst.afw.math.backgroundList`, optional
393 Sky correction frame to apply if doApplySkyCorr=True.
394 externalSkyWcsCatalog : `lsst.afw.table.ExposureCatalog`, optional
395 Exposure catalog with external skyWcs to be applied if
396 config.doApplyExternalSkyWcs=True. Catalog uses the detector id
397 for the catalog id, sorted on id for fast lookup. Deprecated in
398 favor of ``visitSummary`` and will be removed after v26.
399 externalPhotoCalibCatalog : `lsst.afw.table.ExposureCatalog`, optional
400 Exposure catalog with external photoCalib to be applied if
401 config.doApplyExternalPhotoCalib=True. Catalog uses the detector
402 id for the catalog id, sorted on id for fast lookup. Deprecated in
403 favor of ``visitSummary`` and will be removed after v26.
404 finalizedPsfApCorrCatalog : `lsst.afw.table.ExposureCatalog`, optional
405 Exposure catalog with finalized psf models and aperture correction
406 maps to be applied if config.doApplyFinalizedPsf=True. Catalog
407 uses the detector id for the catalog id, sorted on id for fast
408 lookup. Deprecated in favor of ``visitSummary`` and will be removed
410 visitSummary : `lsst.afw.table.ExposureCatalog`, optional
411 Exposure catalog with update calibrations; any not-None calibration
412 objects attached will be used. These are applied first and may be
413 overridden by other arguments.
417 exposure : `lsst.afw.image.exposure.Exposure`
418 Exposure with adjusted calibrations.
420 detectorId = exposure.getInfo().getDetector().getId()
422 if visitSummary
is not None:
423 row = visitSummary.find(detectorId)
425 raise RuntimeError(f
"Detector id {detectorId} not found in visitSummary.")
426 if (photoCalib := row.getPhotoCalib())
is not None:
427 exposure.setPhotoCalib(photoCalib)
428 if (skyWcs := row.getWcs())
is not None:
429 exposure.setWcs(skyWcs)
430 if (psf := row.getPsf())
is not None:
432 if (apCorrMap := row.getApCorrMap())
is not None:
433 exposure.info.setApCorrMap(apCorrMap)
435 if externalPhotoCalibCatalog
is not None:
438 "The 'externalPhotoCalibCatalog' argument is deprecated in favor of 'visitSummary' and will "
439 "be removed after v26.",
441 stacklevel=find_outside_stacklevel(
"lsst.meas.base"),
443 row = externalPhotoCalibCatalog.find(detectorId)
445 self.log.
warning(
"Detector id %s not found in externalPhotoCalibCatalog; "
446 "Using original photoCalib.", detectorId)
448 photoCalib = row.getPhotoCalib()
449 if photoCalib
is None:
450 self.log.
warning(
"Detector id %s has None for photoCalib in externalPhotoCalibCatalog; "
451 "Using original photoCalib.", detectorId)
453 exposure.setPhotoCalib(photoCalib)
455 if externalSkyWcsCatalog
is not None:
458 "The 'externalSkyWcsCatalog' argument is deprecated in favor of 'visitSummary' and will "
459 "be removed after v26.",
461 stacklevel=find_outside_stacklevel(
"lsst.meas.base"),
463 row = externalSkyWcsCatalog.find(detectorId)
465 self.log.
warning(
"Detector id %s not found in externalSkyWcsCatalog; "
466 "Using original skyWcs.", detectorId)
468 skyWcs = row.getWcs()
470 self.log.
warning(
"Detector id %s has None for skyWcs in externalSkyWcsCatalog; "
471 "Using original skyWcs.", detectorId)
473 exposure.setWcs(skyWcs)
475 if finalizedPsfApCorrCatalog
is not None:
478 "The 'finalizedPsfApCorrCatalog' argument is deprecated in favor of 'visitSummary' and will "
479 "be removed after v26.",
481 stacklevel=find_outside_stacklevel(
"lsst.meas.base"),
483 row = finalizedPsfApCorrCatalog.find(detectorId)
485 self.log.
warning(
"Detector id %s not found in finalizedPsfApCorrCatalog; "
486 "Using original psf.", detectorId)
489 apCorrMap = row.getApCorrMap()
490 if psf
is None or apCorrMap
is None:
491 self.log.
warning(
"Detector id %s has None for psf/apCorrMap in "
492 "finalizedPsfApCorrCatalog; Using original psf.", detectorId)
495 exposure.setApCorrMap(apCorrMap)
497 if skyCorr
is not None:
498 exposure.maskedImage -= skyCorr.getImage()
503 """Filter reference catalog so that all sources are within the
504 boundaries of the exposure.
508 exposure : `lsst.afw.image.exposure.Exposure`
509 Exposure to generate the catalog for.
510 refCats : sequence of `lsst.daf.butler.DeferredDatasetHandle`
511 Handles for catalogs of shapes and positions at which to force
513 refWcs : `lsst.afw.image.SkyWcs`
514 Reference world coordinate system.
518 refSources : `lsst.afw.table.SourceCatalog`
519 Filtered catalog of forced sources to measure.
523 The majority of this code is based on the methods of
524 lsst.meas.algorithms.loadReferenceObjects.ReferenceObjectLoader
531 expWcs = exposure.getWcs()
533 self.log.
info(
"Exposure has no WCS. Returning None for mergedRefCat.")
535 expRegion = exposure.getBBox(lsst.afw.image.PARENT)
537 expBoxCorners = expBBox.getCorners()
538 expSkyCorners = [expWcs.pixelToSky(corner).getVector()
for
539 corner
in expBoxCorners]
547 for refCat
in refCats:
548 refCat = refCat.get()
549 if mergedRefCat
is None:
552 for record
in refCat:
553 if (expPolygon.contains(record.getCoord().getVector())
and record.getParent()
555 record.setFootprint(record.getFootprint())
556 mergedRefCat.append(record)
557 containedIds.add(record.getId())
558 if mergedRefCat
is None:
559 raise RuntimeError(
"No reference objects for forced photometry.")
563 def generateMeasCat(self, dataId, exposure, refCat, refWcs):
564 """Generate a measurement catalog.
568 dataId : `lsst.daf.butler.DataCoordinate`
569 Butler data ID for this image, with ``{visit, detector}`` keys.
570 exposure : `lsst.afw.image.exposure.Exposure`
571 Exposure to generate the catalog for.
572 refCat : `lsst.afw.table.SourceCatalog`
573 Catalog of shapes and positions at which to force photometry.
574 refWcs : `lsst.afw.image.SkyWcs`
575 Reference world coordinate system.
576 This parameter is not currently used.
580 measCat : `lsst.afw.table.SourceCatalog`
581 Catalog of forced sources to measure.
583 Unique binary id associated with the input exposure
585 id_generator = self.config.idGenerator.apply(dataId)
586 measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
587 idFactory=id_generator.make_table_id_factory())
588 return measCat, id_generator.catalog_id
590 def run(self, measCat, exposure, refCat, refWcs, exposureId=None):
591 """Perform forced measurement on a single exposure.
595 measCat : `lsst.afw.table.SourceCatalog`
596 The measurement catalog, based on the sources listed in the
598 exposure : `lsst.afw.image.Exposure`
599 The measurement image upon which to perform forced detection.
600 refCat : `lsst.afw.table.SourceCatalog`
601 The reference catalog of sources to measure.
602 refWcs : `lsst.afw.image.SkyWcs`
603 The WCS for the references.
605 Optional unique exposureId used for random seed in measurement
610 result : `lsst.pipe.base.Struct`
611 Structure with fields:
614 Catalog of forced measurement results
615 (`lsst.afw.table.SourceCatalog`).
617 self.measurement.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
618 if self.config.doApCorr:
619 apCorrMap = exposure.getInfo().getApCorrMap()
620 if apCorrMap
is None:
621 self.log.
warning(
"Forced exposure image does not have valid aperture correction; skipping.")
623 self.applyApCorr.run(
627 self.catalogCalculation.run(measCat)
629 return pipeBase.Struct(measCat=measCat)
632 """Attach footprints to blank sources prior to measurements.
636 `~lsst.afw.detection.Footprint` objects for forced photometry must
637 be in the pixel coordinate system of the image being measured, while
638 the actual detections may start out in a different coordinate system.
640 Subclasses of this class may implement this method to define how
641 those `~lsst.afw.detection.Footprint` objects should be generated.
643 This default implementation transforms depends on the
644 ``footprintSource`` configuration parameter.
646 if self.
config.footprintSource ==
"transformed":
647 return self.measurement.attachTransformedFootprints(sources, refCat, exposure, refWcs)
648 elif self.
config.footprintSource ==
"psf":
649 return self.measurement.attachPsfShapeFootprints(sources, exposure,
650 scaling=self.
config.psfFootprintScaling)
654 dimensions=(
"instrument",
"visit",
"detector",
"skymap",
"tract"),
655 defaultTemplates={
"inputCoaddName":
"goodSeeing",
656 "inputName":
"calexp",
657 "skyWcsName":
"gbdesAstrometricFit",
658 "photoCalibName":
"fgcm"},
660 deprecatedTemplates={
661 "skyWcsName":
"Deprecated; will be removed after v26.",
662 "photoCalibName":
"Deprecated; will be removed after v26."
665 doc=
"Catalog of positions at which to force photometry.",
666 name=
"{inputCoaddName}Diff_fullDiaObjTable",
667 storageClass=
"DataFrame",
668 dimensions=[
"skymap",
"tract",
"patch"],
673 doc=
"Input exposure to perform photometry on.",
675 storageClass=
"ExposureF",
676 dimensions=[
"instrument",
"visit",
"detector"],
679 doc=
"Input Sky Correction to be subtracted from the calexp if doApplySkyCorr=True",
681 storageClass=
"Background",
682 dimensions=(
"instrument",
"visit",
"detector"),
684 visitSummary = cT.Input(
685 doc=
"Input visit-summary catalog with updated calibration objects.",
686 name=
"finalVisitSummary",
687 storageClass=
"ExposureCatalog",
688 dimensions=(
"instrument",
"visit"),
690 externalSkyWcsTractCatalog = cT.Input(
691 doc=(
"Per-tract, per-visit wcs calibrations. These catalogs use the detector "
692 "id for the catalog id, sorted on id for fast lookup."),
693 name=
"{skyWcsName}SkyWcsCatalog",
694 storageClass=
"ExposureCatalog",
695 dimensions=[
"instrument",
"visit",
"tract"],
697 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
699 externalSkyWcsGlobalCatalog = cT.Input(
700 doc=(
"Per-visit wcs calibrations computed globally (with no tract information). "
701 "These catalogs use the detector id for the catalog id, sorted on id for "
703 name=
"{skyWcsName}SkyWcsCatalog",
704 storageClass=
"ExposureCatalog",
705 dimensions=[
"instrument",
"visit"],
707 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
709 externalPhotoCalibTractCatalog = cT.Input(
710 doc=(
"Per-tract, per-visit photometric calibrations. These catalogs use the "
711 "detector id for the catalog id, sorted on id for fast lookup."),
712 name=
"{photoCalibName}PhotoCalibCatalog",
713 storageClass=
"ExposureCatalog",
714 dimensions=[
"instrument",
"visit",
"tract"],
716 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
718 externalPhotoCalibGlobalCatalog = cT.Input(
719 doc=(
"Per-visit photometric calibrations computed globally (with no tract "
720 "information). These catalogs use the detector id for the catalog id, "
721 "sorted on id for fast lookup."),
722 name=
"{photoCalibName}PhotoCalibCatalog",
723 storageClass=
"ExposureCatalog",
724 dimensions=[
"instrument",
"visit"],
726 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
728 finalizedPsfApCorrCatalog = cT.Input(
729 doc=(
"Per-visit finalized psf models and aperture correction maps. "
730 "These catalogs use the detector id for the catalog id, "
731 "sorted on id for fast lookup."),
732 name=
"finalized_psf_ap_corr_catalog",
733 storageClass=
"ExposureCatalog",
734 dimensions=[
"instrument",
"visit"],
736 deprecated=
"Deprecated in favor of 'visitSummary'; will be removed after v26."
739 doc=
"Output forced photometry catalog.",
740 name=
"forced_src_diaObject",
741 storageClass=
"SourceCatalog",
742 dimensions=[
"instrument",
"visit",
"detector",
"skymap",
"tract"],
744 outputSchema = cT.InitOutput(
745 doc=
"Schema for the output forced measurement catalogs.",
746 name=
"forced_src_diaObject_schema",
747 storageClass=
"SourceCatalog",
750 def __init__(self, *, config=None):
751 super().__init__(config=config)
752 if not config.doApplySkyCorr:
753 self.inputs.remove(
"skyCorr")
754 if config.doApplyExternalSkyWcs:
755 if config.useGlobalExternalSkyWcs:
756 self.inputs.remove(
"externalSkyWcsTractCatalog")
758 self.inputs.remove(
"externalSkyWcsGlobalCatalog")
760 self.inputs.remove(
"externalSkyWcsTractCatalog")
761 self.inputs.remove(
"externalSkyWcsGlobalCatalog")
762 if config.doApplyExternalPhotoCalib:
763 if config.useGlobalExternalPhotoCalib:
764 self.inputs.remove(
"externalPhotoCalibTractCatalog")
766 self.inputs.remove(
"externalPhotoCalibGlobalCatalog")
768 self.inputs.remove(
"externalPhotoCalibTractCatalog")
769 self.inputs.remove(
"externalPhotoCalibGlobalCatalog")
770 if not config.doApplyFinalizedPsf:
771 self.inputs.remove(
"finalizedPsfApCorrCatalog")
775 pipelineConnections=ForcedPhotCcdFromDataFrameConnections):
776 def setDefaults(self):
777 super().setDefaults()
778 self.footprintSource =
"psf"
779 self.measurement.doReplaceWithNoise =
False
782 self.measurement.plugins.names = [
"base_PixelFlags",
783 "base_TransformedCentroidFromCoord",
785 "base_LocalBackground",
786 "base_LocalPhotoCalib",
789 self.measurement.slots.shape =
None
791 self.measurement.plugins[
'base_PixelFlags'].masksFpAnywhere = [
'STREAK']
792 self.measurement.plugins[
'base_PixelFlags'].masksFpCenter = [
'STREAK']
795 self.catalogCalculation.plugins.names = []
797 self.measurement.copyColumns = {
'id':
'diaObjectId',
'coord_ra':
'coord_ra',
'coord_dec':
'coord_dec'}
798 self.measurement.slots.centroid =
"base_TransformedCentroidFromCoord"
799 self.measurement.slots.psfFlux =
"base_PsfFlux"
803 if self.footprintSource ==
"transformed":
804 raise ValueError(
"Cannot transform footprints from reference catalog, "
805 "because DataFrames can't hold footprints.")
809 """Force Photometry on a per-detector exposure with coords from a DataFrame
811 Uses input from a DataFrame instead of SourceCatalog
812 like the base class ForcedPhotCcd does.
813 Writes out a SourceCatalog so that the downstream
814 WriteForcedSourceTableTask can be reused with output from this Task.
816 _DefaultName =
"forcedPhotCcdFromDataFrame"
817 ConfigClass = ForcedPhotCcdFromDataFrameConfig
819 def __init__(self, refSchema=None, initInputs=None, **kwargs):
821 pipeBase.PipelineTask.__init__(self, **kwargs)
827 self.makeSubtask(
'catalogCalculation', schema=self.measurement.schema)
831 inputs = butlerQC.get(inputRefs)
834 inputs[
'refWcs'] =
None
837 skyCorr = inputs.pop(
'skyCorr',
None)
838 if self.config.useGlobalExternalSkyWcs:
839 externalSkyWcsCatalog = inputs.pop(
'externalSkyWcsGlobalCatalog',
None)
841 externalSkyWcsCatalog = inputs.pop(
'externalSkyWcsTractCatalog',
None)
842 if self.config.useGlobalExternalPhotoCalib:
843 externalPhotoCalibCatalog = inputs.pop(
'externalPhotoCalibGlobalCatalog',
None)
845 externalPhotoCalibCatalog = inputs.pop(
'externalPhotoCalibTractCatalog',
None)
846 finalizedPsfApCorrCatalog = inputs.pop(
'finalizedPsfApCorrCatalog',
None)
848 inputs[
'exposure'] = self.prepareCalibratedExposure(
851 externalSkyWcsCatalog=externalSkyWcsCatalog,
852 externalPhotoCalibCatalog=externalPhotoCalibCatalog,
853 finalizedPsfApCorrCatalog=finalizedPsfApCorrCatalog,
854 visitSummary=inputs.pop(
"visitSummary"),
857 self.log.
info(
"Filtering ref cats: %s",
','.join([str(i.dataId)
for i
in inputs[
'refCat']]))
858 if inputs[
"exposure"].getWcs()
is not None:
859 refCat = self.df2RefCat([i.get(parameters={
"columns": [
'diaObjectId',
'ra',
'dec']})
860 for i
in inputs[
'refCat']],
861 inputs[
'exposure'].getBBox(), inputs[
'exposure'].getWcs())
862 inputs[
'refCat'] = refCat
864 inputs[
'measCat'], inputs[
'exposureId'] = self.generateMeasCat(
865 inputRefs.exposure.dataId, inputs[
'exposure'], inputs[
'refCat'], inputs[
'refWcs']
869 self.attachFootprints(inputs[
"measCat"], inputs[
"refCat"], inputs[
"exposure"], inputs[
"refWcs"])
870 outputs = self.run(**inputs)
872 butlerQC.put(outputs, outputRefs)
874 self.log.
info(
"No WCS for %s. Skipping and no %s catalog will be written.",
875 butlerQC.quantum.dataId, outputRefs.measCat.datasetType.name)
878 """Convert list of DataFrames to reference catalog
880 Concatenate list of DataFrames presumably from multiple patches and
881 downselect rows that overlap the exposureBBox using the exposureWcs.
885 dfList : `list` of `pandas.DataFrame`
886 Each element containst diaObjects with ra/dec position in degrees
887 Columns 'diaObjectId', 'ra', 'dec' are expected
888 exposureBBox : `lsst.geom.Box2I`
889 Bounding box on which to select rows that overlap
890 exposureWcs : `lsst.afw.geom.SkyWcs`
891 World coordinate system to convert sky coords in ref cat to
892 pixel coords with which to compare with exposureBBox
896 refCat : `lsst.afw.table.SourceTable`
897 Source Catalog with minimal schema that overlaps exposureBBox
899 df = pd.concat(dfList)
902 mapping = exposureWcs.getTransform().getMapping()
903 x, y = mapping.applyInverse(np.array(df[[
'ra',
'dec']].values*2*np.pi/360).T)
905 refCat = self.df2SourceCat(df[inBBox])
909 """Create minimal schema SourceCatalog from a pandas DataFrame.
911 The forced measurement subtask expects this as input.
915 df : `pandas.DataFrame`
916 DiaObjects with locations and ids.
920 outputCatalog : `lsst.afw.table.SourceTable`
921 Output catalog with minimal schema.
925 outputCatalog.reserve(
len(df))
927 for diaObjectId, ra, dec
in df[[
'ra',
'dec']].
itertuples():
928 outputRecord = outputCatalog.addNew()
929 outputRecord.setId(diaObjectId)
static Schema makeMinimalSchema()
static Key< RecordId > getParentKey()
df2RefCat(self, dfList, exposureBBox, exposureWcs)
runQuantum(self, butlerQC, inputRefs, outputRefs)