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