LSST Applications  21.0.0+04719a4bac,21.0.0-1-ga51b5d4+f5e6047307,21.0.0-11-g2b59f77+a9c1acf22d,21.0.0-11-ga42c5b2+86977b0b17,21.0.0-12-gf4ce030+76814010d2,21.0.0-13-g1721dae+760e7a6536,21.0.0-13-g3a573fe+768d78a30a,21.0.0-15-g5a7caf0+f21cbc5713,21.0.0-16-g0fb55c1+b60e2d390c,21.0.0-19-g4cded4ca+71a93a33c0,21.0.0-2-g103fe59+bb20972958,21.0.0-2-g45278ab+04719a4bac,21.0.0-2-g5242d73+3ad5d60fb1,21.0.0-2-g7f82c8f+8babb168e8,21.0.0-2-g8f08a60+06509c8b61,21.0.0-2-g8faa9b5+616205b9df,21.0.0-2-ga326454+8babb168e8,21.0.0-2-gde069b7+5e4aea9c2f,21.0.0-2-gecfae73+1d3a86e577,21.0.0-2-gfc62afb+3ad5d60fb1,21.0.0-25-g1d57be3cd+e73869a214,21.0.0-3-g357aad2+ed88757d29,21.0.0-3-g4a4ce7f+3ad5d60fb1,21.0.0-3-g4be5c26+3ad5d60fb1,21.0.0-3-g65f322c+e0b24896a3,21.0.0-3-g7d9da8d+616205b9df,21.0.0-3-ge02ed75+a9c1acf22d,21.0.0-4-g591bb35+a9c1acf22d,21.0.0-4-g65b4814+b60e2d390c,21.0.0-4-gccdca77+0de219a2bc,21.0.0-4-ge8a399c+6c55c39e83,21.0.0-5-gd00fb1e+05fce91b99,21.0.0-6-gc675373+3ad5d60fb1,21.0.0-64-g1122c245+4fb2b8f86e,21.0.0-7-g04766d7+cd19d05db2,21.0.0-7-gdf92d54+04719a4bac,21.0.0-8-g5674e7b+d1bd76f71f,master-gac4afde19b+a9c1acf22d,w.2021.13
LSST Data Management Base Package
forcedPhotCcd.py
Go to the documentation of this file.
1 # This file is part of meas_base.
2 #
3 # Developed for the LSST Data Management System.
4 # This product includes software developed by the LSST Project
5 # (https://www.lsst.org).
6 # See the COPYRIGHT file at the top-level directory of this distribution
7 # for details of code ownership.
8 #
9 # This program is free software: you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation, either version 3 of the License, or
12 # (at your option) any later version.
13 #
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
18 #
19 # You should have received a copy of the GNU General Public License
20 # along with this program. If not, see <https://www.gnu.org/licenses/>.
21 
22 import collections
23 
24 import lsst.pex.config
26 from lsst.log import Log
27 import lsst.pipe.base
28 import lsst.geom
29 import lsst.afw.geom
30 import lsst.afw.image
31 import lsst.afw.table
32 import lsst.sphgeom
33 
34 from lsst.pipe.base import PipelineTaskConnections
36 
37 import lsst.pipe.base as pipeBase
38 from lsst.skymap import BaseSkyMap
39 
40 from .references import MultiBandReferencesTask
41 from .forcedMeasurement import ForcedMeasurementTask
42 from .applyApCorr import ApplyApCorrTask
43 from .catalogCalculation import CatalogCalculationTask
44 
45 try:
46  from lsst.meas.mosaic import applyMosaicResults
47 except ImportError:
48  applyMosaicResults = None
49 
50 __all__ = ("PerTractCcdDataIdContainer", "ForcedPhotCcdConfig", "ForcedPhotCcdTask", "imageOverlapsTract")
51 
52 
53 class PerTractCcdDataIdContainer(pipeBase.DataIdContainer):
54  """A data ID container which combines raw data IDs with a tract.
55 
56  Notes
57  -----
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).
61 
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.
68  """
69 
70  def makeDataRefList(self, namespace):
71  """Make self.refList from self.idList
72  """
73  if self.datasetType is None:
74  raise RuntimeError("Must call setDatasetType first")
75  log = Log.getLogger("meas.base.forcedPhotCcd.PerTractCcdDataIdContainer")
76  skymap = None
77  visitTract = collections.defaultdict(set) # Set of tracts for each visit
78  visitRefs = collections.defaultdict(list) # List of data references for each visit
79  for dataId in self.idList:
80  if "tract" not in dataId:
81  # Discover which tracts the data overlaps
82  log.info("Reading WCS for components of dataId=%s to determine tracts", dict(dataId))
83  if skymap is None:
84  skymap = namespace.butler.get(namespace.config.coaddName + "Coadd_skyMap")
85 
86  for ref in namespace.butler.subset("calexp", dataId=dataId):
87  if not ref.datasetExists("calexp"):
88  continue
89 
90  visit = ref.dataId["visit"]
91  visitRefs[visit].append(ref)
92 
93  md = ref.get("calexp_md", immediate=True)
94  wcs = lsst.afw.geom.makeSkyWcs(md)
96  # Going with just the nearest tract. Since we're throwing all tracts for the visit
97  # together, this shouldn't be a problem unless the tracts are much smaller than a CCD.
98  tract = skymap.findTract(wcs.pixelToSky(box.getCenter()))
99  if imageOverlapsTract(tract, wcs, box):
100  visitTract[visit].add(tract.getId())
101  else:
102  self.refList.extend(ref for ref in namespace.butler.subset(self.datasetType, dataId=dataId))
103 
104  # Ensure all components of a visit are kept together by putting them all in the same set of tracts
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))
110  if visitTract:
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))
115 
116 
117 def imageOverlapsTract(tract, imageWcs, imageBox):
118  """Return whether the given bounding box overlaps the tract given a WCS.
119 
120  Parameters
121  ----------
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.
128 
129  Returns
130  -------
131  overlap : `bool`
132  `True` if the bounding box overlaps the tract; `False` otherwise.
133  """
134  tractPoly = tract.getOuterSkyPolygon()
135 
136  imagePixelCorners = lsst.geom.Box2D(imageBox).getCorners()
137  try:
138  imageSkyCorners = imageWcs.pixelToSky(imagePixelCorners)
139  except lsst.pex.exceptions.LsstCppException as e:
140  # Protecting ourselves from awful Wcs solutions in input images
141  if (not isinstance(e.message, lsst.pex.exceptions.DomainErrorException)
142  and not isinstance(e.message, lsst.pex.exceptions.RuntimeErrorException)):
143  raise
144  return False
145 
146  imagePoly = lsst.sphgeom.ConvexPolygon.convexHull([coord.getVector() for coord in imageSkyCorners])
147  return tractPoly.intersects(imagePoly) # "intersects" also covers "contains" or "is contained by"
148 
149 
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",
158  )
159  outputSchema = cT.InitOutput(
160  doc="Schema for the output forced measurement catalogs.",
161  name="forced_src_schema",
162  storageClass="SourceCatalog",
163  )
164  exposure = cT.Input(
165  doc="Input exposure to perform photometry on.",
166  name="{inputName}",
167  storageClass="ExposureF",
168  dimensions=["instrument", "visit", "detector"],
169  )
170  refCat = cT.Input(
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"],
175  multiple=True
176  )
177  skyMap = cT.Input(
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"],
182  )
183  measCat = cT.Output(
184  doc="Output forced photometry catalog.",
185  name="forced_src",
186  storageClass="SourceCatalog",
187  dimensions=["instrument", "visit", "detector", "skymap", "tract"],
188  )
189 
190 
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"
197  )
198  measurement = lsst.pex.config.ConfigurableField(
199  target=ForcedMeasurementTask,
200  doc="subtask to do forced measurement"
201  )
202  coaddName = lsst.pex.config.Field(
203  doc="coadd name: typically one of deep or goodSeeing",
204  dtype=str,
205  default="deep",
206  )
207  doApCorr = lsst.pex.config.Field(
208  dtype=bool,
209  default=True,
210  doc="Run subtask to apply aperture corrections"
211  )
212  applyApCorr = lsst.pex.config.ConfigurableField(
213  target=ApplyApCorrTask,
214  doc="Subtask to apply aperture corrections"
215  )
216  catalogCalculation = lsst.pex.config.ConfigurableField(
217  target=CatalogCalculationTask,
218  doc="Subtask to run catalogCalculation plugins on catalog"
219  )
220  doApplyUberCal = lsst.pex.config.Field(
221  dtype=bool,
222  doc="Apply meas_mosaic ubercal results to input calexps?",
223  default=False,
224  deprecated="Deprecated by DM-23352; use doApplyExternalPhotoCalib and doApplyExternalSkyWcs instead",
225  )
226  doApplyExternalPhotoCalib = lsst.pex.config.Field(
227  dtype=bool,
228  default=False,
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 "
232  "to load."),
233  )
234  doApplyExternalSkyWcs = lsst.pex.config.Field(
235  dtype=bool,
236  default=False,
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."),
240  )
241  doApplySkyCorr = lsst.pex.config.Field(
242  dtype=bool,
243  default=False,
244  doc="Apply sky correction?",
245  )
246  includePhotoCalibVar = lsst.pex.config.Field(
247  dtype=bool,
248  default=False,
249  doc="Add photometric calibration variance to warp variance plane?",
250  )
251  externalPhotoCalibName = lsst.pex.config.ChoiceField(
252  dtype=str,
253  doc=("Type of external PhotoCalib if ``doApplyExternalPhotoCalib`` is True. "
254  "Unused for Gen3 middleware."),
255  default="jointcal",
256  allowed={
257  "jointcal": "Use jointcal_photoCalib",
258  "fgcm": "Use fgcm_photoCalib",
259  "fgcm_tract": "Use fgcm_tract_photoCalib"
260  },
261  )
262  externalSkyWcsName = lsst.pex.config.ChoiceField(
263  dtype=str,
264  doc="Type of external SkyWcs if ``doApplyExternalSkyWcs`` is True. Unused for Gen3 middleware.",
265  default="jointcal",
266  allowed={
267  "jointcal": "Use jointcal_wcs"
268  },
269  )
270 
271  def setDefaults(self):
272  # Docstring inherited.
273  # Make catalogCalculation a no-op by default as no modelFlux is setup by default in
274  # ForcedMeasurementTask
275  super().setDefaults()
276 
277  self.catalogCalculation.plugins.names = []
278 
279 
280 class ForcedPhotCcdTask(pipeBase.PipelineTask, pipeBase.CmdLineTask):
281  """A command-line driver for performing forced measurement on CCD images.
282 
283  Parameters
284  ----------
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
289  precedence.
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.
294  **kwds
295  Keyword arguments are passed to the supertask constructor.
296 
297  Notes
298  -----
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`).
312  """
313 
314  ConfigClass = ForcedPhotCcdConfig
315  RunnerClass = pipeBase.ButlerInitializedTaskRunner
316  _DefaultName = "forcedPhotCcd"
317  dataPrefix = ""
318 
319  def __init__(self, butler=None, refSchema=None, initInputs=None, **kwds):
320  super().__init__(**kwds)
321 
322  if initInputs is not None:
323  refSchema = initInputs['inputSchema'].schema
324 
325  self.makeSubtask("references", butler=butler, schema=refSchema)
326  if refSchema is None:
327  refSchema = self.references.schema
328  self.makeSubtask("measurement", refSchema=refSchema)
329  # It is necessary to get the schema internal to the forced measurement task until such a time
330  # that the schema is not owned by the measurement task, but is passed in by an external caller
331  if self.config.doApCorr:
332  self.makeSubtask("applyApCorr", schema=self.measurement.schema)
333  self.makeSubtask('catalogCalculation', schema=self.measurement.schema)
334  self.outputSchema = lsst.afw.table.SourceCatalog(self.measurement.schema)
335 
336  def runQuantum(self, butlerQC, inputRefs, outputRefs):
337  inputs = butlerQC.get(inputRefs)
338 
339  tract = butlerQC.quantum.dataId['tract']
340  skyMap = inputs.pop("skyMap")
341  inputs['refWcs'] = skyMap[tract].getWcs()
342 
343  inputs['refCat'] = self.mergeAndFilterReferences(inputs['exposure'], inputs['refCat'],
344  inputs['refWcs'])
345 
346  inputs['measCat'], inputs['exposureId'] = self.generateMeasCat(inputRefs.exposure.dataId,
347  inputs['exposure'],
348  inputs['refCat'], inputs['refWcs'],
349  "visit_detector")
350  self.attachFootprints(inputs['measCat'], inputs['refCat'], inputs['exposure'], inputs['refWcs'])
351  # TODO: apply external calibrations (DM-17062)
352  outputs = self.run(**inputs)
353  butlerQC.put(outputs, outputRefs)
354 
355  def mergeAndFilterReferences(self, exposure, refCats, refWcs):
356  """Filter reference catalog so that all sources are within the
357  boundaries of the exposure.
358 
359  Parameters
360  ----------
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.
367 
368  Returns
369  -------
370  refSources : `lsst.afw.table.SourceCatalog`
371  Filtered catalog of forced sources to measure.
372 
373  Notes
374  -----
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
380 
381  """
382 
383  # Step 1: Determine bounds of the exposure photometry will
384  # be performed on.
385  expWcs = exposure.getWcs()
386  expRegion = exposure.getBBox(lsst.afw.image.PARENT)
387  expBBox = lsst.geom.Box2D(expRegion)
388  expBoxCorners = expBBox.getCorners()
389  expSkyCorners = [expWcs.pixelToSky(corner).getVector() for
390  corner in expBoxCorners]
391  expPolygon = lsst.sphgeom.ConvexPolygon(expSkyCorners)
392 
393  # Step 2: Filter out reference catalog sources that are
394  # not contained within the exposure boundaries, or whose
395  # parents are not within the exposure boundaries. Note
396  # that within a single input refCat, the parents always
397  # appear before the children.
398  mergedRefCat = lsst.afw.table.SourceCatalog(refCats[0].table)
399  for refCat in refCats:
400  containedIds = {0} # zero as a parent ID means "this is a parent"
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())
406  mergedRefCat.sort(lsst.afw.table.SourceTable.getParentKey())
407  return mergedRefCat
408 
409  def generateMeasCat(self, exposureDataId, exposure, refCat, refWcs, idPackerName):
410  """Generate a measurement catalog for Gen3.
411 
412  Parameters
413  ----------
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.
422  idPackerName : `str`
423  Type of ID packer to construct from the registry.
424 
425  Returns
426  -------
427  measCat : `lsst.afw.table.SourceCatalog`
428  Catalog of forced sources to measure.
429  expId : `int`
430  Unique binary id associated with the input exposure
431  """
432  expId, expBits = exposureDataId.pack(idPackerName, returnMaxBits=True)
433  idFactory = lsst.afw.table.IdFactory.makeSource(expId, 64 - expBits)
434 
435  measCat = self.measurement.generateMeasCat(exposure, refCat, refWcs,
436  idFactory=idFactory)
437  return measCat, expId
438 
439  def runDataRef(self, dataRef, psfCache=None):
440  """Perform forced measurement on a single exposure.
441 
442  Parameters
443  ----------
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.
455 
456  Notes
457  -----
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.
463  """
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)
469 
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)
474 
475  exposureId = self.getExposureId(dataRef)
476 
477  forcedPhotResult = self.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
478 
479  self.writeOutput(dataRef, forcedPhotResult.measCat)
480 
481  def run(self, measCat, exposure, refCat, refWcs, exposureId=None):
482  """Perform forced measurement on a single exposure.
483 
484  Parameters
485  ----------
486  measCat : `lsst.afw.table.SourceCatalog`
487  The measurement catalog, based on the sources listed in the
488  reference catalog.
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.
495  exposureId : `int`
496  Optional unique exposureId used for random seed in measurement
497  task.
498 
499  Returns
500  -------
501  result : `lsst.pipe.base.Struct`
502  Structure with fields:
503 
504  ``measCat``
505  Catalog of forced measurement results
506  (`lsst.afw.table.SourceCatalog`).
507  """
508  self.measurement.run(measCat, exposure, refCat, refWcs, exposureId=exposureId)
509  if self.config.doApCorr:
510  self.applyApCorr.run(
511  catalog=measCat,
512  apCorrMap=exposure.getInfo().getApCorrMap()
513  )
514  self.catalogCalculation.run(measCat)
515 
516  return pipeBase.Struct(measCat=measCat)
517 
518  def makeIdFactory(self, dataRef):
519  """Create an object that generates globally unique source IDs.
520 
521  Source IDs are created based on a per-CCD ID and the ID of the CCD
522  itself.
523 
524  Parameters
525  ----------
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).
532  """
533  expBits = dataRef.get("ccdExposureId_bits")
534  expId = int(dataRef.get("ccdExposureId"))
535  return lsst.afw.table.IdFactory.makeSource(expId, 64 - expBits)
536 
537  def getExposureId(self, dataRef):
538  return int(dataRef.get("ccdExposureId", immediate=True))
539 
540  def fetchReferences(self, dataRef, exposure):
541  """Get sources that overlap the exposure.
542 
543  Parameters
544  ----------
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
550  box).
551 
552  Returns
553  -------
554  referencs : `lsst.afw.table.SourceCatalog`
555  Catalog of sources that overlap the exposure
556 
557  Notes
558  -----
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.
561 
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.
565  """
566  references = lsst.afw.table.SourceCatalog(self.references.schema)
567  badParents = set()
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())
574  else:
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)
579  # catalog must be sorted by parent ID for lsst.afw.table.getChildren to work
580  references.sort(lsst.afw.table.SourceTable.getParentKey())
581  return references
582 
583  def attachFootprints(self, sources, refCat, exposure, refWcs):
584  r"""Attach footprints to blank sources prior to measurements.
585 
586  Notes
587  -----
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.
591 
592  Subclasses of this class must implement this method to define how
593  those `~lsst.afw.detection.Footprint`\ s should be generated.
594 
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.
600  """
601  return self.measurement.attachTransformedFootprints(sources, refCat, exposure, refWcs)
602 
603  def getExposure(self, dataRef):
604  """Read input exposure for measurement.
605 
606  Parameters
607  ----------
608  dataRef : `lsst.daf.persistence.ButlerDataRef`
609  Butler data reference.
610  """
611  exposure = dataRef.get(self.dataPrefix + "calexp", immediate=True)
612 
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) # No need for calibrateImage; having the photoCalib suffices
618 
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)
624 
625  if self.config.doApplySkyCorr:
626  self.log.info("Apply sky correction")
627  skyCorr = dataRef.get("skyCorr")
628  exposure.maskedImage -= skyCorr.getImage()
629 
630  return exposure
631 
632  def writeOutput(self, dataRef, sources):
633  """Write forced source table
634 
635  Parameters
636  ----------
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.
642  """
643  dataRef.put(sources, self.dataPrefix + "forced_src", flags=lsst.afw.table.SOURCE_IO_NO_FOOTPRINTS)
644 
645  def getSchemaCatalogs(self):
646  """The schema catalogs that will be used by this task.
647 
648  Returns
649  -------
650  schemaCatalogs : `dict`
651  Dictionary mapping dataset type to schema catalog.
652 
653  Notes
654  -----
655  There is only one schema for each type of forced measurement. The
656  dataset type for this measurement is defined in the mapper.
657  """
658  catalog = lsst.afw.table.SourceCatalog(self.measurement.schema)
659  catalog.getTable().setMetadata(self.measurement.algMetadata)
660  datasetType = self.dataPrefix + "forced_src"
661  return {datasetType: catalog}
662 
663  def _getConfigName(self):
664  # Documented in superclass.
665  return self.dataPrefix + "forcedPhotCcd_config"
666 
667  def _getMetadataName(self):
668  # Documented in superclass
669  return self.dataPrefix + "forcedPhotCcd_metadata"
670 
671  @classmethod
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)
677  return parser
static std::shared_ptr< IdFactory > makeSource(RecordId expId, int reserved)
Return an IdFactory that includes another, fixed ID in the higher-order bits.
Definition: IdFactory.cc:72
static Key< RecordId > getParentKey()
Key for the parent ID.
Definition: Source.h:275
A floating-point coordinate rectangle geometry.
Definition: Box.h:413
ConvexPolygon is a closed convex polygon on the unit sphere.
Definition: ConvexPolygon.h:57
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...
Definition: ConvexPolygon.h:65
daf::base::PropertySet * set
Definition: fits.cc:912
std::shared_ptr< FrameSet > append(FrameSet const &first, FrameSet const &second)
Construct a FrameSet that performs two transformations in series.
Definition: functional.cc:33
std::shared_ptr< SkyWcs > makeSkyWcs(daf::base::PropertySet &metadata, bool strip=false)
Construct a SkyWcs from FITS keywords.
Definition: SkyWcs.cc:526
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)
Definition: Image.cc:694
Definition: Log.h:706
def imageOverlapsTract(tract, imageWcs, imageBox)
def attachFootprints(self, sources, refCat, exposure, refWcs, dataRef)
def writeOutput(self, dataRef, sources)
def fetchReferences(self, dataRef, exposure)
def run(self, skyInfo, tempExpRefList, imageScalerList, weightList, altMaskList=None, mask=None, supplementaryData=None)