40 from .references
import MultiBandReferencesTask
41 from .forcedMeasurement
import ForcedMeasurementTask
42 from .applyApCorr
import ApplyApCorrTask
43 from .catalogCalculation
import CatalogCalculationTask
46 from lsst.meas.mosaic
import applyMosaicResults
48 applyMosaicResults =
None
50 __all__ = (
"PerTractCcdDataIdContainer",
"ForcedPhotCcdConfig",
"ForcedPhotCcdTask",
"imageOverlapsTract")
54 """A data ID container which combines raw data IDs with a tract.
58 Required because we need to add "tract" to the raw data ID keys (defined as
59 whatever we use for ``src``) when no tract is provided (so that the user is
60 not required to know which tracts are spanned by the raw data ID).
62 This subclass of `~lsst.pipe.base.DataIdContainer` assumes that a calexp is
63 being measured using the detection information, a set of reference
64 catalogs, from the set of coadds which intersect with the calexp. It needs
65 the calexp id (e.g. visit, raft, sensor), but is also uses the tract to
66 decide what set of coadds to use. The references from the tract whose
67 patches intersect with the calexp are used.
71 """Make self.refList from self.idList
73 if self.datasetType
is None:
74 raise RuntimeError(
"Must call setDatasetType first")
75 log = Log.getLogger(
"meas.base.forcedPhotCcd.PerTractCcdDataIdContainer")
77 visitTract = collections.defaultdict(set)
78 visitRefs = collections.defaultdict(list)
79 for dataId
in self.idList:
80 if "tract" not in dataId:
82 log.info(
"Reading WCS for components of dataId=%s to determine tracts", dict(dataId))
84 skymap = namespace.butler.get(namespace.config.coaddName +
"Coadd_skyMap")
86 for ref
in namespace.butler.subset(
"calexp", dataId=dataId):
87 if not ref.datasetExists(
"calexp"):
90 visit = ref.dataId[
"visit"]
91 visitRefs[visit].
append(ref)
93 md = ref.get(
"calexp_md", immediate=
True)
98 tract = skymap.findTract(wcs.pixelToSky(box.getCenter()))
100 visitTract[visit].add(tract.getId())
102 self.refList.extend(ref
for ref
in namespace.butler.subset(self.datasetType, dataId=dataId))
105 for visit, tractSet
in visitTract.items():
106 for ref
in visitRefs[visit]:
107 for tract
in tractSet:
108 self.refList.
append(namespace.butler.dataRef(datasetType=self.datasetType,
109 dataId=ref.dataId, tract=tract))
111 tractCounter = collections.Counter()
112 for tractSet
in visitTract.values():
113 tractCounter.update(tractSet)
114 log.info(
"Number of visits for each tract: %s", dict(tractCounter))
118 """Return whether the given bounding box overlaps the tract given a WCS.
122 tract : `lsst.skymap.TractInfo`
123 TractInfo specifying a tract.
124 imageWcs : `lsst.afw.geom.SkyWcs`
125 World coordinate system for the image.
126 imageBox : `lsst.geom.Box2I`
127 Bounding box for the image.
132 `True` if the bounding box overlaps the tract; `False` otherwise.
134 tractPoly = tract.getOuterSkyPolygon()
138 imageSkyCorners = imageWcs.pixelToSky(imagePixelCorners)
139 except lsst.pex.exceptions.LsstCppException
as e:
141 if (
not isinstance(e.message, lsst.pex.exceptions.DomainErrorException)
142 and not isinstance(e.message, lsst.pex.exceptions.RuntimeErrorException)):
147 return tractPoly.intersects(imagePoly)
151 dimensions=(
"instrument",
"visit",
"detector",
"skymap",
"tract"),
152 defaultTemplates={
"inputCoaddName":
"deep",
153 "inputName":
"calexp"}):
154 inputSchema = cT.InitInput(
155 doc=
"Schema for the input measurement catalogs.",
156 name=
"{inputCoaddName}Coadd_ref_schema",
157 storageClass=
"SourceCatalog",
159 outputSchema = cT.InitOutput(
160 doc=
"Schema for the output forced measurement catalogs.",
161 name=
"forced_src_schema",
162 storageClass=
"SourceCatalog",
165 doc=
"Input exposure to perform photometry on.",
167 storageClass=
"ExposureF",
168 dimensions=[
"instrument",
"visit",
"detector"],
171 doc=
"Catalog of shapes and positions at which to force photometry.",
172 name=
"{inputCoaddName}Coadd_ref",
173 storageClass=
"SourceCatalog",
174 dimensions=[
"skymap",
"tract",
"patch"],
178 doc=
"SkyMap dataset that defines the coordinate system of the reference catalog.",
179 name=BaseSkyMap.SKYMAP_DATASET_TYPE_NAME,
180 storageClass=
"SkyMap",
181 dimensions=[
"skymap"],
184 doc=
"Output forced photometry catalog.",
186 storageClass=
"SourceCatalog",
187 dimensions=[
"instrument",
"visit",
"detector",
"skymap",
"tract"],
191 class ForcedPhotCcdConfig(pipeBase.PipelineTaskConfig,
192 pipelineConnections=ForcedPhotCcdConnections):
193 """Config class for forced measurement driver task."""
195 target=MultiBandReferencesTask,
196 doc=
"subtask to retrieve reference source catalog"
199 target=ForcedMeasurementTask,
200 doc=
"subtask to do forced measurement"
203 doc=
"coadd name: typically one of deep or goodSeeing",
210 doc=
"Run subtask to apply aperture corrections"
213 target=ApplyApCorrTask,
214 doc=
"Subtask to apply aperture corrections"
217 target=CatalogCalculationTask,
218 doc=
"Subtask to run catalogCalculation plugins on catalog"
222 doc=
"Apply meas_mosaic ubercal results to input calexps?",
224 deprecated=
"Deprecated by DM-23352; use doApplyExternalPhotoCalib and doApplyExternalSkyWcs instead",
229 doc=(
"Whether to apply external photometric calibration via an "
230 "`lsst.afw.image.PhotoCalib` object. Uses the "
231 "``externalPhotoCalibName`` field to determine which calibration "
237 doc=(
"Whether to apply external astrometric calibration via an "
238 "`lsst.afw.geom.SkyWcs` object. Uses ``externalSkyWcsName`` "
239 "field to determine which calibration to load."),
244 doc=
"Apply sky correction?",
249 doc=
"Add photometric calibration variance to warp variance plane?",
253 doc=(
"Type of external PhotoCalib if ``doApplyExternalPhotoCalib`` is True. "
254 "Unused for Gen3 middleware."),
257 "jointcal":
"Use jointcal_photoCalib",
258 "fgcm":
"Use fgcm_photoCalib",
259 "fgcm_tract":
"Use fgcm_tract_photoCalib"
264 doc=
"Type of external SkyWcs if ``doApplyExternalSkyWcs`` is True. Unused for Gen3 middleware.",
267 "jointcal":
"Use jointcal_wcs"
277 self.catalogCalculation.plugins.names = []
280 class ForcedPhotCcdTask(pipeBase.PipelineTask, pipeBase.CmdLineTask):
281 """A command-line driver for performing forced measurement on CCD images.
285 butler : `lsst.daf.persistence.butler.Butler`, optional
286 A Butler which will be passed to the references subtask to allow it to
287 load its schema from disk. Optional, but must be specified if
288 ``refSchema`` is not; if both are specified, ``refSchema`` takes
290 refSchema : `lsst.afw.table.Schema`, optional
291 The schema of the reference catalog, passed to the constructor of the
292 references subtask. Optional, but must be specified if ``butler`` is
293 not; if both are specified, ``refSchema`` takes precedence.
295 Keyword arguments are passed to the supertask constructor.
299 The `runDataRef` method takes a `~lsst.daf.persistence.ButlerDataRef` argument
300 that corresponds to a single CCD. This should contain the data ID keys that
301 correspond to the ``forced_src`` dataset (the output dataset for this
302 task), which are typically all those used to specify the ``calexp`` dataset
303 (``visit``, ``raft``, ``sensor`` for LSST data) as well as a coadd tract.
304 The tract is used to look up the appropriate coadd measurement catalogs to
305 use as references (e.g. ``deepCoadd_src``; see
306 :lsst-task:`lsst.meas.base.references.CoaddSrcReferencesTask` for more
307 information). While the tract must be given as part of the dataRef, the
308 patches are determined automatically from the bounding box and WCS of the
309 calexp to be measured, and the filter used to fetch references is set via
310 the ``filter`` option in the configuration of
311 :lsst-task:`lsst.meas.base.references.BaseReferencesTask`).
314 ConfigClass = ForcedPhotCcdConfig
315 RunnerClass = pipeBase.ButlerInitializedTaskRunner
316 _DefaultName =
"forcedPhotCcd"
319 def __init__(self, butler=None, refSchema=None, initInputs=None, **kwds):
320 super().__init__(**kwds)
322 if initInputs
is not None:
323 refSchema = initInputs[
'inputSchema'].schema
325 self.makeSubtask(
"references", butler=butler, schema=refSchema)
326 if refSchema
is None:
327 refSchema = self.references.schema
328 self.makeSubtask(
"measurement", refSchema=refSchema)
331 if self.config.doApCorr:
332 self.makeSubtask(
"applyApCorr", schema=self.measurement.schema)
333 self.makeSubtask(
'catalogCalculation', schema=self.measurement.schema)
336 def runQuantum(self, butlerQC, inputRefs, outputRefs):
337 inputs = butlerQC.get(inputRefs)
339 tract = butlerQC.quantum.dataId[
'tract']
340 skyMap = inputs.pop(
"skyMap")
341 inputs[
'refWcs'] = skyMap[tract].getWcs()
343 inputs[
'refCat'] = self.mergeAndFilterReferences(inputs[
'exposure'], inputs[
'refCat'],
346 inputs[
'measCat'], inputs[
'exposureId'] = self.generateMeasCat(inputRefs.exposure.dataId,
348 inputs[
'refCat'], inputs[
'refWcs'],
350 self.attachFootprints(inputs[
'measCat'], inputs[
'refCat'], inputs[
'exposure'], inputs[
'refWcs'])
352 outputs = self.run(**inputs)
353 butlerQC.put(outputs, outputRefs)
355 def mergeAndFilterReferences(self, exposure, refCats, refWcs):
356 """Filter reference catalog so that all sources are within the
357 boundaries of the exposure.
361 exposure : `lsst.afw.image.exposure.Exposure`
362 Exposure to generate the catalog for.
363 refCats : sequence of `lsst.afw.table.SourceCatalog`
364 Catalogs of shapes and positions at which to force photometry.
365 refWcs : `lsst.afw.image.SkyWcs`
366 Reference world coordinate system.
370 refSources : `lsst.afw.table.SourceCatalog`
371 Filtered catalog of forced sources to measure.
375 Filtering the reference catalog is currently handled by Gen2
376 specific methods. To function for Gen3, this method copies
377 code segments to do the filtering and transformation. The
378 majority of this code is based on the methods of
379 lsst.meas.algorithms.loadReferenceObjects.ReferenceObjectLoader
385 expWcs = exposure.getWcs()
386 expRegion = exposure.getBBox(lsst.afw.image.PARENT)
388 expBoxCorners = expBBox.getCorners()
389 expSkyCorners = [expWcs.pixelToSky(corner).getVector()
for
390 corner
in expBoxCorners]
399 for refCat
in refCats:
401 for record
in refCat:
402 if expPolygon.contains(record.getCoord().getVector())
and record.getParent()
in containedIds:
403 record.setFootprint(record.getFootprint().
transform(refWcs, expWcs, expRegion))
404 mergedRefCat.append(record)
405 containedIds.add(record.getId())
409 def generateMeasCat(self, exposureDataId, exposure, refCat, refWcs, idPackerName):
410 """Generate a measurement catalog for Gen3.
414 exposureDataId : `DataId`
415 Butler dataId for this exposure.
416 exposure : `lsst.afw.image.exposure.Exposure`
417 Exposure to generate the catalog for.
418 refCat : `lsst.afw.table.SourceCatalog`
419 Catalog of shapes and positions at which to force photometry.
420 refWcs : `lsst.afw.image.SkyWcs`
421 Reference world coordinate system.
423 Type of ID packer to construct from the registry.
427 measCat : `lsst.afw.table.SourceCatalog`
428 Catalog of forced sources to measure.
430 Unique binary id associated with the input exposure
432 expId, expBits = exposureDataId.pack(idPackerName, returnMaxBits=
True)
435 measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
437 return measCat, expId
439 def runDataRef(self, dataRef, psfCache=None):
440 """Perform forced measurement on a single exposure.
444 dataRef : `lsst.daf.persistence.ButlerDataRef`
445 Passed to the ``references`` subtask to obtain the reference WCS,
446 the ``getExposure`` method (implemented by derived classes) to
447 read the measurment image, and the ``fetchReferences`` method to
448 get the exposure and load the reference catalog (see
449 :lsst-task`lsst.meas.base.references.CoaddSrcReferencesTask`).
450 Refer to derived class documentation for details of the datasets
451 and data ID keys which are used.
452 psfCache : `int`, optional
453 Size of PSF cache, or `None`. The size of the PSF cache can have
454 a significant effect upon the runtime for complicated PSF models.
458 Sources are generated with ``generateMeasCat`` in the ``measurement``
459 subtask. These are passed to ``measurement``'s ``run`` method, which
460 fills the source catalog with the forced measurement results. The
461 sources are then passed to the ``writeOutputs`` method (implemented by
462 derived classes) which writes the outputs.
464 refWcs = self.references.getWcs(dataRef)
465 exposure = self.getExposure(dataRef)
466 if psfCache
is not None:
467 exposure.getPsf().setCacheSize(psfCache)
468 refCat = self.fetchReferences(dataRef, exposure)
470 measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
471 idFactory=self.makeIdFactory(dataRef))
472 self.log.
info(
"Performing forced measurement on %s" % (dataRef.dataId,))
473 self.attachFootprints(measCat, refCat, exposure, refWcs)
475 exposureId = self.getExposureId(dataRef)
477 forcedPhotResult = self.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
479 self.writeOutput(dataRef, forcedPhotResult.measCat)
481 def run(self, measCat, exposure, refCat, refWcs, exposureId=None):
482 """Perform forced measurement on a single exposure.
486 measCat : `lsst.afw.table.SourceCatalog`
487 The measurement catalog, based on the sources listed in the
489 exposure : `lsst.afw.image.Exposure`
490 The measurement image upon which to perform forced detection.
491 refCat : `lsst.afw.table.SourceCatalog`
492 The reference catalog of sources to measure.
493 refWcs : `lsst.afw.image.SkyWcs`
494 The WCS for the references.
496 Optional unique exposureId used for random seed in measurement
501 result : `lsst.pipe.base.Struct`
502 Structure with fields:
505 Catalog of forced measurement results
506 (`lsst.afw.table.SourceCatalog`).
508 self.measurement.
run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
509 if self.config.doApCorr:
510 self.applyApCorr.
run(
512 apCorrMap=exposure.getInfo().getApCorrMap()
514 self.catalogCalculation.
run(measCat)
516 return pipeBase.Struct(measCat=measCat)
518 def makeIdFactory(self, dataRef):
519 """Create an object that generates globally unique source IDs.
521 Source IDs are created based on a per-CCD ID and the ID of the CCD
526 dataRef : `lsst.daf.persistence.ButlerDataRef`
527 Butler data reference. The ``ccdExposureId_bits`` and
528 ``ccdExposureId`` datasets are accessed. The data ID must have the
529 keys that correspond to ``ccdExposureId``, which are generally the
530 same as those that correspond to ``calexp`` (``visit``, ``raft``,
531 ``sensor`` for LSST data).
533 expBits = dataRef.get(
"ccdExposureId_bits")
534 expId = int(dataRef.get(
"ccdExposureId"))
538 return int(dataRef.get(
"ccdExposureId", immediate=
True))
541 """Get sources that overlap the exposure.
545 dataRef : `lsst.daf.persistence.ButlerDataRef`
546 Butler data reference corresponding to the image to be measured;
547 should have ``tract``, ``patch``, and ``filter`` keys.
548 exposure : `lsst.afw.image.Exposure`
549 The image to be measured (used only to obtain a WCS and bounding
554 referencs : `lsst.afw.table.SourceCatalog`
555 Catalog of sources that overlap the exposure
559 The returned catalog is sorted by ID and guarantees that all included
560 children have their parent included and that all Footprints are valid.
562 All work is delegated to the references subtask; see
563 :lsst-task:`lsst.meas.base.references.CoaddSrcReferencesTask`
564 for information about the default behavior.
568 unfiltered = self.references.fetchInBox(dataRef, exposure.getBBox(), exposure.getWcs())
569 for record
in unfiltered:
570 if record.getFootprint()
is None or record.getFootprint().getArea() == 0:
571 if record.getParent() != 0:
572 self.log.
warn(
"Skipping reference %s (child of %s) with bad Footprint",
573 record.getId(), record.getParent())
575 self.log.
warn(
"Skipping reference parent %s with bad Footprint", record.getId())
576 badParents.add(record.getId())
577 elif record.getParent()
not in badParents:
578 references.append(record)
584 r"""Attach footprints to blank sources prior to measurements.
588 `~lsst.afw.detection.Footprint`\ s for forced photometry must be in the
589 pixel coordinate system of the image being measured, while the actual
590 detections may start out in a different coordinate system.
592 Subclasses of this class must implement this method to define how
593 those `~lsst.afw.detection.Footprint`\ s should be generated.
595 This default implementation transforms the
596 `~lsst.afw.detection.Footprint`\ s from the reference catalog from the
597 reference WCS to the exposure's WcS, which downgrades
598 `lsst.afw.detection.heavyFootprint.HeavyFootprint`\ s into regular
599 `~lsst.afw.detection.Footprint`\ s, destroying deblend information.
601 return self.measurement.attachTransformedFootprints(sources, refCat, exposure, refWcs)
604 """Read input exposure for measurement.
608 dataRef : `lsst.daf.persistence.ButlerDataRef`
609 Butler data reference.
611 exposure = dataRef.get(self.dataPrefix +
"calexp", immediate=
True)
613 if self.config.doApplyExternalPhotoCalib:
614 source = f
"{self.config.externalPhotoCalibName}_photoCalib"
615 self.log.
info(
"Applying external photoCalib from %s", source)
616 photoCalib = dataRef.get(source)
617 exposure.setPhotoCalib(photoCalib)
619 if self.config.doApplyExternalSkyWcs:
620 source = f
"{self.config.externalSkyWcsName}_wcs"
621 self.log.
info(
"Applying external skyWcs from %s", source)
622 skyWcs = dataRef.get(source)
623 exposure.setWcs(skyWcs)
625 if self.config.doApplySkyCorr:
626 self.log.
info(
"Apply sky correction")
627 skyCorr = dataRef.get(
"skyCorr")
628 exposure.maskedImage -= skyCorr.getImage()
633 """Write forced source table
637 dataRef : `lsst.daf.persistence.ButlerDataRef`
638 Butler data reference. The forced_src dataset (with
639 self.dataPrefix prepended) is all that will be modified.
640 sources : `lsst.afw.table.SourceCatalog`
641 Catalog of sources to save.
643 dataRef.put(sources, self.dataPrefix +
"forced_src", flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS)
646 """The schema catalogs that will be used by this task.
650 schemaCatalogs : `dict`
651 Dictionary mapping dataset type to schema catalog.
655 There is only one schema for each type of forced measurement. The
656 dataset type for this measurement is defined in the mapper.
659 catalog.getTable().setMetadata(self.measurement.algMetadata)
660 datasetType = self.dataPrefix +
"forced_src"
661 return {datasetType: catalog}
663 def _getConfigName(self):
665 return self.dataPrefix +
"forcedPhotCcd_config"
667 def _getMetadataName(self):
669 return self.dataPrefix +
"forcedPhotCcd_metadata"
672 def _makeArgumentParser(cls):
673 parser = pipeBase.ArgumentParser(name=cls._DefaultName)
674 parser.add_id_argument(
"--id",
"forced_src", help=
"data ID with raw CCD keys [+ tract optionally], "
675 "e.g. --id visit=12345 ccd=1,2 [tract=0]",
676 ContainerClass=PerTractCcdDataIdContainer)
static std::shared_ptr< IdFactory > makeSource(RecordId expId, int reserved)
Return an IdFactory that includes another, fixed ID in the higher-order bits.
static Key< RecordId > getParentKey()
Key for the parent ID.
A floating-point coordinate rectangle geometry.
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 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)
def getExposureId(self, dataRef)
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)