LSSTApplications  8.0.0.0+107,8.0.0.1+13,9.1+18,9.2,master-g084aeec0a4,master-g0aced2eed8+6,master-g15627eb03c,master-g28afc54ef9,master-g3391ba5ea0,master-g3d0fb8ae5f,master-g4432ae2e89+36,master-g5c3c32f3ec+17,master-g60f1e072bb+1,master-g6a3ac32d1b,master-g76a88a4307+1,master-g7bce1f4e06+57,master-g8ff4092549+31,master-g98e65bf68e,master-ga6b77976b1+53,master-gae20e2b580+3,master-gb584cd3397+53,master-gc5448b162b+1,master-gc54cf9771d,master-gc69578ece6+1,master-gcbf758c456+22,master-gcec1da163f+63,master-gcf15f11bcc,master-gd167108223,master-gf44c96c709
LSSTDataManagementBasePackage
forcedPhot.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 
3 import math
4 
5 from lsst.pex.config import Config, ConfigurableField, DictField, Field
6 from lsst.pipe.base import Task, CmdLineTask, ArgumentParser, timeMethod
7 
8 import lsst.daf.base as dafBase
9 import lsst.afw.table as afwTable
10 import lsst.afw.image as afwImage
11 import lsst.afw.math as afwMath
12 import lsst.afw.geom as afwGeom
13 import lsst.meas.algorithms as measAlg
14 
15 class ReferencesConfig(Config):
16  """Configuration for reference source catalog retrieval
17 
18  This is bare, but will be extended by subclasses
19  to support getting the list of reference sources.
20  """
21  correct = Field(dtype=bool, default=False, doc="Correct references for astrometric offsets?")
22  minFlux = Field(dtype=float, default=3000, doc="Minimum flux for calculating offsets")
23  radius = Field(dtype=float, default=0.5, doc="Association radius for matching, arcsec")
24 
25 class ReferencesTask(Task):
26  """Task to generate a reference source catalog for forced photometry
27 
28  This is a base class, as it is not clear how to generate the
29  reference sources in the generic case (different projects will
30  want to do this differently: perhaps from measuring a coadd, or
31  perhaps from a database, or ...) and so this class MUST be
32  overridden to properly define the getReferences() method.
33  """
34 
35  ConfigClass = ReferencesConfig
36 
37  def run(self, dataRef, exposure):
38  references = self.getReferences(dataRef, exposure)
39  self.log.info("Retrieved %d reference sources" % len(references))
40  references = self.subsetReferences(references, exposure)
41  self.log.info("Subset to %d reference sources" % len(references))
42  if self.config.correct:
43  references = self.correctReferences(dataRef, references)
44  return references
45 
46  def getReferences(self, dataRef, exposure):
47  """Get reference sources on (or close to) exposure.
48 
49  This method must be overridden by subclasses to return
50  a lsst.afw.table.SourceCatalog.
51 
52  @param dataRef Data reference from butler
53  @param exposure Exposure that has been read
54  @return Catalog (lsst.afw.table.SourceCatalog) of reference sources
55  """
56  # XXX put something in the Mapper???
57  self.log.fatal(
58  """Calling base class implementation of ReferencesTask.getReferences()!
59  You need to configure a subclass of ReferencesTask. Put in your configuration
60  override file something like:
61  from some.namespace import SubclassReferencesTask
62  root.references.retarget(SubclassReferencesTask)
63  """)
64  raise NotImplementedError("Don't know how to get reference sources in the generic case")
65 
66  def subsetReferences(self, references, exposure):
67  """Generate a subset of reference sources to ensure all are in the exposure
68 
69  @param references Reference source catalog
70  @param exposure Exposure of interest
71  @return Reference catalog with subset
72  """
73  box = afwGeom.Box2D(exposure.getBBox(afwImage.LOCAL))
74  wcs = exposure.getWcs()
75  subset = afwTable.SourceCatalog(references.table)
76  for ref in references:
77  coord = ref.getCoord()
78  if box.contains(wcs.skyToPixel(coord)):
79  subset.append(ref)
80  return subset
81 
82  def correctReferences(self, dataRef, references):
83  self.log.info("Correcting reference positions...")
84  sources = dataRef.get("src")
85  matches = afwTable.matchRaDec(sources, references, self.config.radius * afwGeom.arcseconds)
86  num = len(matches)
87  self.log.info("%d matches between source and reference catalogs" % num)
89  # XXX statistics parameters?
90  dra, ddec = afwMath.vectorF(), afwMath.vectorF()
91  dra.reserve(num)
92  ddec.reserve(num)
93  units = afwGeom.arcseconds
94  # XXX errors in positions?
95  for m in matches:
96  src = m.first
97  if src.getPsfFlux() < self.config.minFlux:
98  continue
99  ref = m.second
100  offset = ref.getCoord().getTangentPlaneOffset(src.getCoord())
101  dra.push_back(offset[0].asAngularUnits(units))
102  ddec.push_back(offset[1].asAngularUnits(units))
103  num = len(dra)
104  draStats = afwMath.makeStatistics(dra, afwMath.MEANCLIP | afwMath.STDEVCLIP, stats)
105  ddecStats = afwMath.makeStatistics(ddec, afwMath.MEANCLIP | afwMath.STDEVCLIP, stats)
106  draMean = draStats.getValue(afwMath.MEANCLIP)
107  ddecMean = ddecStats.getValue(afwMath.MEANCLIP)
108  self.log.info("Offset from %d sources is dRA = %f +/- %f arcsec, dDec = %f +/- %f arcsec" %
109  (num, draMean, draStats.getValue(afwMath.STDEVCLIP), ddecMean,
110  ddecStats.getValue(afwMath.STDEVCLIP)))
111  angle = math.atan2(ddecMean, draMean)*afwGeom.radians
112  distance = math.hypot(draMean, ddecMean)*units
113  for ref in references:
114  coord = ref.getCoord()
115  coord.offset(angle, distance)
116  ref.setCoord(coord)
117  return references
118 
119 class ForcedPhotConfig(Config):
120  """Configuration for forced photometry.
121 
122  """
123  references = ConfigurableField(target=ReferencesTask, doc="Retrieve reference source catalog")
124  measurement = ConfigurableField(target=measAlg.SourceMeasurementTask, doc="Forced measurement")
125  copyColumns = DictField(keytype=str, itemtype=str, doc="Mapping of reference columns to source columns",
126  default={"id": "objectId"})
127 
128 
129 class ForcedPhotTask(CmdLineTask):
130  """Task to perform forced photometry.
131 
132  "Forced photometry" is measurement on an image using the
133  position from another source as the centroid, and without
134  recentering.
135  """
136 
137  ConfigClass = ForcedPhotConfig
138  _DefaultName = "forcedPhot"
139 
140  def __init__(self, *args, **kwargs):
141  super(ForcedPhotTask, self).__init__(*args, **kwargs)
142  self.schema = afwTable.SourceTable.makeMinimalSchema()
144  self.makeSubtask("references")
145  self.makeSubtask("measurement", schema=self.schema, algMetadata=self.algMetadata)
146 
147  @classmethod
149  """Overriding CmdLineTask._makeArgumentParser to set dataset type"""
150  parser = ArgumentParser(name=cls._DefaultName)
151  parser.add_id_argument("--id", "calexp", help="data ID, e.g. --id visit=12345 ccd=1,2")
152  return parser
153 
154  @timeMethod
155  def run(self, dataRef):
156  exposure = dataRef.get("calexp")
157 
158  expBits = dataRef.get("ccdExposureId_bits")
159  expId = long(dataRef.get("ccdExposureId"))
160  idFactory = afwTable.IdFactory.makeSource(expId, 64 - expBits)
161 
162  references = self.references.run(dataRef, exposure)
163  self.log.info("Performing forced measurement on %d sources" % len(references))
164  sources = self.makeSources(references, idFactory)
165  self.measurement.run(exposure, sources, references=references)
166  self.writeOutput(dataRef, sources)
167 
168  def makeSources(self, references, idFactory):
169  """Generate sources to be measured
170 
171  @param references Reference source catalog
172  @param idFactory Factory to generate unique ids for forced sources
173  @return Source catalog ready for measurement
174  """
175  schema = afwTable.Schema(self.schema)
176 
177  copyKeys = []
178  for fromCol, toCol in self.config.copyColumns.items():
179  item = references.schema.find(fromCol)
180  schema.addField(toCol, item.field.getTypeString(), item.field.getDoc(), item.field.getUnits())
181  keys = (item.key, schema.find(toCol).key)
182  copyKeys.append(keys)
183 
184  table = afwTable.SourceTable.make(schema, idFactory)
185  sources = afwTable.SourceCatalog(table)
186  table = sources.table
187  table.setMetadata(self.algMetadata)
188  sources.preallocate(len(references))
189  for ref in references:
190  src = table.makeRecord()
191  for fromKey, toKey in copyKeys:
192  src.set(toKey, ref.get(fromKey))
193  sources.append(src)
194  return sources
195 
196  def writeOutput(self, dataRef, sources, outName="forcedsources"):
197  """Write sources out.
198 
199  @param outName Name of forced sources in butler
200  """
201  dataRef.put(sources, outName)
202 
Defines the fields and offsets for a table.
Definition: Schema.h:46
Class for storing ordered metadata with comments.
Definition: PropertyList.h:81
Pass parameters to a Statistics objectA class to pass parameters which control how the stats are calc...
Definition: Statistics.h:92
Custom catalog class for record/table subclasses that are guaranteed to have an ID, and should generally be sorted by that ID.
Definition: fwd.h:55
Statistics makeStatistics(afwImage::Mask< afwImage::MaskPixel > const &msk, int const flags, StatisticsControl const &sctrl)
Specialization to handle Masks.
Definition: Statistics.cc:1023
std::vector< Match< typename Cat::Record, typename Cat::Record > > matchRaDec(Cat const &cat, Angle radius, bool symmetric=true)
A floating-point coordinate rectangle geometry.
Definition: Box.h:271