LSST Applications g011c388f00+c65bd04cee,g0265f82a02+cefac37fe7,g16a3bce237+cefac37fe7,g2079a07aa2+b9108c1c87,g2bbee38e9b+cefac37fe7,g337abbeb29+cefac37fe7,g3ddfee87b4+416e7893ae,g44050f54f7+6b8bd3088b,g4cf46543a9+6b26e01e3d,g50ff169b8f+8309cf5058,g52b1c1532d+43dac7135f,g83996f0134+7425ca6be0,g858d7b2824+6b8bd3088b,g8a8a8dda67+43dac7135f,g99855d9996+74fd63a160,g9d147d8712+4559cd7206,g9ddcbc5298+389b8f2b7e,ga1e77700b3+4bafba478f,ga8c6da7877+c9aad6bf16,gae46bcf261+cefac37fe7,gb700894bec+6c2aee6a4c,gb8350603e9+ee20daaa23,gba4ed39666+fb465f0d3e,gbeb006f7da+404eccc661,gc18e0e788c+c3f5ccaa8e,gc86a011abf+6b8bd3088b,gcf0d15dbbd+416e7893ae,gd162630629+5d7ded2167,gd44f2fa1a7+afcca147eb,gdaeeff99f8+6b435c3f92,ge79ae78c31+cefac37fe7,ge9008a0c34+416e7893ae,gee10cc3b42+43dac7135f,gf041782ebf+713927f999,gf1cff7945b+6b8bd3088b,w.2024.07
LSST Data Management Base Package
Loading...
Searching...
No Matches
setPrimaryFlags.py
Go to the documentation of this file.
1# This file is part of pipe_tasks.
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__all__ = ["SetPrimaryFlagsConfig", "SetPrimaryFlagsTask"]
23
24import numpy as np
25from lsst.pex.config import Config, Field, ListField
26from lsst.pipe.base import Task
27from lsst.geom import Box2D
28
29
30def getPatchInner(sources, patchInfo):
31 """Set a flag for each source if it is in the innerBBox of a patch.
32
33 Parameters
34 ----------
35 sources : `lsst.afw.table.SourceCatalog`
36 A sourceCatalog with pre-calculated centroids.
37 patchInfo : `lsst.skymap.PatchInfo`
38 Information about a `SkyMap` `Patch`.
39
40 Returns
41 -------
42 isPatchInner : array-like of `bool`
43 `True` for each source that has a centroid
44 in the inner region of a patch.
45 """
46 # Extract the centroid position for all the sources
47 x = sources["slot_Centroid_x"]
48 y = sources["slot_Centroid_y"]
49 centroidFlag = sources["slot_Centroid_flag"]
50
51 # set inner flags for each source and set primary flags for
52 # sources with no children (or all sources if deblend info not available)
53 innerFloatBBox = Box2D(patchInfo.getInnerBBox())
54 inInner = innerFloatBBox.contains(x, y)
55
56 # When the centroider fails, we can still fall back to the peak,
57 # but we don't trust that quite as much -
58 # so we use a slightly smaller box for the patch comparison.
59 shrunkInnerFloatBBox = Box2D(innerFloatBBox)
60 shrunkInnerFloatBBox.grow(-1)
61 inShrunkInner = shrunkInnerFloatBBox.contains(x, y)
62
63 # Flag sources contained in the inner region of a patch
64 isPatchInner = (centroidFlag & inShrunkInner) | (~centroidFlag & inInner)
65 return isPatchInner
66
67
68def getTractInner(sources, tractInfo, skyMap):
69 """Set a flag for each source that the skyMap includes in tractInfo.
70
71 Parameters
72 ----------
73 sources : `lsst.afw.table.SourceCatalog`
74 A sourceCatalog with pre-calculated centroids.
75 tractInfo : `lsst.skymap.TractInfo`
76 Tract object
77 skyMap : `lsst.skymap.BaseSkyMap`
78 Sky tessellation object
79
80 Returns
81 -------
82 isTractInner : array-like of `bool`
83 True if the skyMap.findTract method returns
84 the same tract as tractInfo.
85 """
86 tractId = tractInfo.getId()
87 isTractInner = np.array([skyMap.findTract(s.getCoord()).getId() == tractId for s in sources])
88 return isTractInner
89
90
91def getPseudoSources(sources, pseudoFilterList, schema, log):
92 """Get a flag that marks pseudo sources.
93
94 Some categories of sources, for example sky objects,
95 are not really detected sources and should not be considered primary
96 sources.
97
98 Parameters
99 ----------
100 sources : `lsst.afw.table.SourceCatalog`
101 The catalog of sources for which to identify "pseudo"
102 (e.g. sky) objects.
103 pseudoFilterList : `list` of `str`
104 Names of filters which should never be primary
105
106 Returns
107 -------
108 isPseudo : array-like of `bool`
109 True for each source that is a pseudo source.
110 Note: to remove pseudo sources use `~isPseudo`.
111 """
112 # Filter out sources that should never be primary
113 isPseudo = np.zeros(len(sources), dtype=bool)
114 for filt in pseudoFilterList:
115 try:
116 pseudoFilterKey = schema.find("merge_peak_%s" % filt).getKey()
117 isPseudo |= sources[pseudoFilterKey]
118 except KeyError:
119 log.warning("merge_peak is not set for pseudo-filter %s", filt)
120 return isPseudo
121
122
124 """Get flags generated by the deblender
125
126 scarlet is different than meas_deblender in that it is not
127 (necessarily) flux conserving. For consistency in scarlet,
128 all of the parents with only a single child (isolated sources)
129 need to be deblended. This creates a question: which type
130 of isolated source should we make measurements on, the
131 undeblended "parent" or the deblended child?
132 For that reason we distinguish between a DeblendedSource,
133 which is a source that has no children and uses the
134 isolated parents, and a DeblendedModelSource, which uses
135 the scarlet models for both isolated and blended sources.
136 In the case of meas_deblender, DeblendedModelSource is
137 `None` because it is not contained in the output catalog.
138
139 Parameters
140 ----------
141 sources : `lsst.afw.table.SourceCatalog`
142 A sourceCatalog that has already been deblended using
143 either meas_extensions_scarlet or meas_deblender.
144
145 Returns
146 -------
147 fromBlend : array-like of `bool`
148 True for each source modeled by the deblender from a `Peak`
149 in a parent footprint that contained at least one other `Peak`.
150 While these models can be approximated as isolated,
151 and measurements are made on them as if that's the case,
152 we know deblending to introduce biases in the shape and centroid
153 of objects and it is important to know that the sources that these
154 models are based on are all bleneded in the true image.
155 isIsolated : array-like of `bool`
156 True for isolated sources, regardless of whether or not they
157 were modeled by the deblender.
158 isDeblendedSource : array-like of `bool`
159 True for each source that is a "DeblendedSource" as defined above.
160 isDeblendedModelSource : array-like of `bool`
161 True for each source that is a "DeblendedSourceModel"
162 as defined above.
163 """
164 nChildKey = "deblend_nChild"
165 nChild = sources[nChildKey]
166 parent = sources["parent"]
167
168 if "deblend_scarletFlux" in sources.schema:
169 # The number of peaks in the sources footprint.
170 # This (may be) different than nChild,
171 # the number of deblended sources in the catalog,
172 # because some peaks might have been culled during deblending.
173 nPeaks = sources["deblend_nPeaks"]
174 parentNChild = sources["deblend_parentNChild"]
175 # It is possible for a catalog to contain a hierarchy of sources,
176 # so we mark the leaves (end nodes of the hierarchy tree with no
177 # children).
178 isLeaf = nPeaks == 1
179 fromBlend = parentNChild > 1
180 isIsolated = isLeaf & ((parent == 0) | parentNChild == 1)
181 isDeblendedSource = (fromBlend & isLeaf) | (isIsolated & (parent == 0))
182 isDeblendedModelSource = (parent != 0) & isLeaf
183 else:
184 # Set the flags for meas_deblender
185 fromBlend = parent != 0
186 isIsolated = (nChild == 0) & (parent == 0)
187 isDeblendedSource = nChild == 0
188 isDeblendedModelSource = None
189 return fromBlend, isIsolated, isDeblendedSource, isDeblendedModelSource
190
191
193 nChildKeyName = Field(dtype=str, default="deprecated",
194 doc="Deprecated. This parameter is not used.")
195 pseudoFilterList = ListField(dtype=str, default=['sky'],
196 doc="Names of filters which should never be primary")
197
198
200 """Add isPrimaryKey to a given schema.
201
202 Parameters
203 ----------
204 schema : `lsst.afw.table.Schema`
205 The input schema.
206 isSingleFrame : `bool`
207 Flag specifying if task is operating with single frame imaging.
208 includeDeblend : `bool`
209 Include deblend information in isPrimary and
210 add isDeblendedSource field?
211 kwargs :
212 Keyword arguments passed to the task.
213 """
214
215 ConfigClass = SetPrimaryFlagsConfig
216
217 def __init__(self, schema, isSingleFrame=False, **kwargs):
218 Task.__init__(self, **kwargs)
219 self.schema = schema
220 self.isSingleFrame = isSingleFrame
221 self.includeDeblend = False
222 if not self.isSingleFrame:
223 primaryDoc = ("true if source has no children and is in the inner region of a coadd patch "
224 "and is in the inner region of a coadd tract "
225 "and is not \"detected\" in a pseudo-filter (see config.pseudoFilterList)")
226 self.isPatchInnerKey = self.schema.addField(
227 "detect_isPatchInner", type="Flag",
228 doc="true if source is in the inner region of a coadd patch",
229 )
230 self.isTractInnerKey = self.schema.addField(
231 "detect_isTractInner", type="Flag",
232 doc="true if source is in the inner region of a coadd tract",
233 )
234 else:
235 primaryDoc = "true if source has no children and is not a sky source"
236 self.isPrimaryKey = self.schema.addField(
237 "detect_isPrimary", type="Flag",
238 doc=primaryDoc,
239 )
240
241 if "deblend_nChild" in schema.getNames():
242 self.includeDeblend = True
243 self.isDeblendedSourceKey = self.schema.addField(
244 "detect_isDeblendedSource", type="Flag",
245 doc=primaryDoc + " and is either an unblended isolated source or a "
246 "deblended child from a parent with 'deblend_nChild' > 1")
247 self.fromBlendKey = self.schema.addField(
248 "detect_fromBlend", type="Flag",
249 doc="This source is deblended from a parent with more than one child."
250 )
251 self.isIsolatedKey = self.schema.addField(
252 "detect_isIsolated", type="Flag",
253 doc="This source is not a part of a blend."
254 )
255 if "deblend_scarletFlux" in schema.getNames():
256 self.isDeblendedModelKey = self.schema.addField(
257 "detect_isDeblendedModelSource", type="Flag",
258 doc=primaryDoc + " and is a deblended child")
259 else:
260 self.isDeblendedModelKey = None
261
262 def run(self, sources, skyMap=None, tractInfo=None, patchInfo=None):
263 """Set isPrimary and related flags on sources.
264
265 For coadded imaging, the `isPrimary` flag returns True when an object
266 has no children, is in the inner region of a coadd patch, is in the
267 inner region of a coadd trach, and is not detected in a pseudo-filter
268 (e.g., a sky_object).
269 For single frame imaging, the isPrimary flag returns True when a
270 source has no children and is not a sky source.
271
272 Parameters
273 ----------
274 sources : `lsst.afw.table.SourceCatalog`
275 A sourceTable. Reads in centroid fields and an nChild field.
276 Writes is-patch-inner, is-tract-inner, and is-primary flags.
277 skyMap : `lsst.skymap.BaseSkyMap`
278 Sky tessellation object
279 tractInfo : `lsst.skymap.TractInfo`
280 Tract object
281 patchInfo : `lsst.skymap.PatchInfo`
282 Patch object
283 """
284 # Mark whether sources are contained within the inner regions of the
285 # given tract/patch and are not "pseudo" (e.g. sky) sources.
286 if not self.isSingleFrame:
287 isPatchInner = getPatchInner(sources, patchInfo)
288 isTractInner = getTractInner(sources, tractInfo, skyMap)
289 isPseudo = getPseudoSources(sources, self.config.pseudoFilterList, self.schema, self.log)
290 isPrimary = isTractInner & isPatchInner & ~isPseudo
291
292 sources[self.isPatchInnerKey] = isPatchInner
293 sources[self.isTractInnerKey] = isTractInner
294 else:
295 # Mark all of the sky sources in SingleFrame images
296 # (if they were added)
297 if "sky_source" in sources.schema:
298 isSky = sources["sky_source"]
299 else:
300 isSky = np.zeros(len(sources), dtype=bool)
301 isPrimary = ~isSky
302
303 if self.includeDeblend:
304 result = getDeblendPrimaryFlags(sources)
305 fromBlend, isIsolated, isDeblendedSource, isDeblendedModelSource = result
306 sources[self.fromBlendKey] = fromBlend
307 sources[self.isIsolatedKey] = isIsolated
308 sources[self.isDeblendedSourceKey] = isDeblendedSource
309 if self.isDeblendedModelKey is not None:
310 sources[self.isDeblendedModelKey] = isDeblendedModelSource
311 isPrimary = isPrimary & isDeblendedSource
312
313 sources[self.isPrimaryKey] = isPrimary
A floating-point coordinate rectangle geometry.
Definition Box.h:413
__init__(self, schema, isSingleFrame=False, **kwargs)
getPatchInner(sources, patchInfo)
getTractInner(sources, tractInfo, skyMap)
getPseudoSources(sources, pseudoFilterList, schema, log)