lsst.meas.base  14.0-22-gcfb0d17+3
references.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # LSST Data Management System
4 # Copyright 2008-2015 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 """
24 Subtasks for creating the reference catalogs used in forced measurement.
25 """
26 
27 import lsst.afw.geom
28 import lsst.pex.config
29 import lsst.pipe.base
30 
31 __all__ = ("BaseReferencesTask", "CoaddSrcReferencesTask")
32 
33 
34 class BaseReferencesConfig(lsst.pex.config.Config):
35  removePatchOverlaps = lsst.pex.config.Field(
36  doc="Only include reference sources for each patch that lie within the patch's inner bbox",
37  dtype=bool,
38  default=True
39  )
40  filter = lsst.pex.config.Field(
41  doc="Bandpass for reference sources; None indicates chi-squared detections.",
42  dtype=str,
43  optional=True
44  )
45 
46 
47 class BaseReferencesTask(lsst.pipe.base.Task):
48  """!
49  Base class for forced photometry subtask that retrieves reference sources.
50 
51  BaseReferencesTask defines the required API for the references task, which includes:
52  - getSchema(butler)
53  - fetchInPatches(butler, tract, filter, patchList)
54  - fetchInBox(self, butler, tract, filter, bbox, wcs)
55  - the removePatchOverlaps config option
56 
57  It also provides the subset() method, which may be of use to derived classes when
58  reimplementing fetchInBox.
59  """
60 
61  ConfigClass = BaseReferencesConfig
62 
63  def __init__(self, butler=None, schema=None, **kwargs):
64  """!Initialize the task.
65 
66  BaseReferencesTask and its subclasses take two keyword arguments beyond the usual Task arguments:
67  - schema: the Schema of the reference catalog
68  - butler: a butler that will allow the task to load its Schema from disk.
69  At least one of these arguments must be present; if both are, schema takes precedence.
70  """
71  lsst.pipe.base.Task.__init__(self, **kwargs)
72 
73  def getSchema(self, butler):
74  """!
75  Return the schema for the reference sources.
76 
77  Must be available even before any data has been processed.
78  """
79  raise NotImplementedError("BaseReferencesTask is pure abstract, and cannot be used directly.")
80 
81  def getWcs(self, dataRef):
82  """!
83  Return the WCS for reference sources. The given dataRef must include the tract in its dataId.
84  """
85  raise NotImplementedError("BaseReferencesTask is pure abstract, and cannot be used directly.")
86 
87  def fetchInBox(self, dataRef, bbox, wcs):
88  """!
89  Return reference sources that overlap a region defined by a pixel-coordinate bounding box
90  and corresponding Wcs.
91 
92  @param[in] dataRef ButlerDataRef; the implied data ID must contain the 'tract' key.
93  @param[in] bbox a afw.geom.Box2I or Box2D that defines the region in pixel coordinates
94  @param[in] wcs afw.image.Wcs that maps the bbox to sky coordinates
95 
96  @return an iterable of reference sources
97 
98  It is not required that the returned object be a SourceCatalog; it may be any Python iterable
99  containing SourceRecords (including a lazy iterator).
100 
101  The returned set of sources should be complete and close to minimal.
102  """
103  raise NotImplementedError("BaseReferencesTask is pure abstract, and cannot be used directly.")
104 
105  def fetchInPatches(self, dataRef, patchList):
106  """!
107  Return reference sources that overlap a region defined by one or more SkyMap patches.
108 
109  @param[in] dataRef ButlerDataRef; the implied data ID must contain the 'tract' key.
110  @param[in] patchList list of skymap.PatchInfo instances for which to fetch reference sources
111 
112  @return an iterable of reference sources
113 
114  It is not required that the returned object be a SourceCatalog; it may be any Python sequence
115  containing SourceRecords (including a lazy iterator).
116 
117  The returned set of sources should be complete and close to minimal. If
118  config.removePatchOverlaps is True, only sources within each patch's "inner" bounding box
119  should be returned.
120  """
121  raise NotImplementedError("BaseReferencesTask is pure abstract, and cannot be used directly.")
122 
123  def subset(self, sources, bbox, wcs):
124  """!
125  Filter sources to contain only those within the given box, defined in the coordinate system
126  defined by the given Wcs.
127 
128  @param[in] sources input iterable of SourceRecords
129  @param[in] bbox bounding box with which to filter reference sources (Box2I or Box2D)
130  @param[in] wcs afw.image.Wcs that defines the coordinate system of bbox
131 
132  Instead of filtering sources directly via their positions, we filter based on the positions
133  of parent objects, then include or discard all children based on their parent's status. This
134  is necessary to support ReplaceWithNoise in measurement, which requires all child sources have
135  their parent present.
136 
137  @return an iterable of filtered reference sources
138 
139  This is not a part of the required BaseReferencesTask interface; it's a convenience function
140  used in implementing fetchInBox that may be of use to subclasses.
141  """
142  boxD = lsst.afw.geom.Box2D(bbox)
143  # We're passed an arbitrary iterable, but we need a catalog so we can iterate
144  # over parents and then children.
145  catalog = lsst.afw.table.SourceCatalog(self.schema)
146  catalog.extend(sources)
147  # catalog must be sorted by parent ID for lsst.afw.table.getChildren to work
149  # Iterate over objects that have no parent.
150  parentSources = catalog.getChildren(0)
151  skyCoordList = [source.getCoord() for source in parentSources]
152  pixelPosList = wcs.skyToPixel(skyCoordList)
153  for parent, pixel in zip(parentSources, pixelPosList):
154  if boxD.contains(pixel):
155  yield parent
156  for child in catalog.getChildren(parent.getId()):
157  yield child
158 
159 
160 class CoaddSrcReferencesConfig(BaseReferencesTask.ConfigClass):
161  coaddName = lsst.pex.config.Field(
162  doc="Coadd name: typically one of deep or goodSeeing.",
163  dtype=str,
164  default="deep",
165  )
166  skipMissing = lsst.pex.config.Field(
167  doc="Silently skip patches where the reference catalog does not exist.",
168  dtype=bool,
169  default=False
170  )
171 
172  def validate(self):
173  if (self.coaddName == "chiSquared") != (self.filter is None):
174  raise lsst.pex.config.FieldValidationError(
175  field=CoaddSrcReferencesConfig.coaddName,
176  config=self,
177  msg="filter may be None if and only if coaddName is chiSquared"
178  )
179 
180 
182  """!
183  A references task implementation that loads the coadd_datasetSuffix dataset directly from
184  disk using the butler.
185  """
186 
187  ConfigClass = CoaddSrcReferencesConfig
188  datasetSuffix = "src" # Suffix to add to "Coadd_" for dataset name
189 
190  def __init__(self, butler=None, schema=None, **kwargs):
191  """! Initialize the task.
192  Additional keyword arguments (forwarded to BaseReferencesTask.__init__):
193  - schema: the schema of the detection catalogs used as input to this one
194  - butler: a butler used to read the input schema from disk, if schema is None
195  The task will set its own self.schema attribute to the schema of the output merged catalog.
196  """
197  BaseReferencesTask.__init__(self, butler=butler, schema=schema, **kwargs)
198  if schema is None:
199  assert butler is not None, "No butler nor schema provided"
200  schema = butler.get("{}Coadd_{}_schema".format(self.config.coaddName, self.datasetSuffix),
201  immediate=True).getSchema()
202  self.schema = schema
203 
204  def getWcs(self, dataRef):
205  """Return the WCS for reference sources. The given dataRef must include the tract in its dataId.
206  """
207  skyMap = dataRef.get(self.config.coaddName + "Coadd_skyMap", immediate=True)
208  return skyMap[dataRef.dataId["tract"]].getWcs()
209 
210  def fetchInPatches(self, dataRef, patchList):
211  """!
212  An implementation of BaseReferencesTask.fetchInPatches that loads 'coadd_' + datasetSuffix
213  catalogs using the butler.
214 
215  The given dataRef must include the tract in its dataId.
216  """
217  dataset = "{}Coadd_{}".format(self.config.coaddName, self.datasetSuffix)
218  tract = dataRef.dataId["tract"]
219  butler = dataRef.butlerSubset.butler
220  for patch in patchList:
221  dataId = {'tract': tract, 'patch': "%d,%d" % patch.getIndex()}
222  if self.config.filter is not None:
223  dataId['filter'] = self.config.filter
224 
225  if not butler.datasetExists(dataset, dataId):
226  if self.config.skipMissing:
227  continue
228  raise lsst.pipe.base.TaskError("Reference %s doesn't exist" % (dataId,))
229  self.log.info("Getting references in %s" % (dataId,))
230  catalog = butler.get(dataset, dataId, immediate=True)
231  if self.config.removePatchOverlaps:
232  bbox = lsst.afw.geom.Box2D(patch.getInnerBBox())
233  for source in catalog:
234  if bbox.contains(source.getCentroid()):
235  yield source
236  else:
237  for source in catalog:
238  yield source
239 
240  def fetchInBox(self, dataRef, bbox, wcs, pad=0):
241  """!
242  Return reference sources that overlap a region defined by a pixel-coordinate bounding box
243  and corresponding Wcs.
244 
245  @param[in] dataRef ButlerDataRef; the implied data ID must contain the 'tract' key.
246  @param[in] bbox a afw.geom.Box2I or Box2D that defines the region in pixel coordinates
247  @param[in] wcs afw.image.Wcs that maps the bbox to sky coordinates
248  @param[in] pad a buffer to grow the bounding box by after catalogs have been loaded, but
249  before filtering them to include just the given bounding box.
250 
251  @return an iterable of reference sources
252  """
253  skyMap = dataRef.get(self.config.coaddName + "Coadd_skyMap", immediate=True)
254  tract = skyMap[dataRef.dataId["tract"]]
255  coordList = [wcs.pixelToSky(corner) for corner in lsst.afw.geom.Box2D(bbox).getCorners()]
256  self.log.info("Getting references in region with corners %s [degrees]" %
257  ", ".join("(%s)" % (coord.getPosition(lsst.afw.geom.degrees),) for coord in coordList))
258  patchList = tract.findPatchList(coordList)
259  # After figuring out which patch catalogs to read from the bbox, pad out the bbox if desired
260  # But don't add any new patches while padding
261  if pad:
262  bbox.grow(pad)
263  return self.subset(self.fetchInPatches(dataRef, patchList), bbox, wcs)
264 
265 
266 class MultiBandReferencesConfig(CoaddSrcReferencesTask.ConfigClass):
267 
268  def validate(self):
269  if self.filter is not None:
270  raise lsst.pex.config.FieldValidationError(
271  field=MultiBandReferencesConfig.filter,
272  config=self,
273  msg="Filter should not be set for the multiband processing scheme")
274  # Delegate to ultimate base class, because the direct one has a check we don't want.
275  BaseReferencesTask.ConfigClass.validate(self)
276 
277 
279  """Loads references from the multiband processing scheme"""
280  ConfigClass = MultiBandReferencesConfig
281  datasetSuffix = "ref"
def fetchInBox(self, dataRef, bbox, wcs, pad=0)
Return reference sources that overlap a region defined by a pixel-coordinate bounding box and corresp...
Definition: references.py:240
def getSchema(self, butler)
Return the schema for the reference sources.
Definition: references.py:73
def getWcs(self, dataRef)
Return the WCS for reference sources.
Definition: references.py:81
Base class for forced photometry subtask that retrieves reference sources.
Definition: references.py:47
A references task implementation that loads the coadd_datasetSuffix dataset directly from disk using ...
Definition: references.py:181
def fetchInBox(self, dataRef, bbox, wcs)
Return reference sources that overlap a region defined by a pixel-coordinate bounding box and corresp...
Definition: references.py:87
def fetchInPatches(self, dataRef, patchList)
An implementation of BaseReferencesTask.fetchInPatches that loads &#39;coadd_&#39; + datasetSuffix catalogs u...
Definition: references.py:210
def __init__(self, butler=None, schema=None, kwargs)
Initialize the task.
Definition: references.py:190
def __init__(self, butler=None, schema=None, kwargs)
Initialize the task.
Definition: references.py:63
static Key< RecordId > getParentKey()
def fetchInPatches(self, dataRef, patchList)
Return reference sources that overlap a region defined by one or more SkyMap patches.
Definition: references.py:105
def subset(self, sources, bbox, wcs)
Filter sources to contain only those within the given box, defined in the coordinate system defined b...
Definition: references.py:123