LSST Applications g0b6bd0c080+a72a5dd7e6,g1182afd7b4+2a019aa3bb,g17e5ecfddb+2b8207f7de,g1d67935e3f+06cf436103,g38293774b4+ac198e9f13,g396055baef+6a2097e274,g3b44f30a73+6611e0205b,g480783c3b1+98f8679e14,g48ccf36440+89c08d0516,g4b93dc025c+98f8679e14,g5c4744a4d9+a302e8c7f0,g613e996a0d+e1c447f2e0,g6c8d09e9e7+25247a063c,g7271f0639c+98f8679e14,g7a9cd813b8+124095ede6,g9d27549199+a302e8c7f0,ga1cf026fa3+ac198e9f13,ga32aa97882+7403ac30ac,ga786bb30fb+7a139211af,gaa63f70f4e+9994eb9896,gabf319e997+ade567573c,gba47b54d5d+94dc90c3ea,gbec6a3398f+06cf436103,gc6308e37c7+07dd123edb,gc655b1545f+ade567573c,gcc9029db3c+ab229f5caf,gd01420fc67+06cf436103,gd877ba84e5+06cf436103,gdb4cecd868+6f279b5b48,ge2d134c3d5+cc4dbb2e3f,ge448b5faa6+86d1ceac1d,gecc7e12556+98f8679e14,gf3ee170dca+25247a063c,gf4ac96e456+ade567573c,gf9f5ea5b4d+ac198e9f13,gff490e6085+8c2580be5c,w.2022.27
LSST Data Management Base Package
references.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"""
23Subtasks for creating the reference catalogs used in forced measurement.
24"""
25
26import lsst.geom
27import lsst.pex.config
28import lsst.pipe.base
29
30__all__ = ("BaseReferencesTask", "CoaddSrcReferencesTask")
31
32
34 """Default configuration for reference source selection.
35 """
36
37 removePatchOverlaps = lsst.pex.config.Field(
38 doc="Only include reference sources for each patch that lie within the patch's inner bbox",
39 dtype=bool,
40 default=True
41 )
43 doc="Bandpass for reference sources; None indicates chi-squared detections.",
44 dtype=str,
45 optional=True
46 )
47
48
49class BaseReferencesTask(lsst.pipe.base.Task):
50 """Base class for forced photometry subtask that fetches reference sources.
51
52 Parameters
53 ----------
54 schema : `lsst.afw.table.Schema`, optional
55 The schema of the reference catalog.
56 butler : `lsst.daf.persistence.butler.Butler`, optional
57 A butler that will allow the task to load its schema from disk.
58
59 Notes
60 -----
61 At least one of the ``schema`` and ``butler`` arguments must be present;
62 if both are, ``schema`` takes precedence.
63
64 ``BaseReferencesTask`` defines the required API for the references task,
65 which consists of:
66
67 - ``getSchema(butler)``
68 - ``fetchInPatches(butler, tract, filter, patchList)``
69 - ``fetchInBox(self, butler, tract, filter, bbox, wcs)``
70 - the ``removePatchOverlaps`` config option
71
72 It also provides the ``subset`` method, which may be of use to derived
73 classes when reimplementing ``fetchInBox``.
74 """
75
76 ConfigClass = BaseReferencesConfig
77 """Configuration class associated with this task (`lsst.pex.config.Config`).
78 """
79
80 def __init__(self, butler=None, schema=None, **kwargs):
81 lsst.pipe.base.Task.__init__(self, **kwargs)
82
83 def getSchema(self, butler):
84 """Return the schema for the reference sources.
85
86 Parameters
87 ----------
89 Data butler from which the schema will be fetched.
90
91 Notes
92 -----
93 Must be available even before any data has been processed.
94 """
95 raise NotImplementedError("BaseReferencesTask is pure abstract, and cannot be used directly.")
96
97 def getWcs(self, dataRef):
98 """Return the WCS for reference sources.
99
100 Parameters
101 ----------
103 The data reference from which the WCS will be fetched. This must
104 include the tract in its dataId.
105 """
106 raise NotImplementedError("BaseReferencesTask is pure abstract, and cannot be used directly.")
107
108 def fetchInBox(self, dataRef, bbox, wcs):
109 """Return reference sources within a given bounding box.
110
111 Reference sources are selected if they overlap a region defined by a
112 pixel-coordinate bounding box and corresponding WCS.
113
114 Parameters
115 ----------
117 Butler data reference. The implied data ID must contain the
118 ``tract`` key.
119 bbox : `lsst.afw.geom.Box2I` or `lsst.afw.geom.Box2D`
120 Defines the selection region in pixel coordinates.
121 wcs : `lsst.afw.image.SkyWcs`
122 Maps ``bbox`` to sky coordinates.
123
124 Returns
125 -------
126 sources : iterable of `~lsst.afw.table.SourceRecord`
127 Reference sources. May be any Python iterable, including a lazy
128 iterator.
129
130 Notes
131 -----
132 The returned set of sources should be complete and close to minimal.
133 """
134 raise NotImplementedError("BaseReferencesTask is pure abstract, and cannot be used directly.")
135
136 def fetchInPatches(self, dataRef, patchList):
137 """Return reference sources within one or more patches.
138
139 Parameters
140 ----------
142 Butler data reference. The implied data ID must contain the
143 ``tract`` key.
144 patchList : `list` of `lsst.skymap.PatchInfo`
145 Patches for which to fetch reference sources.
146
147 Returns
148 -------
149 sources : iterable of `~lsst.afw.table.SourceRecord`
150 Reference sources. May be any Python iterable, including a lazy
151 iterator.
152
153 Notes
154 -----
155 The returned set of sources should be complete and close to minimal.
156
157 If ``config.removePatchOverlaps`` is `True`, only sources within each
158 patch's "inner" bounding box should be returned.
159 """
160 raise NotImplementedError("BaseReferencesTask is pure abstract, and cannot be used directly.")
161
162 def subset(self, sources, bbox, wcs):
163 """Filter a list of sources to only those within the bounding box.
164
165 Parameters
166 ----------
167 sources : iterable of `~lsst.afw.table.SourceRecord`
168 Reference sources. May be any Python iterable, including a lazy
169 iterator.
170 bbox : `lsst.afw.geom.Box2I` or `lsst.afw.geom.Box2D`
171 Defines the selection region.
172 wcs : `lsst.afw.image.SkyWcs`
173 Maps ``bbox`` to sky coordinates.
174
175 Returns
176 -------
177 sources : iterable of `~lsst.afw.table.SourceRecord`
178 Filtered sources. May be any Python iterable, including a lazy
179 iterator.
180
181 Notes
182 -----
183 Instead of filtering sources directly via their positions, we filter
184 based on the positions of parent objects, then include or discard all
185 children based on their parent's status. This is necessary to support
186 replacement with noise in measurement, which requires all child
187 sources have their parent present.
188
189 This is not a part of the required `BaseReferencesTask` interface;
190 it's a convenience function used in implementing `fetchInBox` that may
191 be of use to subclasses.
192 """
193 boxD = lsst.geom.Box2D(bbox)
194 # We're passed an arbitrary iterable, but we need a catalog so we can
195 # iterate over parents and then children.
196 catalog = lsst.afw.table.SourceCatalog(self.schema)
197 catalog.extend(sources)
198 # catalog must be sorted by parent ID for lsst.afw.table.getChildren
199 # to work
201 # Iterate over objects that have no parent.
202 parentSources = catalog.getChildren(0)
203 skyCoordList = [source.getCoord() for source in parentSources]
204 pixelPosList = wcs.skyToPixel(skyCoordList)
205 parentList = [parent for parent, pixel in zip(parentSources, pixelPosList) if boxD.contains(pixel)]
206 childrenIter = catalog.getChildren((parent.getId() for parent in parentList))
207 for parent, children in zip(parentList, childrenIter):
208 yield parent
209 yield from children
210
211
212class CoaddSrcReferencesConfig(BaseReferencesTask.ConfigClass):
213 """Default configuration for coadd reference source selection.
214 """
215
217 doc="Coadd name: typically one of deep or goodSeeing.",
218 dtype=str,
219 default="deep",
220 )
222 doc="Silently skip patches where the reference catalog does not exist.",
223 dtype=bool,
224 default=False
225 )
226
227 def validate(self):
228 if (self.coaddNamecoaddName == "chiSquared") != (self.filter is None):
230 field=CoaddSrcReferencesConfig.coaddName,
231 config=self,
232 msg="filter may be None if and only if coaddName is chiSquared"
233 )
234
235
237 """Select reference sources by loading the “coadd source” dataset directly.
238
239 The name of the dataset to read is generated by appending the
240 `datasetSuffix` attribute to the string ``Coadd_``. The dataset is then
241 read directly from disk using the Butler.
242
243 Parameters
244 ----------
245 schema : `lsst.afw.table.Schema`, optional
246 The schema of the detection catalogs used as input to this one.
247 butler : `lsst.daf.persistence.butler.Butler`, optional
248 A Butler used to read the input schema from disk. Required if
249 ``schema`` is `None`.
250
251 Notes
252 -----
253 The task will set its own ``self.schemaschema`` attribute to the schema of the
254 output merged catalog.
255 """
256
257 ConfigClass = CoaddSrcReferencesConfig
258 """Configuration class associated with this task (`lsst.pex.config.Config`).
259 """
260
261 datasetSuffix = "src"
262 """Suffix to append to ``Coadd_`` to generate the dataset name (`str`).
263 """
264
265 def __init__(self, butler=None, schema=None, **kwargs):
266 BaseReferencesTask.__init__(self, butler=butler, schema=schema, **kwargs)
267 if schema is None:
268 assert butler is not None, "No butler nor schema provided"
269 schema = butler.get("{}Coadd_{}_schema".format(self.config.coaddName, self.datasetSuffixdatasetSuffix),
270 immediate=True).getSchema()
271 self.schemaschema = schema
272
273 def getWcs(self, dataRef):
274 """Return the WCS for reference sources.
275
276 Parameters
277 ----------
279 Butler data reference. Must includ the trac in its dataId.
280 """
281 skyMap = dataRef.get(self.config.coaddName + "Coadd_skyMap", immediate=True)
282 return skyMap[dataRef.dataId["tract"]].getWcs()
283
284 def fetchInPatches(self, dataRef, patchList):
285 """Fetch the source catalog using the Butler.
286
287 Parameters
288 ----------
290 Butler data reference. The implied data ID must contain the
291 ``tract`` key.
292 patchList : `list` of `lsst.skymap.PatchInfo`
293 Patches for which to fetch reference sources.
294
295 Returns
296 -------
297 sources : iterable of `~lsst.afw.table.SourceRecord`
298 Reference sources. May be any Python iterable, including a lazy
299 iterator.
300
301 Notes
302 -----
303 An implementation of `BaseReferencesTask.fetchInPatches` that loads
304 ``Coadd_`` + `datasetSuffix` catalogs using the butler.
305 """
306 dataset = "{}Coadd_{}".format(self.config.coaddName, self.datasetSuffixdatasetSuffix)
307 tract = dataRef.dataId["tract"]
308 butler = dataRef.butlerSubset.butler
309 for patch in patchList:
310 dataId = {'tract': tract, 'patch': "%d,%d" % patch.getIndex()}
311 if self.config.filter is not None:
312 dataId['filter'] = self.config.filter
313
314 if not butler.datasetExists(dataset, dataId):
315 if self.config.skipMissing:
316 continue
317 raise lsst.pipe.base.TaskError("Reference %s doesn't exist" % (dataId,))
318 self.log.info("Getting references in %s", dataId)
319 catalog = butler.get(dataset, dataId, immediate=True)
320 if self.config.removePatchOverlaps:
321 bbox = lsst.geom.Box2D(patch.getInnerBBox())
322 for source in catalog:
323 if bbox.contains(source.getCentroid()):
324 yield source
325 else:
326 for source in catalog:
327 yield source
328
329 def fetchInBox(self, dataRef, bbox, wcs, pad=0):
330 """Return reference sources within a given bounding box.
331
332 Reference sources are selected if they overlap a region defined by a
333 pixel-coordinate bounding box and corresponding WCS.
334
335 Parameters
336 ----------
338 Butler data reference. The implied data ID must contain the
339 ``tract`` key.
340 bbox : `lsst.afw.geom.Box2I` or `lsst.afw.geom.Box2D`
341 Defines the selection region in pixel coordinates.
342 wcs : `lsst.afw.image.SkyWcs`
343 Maps ``bbox`` to sky coordinates.
344 pad : `int`
345 a buffer to grow the bounding box by after catalogs have been loaded, but
346 before filtering them to include just the given bounding box.
347
348 Returns
349 -------
350 sources : iterable of `~lsst.afw.table.SourceRecord`
351 Reference sources. May be any Python iterable, including a lazy
352 iterator.
353 """
354 skyMap = dataRef.get(self.config.coaddName + "Coadd_skyMap", immediate=True)
355 tract = skyMap[dataRef.dataId["tract"]]
356 coordList = [wcs.pixelToSky(corner) for corner in lsst.geom.Box2D(bbox).getCorners()]
357 self.log.info("Getting references in region with corners %s [degrees]",
358 ", ".join("(%s)" % (coord.getPosition(lsst.geom.degrees),) for coord in coordList))
359 patchList = tract.findPatchList(coordList)
360 # After figuring out which patch catalogs to read from the bbox, pad out the bbox if desired
361 # But don't add any new patches while padding
362 if pad:
363 bbox.grow(pad)
364 return self.subsetsubset(self.fetchInPatchesfetchInPatchesfetchInPatches(dataRef, patchList), bbox, wcs)
365
366
367class MultiBandReferencesConfig(CoaddSrcReferencesTask.ConfigClass):
368 """Default configuration for multi-band reference source selection.
369 """
370
371 def validate(self):
372 if self.filter is not None:
374 field=MultiBandReferencesConfig.filter,
375 config=self,
376 msg="Filter should not be set for the multiband processing scheme")
377 # Delegate to ultimate base class, because the direct one has a check we don't want.
378 BaseReferencesTask.ConfigClass.validate(self)
379
380
382 """Loads references from the multi-band processing scheme.
383 """
384
385 ConfigClass = MultiBandReferencesConfig
386 datasetSuffix = "ref" # Documented in superclass
Defines the fields and offsets for a table.
Definition: Schema.h:51
Record class that contains measurements made on a single exposure.
Definition: Source.h:78
static Key< RecordId > getParentKey()
Key for the parent ID.
Definition: Source.h:273
A floating-point coordinate rectangle geometry.
Definition: Box.h:413
def subset(self, sources, bbox, wcs)
Definition: references.py:162
def __init__(self, butler=None, schema=None, **kwargs)
Definition: references.py:80
def fetchInPatches(self, dataRef, patchList)
Definition: references.py:136
def fetchInBox(self, dataRef, bbox, wcs)
Definition: references.py:108
def __init__(self, butler=None, schema=None, **kwargs)
Definition: references.py:265
def fetchInBox(self, dataRef, bbox, wcs, pad=0)
Definition: references.py:329
def fetchInPatches(self, dataRef, patchList)
Definition: references.py:284
def format(config, name=None, writeSourceLine=True, prefix="", verbose=False)
Definition: history.py:174