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