lsst.meas.algorithms  13.0-18-gc4ad4228+1
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
loadReferenceObjects.py
Go to the documentation of this file.
1 #
2 # LSST Data Management System
3 #
4 # Copyright 2008-2017 AURA/LSST.
5 #
6 # This product includes software developed by the
7 # LSST Project (http://www.lsst.org/).
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 LSST License Statement and
20 # the GNU General Public License along with this program. If not,
21 # see <https://www.lsstcorp.org/LegalNotices/>.
22 #
23 from __future__ import absolute_import, division, print_function
24 
25 __all__ = ["getRefFluxField", "getRefFluxKeys", "LoadReferenceObjectsTask", "LoadReferenceObjectsConfig"]
26 
27 import abc
28 
29 import numpy
30 
31 import lsst.afw.coord as afwCoord
32 import lsst.afw.geom as afwGeom
33 import lsst.afw.table as afwTable
34 import lsst.pex.config as pexConfig
35 import lsst.pipe.base as pipeBase
36 from future.utils import with_metaclass
37 
38 
39 def getRefFluxField(schema, filterName=None):
40  """!Get name of flux field in schema
41 
42  if filterName is specified:
43  return *filterName*_camFlux if present
44  else return *filterName*_flux if present (camera filter name matches reference filter name)
45  else throw RuntimeError
46  else:
47  return camFlux, if present,
48  else throw RuntimeError
49 
50  @param[in] schema reference catalog schema
51  @param[in] filterName name of camera filter
52  @return flux field name
53  @throw RuntimeError if appropriate field is not found
54  """
55  if not isinstance(schema, afwTable.Schema):
56  raise RuntimeError("schema=%s is not a schema" % (schema,))
57  if filterName:
58  fluxFieldList = [filterName + "_camFlux", filterName + "_flux"]
59  else:
60  fluxFieldList = ["camFlux"]
61  for fluxField in fluxFieldList:
62  if fluxField in schema:
63  return fluxField
64 
65  raise RuntimeError("Could not find flux field(s) %s" % (", ".join(fluxFieldList)))
66 
67 
68 def getRefFluxKeys(schema, filterName=None):
69  """!Return flux and flux error keys
70 
71  @param[in] schema reference catalog schema
72  @param[in] filterName name of camera filter
73  @return a pair of keys:
74  flux key
75  flux error key, if present, else None
76  @throw RuntimeError if flux field not found
77  """
78  fluxField = getRefFluxField(schema, filterName)
79  fluxErrField = fluxField + "Sigma"
80  fluxKey = schema[fluxField].asKey()
81  try:
82  fluxErrKey = schema[fluxErrField].asKey()
83  except Exception:
84  fluxErrKey = None
85  return (fluxKey, fluxErrKey)
86 
87 
88 class LoadReferenceObjectsConfig(pexConfig.Config):
89  pixelMargin = pexConfig.RangeField(
90  doc="Padding to add to 4 all edges of the bounding box (pixels)",
91  dtype=int,
92  default=50,
93  min=0,
94  )
95  defaultFilter = pexConfig.Field(
96  doc="Default reference catalog filter to use if filter not specified in exposure; " +
97  "if blank then filter must be specified in exposure",
98  dtype=str,
99  default="",
100  )
101  filterMap = pexConfig.DictField(
102  doc="Mapping of camera filter name: reference catalog filter name; " +
103  "each reference filter must exist",
104  keytype=str,
105  itemtype=str,
106  default={},
107  )
108 
109 # The following comment block adds a link to this task from the Task Documentation page.
110 ## \addtogroup LSST_task_documentation
111 ## \{
112 ## \page LoadReferenceObjectsTask
113 ## \ref LoadReferenceObjectsTask_ "LoadReferenceObjectsTask"
114 ## \copybrief LoadReferenceObjectsTask
115 ## \}
116 
117 
118 class LoadReferenceObjectsTask(with_metaclass(abc.ABCMeta, pipeBase.Task)):
119  """!Abstract base class to load objects from reference catalogs
120 
121  @anchor LoadReferenceObjectsTask_
122 
123  @section meas_algorithms_loadReferenceObjects_Contents Contents
124 
125  - @ref meas_algorithms_loadReferenceObjects_Purpose
126  - @ref meas_algorithms_loadReferenceObjects_Initialize
127  - @ref meas_algorithms_loadReferenceObjects_IO
128  - @ref meas_algorithms_loadReferenceObjects_Schema
129  - @ref meas_algorithms_loadReferenceObjects_Config
130 
131  @section meas_algorithms_loadReferenceObjects_Purpose Description
132 
133  Abstract base class for tasks that load objects from a reference catalog
134  in a particular region of the sky.
135 
136  Implementations must subclass this class, override the loadSkyCircle method,
137  and will typically override the value of ConfigClass with a task-specific config class.
138 
139  @section meas_algorithms_loadReferenceObjects_Initialize Task initialisation
140 
141  @copydoc \_\_init\_\_
142 
143  @section meas_algorithms_loadReferenceObjects_IO Invoking the Task
144 
145  @copydoc loadObjectsInBBox
146 
147  @section meas_algorithms_loadReferenceObjects_Schema Schema of the reference object catalog
148 
149  Reference object catalogs are instances of lsst.afw.table.SimpleCatalog with the following schema
150  (other fields may also be present):
151  - coord: position of star on sky (an lsst.afw.coord.IcrsCoord)
152  - centroid: position of star on an exposure, if relevant (an lsst.afw.Point2D)
153  - hasCentroid: is centroid usable?
154  - *referenceFilterName*_flux: brightness in the specified reference catalog filter (Jy)
155  Note: the function lsst.afw.image.abMagFromFlux will convert flux in Jy to AB Magnitude.
156  - *referenceFilterName*_fluxSigma (optional): brightness standard deviation (Jy);
157  omitted if no data is available; possibly nan if data is available for some objects but not others
158  - camFlux: brightness in default camera filter (Jy); omitted if defaultFilter not specified
159  - camFluxSigma: brightness standard deviation for default camera filter;
160  omitted if defaultFilter not specified or standard deviation not available that filter
161  - *cameraFilterName*_camFlux: brightness in specified camera filter (Jy)
162  - *cameraFilterName*_camFluxSigma (optional): brightness standard deviation
163  in specified camera filter (Jy); omitted if no data is available;
164  possibly nan if data is available for some objects but not others
165  - photometric (optional): is the object usable for photometric calibration?
166  - resolved (optional): is the object spatially resolved?
167  - variable (optional): does the object have variable brightness?
168 
169  @section meas_algorithms_loadReferenceObjects_Config Configuration parameters
170 
171  See @ref LoadReferenceObjectsConfig for a base set of configuration parameters.
172  Most subclasses will add configuration variables.
173  """
174  ConfigClass = LoadReferenceObjectsConfig
175  _DefaultName = "LoadReferenceObjects"
176 
177  def __init__(self, butler=None, *args, **kwargs):
178  """!Construct a LoadReferenceObjectsTask
179 
180  @param[in] butler A daf.persistence.Butler object. This allows subclasses to use the butler to
181  access reference catalog files using the stack I/O abstraction scheme.
182  """
183  pipeBase.Task.__init__(self, *args, **kwargs)
184  self.butler = butler
185 
186  @pipeBase.timeMethod
187  def loadPixelBox(self, bbox, wcs, filterName=None, calib=None):
188  """!Load reference objects that overlap a pixel-based rectangular region
189 
190  The search algorithm works by searching in a region in sky coordinates whose center is the center
191  of the bbox and radius is large enough to just include all 4 corners of the bbox.
192  Stars that lie outside the bbox are then trimmed from the list.
193 
194  @param[in] bbox bounding box for pixels (an lsst.afw.geom.Box2I or Box2D)
195  @param[in] wcs WCS (an lsst.afw.image.Wcs)
196  @param[in] filterName name of camera filter, or None or blank for the default filter
197  @param[in] calib calibration, or None if unknown
198 
199  @return an lsst.pipe.base.Struct containing:
200  - refCat a catalog of reference objects with the
201  \link meas_algorithms_loadReferenceObjects_Schema standard schema \endlink
202  as documented in LoadReferenceObjects, including photometric, resolved and variable;
203  hasCentroid is False for all objects.
204  - fluxField = name of flux field for specified filterName
205  """
206  # compute on-sky center and radius of search region, for loadSkyCircle
207  bbox = afwGeom.Box2D(bbox) # make sure bbox is double and that we have a copy
208  bbox.grow(self.config.pixelMargin)
209  ctrCoord = wcs.pixelToSky(bbox.getCenter())
210  maxRadius = max(ctrCoord.angularSeparation(wcs.pixelToSky(pp)) for pp in bbox.getCorners())
211 
212  # find objects in circle
213  self.log.info("Loading reference objects using center %s pix = %s sky and radius %s deg" %
214  (bbox.getCenter(), ctrCoord, maxRadius.asDegrees()))
215  loadRes = self.loadSkyCircle(ctrCoord, maxRadius, filterName)
216  refCat = loadRes.refCat
217  numFound = len(refCat)
218 
219  # trim objects outside bbox
220  refCat = self._trimToBBox(refCat=refCat, bbox=bbox, wcs=wcs)
221  numTrimmed = numFound - len(refCat)
222  self.log.debug("trimmed %d out-of-bbox objects, leaving %d", numTrimmed, len(refCat))
223  self.log.info("Loaded %d reference objects", len(refCat))
224 
225  loadRes.refCat = refCat # should be a no-op, but just in case
226  return loadRes
227 
228  @abc.abstractmethod
229  def loadSkyCircle(self, ctrCoord, radius, filterName=None):
230  """!Load reference objects that overlap a circular sky region
231 
232  @param[in] ctrCoord center of search region (an lsst.afw.geom.Coord)
233  @param[in] radius radius of search region (an lsst.afw.geom.Angle)
234  @param[in] filterName name of filter, or None for the default filter;
235  used for flux values in case we have flux limits (which are not yet implemented)
236 
237  @return an lsst.pipe.base.Struct containing:
238  - refCat a catalog of reference objects with the
239  \link meas_algorithms_loadReferenceObjects_Schema standard schema \endlink
240  as documented in LoadReferenceObjects, including photometric, resolved and variable;
241  hasCentroid is False for all objects.
242  - fluxField = name of flux field for specified filterName
243  """
244  return
245 
246  @staticmethod
247  def _trimToBBox(refCat, bbox, wcs):
248  """!Remove objects outside a given pixel-based bbox and set centroid and hasCentroid fields
249 
250  @param[in] refCat a catalog of objects (an lsst.afw.table.SimpleCatalog,
251  or other table type that supports getCoord() on records)
252  @param[in] bbox pixel region (an afwImage.Box2D)
253  @param[in] wcs WCS used to convert sky position to pixel position (an lsst.afw.math.WCS)
254 
255  @return a catalog of reference objects in bbox, with centroid and hasCentroid fields set
256  """
257  centroidKey = afwTable.Point2DKey(refCat.schema["centroid"])
258  hasCentroidKey = refCat.schema["hasCentroid"].asKey()
259  retStarCat = type(refCat)(refCat.table)
260  for star in refCat:
261  point = wcs.skyToPixel(star.getCoord())
262  if bbox.contains(point):
263  star.set(centroidKey, point)
264  star.set(hasCentroidKey, True)
265  retStarCat.append(star)
266  return retStarCat
267 
268  def _addFluxAliases(self, schema):
269  """Add aliases for camera filter fluxes to the schema
270 
271  If self.config.defaultFilter then adds these aliases:
272  camFlux: <defaultFilter>_flux
273  camFluxSigma: <defaultFilter>_fluxSigma, if the latter exists
274 
275  For each camFilter: refFilter in self.config.filterMap adds these aliases:
276  <camFilter>_camFlux: <refFilter>_flux
277  <camFilter>_camFluxSigma: <refFilter>_fluxSigma, if the latter exists
278 
279  @throw RuntimeError if any reference flux field is missing from the schema
280  """
281  aliasMap = schema.getAliasMap()
282 
283  def addAliasesForOneFilter(filterName, refFilterName):
284  """Add aliases for a single filter
285 
286  @param[in] filterName camera filter name, or ""
287  the name is <filterName>_camFlux or camFlux if filterName is None
288  @param[in] refFilterName reference filter name; <refFilterName>_flux must exist
289  """
290  camFluxName = filterName + "_camFlux" if filterName is not None else "camFlux"
291  refFluxName = refFilterName + "_flux"
292  if refFluxName not in schema:
293  raise RuntimeError("Unknown reference filter %s" % (refFluxName,))
294  aliasMap.set(camFluxName, refFluxName)
295  refFluxErrName = refFluxName + "Sigma"
296  if refFluxErrName in schema:
297  camFluxErrName = camFluxName + "Sigma"
298  aliasMap.set(camFluxErrName, refFluxErrName)
299 
300  if self.config.defaultFilter:
301  addAliasesForOneFilter(None, self.config.defaultFilter)
302 
303  for filterName, refFilterName in self.config.filterMap.items():
304  addAliasesForOneFilter(filterName, refFilterName)
305 
306  @staticmethod
307  def makeMinimalSchema(filterNameList, addFluxSigma=False,
308  addIsPhotometric=False, addIsResolved=False, addIsVariable=False):
309  """!Make the standard schema for reference object catalogs
310 
311  @param[in] filterNameList list of filter names; used to create *filterName*_flux fields
312  @param[in] addFluxSigma if True then include flux sigma fields
313  @param[in] addIsPhotometric if True add field "photometric"
314  @param[in] addIsResolved if True add field "resolved"
315  @param[in] addIsVariable if True add field "variable"
316  """
317  schema = afwTable.SimpleTable.makeMinimalSchema()
318  afwTable.Point2DKey.addFields(
319  schema,
320  "centroid",
321  "centroid on an exposure, if relevant",
322  "pixel",
323  )
324  schema.addField(
325  field="hasCentroid",
326  type="Flag",
327  doc="is position known?",
328  )
329  for filterName in filterNameList:
330  schema.addField(
331  field="%s_flux" % (filterName,),
332  type=numpy.float64,
333  doc="flux in filter %s" % (filterName,),
334  units="Jy",
335  )
336  if addFluxSigma:
337  for filterName in filterNameList:
338  schema.addField(
339  field="%s_fluxSigma" % (filterName,),
340  type=numpy.float64,
341  doc="flux uncertainty in filter %s" % (filterName,),
342  units="Jy",
343  )
344  if addIsPhotometric:
345  schema.addField(
346  field="photometric",
347  type="Flag",
348  doc="set if the object can be used for photometric calibration",
349  )
350  if addIsResolved:
351  schema.addField(
352  field="resolved",
353  type="Flag",
354  doc="set if the object is spatially resolved",
355  )
356  if addIsVariable:
357  schema.addField(
358  field="variable",
359  type="Flag",
360  doc="set if the object has variable brightness",
361  )
362  return schema
363 
364  def joinMatchListWithCatalog(self, matchCat, sourceCat):
365  """!Relink an unpersisted match list to sources and reference objects
366 
367  A match list is persisted and unpersisted as a catalog of IDs produced by
368  afw.table.packMatches(), with match metadata (as returned by the astrometry tasks)
369  in the catalog's metadata attribute. This method converts such a match catalog
370  into a match list (an lsst.afw.table.ReferenceMatchVector) with links to source
371  records and reference object records.
372 
373  @param[in] matchCat Unperisted packed match list (an lsst.afw.table.BaseCatalog).
374  matchCat.table.getMetadata() must contain match metadata,
375  as returned by the astrometry tasks.
376  @param[in,out] sourceCat Source catalog (an lsst.afw.table.SourceCatalog).
377  As a side effect, the catalog will be sorted by ID.
378 
379  @return the match list (an lsst.afw.table.ReferenceMatchVector)
380  """
381  matchmeta = matchCat.table.getMetadata()
382  version = matchmeta.getInt('SMATCHV')
383  if version != 1:
384  raise ValueError('SourceMatchVector version number is %i, not 1.' % version)
385  filterName = matchmeta.getString('FILTER').strip()
386  ctrCoord = afwCoord.IcrsCoord(
387  matchmeta.getDouble('RA') * afwGeom.degrees,
388  matchmeta.getDouble('DEC') * afwGeom.degrees,
389  )
390  rad = matchmeta.getDouble('RADIUS') * afwGeom.degrees
391  refCat = self.loadSkyCircle(ctrCoord, rad, filterName).refCat
392  refCat.sort()
393  sourceCat.sort()
394  return afwTable.unpackMatches(matchCat, refCat, sourceCat)
def getRefFluxKeys
Return flux and flux error keys.
def _trimToBBox
Remove objects outside a given pixel-based bbox and set centroid and hasCentroid fields.
def loadPixelBox
Load reference objects that overlap a pixel-based rectangular region.
def loadSkyCircle
Load reference objects that overlap a circular sky region.
Abstract base class to load objects from reference catalogs.
def getRefFluxField
Get name of flux field in schema.
def joinMatchListWithCatalog
Relink an unpersisted match list to sources and reference objects.
def makeMinimalSchema
Make the standard schema for reference object catalogs.