24 import lsst.pex.config
36 from .forcedPhotImage
import ForcedPhotImageTask, ForcedPhotImageConfig
39 from lsst.meas.mosaic
import applyMosaicResults
41 applyMosaicResults =
None 43 __all__ = (
"PerTractCcdDataIdContainer",
"ForcedPhotCcdConfig",
"ForcedPhotCcdTask",
"imageOverlapsTract")
47 """A data ID container which combines raw data IDs with a tract. 51 Required because we need to add "tract" to the raw data ID keys (defined as 52 whatever we use for ``src``) when no tract is provided (so that the user is 53 not required to know which tracts are spanned by the raw data ID). 55 This subclass of `~lsst.pipe.base.DataIdContainer` assumes that a calexp is 56 being measured using the detection information, a set of reference 57 catalogs, from the set of coadds which intersect with the calexp. It needs 58 the calexp id (e.g. visit, raft, sensor), but is also uses the tract to 59 decide what set of coadds to use. The references from the tract whose 60 patches intersect with the calexp are used. 64 """Make self.refList from self.idList 67 raise RuntimeError(
"Must call setDatasetType first")
68 log = Log.getLogger(
"meas.base.forcedPhotCcd.PerTractCcdDataIdContainer")
70 visitTract = collections.defaultdict(set)
71 visitRefs = collections.defaultdict(list)
73 if "tract" not in dataId:
75 log.info(
"Reading WCS for components of dataId=%s to determine tracts", dict(dataId))
77 skymap = namespace.butler.get(namespace.config.coaddName +
"Coadd_skyMap")
79 for ref
in namespace.butler.subset(
"calexp", dataId=dataId):
80 if not ref.datasetExists(
"calexp"):
83 visit = ref.dataId[
"visit"]
84 visitRefs[visit].
append(ref)
86 md = ref.get(
"calexp_md", immediate=
True)
91 tract = skymap.findTract(wcs.pixelToSky(box.getCenter()))
93 visitTract[visit].add(tract.getId())
95 self.
refList.extend(ref
for ref
in namespace.butler.subset(self.
datasetType, dataId=dataId))
98 for visit, tractSet
in visitTract.items():
99 for ref
in visitRefs[visit]:
100 for tract
in tractSet:
102 dataId=ref.dataId, tract=tract))
104 tractCounter = collections.Counter()
105 for tractSet
in visitTract.values():
106 tractCounter.update(tractSet)
107 log.info(
"Number of visits for each tract: %s", dict(tractCounter))
111 """Return whether the given bounding box overlaps the tract given a WCS. 115 tract : `lsst.skymap.TractInfo` 116 TractInfo specifying a tract. 117 imageWcs : `lsst.afw.geom.SkyWcs` 118 World coordinate system for the image. 119 imageBox : `lsst.geom.Box2I` 120 Bounding box for the image. 125 `True` if the bounding box overlaps the tract; `False` otherwise. 127 tractPoly = tract.getOuterSkyPolygon()
131 imageSkyCorners = imageWcs.pixelToSky(imagePixelCorners)
132 except lsst.pex.exceptions.LsstCppException
as e:
134 if (
not isinstance(e.message, lsst.pex.exceptions.DomainErrorException)
and 135 not isinstance(e.message, lsst.pex.exceptions.RuntimeErrorException)):
140 return tractPoly.intersects(imagePoly)
144 dimensions=(
"instrument",
"visit",
"detector",
"skymap",
"tract"),
145 defaultTemplates={
"inputCoaddName":
"deep",
146 "inputName":
"calexp"}):
147 inputSchema = cT.InitInput(
148 doc=
"Schema for the input measurement catalogs.",
149 name=
"{inputCoaddName}Coadd_ref_schema",
150 storageClass=
"SourceCatalog",
152 outputSchema = cT.InitOutput(
153 doc=
"Schema for the output forced measurement catalogs.",
154 name=
"forced_src_schema",
155 storageClass=
"SourceCatalog",
158 doc=
"Input exposure to perform photometry on.",
160 storageClass=
"ExposureF",
161 dimensions=[
"instrument",
"visit",
"detector"],
164 doc=
"Catalog of shapes and positions at which to force photometry.",
165 name=
"{inputCoaddName}Coadd_ref",
166 storageClass=
"SourceCatalog",
167 dimensions=[
"skymap",
"tract",
"patch"],
170 doc=
"Reference world coordinate system.",
171 name=
"{inputCoaddName}Coadd.wcs",
173 dimensions=[
"abstract_filter",
"skymap",
"tract",
"patch"],
176 doc=
"Output forced photometry catalog.",
178 storageClass=
"SourceCatalog",
179 dimensions=[
"instrument",
"visit",
"detector",
"skymap",
"tract"],
183 class ForcedPhotCcdConfig(ForcedPhotImageConfig, pipelineConnections=ForcedPhotCcdConnections):
184 doApplyUberCal = lsst.pex.config.Field(
186 doc=
"Apply meas_mosaic ubercal results to input calexps?",
191 class ForcedPhotCcdTask(ForcedPhotImageTask):
192 """A command-line driver for performing forced measurement on CCD images. 196 This task is a subclass of 197 :lsst-task:`lsst.meas.base.forcedPhotImage.ForcedPhotImageTask` which is 198 specifically for doing forced measurement on a single CCD exposure, using 199 as a reference catalog the detections which were made on overlapping 202 The `run` method (inherited from `ForcedPhotImageTask`) takes a 203 `~lsst.daf.persistence.ButlerDataRef` argument that corresponds to a single 204 CCD. This should contain the data ID keys that correspond to the 205 ``forced_src`` dataset (the output dataset for this task), which are 206 typically all those used to specify the ``calexp`` dataset (``visit``, 207 ``raft``, ``sensor`` for LSST data) as well as a coadd tract. The tract is 208 used to look up the appropriate coadd measurement catalogs to use as 209 references (e.g. ``deepCoadd_src``; see 210 :lsst-task:`lsst.meas.base.references.CoaddSrcReferencesTask` for more 211 information). While the tract must be given as part of the dataRef, the 212 patches are determined automatically from the bounding box and WCS of the 213 calexp to be measured, and the filter used to fetch references is set via 214 the ``filter`` option in the configuration of 215 :lsst-task:`lsst.meas.base.references.BaseReferencesTask`). 217 In addition to the `run` method, `ForcedPhotCcdTask` overrides several 218 methods of `ForcedPhotImageTask` to specialize it for single-CCD 219 processing, including `~ForcedPhotImageTask.makeIdFactory`, 220 `~ForcedPhotImageTask.fetchReferences`, and 221 `~ForcedPhotImageTask.getExposure`. None of these should be called 222 directly by the user, though it may be useful to override them further in 226 ConfigClass = ForcedPhotCcdConfig
228 _DefaultName =
"forcedPhotCcd" 231 def runQuantum(self, butlerQC, inputRefs, outputRefs):
232 inputs = butlerQC.get(inputRefs)
233 inputs[
'refCat'] = self.filterReferences(inputs[
'exposure'], inputs[
'refCat'], inputs[
'refWcs'])
234 inputs[
'measCat'] = self.generateMeasCat(inputRefs.exposure.dataId,
236 inputs[
'refCat'], inputs[
'refWcs'],
238 outputs = self.run(**inputs)
239 butlerQC.put(outputs, outputRefs)
241 def filterReferences(self, exposure, refCat, refWcs):
242 """Filter reference catalog so that all sources are within the 243 boundaries of the exposure. 247 exposure : `lsst.afw.image.exposure.Exposure` 248 Exposure to generate the catalog for. 249 refCat : `lsst.afw.table.SourceCatalog` 250 Catalog of shapes and positions at which to force photometry. 251 refWcs : `lsst.afw.image.SkyWcs` 252 Reference world coordinate system. 256 refSources : `lsst.afw.table.SourceCatalog` 257 Filtered catalog of forced sources to measure. 261 Filtering the reference catalog is currently handled by Gen2 262 specific methods. To function for Gen3, this method copies 263 code segments to do the filtering and transformation. The 264 majority of this code is based on the methods of 265 lsst.meas.algorithms.loadReferenceObjects.ReferenceObjectLoader 271 expWcs = exposure.getWcs()
272 expRegion = exposure.getBBox(lsst.afw.image.PARENT)
274 expBoxCorners = expBBox.getCorners()
275 expSkyCorners = [expWcs.pixelToSky(corner).getVector()
for 276 corner
in expBoxCorners]
281 sources =
type(refCat)(refCat.table)
282 for record
in refCat:
283 if expPolygon.contains(record.getCoord().getVector()):
284 sources.append(record)
285 refCatIdDict = {ref.getId(): ref.getParent()
for ref
in sources}
290 refSources =
type(refCat)(refCat.table)
291 for record
in refCat:
292 if expPolygon.contains(record.getCoord().getVector()):
293 recordId = record.getId()
296 if topId
in refCatIdDict:
297 topId = refCatIdDict[topId]
301 refSources.append(record)
305 for refRecord
in refSources:
306 refRecord.setFootprint(refRecord.getFootprint().
transform(refWcs,
311 def makeIdFactory(self, dataRef):
312 """Create an object that generates globally unique source IDs. 314 Source IDs are created based on a per-CCD ID and the ID of the CCD 319 dataRef : `lsst.daf.persistence.ButlerDataRef` 320 Butler data reference. The ``ccdExposureId_bits`` and 321 ``ccdExposureId`` datasets are accessed. The data ID must have the 322 keys that correspond to ``ccdExposureId``, which are generally the 323 same as those that correspond to ``calexp`` (``visit``, ``raft``, 324 ``sensor`` for LSST data). 326 expBits = dataRef.get(
"ccdExposureId_bits")
327 expId = int(dataRef.get(
"ccdExposureId"))
330 def getExposureId(self, dataRef):
331 return int(dataRef.get(
"ccdExposureId", immediate=
True))
333 def fetchReferences(self, dataRef, exposure):
334 """Get sources that overlap the exposure. 338 dataRef : `lsst.daf.persistence.ButlerDataRef` 339 Butler data reference corresponding to the image to be measured; 340 should have ``tract``, ``patch``, and ``filter`` keys. 341 exposure : `lsst.afw.image.Exposure` 342 The image to be measured (used only to obtain a WCS and bounding 347 referencs : `lsst.afw.table.SourceCatalog` 348 Catalog of sources that overlap the exposure 352 The returned catalog is sorted by ID and guarantees that all included 353 children have their parent included and that all Footprints are valid. 355 All work is delegated to the references subtask; see 356 :lsst-task:`lsst.meas.base.references.CoaddSrcReferencesTask` 357 for information about the default behavior. 361 unfiltered = self.references.fetchInBox(dataRef, exposure.getBBox(), exposure.getWcs())
362 for record
in unfiltered:
363 if record.getFootprint()
is None or record.getFootprint().getArea() == 0:
364 if record.getParent() != 0:
365 self.log.
warn(
"Skipping reference %s (child of %s) with bad Footprint",
366 record.getId(), record.getParent())
368 self.log.
warn(
"Skipping reference parent %s with bad Footprint", record.getId())
369 badParents.add(record.getId())
370 elif record.getParent()
not in badParents:
371 references.append(record)
376 def getExposure(self, dataRef):
377 """Read input exposure for measurement. 381 dataRef : `lsst.daf.persistence.ButlerDataRef` 382 Butler data reference. Only the ``calexp`` dataset is used, unless 383 ``config.doApplyUberCal`` is `True`, in which case the 384 corresponding meas_mosaic outputs are used as well. 386 exposure = ForcedPhotImageTask.getExposure(self, dataRef)
387 if not self.
config.doApplyUberCal:
389 if applyMosaicResults
is None:
391 "Cannot use improved calibrations for %s because meas_mosaic could not be imported." 397 def _getConfigName(self):
399 return self.dataPrefix +
"forcedPhotCcd_config" 401 def _getMetadataName(self):
403 return self.dataPrefix +
"forcedPhotCcd_metadata" 406 def _makeArgumentParser(cls):
408 parser.add_id_argument(
"--id",
"forced_src", help=
"data ID with raw CCD keys [+ tract optionally], " 409 "e.g. --id visit=12345 ccd=1,2 [tract=0]",
410 ContainerClass=PerTractCcdDataIdContainer)
A floating-point coordinate rectangle geometry.
def makeDataRefList(self, namespace)
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
daf::base::PropertySet * set
static std::shared_ptr< IdFactory > makeSource(RecordId expId, int reserved)
Return an IdFactory that includes another, fixed ID in the higher-order bits.
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...
def imageOverlapsTract(tract, imageWcs, imageBox)
ConvexPolygon is a closed convex polygon on the unit sphere.
std::shared_ptr< SkyWcs > makeSkyWcs(TransformPoint2ToPoint2 const &pixelsToFieldAngle, lsst::geom::Angle const &orientation, bool flipX, lsst::geom::SpherePoint const &boresight, std::string const &projection="TAN")
Construct a FITS SkyWcs from camera geometry.
static Key< RecordId > getParentKey()
Key for the parent ID.
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)