39import lsst.pipe.base.connectionTypes
as cT
44from .references
import MultiBandReferencesTask
45from .forcedMeasurement
import ForcedMeasurementTask
46from .applyApCorr
import ApplyApCorrTask
47from .catalogCalculation
import CatalogCalculationTask
50 from lsst.meas.mosaic
import applyMosaicResults
52 applyMosaicResults =
None
54__all__ = (
"PerTractCcdDataIdContainer",
"ForcedPhotCcdConfig",
"ForcedPhotCcdTask",
"imageOverlapsTract",
55 "ForcedPhotCcdFromDataFrameTask",
"ForcedPhotCcdFromDataFrameConfig")
59 """A data ID container which combines raw data IDs with a tract.
63 Required because we need to add "tract" to the raw data ID keys (defined
as
64 whatever we use
for ``src``) when no tract
is provided (so that the user
is
65 not required to know which tracts are spanned by the raw data ID).
67 This subclass of `~lsst.pipe.base.DataIdContainer` assumes that a calexp
is
68 being measured using the detection information, a set of reference
69 catalogs,
from the set of coadds which intersect
with the calexp. It needs
70 the calexp id (e.g. visit, raft, sensor), but
is also uses the tract to
71 decide what set of coadds to use. The references
from the tract whose
72 patches intersect
with the calexp are used.
76 """Make self.refList from self.idList
78 if self.datasetType
is None:
79 raise RuntimeError(
"Must call setDatasetType first")
80 log = logging.getLogger(__name__).getChild(
"PerTractCcdDataIdContainer")
82 visitTract = collections.defaultdict(set)
83 visitRefs = collections.defaultdict(list)
84 for dataId
in self.idList:
85 if "tract" not in dataId:
87 log.info(
"Reading WCS for components of dataId=%s to determine tracts", dict(dataId))
89 skymap = namespace.butler.get(namespace.config.coaddName +
"Coadd_skyMap")
91 for ref
in namespace.butler.subset(
"calexp", dataId=dataId):
92 if not ref.datasetExists(
"calexp"):
95 visit = ref.dataId[
"visit"]
96 visitRefs[visit].
append(ref)
98 md = ref.get(
"calexp_md", immediate=
True)
103 tract = skymap.findTract(wcs.pixelToSky(box.getCenter()))
105 visitTract[visit].add(tract.getId())
107 self.refList.extend(ref
for ref
in namespace.butler.subset(self.datasetType, dataId=dataId))
110 for visit, tractSet
in visitTract.items():
111 for ref
in visitRefs[visit]:
112 for tract
in tractSet:
113 self.refList.
append(namespace.butler.dataRef(datasetType=self.datasetType,
114 dataId=ref.dataId, tract=tract))
116 tractCounter = collections.Counter()
117 for tractSet
in visitTract.values():
118 tractCounter.update(tractSet)
119 log.info(
"Number of visits for each tract: %s", dict(tractCounter))
123 """Return whether the given bounding box overlaps the tract given a WCS.
128 TractInfo specifying a tract.
130 World coordinate system for the image.
132 Bounding box
for the image.
137 `
True`
if the bounding box overlaps the tract; `
False` otherwise.
139 tractPoly = tract.getOuterSkyPolygon()
143 imageSkyCorners = imageWcs.pixelToSky(imagePixelCorners)
144 except lsst.pex.exceptions.LsstCppException
as e:
146 if (
not isinstance(e.message, lsst.pex.exceptions.DomainErrorException)
147 and not isinstance(e.message, lsst.pex.exceptions.RuntimeErrorException)):
152 return tractPoly.intersects(imagePoly)
156 dimensions=(
"instrument",
"visit",
"detector",
"skymap",
"tract"),
157 defaultTemplates={
"inputCoaddName":
"deep",
158 "inputName":
"calexp",
159 "skyWcsName":
"jointcal",
160 "photoCalibName":
"fgcm"}):
161 inputSchema = cT.InitInput(
162 doc=
"Schema for the input measurement catalogs.",
163 name=
"{inputCoaddName}Coadd_ref_schema",
164 storageClass=
"SourceCatalog",
166 outputSchema = cT.InitOutput(
167 doc=
"Schema for the output forced measurement catalogs.",
168 name=
"forced_src_schema",
169 storageClass=
"SourceCatalog",
172 doc=
"Input exposure to perform photometry on.",
174 storageClass=
"ExposureF",
175 dimensions=[
"instrument",
"visit",
"detector"],
178 doc=
"Catalog of shapes and positions at which to force photometry.",
179 name=
"{inputCoaddName}Coadd_ref",
180 storageClass=
"SourceCatalog",
181 dimensions=[
"skymap",
"tract",
"patch"],
186 doc=
"SkyMap dataset that defines the coordinate system of the reference catalog.",
187 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
188 storageClass=
"SkyMap",
189 dimensions=[
"skymap"],
192 doc=
"Input Sky Correction to be subtracted from the calexp if doApplySkyCorr=True",
194 storageClass=
"Background",
195 dimensions=(
"instrument",
"visit",
"detector"),
197 externalSkyWcsTractCatalog = cT.Input(
198 doc=(
"Per-tract, per-visit wcs calibrations. These catalogs use the detector "
199 "id for the catalog id, sorted on id for fast lookup."),
200 name=
"{skyWcsName}SkyWcsCatalog",
201 storageClass=
"ExposureCatalog",
202 dimensions=[
"instrument",
"visit",
"tract"],
204 externalSkyWcsGlobalCatalog = cT.Input(
205 doc=(
"Per-visit wcs calibrations computed globally (with no tract information). "
206 "These catalogs use the detector id for the catalog id, sorted on id for "
208 name=
"{skyWcsName}SkyWcsCatalog",
209 storageClass=
"ExposureCatalog",
210 dimensions=[
"instrument",
"visit"],
212 externalPhotoCalibTractCatalog = cT.Input(
213 doc=(
"Per-tract, per-visit photometric calibrations. These catalogs use the "
214 "detector id for the catalog id, sorted on id for fast lookup."),
215 name=
"{photoCalibName}PhotoCalibCatalog",
216 storageClass=
"ExposureCatalog",
217 dimensions=[
"instrument",
"visit",
"tract"],
219 externalPhotoCalibGlobalCatalog = cT.Input(
220 doc=(
"Per-visit photometric calibrations computed globally (with no tract "
221 "information). These catalogs use the detector id for the catalog id, "
222 "sorted on id for fast lookup."),
223 name=
"{photoCalibName}PhotoCalibCatalog",
224 storageClass=
"ExposureCatalog",
225 dimensions=[
"instrument",
"visit"],
227 finalizedPsfApCorrCatalog = cT.Input(
228 doc=(
"Per-visit finalized psf models and aperture correction maps. "
229 "These catalogs use the detector id for the catalog id, "
230 "sorted on id for fast lookup."),
231 name=
"finalized_psf_ap_corr_catalog",
232 storageClass=
"ExposureCatalog",
233 dimensions=[
"instrument",
"visit"],
236 doc=
"Output forced photometry catalog.",
238 storageClass=
"SourceCatalog",
239 dimensions=[
"instrument",
"visit",
"detector",
"skymap",
"tract"],
242 def __init__(self, *, config=None):
243 super().__init__(config=config)
244 if not config.doApplySkyCorr:
245 self.inputs.remove(
"skyCorr")
246 if config.doApplyExternalSkyWcs:
247 if config.useGlobalExternalSkyWcs:
248 self.inputs.remove(
"externalSkyWcsTractCatalog")
250 self.inputs.remove(
"externalSkyWcsGlobalCatalog")
252 self.inputs.remove(
"externalSkyWcsTractCatalog")
253 self.inputs.remove(
"externalSkyWcsGlobalCatalog")
254 if config.doApplyExternalPhotoCalib:
255 if config.useGlobalExternalPhotoCalib:
256 self.inputs.remove(
"externalPhotoCalibTractCatalog")
258 self.inputs.remove(
"externalPhotoCalibGlobalCatalog")
260 self.inputs.remove(
"externalPhotoCalibTractCatalog")
261 self.inputs.remove(
"externalPhotoCalibGlobalCatalog")
262 if not config.doApplyFinalizedPsf:
263 self.inputs.remove(
"finalizedPsfApCorrCatalog")
266class ForcedPhotCcdConfig(pipeBase.PipelineTaskConfig,
267 pipelineConnections=ForcedPhotCcdConnections):
268 """Config class for forced measurement driver task."""
270 target=MultiBandReferencesTask,
271 doc=
"subtask to retrieve reference source catalog"
274 target=ForcedMeasurementTask,
275 doc=
"subtask to do forced measurement"
278 doc=
"coadd name: typically one of deep or goodSeeing",
285 doc=
"Run subtask to apply aperture corrections"
288 target=ApplyApCorrTask,
289 doc=
"Subtask to apply aperture corrections"
292 target=CatalogCalculationTask,
293 doc=
"Subtask to run catalogCalculation plugins on catalog"
297 doc=
"Apply meas_mosaic ubercal results to input calexps?",
299 deprecated=
"Deprecated by DM-23352; use doApplyExternalPhotoCalib and doApplyExternalSkyWcs instead",
304 doc=(
"Whether to apply external photometric calibration via an "
305 "`lsst.afw.image.PhotoCalib` object. Uses the "
306 "``externalPhotoCalibName`` field to determine which calibration "
312 doc=(
"When using doApplyExternalPhotoCalib, use 'global' calibrations "
313 "that are not run per-tract. When False, use per-tract photometric "
314 "calibration files.")
319 doc=(
"Whether to apply external astrometric calibration via an "
320 "`lsst.afw.geom.SkyWcs` object. Uses ``externalSkyWcsName`` "
321 "field to determine which calibration to load."),
326 doc=(
"When using doApplyExternalSkyWcs, use 'global' calibrations "
327 "that are not run per-tract. When False, use per-tract wcs "
333 doc=
"Whether to apply finalized psf models and aperture correction map.",
338 doc=
"Apply sky correction?",
343 doc=
"Add photometric calibration variance to warp variance plane?",
347 doc=(
"Type of external PhotoCalib if ``doApplyExternalPhotoCalib`` is True. "
348 "Unused for Gen3 middleware."),
351 "jointcal":
"Use jointcal_photoCalib",
352 "fgcm":
"Use fgcm_photoCalib",
353 "fgcm_tract":
"Use fgcm_tract_photoCalib"
358 doc=
"Type of external SkyWcs if ``doApplyExternalSkyWcs`` is True. Unused for Gen3 middleware.",
361 "jointcal":
"Use jointcal_wcs"
366 doc=
"Where to obtain footprints to install in the measurement catalog, prior to measurement.",
368 "transformed":
"Transform footprints from the reference catalog (downgrades HeavyFootprints).",
369 "psf": (
"Use the scaled shape of the PSF at the position of each source (does not generate "
370 "HeavyFootprints)."),
373 default=
"transformed",
377 doc=
"Scaling factor to apply to the PSF shape when footprintSource='psf' (ignored otherwise).",
386 self.measurement.plugins.names |= [
'base_LocalPhotoCalib',
'base_LocalWcs']
387 self.catalogCalculation.plugins.names = []
390class ForcedPhotCcdTask(pipeBase.PipelineTask, pipeBase.CmdLineTask):
391 """A command-line driver for performing forced measurement on CCD images.
396 A Butler which will be passed to the references subtask to allow it to
397 load its schema from disk. Optional, but must be specified
if
398 ``refSchema``
is not;
if both are specified, ``refSchema`` takes
401 The schema of the reference catalog, passed to the constructor of the
402 references subtask. Optional, but must be specified
if ``butler``
is
403 not;
if both are specified, ``refSchema`` takes precedence.
405 Keyword arguments are passed to the supertask constructor.
410 that corresponds to a single CCD. This should contain the data ID keys that
411 correspond to the ``forced_src`` dataset (the output dataset
for this
412 task), which are typically all those used to specify the ``calexp`` dataset
413 (``visit``, ``raft``, ``sensor``
for LSST data)
as well
as a coadd tract.
414 The tract
is used to look up the appropriate coadd measurement catalogs to
415 use
as references (e.g. ``deepCoadd_src``; see
417 information). While the tract must be given
as part of the dataRef, the
418 patches are determined automatically
from the bounding box
and WCS of the
419 calexp to be measured,
and the filter used to fetch references
is set via
420 the ``filter`` option
in the configuration of
424 ConfigClass = ForcedPhotCcdConfig
425 RunnerClass = pipeBase.ButlerInitializedTaskRunner
426 _DefaultName = "forcedPhotCcd"
429 def __init__(self, butler=None, refSchema=None, initInputs=None, **kwds):
430 super().__init__(**kwds)
432 if initInputs
is not None:
433 refSchema = initInputs[
'inputSchema'].schema
435 self.makeSubtask(
"references", butler=butler, schema=refSchema)
436 if refSchema
is None:
437 refSchema = self.references.schema
438 self.makeSubtask(
"measurement", refSchema=refSchema)
441 if self.config.doApCorr:
442 self.makeSubtask(
"applyApCorr", schema=self.measurement.schema)
443 self.makeSubtask(
'catalogCalculation', schema=self.measurement.schema)
446 def runQuantum(self, butlerQC, inputRefs, outputRefs):
447 inputs = butlerQC.get(inputRefs)
449 tract = butlerQC.quantum.dataId[
'tract']
450 skyMap = inputs.pop(
'skyMap')
451 inputs[
'refWcs'] = skyMap[tract].getWcs()
454 skyCorr = inputs.pop(
'skyCorr',
None)
455 if self.config.useGlobalExternalSkyWcs:
456 externalSkyWcsCatalog = inputs.pop(
'externalSkyWcsGlobalCatalog',
None)
458 externalSkyWcsCatalog = inputs.pop(
'externalSkyWcsTractCatalog',
None)
459 if self.config.useGlobalExternalPhotoCalib:
460 externalPhotoCalibCatalog = inputs.pop(
'externalPhotoCalibGlobalCatalog',
None)
462 externalPhotoCalibCatalog = inputs.pop(
'externalPhotoCalibTractCatalog',
None)
463 finalizedPsfApCorrCatalog = inputs.pop(
'finalizedPsfApCorrCatalog',
None)
465 inputs[
'exposure'] = self.prepareCalibratedExposure(
468 externalSkyWcsCatalog=externalSkyWcsCatalog,
469 externalPhotoCalibCatalog=externalPhotoCalibCatalog,
470 finalizedPsfApCorrCatalog=finalizedPsfApCorrCatalog
473 inputs[
'refCat'] = self.mergeAndFilterReferences(inputs[
'exposure'], inputs[
'refCat'],
476 inputs[
'measCat'], inputs[
'exposureId'] = self.generateMeasCat(inputRefs.exposure.dataId,
478 inputs[
'refCat'], inputs[
'refWcs'],
480 self.attachFootprints(inputs[
'measCat'], inputs[
'refCat'], inputs[
'exposure'], inputs[
'refWcs'])
481 outputs = self.run(**inputs)
482 butlerQC.put(outputs, outputRefs)
484 def prepareCalibratedExposure(self, exposure, skyCorr=None, externalSkyWcsCatalog=None,
485 externalPhotoCalibCatalog=None, finalizedPsfApCorrCatalog=None):
486 """Prepare a calibrated exposure and apply external calibrations
487 and sky corrections
if so configured.
492 Input exposure to adjust calibrations.
493 skyCorr : `lsst.afw.math.backgroundList`, optional
494 Sky correction frame to apply
if doApplySkyCorr=
True.
496 Exposure catalog
with external skyWcs to be applied
497 if config.doApplyExternalSkyWcs=
True. Catalog uses the detector id
498 for the catalog id, sorted on id
for fast lookup.
500 Exposure catalog
with external photoCalib to be applied
501 if config.doApplyExternalPhotoCalib=
True. Catalog uses the detector
502 id
for the catalog id, sorted on id
for fast lookup.
504 Exposure catalog
with finalized psf models
and aperture correction
505 maps to be applied
if config.doApplyFinalizedPsf=
True. Catalog uses
506 the detector id
for the catalog id, sorted on id
for fast lookup.
511 Exposure
with adjusted calibrations.
513 detectorId = exposure.getInfo().getDetector().getId()
515 if externalPhotoCalibCatalog
is not None:
516 row = externalPhotoCalibCatalog.find(detectorId)
518 self.log.
warning(
"Detector id %s not found in externalPhotoCalibCatalog; "
519 "Using original photoCalib.", detectorId)
521 photoCalib = row.getPhotoCalib()
522 if photoCalib
is None:
523 self.log.
warning(
"Detector id %s has None for photoCalib in externalPhotoCalibCatalog; "
524 "Using original photoCalib.", detectorId)
526 exposure.setPhotoCalib(photoCalib)
528 if externalSkyWcsCatalog
is not None:
529 row = externalSkyWcsCatalog.find(detectorId)
531 self.log.
warning(
"Detector id %s not found in externalSkyWcsCatalog; "
532 "Using original skyWcs.", detectorId)
534 skyWcs = row.getWcs()
536 self.log.
warning(
"Detector id %s has None for skyWcs in externalSkyWcsCatalog; "
537 "Using original skyWcs.", detectorId)
539 exposure.setWcs(skyWcs)
541 if finalizedPsfApCorrCatalog
is not None:
542 row = finalizedPsfApCorrCatalog.find(detectorId)
544 self.log.
warning(
"Detector id %s not found in finalizedPsfApCorrCatalog; "
545 "Using original psf.", detectorId)
548 apCorrMap = row.getApCorrMap()
549 if psf
is None or apCorrMap
is None:
550 self.log.
warning(
"Detector id %s has None for psf/apCorrMap in "
551 "finalizedPsfApCorrCatalog; Using original psf.", detectorId)
554 exposure.setApCorrMap(apCorrMap)
556 if skyCorr
is not None:
557 exposure.maskedImage -= skyCorr.getImage()
561 def mergeAndFilterReferences(self, exposure, refCats, refWcs):
562 """Filter reference catalog so that all sources are within the
563 boundaries of the exposure.
568 Exposure to generate the catalog for.
569 refCats : sequence of `lsst.daf.butler.DeferredDatasetHandle`
570 Handles
for catalogs of shapes
and positions at which to force
572 refWcs : `lsst.afw.image.SkyWcs`
573 Reference world coordinate system.
578 Filtered catalog of forced sources to measure.
582 Filtering the reference catalog
is currently handled by Gen2
583 specific methods. To function
for Gen3, this method copies
584 code segments to do the filtering
and transformation. The
585 majority of this code
is based on the methods of
592 expWcs = exposure.getWcs()
593 expRegion = exposure.getBBox(lsst.afw.image.PARENT)
595 expBoxCorners = expBBox.getCorners()
596 expSkyCorners = [expWcs.pixelToSky(corner).getVector()
for
597 corner
in expBoxCorners]
606 for refCat
in refCats:
607 refCat = refCat.get()
608 if mergedRefCat
is None:
611 for record
in refCat:
612 if expPolygon.contains(record.getCoord().getVector())
and record.getParent()
in containedIds:
613 record.setFootprint(record.getFootprint())
614 mergedRefCat.append(record)
615 containedIds.add(record.getId())
616 if mergedRefCat
is None:
617 raise RuntimeError(
"No reference objects for forced photometry.")
621 def generateMeasCat(self, exposureDataId, exposure, refCat, refWcs, idPackerName):
622 """Generate a measurement catalog for Gen3.
626 exposureDataId : `DataId`
627 Butler dataId for this exposure.
629 Exposure to generate the catalog
for.
631 Catalog of shapes
and positions at which to force photometry.
632 refWcs : `lsst.afw.image.SkyWcs`
633 Reference world coordinate system.
634 This parameter
is not currently used.
636 Type of ID packer to construct
from the registry.
641 Catalog of forced sources to measure.
643 Unique binary id associated
with the input exposure
645 exposureIdInfo = ExposureIdInfo.fromDataId(exposureDataId, idPackerName)
646 idFactory = exposureIdInfo.makeSourceIdFactory()
648 measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
650 return measCat, exposureIdInfo.expId
652 def runDataRef(self, dataRef, psfCache=None):
653 """Perform forced measurement on a single exposure.
658 Passed to the ``references`` subtask to obtain the reference WCS,
659 the ``getExposure`` method (implemented by derived classes) to
660 read the measurment image, and the ``fetchReferences`` method to
661 get the exposure
and load the reference catalog (see
664 and data ID keys which are used.
665 psfCache : `int`, optional
666 Size of PSF cache,
or `
None`. The size of the PSF cache can have
667 a significant effect upon the runtime
for complicated PSF models.
671 Sources are generated
with ``generateMeasCat``
in the ``measurement``
672 subtask. These are passed to ``measurement``
's ``run`` method, which
673 fills the source catalog with the forced measurement results. The
674 sources are then passed to the ``writeOutputs`` method (implemented by
675 derived classes) which writes the outputs.
677 refWcs = self.references.getWcs(dataRef)
678 exposure = self.getExposure(dataRef)
679 if psfCache
is not None:
680 exposure.getPsf().setCacheSize(psfCache)
681 refCat = self.fetchReferences(dataRef, exposure)
682 measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
683 idFactory=self.makeIdFactory(dataRef))
684 self.log.
info(
"Performing forced measurement on %s", dataRef.dataId)
685 self.attachFootprints(measCat, refCat, exposure, refWcs)
687 exposureId = self.getExposureId(dataRef)
689 forcedPhotResult = self.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
691 self.writeOutput(dataRef, forcedPhotResult.measCat)
693 def run(self, measCat, exposure, refCat, refWcs, exposureId=None):
694 """Perform forced measurement on a single exposure.
699 The measurement catalog, based on the sources listed in the
702 The measurement image upon which to perform forced detection.
704 The reference catalog of sources to measure.
705 refWcs : `lsst.afw.image.SkyWcs`
706 The WCS
for the references.
708 Optional unique exposureId used
for random seed
in measurement
713 result : `lsst.pipe.base.Struct`
714 Structure
with fields:
717 Catalog of forced measurement results
720 self.measurement.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
721 if self.config.doApCorr:
722 self.applyApCorr.
run(
724 apCorrMap=exposure.getInfo().getApCorrMap()
726 self.catalogCalculation.
run(measCat)
728 return pipeBase.Struct(measCat=measCat)
730 def makeIdFactory(self, dataRef):
731 """Create an object that generates globally unique source IDs.
733 Source IDs are created based on a per-CCD ID and the ID of the CCD
739 Butler data reference. The ``ccdExposureId_bits``
and
740 ``ccdExposureId`` datasets are accessed. The data ID must have the
741 keys that correspond to ``ccdExposureId``, which are generally the
742 same
as those that correspond to ``calexp`` (``visit``, ``raft``,
743 ``sensor``
for LSST data).
745 exposureIdInfo = ExposureIdInfo(int(dataRef.get("ccdExposureId")), dataRef.get(
"ccdExposureId_bits"))
746 return exposureIdInfo.makeSourceIdFactory()
749 return int(dataRef.get(
"ccdExposureId", immediate=
True))
752 """Get sources that overlap the exposure.
757 Butler data reference corresponding to the image to be measured;
758 should have ``tract``, ``patch``, and ``filter`` keys.
760 The image to be measured (used only to obtain a WCS
and bounding
766 Catalog of sources that overlap the exposure
770 The returned catalog
is sorted by ID
and guarantees that all included
771 children have their parent included
and that all Footprints are valid.
773 All work
is delegated to the references subtask; see
775 for information about the default behavior.
779 unfiltered = self.references.fetchInBox(dataRef, exposure.getBBox(), exposure.getWcs())
780 for record
in unfiltered:
781 if record.getFootprint()
is None or record.getFootprint().getArea() == 0:
782 if record.getParent() != 0:
783 self.log.
warning(
"Skipping reference %s (child of %s) with bad Footprint",
784 record.getId(), record.getParent())
786 self.log.
warning(
"Skipping reference parent %s with bad Footprint", record.getId())
787 badParents.add(record.getId())
788 elif record.getParent()
not in badParents:
789 references.append(record)
795 r"""Attach footprints to blank sources prior to measurements.
800 pixel coordinate system of the image being measured,
while the actual
801 detections may start out
in a different coordinate system.
803 Subclasses of this
class may implement this method
to define how
806 This default implementation transforms depends on the
807 ``footprintSource`` configuration parameter.
809 if self.config.footprintSource ==
"transformed":
810 return self.measurement.attachTransformedFootprints(sources, refCat, exposure, refWcs)
811 elif self.config.footprintSource ==
"psf":
812 return self.measurement.attachPsfShapeFootprints(sources, exposure,
813 scaling=self.config.psfFootprintScaling)
816 """Read input exposure for measurement.
821 Butler data reference.
823 exposure = dataRef.get(self.dataPrefix + "calexp", immediate=
True)
825 if self.config.doApplyExternalPhotoCalib:
826 source = f
"{self.config.externalPhotoCalibName}_photoCalib"
827 self.log.
info(
"Applying external photoCalib from %s", source)
828 photoCalib = dataRef.get(source)
829 exposure.setPhotoCalib(photoCalib)
831 if self.config.doApplyExternalSkyWcs:
832 source = f
"{self.config.externalSkyWcsName}_wcs"
833 self.log.
info(
"Applying external skyWcs from %s", source)
834 skyWcs = dataRef.get(source)
835 exposure.setWcs(skyWcs)
837 if self.config.doApplySkyCorr:
838 self.log.
info(
"Apply sky correction")
839 skyCorr = dataRef.get(
"skyCorr")
840 exposure.maskedImage -= skyCorr.getImage()
845 """Write forced source table
850 Butler data reference. The forced_src dataset (with
851 self.dataPrefix prepended)
is all that will be modified.
853 Catalog of sources to save.
855 dataRef.put(sources, self.dataPrefix + "forced_src", flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS)
858 """The schema catalogs that will be used by this task.
862 schemaCatalogs : `dict`
863 Dictionary mapping dataset type to schema catalog.
867 There is only one schema
for each type of forced measurement. The
868 dataset type
for this measurement
is defined
in the mapper.
871 catalog.getTable().setMetadata(self.measurement.algMetadata)
872 datasetType = self.dataPrefix + "forced_src"
873 return {datasetType: catalog}
875 def _getConfigName(self):
877 return self.dataPrefix +
"forcedPhotCcd_config"
879 def _getMetadataName(self):
881 return self.dataPrefix +
"forcedPhotCcd_metadata"
884 def _makeArgumentParser(cls):
885 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
886 parser.add_id_argument(
"--id",
"forced_src", help=
"data ID with raw CCD keys [+ tract optionally], "
887 "e.g. --id visit=12345 ccd=1,2 [tract=0]",
888 ContainerClass=PerTractCcdDataIdContainer)
892class ForcedPhotCcdFromDataFrameConnections(PipelineTaskConnections,
893 dimensions=(
"instrument",
"visit",
"detector",
"skymap",
"tract"),
894 defaultTemplates={
"inputCoaddName":
"goodSeeing",
895 "inputName":
"calexp",
896 "skyWcsName":
"jointcal",
897 "photoCalibName":
"fgcm"}):
899 doc=
"Catalog of positions at which to force photometry.",
900 name=
"{inputCoaddName}Diff_fullDiaObjTable",
901 storageClass=
"DataFrame",
902 dimensions=[
"skymap",
"tract",
"patch"],
907 doc=
"Input exposure to perform photometry on.",
909 storageClass=
"ExposureF",
910 dimensions=[
"instrument",
"visit",
"detector"],
913 doc=
"Input Sky Correction to be subtracted from the calexp if doApplySkyCorr=True",
915 storageClass=
"Background",
916 dimensions=(
"instrument",
"visit",
"detector"),
918 externalSkyWcsTractCatalog = cT.Input(
919 doc=(
"Per-tract, per-visit wcs calibrations. These catalogs use the detector "
920 "id for the catalog id, sorted on id for fast lookup."),
921 name=
"{skyWcsName}SkyWcsCatalog",
922 storageClass=
"ExposureCatalog",
923 dimensions=[
"instrument",
"visit",
"tract"],
925 externalSkyWcsGlobalCatalog = cT.Input(
926 doc=(
"Per-visit wcs calibrations computed globally (with no tract information). "
927 "These catalogs use the detector id for the catalog id, sorted on id for "
929 name=
"{skyWcsName}SkyWcsCatalog",
930 storageClass=
"ExposureCatalog",
931 dimensions=[
"instrument",
"visit"],
933 externalPhotoCalibTractCatalog = cT.Input(
934 doc=(
"Per-tract, per-visit photometric calibrations. These catalogs use the "
935 "detector id for the catalog id, sorted on id for fast lookup."),
936 name=
"{photoCalibName}PhotoCalibCatalog",
937 storageClass=
"ExposureCatalog",
938 dimensions=[
"instrument",
"visit",
"tract"],
940 externalPhotoCalibGlobalCatalog = cT.Input(
941 doc=(
"Per-visit photometric calibrations computed globally (with no tract "
942 "information). These catalogs use the detector id for the catalog id, "
943 "sorted on id for fast lookup."),
944 name=
"{photoCalibName}PhotoCalibCatalog",
945 storageClass=
"ExposureCatalog",
946 dimensions=[
"instrument",
"visit"],
948 finalizedPsfApCorrCatalog = cT.Input(
949 doc=(
"Per-visit finalized psf models and aperture correction maps. "
950 "These catalogs use the detector id for the catalog id, "
951 "sorted on id for fast lookup."),
952 name=
"finalized_psf_ap_corr_catalog",
953 storageClass=
"ExposureCatalog",
954 dimensions=[
"instrument",
"visit"],
957 doc=
"Output forced photometry catalog.",
958 name=
"forced_src_diaObject",
959 storageClass=
"SourceCatalog",
960 dimensions=[
"instrument",
"visit",
"detector",
"skymap",
"tract"],
962 outputSchema = cT.InitOutput(
963 doc=
"Schema for the output forced measurement catalogs.",
964 name=
"forced_src_diaObject_schema",
965 storageClass=
"SourceCatalog",
968 def __init__(self, *, config=None):
969 super().__init__(config=config)
970 if not config.doApplySkyCorr:
971 self.inputs.remove(
"skyCorr")
972 if config.doApplyExternalSkyWcs:
973 if config.useGlobalExternalSkyWcs:
974 self.inputs.remove(
"externalSkyWcsTractCatalog")
976 self.inputs.remove(
"externalSkyWcsGlobalCatalog")
978 self.inputs.remove(
"externalSkyWcsTractCatalog")
979 self.inputs.remove(
"externalSkyWcsGlobalCatalog")
980 if config.doApplyExternalPhotoCalib:
981 if config.useGlobalExternalPhotoCalib:
982 self.inputs.remove(
"externalPhotoCalibTractCatalog")
984 self.inputs.remove(
"externalPhotoCalibGlobalCatalog")
986 self.inputs.remove(
"externalPhotoCalibTractCatalog")
987 self.inputs.remove(
"externalPhotoCalibGlobalCatalog")
988 if not config.doApplyFinalizedPsf:
989 self.inputs.remove(
"finalizedPsfApCorrCatalog")
992class ForcedPhotCcdFromDataFrameConfig(ForcedPhotCcdConfig,
993 pipelineConnections=ForcedPhotCcdFromDataFrameConnections):
996 self.footprintSource =
"psf"
997 self.measurement.doReplaceWithNoise =
False
998 self.measurement.plugins = [
"base_TransformedCentroidFromCoord",
"base_PsfFlux",
"base_PixelFlags"]
999 self.measurement.copyColumns = {
'id':
'diaObjectId',
'coord_ra':
'coord_ra',
'coord_dec':
'coord_dec'}
1000 self.measurement.slots.centroid =
"base_TransformedCentroidFromCoord"
1001 self.measurement.slots.psfFlux =
"base_PsfFlux"
1002 self.measurement.slots.shape =
None
1006 if self.footprintSource ==
"transformed":
1007 raise ValueError(
"Cannot transform footprints from reference catalog, "
1008 "because DataFrames can't hold footprints.")
1011class ForcedPhotCcdFromDataFrameTask(ForcedPhotCcdTask):
1012 """Force Photometry on a per-detector exposure with coords from a DataFrame
1014 Uses input from a DataFrame instead of SourceCatalog
1016 Writes out a SourceCatalog so that the downstream
1017 WriteForcedSourceTableTask can be reused
with output
from this Task.
1019 _DefaultName = "forcedPhotCcdFromDataFrame"
1020 ConfigClass = ForcedPhotCcdFromDataFrameConfig
1022 def __init__(self, butler=None, refSchema=None, initInputs=None, **kwds):
1024 pipeBase.PipelineTask.__init__(self, **kwds)
1028 if self.config.doApCorr:
1029 self.makeSubtask(
"applyApCorr", schema=self.measurement.schema)
1030 self.makeSubtask(
'catalogCalculation', schema=self.measurement.schema)
1033 def runQuantum(self, butlerQC, inputRefs, outputRefs):
1034 inputs = butlerQC.get(inputRefs)
1037 inputs[
'refWcs'] =
None
1040 skyCorr = inputs.pop(
'skyCorr',
None)
1041 if self.config.useGlobalExternalSkyWcs:
1042 externalSkyWcsCatalog = inputs.pop(
'externalSkyWcsGlobalCatalog',
None)
1044 externalSkyWcsCatalog = inputs.pop(
'externalSkyWcsTractCatalog',
None)
1045 if self.config.useGlobalExternalPhotoCalib:
1046 externalPhotoCalibCatalog = inputs.pop(
'externalPhotoCalibGlobalCatalog',
None)
1048 externalPhotoCalibCatalog = inputs.pop(
'externalPhotoCalibTractCatalog',
None)
1049 finalizedPsfApCorrCatalog = inputs.pop(
'finalizedPsfApCorrCatalog',
None)
1051 inputs[
'exposure'] = self.prepareCalibratedExposure(
1054 externalSkyWcsCatalog=externalSkyWcsCatalog,
1055 externalPhotoCalibCatalog=externalPhotoCalibCatalog,
1056 finalizedPsfApCorrCatalog=finalizedPsfApCorrCatalog
1059 self.log.
info(
"Filtering ref cats: %s",
','.join([
str(i.dataId)
for i
in inputs[
'refCat']]))
1060 refCat = self.df2RefCat([i.get(parameters={
"columns": [
'diaObjectId',
'ra',
'decl']})
1061 for i
in inputs[
'refCat']],
1062 inputs[
'exposure'].getBBox(), inputs[
'exposure'].getWcs())
1063 inputs[
'refCat'] = refCat
1065 inputs[
'measCat'], inputs[
'exposureId'] = self.generateMeasCat(inputRefs.exposure.dataId,
1066 inputs[
'exposure'], inputs[
'refCat'],
1071 self.attachFootprints(inputs[
"measCat"], inputs[
"refCat"], inputs[
"exposure"], inputs[
"refWcs"])
1072 outputs = self.run(**inputs)
1074 butlerQC.put(outputs, outputRefs)
1076 def df2RefCat(self, dfList, exposureBBox, exposureWcs):
1077 """Convert list of DataFrames to reference catalog
1079 Concatenate list of DataFrames presumably from multiple patches
and
1080 downselect rows that overlap the exposureBBox using the exposureWcs.
1084 dfList : `list` of `pandas.DataFrame`
1085 Each element containst diaObjects
with ra/decl position
in degrees
1086 Columns
'diaObjectId',
'ra',
'decl' are expected
1088 Bounding box on which to select rows that overlap
1090 World coordinate system to convert sky coords
in ref cat to
1091 pixel coords
with which to compare
with exposureBBox
1096 Source Catalog
with minimal schema that overlaps exposureBBox
1098 df = pd.concat(dfList)
1101 mapping = exposureWcs.getTransform().getMapping()
1102 x, y = mapping.applyInverse(np.array(df[[
'ra',
'decl']].values*2*np.pi/360).T)
1104 refCat = self.df2SourceCat(df[inBBox])
1107 def df2SourceCat(self, df):
1108 """Create minimal schema SourceCatalog from a pandas DataFrame.
1110 The forced measurement subtask expects this as input.
1114 df : `pandas.DataFrame`
1115 DiaObjects
with locations
and ids.
1120 Output catalog
with minimal schema.
1124 outputCatalog.reserve(len(df))
1126 for diaObjectId, ra, decl
in df[[
'ra',
'decl']].itertuples():
1127 outputRecord = outputCatalog.addNew()
1128 outputRecord.setId(diaObjectId)
1130 return outputCatalog
A 2-dimensional celestial WCS that transform pixels to ICRS RA/Dec, using the LSST standard for pixel...
A class to contain the data, WCS, and other information needed to describe an image of the sky.
Custom catalog class for ExposureRecord/Table.
Defines the fields and offsets for a table.
Table class that contains measurements made on a single exposure.
static Schema makeMinimalSchema()
Return a minimal schema for Source tables and records.
static Key< RecordId > getParentKey()
Key for the parent ID.
A floating-point coordinate rectangle geometry.
An integer coordinate rectangle.
Point in an unspecified spherical coordinate system.
def makeDataRefList(self, namespace)
ConvexPolygon is a closed convex polygon on the unit sphere.
static ConvexPolygon convexHull(std::vector< UnitVector3d > const &points)
convexHull returns the convex hull of the given set of points if it exists and throws an exception ot...
daf::base::PropertySet * set
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Construct a SkyWcs from FITS keywords.
Backwards-compatibility support for depersisting the old Calib (FluxMag0/FluxMag0Err) objects.
lsst::geom::Box2I bboxFromMetadata(daf::base::PropertySet &metadata)
Determine the image bounding box from its metadata (FITS header)
def run(self, coaddExposures, bbox, wcs, dataIds, **kwargs)
def imageOverlapsTract(tract, imageWcs, imageBox)
def attachFootprints(self, sources, refCat, exposure, refWcs, dataRef)
def writeOutput(self, dataRef, sources)
def fetchReferences(self, dataRef, exposure)
def getExposure(self, dataRef)
def getSchemaCatalogs(self)