LSST Applications  21.0.0-147-g0e635eb1+1acddb5be5,22.0.0+052faf71bd,22.0.0+1ea9a8b2b2,22.0.0+6312710a6c,22.0.0+729191ecac,22.0.0+7589c3a021,22.0.0+9f079a9461,22.0.1-1-g7d6de66+b8044ec9de,22.0.1-1-g87000a6+536b1ee016,22.0.1-1-g8e32f31+6312710a6c,22.0.1-10-gd060f87+016f7cdc03,22.0.1-12-g9c3108e+df145f6f68,22.0.1-16-g314fa6d+c825727ab8,22.0.1-19-g93a5c75+d23f2fb6d8,22.0.1-19-gb93eaa13+aab3ef7709,22.0.1-2-g8ef0a89+b8044ec9de,22.0.1-2-g92698f7+9f079a9461,22.0.1-2-ga9b0f51+052faf71bd,22.0.1-2-gac51dbf+052faf71bd,22.0.1-2-gb66926d+6312710a6c,22.0.1-2-gcb770ba+09e3807989,22.0.1-20-g32debb5+b8044ec9de,22.0.1-23-gc2439a9a+fb0756638e,22.0.1-3-g496fd5d+09117f784f,22.0.1-3-g59f966b+1e6ba2c031,22.0.1-3-g849a1b8+f8b568069f,22.0.1-3-gaaec9c0+c5c846a8b1,22.0.1-32-g5ddfab5d3+60ce4897b0,22.0.1-4-g037fbe1+64e601228d,22.0.1-4-g8623105+b8044ec9de,22.0.1-5-g096abc9+d18c45d440,22.0.1-5-g15c806e+57f5c03693,22.0.1-7-gba73697+57f5c03693,master-g6e05de7fdc+c1283a92b8,master-g72cdda8301+729191ecac,w.2021.39
LSST Data Management Base Package
setPrimaryFlags.py
Go to the documentation of this file.
1 #!/usr/bin/env python
2 #
3 # LSST Data Management System
4 # Copyright 2008-2016 LSST/AURA
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 <http://www.lsstcorp.org/LegalNotices/>.
22 #
23 import numpy as np
24 from lsst.pex.config import Config, Field, ListField
25 from lsst.pipe.base import Task
26 from lsst.geom import Box2D
27 
28 
29 def getPatchInner(sources, patchInfo):
30  """Set a flag for each source if it is in the innerBBox of a patch.
31 
32  Parameters
33  ----------
34  sources : `lsst.afw.table.SourceCatalog`
35  A sourceCatalog with pre-calculated centroids.
36  patchInfo : `lsst.skymap.PatchInfo`
37  Information about a `SkyMap` `Patch`.
38 
39  Returns
40  --------
41  isPatchInner : array-like of `bool`
42  `True` for each source that has a centroid
43  in the inner region of a patch.
44  """
45  # Extract the centroid position for all the sources
46  x = sources["slot_Centroid_x"]
47  y = sources["slot_Centroid_y"]
48  centroidFlag = sources["slot_Centroid_flag"]
49 
50  # set inner flags for each source and set primary flags for
51  # sources with no children (or all sources if deblend info not available)
52  innerFloatBBox = Box2D(patchInfo.getInnerBBox())
53  inInner = innerFloatBBox.contains(x, y)
54 
55  # When the centroider fails, we can still fall back to the peak,
56  # but we don't trust that quite as much -
57  # so we use a slightly smaller box for the patch comparison.
58  shrunkInnerFloatBBox = Box2D(innerFloatBBox)
59  shrunkInnerFloatBBox.grow(-1)
60  inShrunkInner = shrunkInnerFloatBBox.contains(x, y)
61 
62  # Flag sources contained in the inner region of a patch
63  isPatchInner = (centroidFlag & inShrunkInner) | (~centroidFlag & inInner)
64  return isPatchInner
65 
66 
67 def getTractInner(sources, tractInfo, skyMap):
68  """Set a flag for each source that the skyMap includes in tractInfo.
69 
70  Parameters
71  ----------
72  sources : `lsst.afw.table.SourceCatalog`
73  A sourceCatalog with pre-calculated centroids.
74  tractInfo : `lsst.skymap.TractInfo`
75  Tract object
76  skyMap : `lsst.skymap.BaseSkyMap`
77  Sky tessellation object
78 
79  Returns
80  -------
81  isTractInner : array-like of `bool`
82  True if the skyMap.findTract method returns
83  the same tract as tractInfo.
84  """
85  tractId = tractInfo.getId()
86  isTractInner = np.array([skyMap.findTract(s.getCoord()).getId() == tractId for s in sources])
87  return isTractInner
88 
89 
90 def getPseudoSources(sources, pseudoFilterList, schema, log):
91  """Get a flag that marks pseudo sources.
92 
93  Some categories of sources, for example sky objects,
94  are not really detected sources and should not be considered primary
95  sources.
96 
97  Parameters
98  ----------
99  sources : `lsst.afw.table.SourceCatalog`
100  The catalog of sources for which to identify "pseudo"
101  (e.g. sky) objects.
102  pseudoFilterList : `list` of `str`
103  Names of filters which should never be primary
104 
105  Returns
106  -------
107  isPseudo : array-like of `bool`
108  True for each source that is a pseudo source.
109  Note: to remove pseudo sources use `~isPseudo`.
110  """
111  # Filter out sources that should never be primary
112  isPseudo = np.zeros(len(sources), dtype=bool)
113  for filt in pseudoFilterList:
114  try:
115  pseudoFilterKey = schema.find("merge_peak_%s" % filt).getKey()
116  isPseudo |= sources[pseudoFilterKey]
117  except KeyError:
118  log.warning("merge_peak is not set for pseudo-filter %s", filt)
119  return isPseudo
120 
121 
123  """Get flags generated by the deblender
124 
125  scarlet is different than meas_deblender in that it is not
126  (necessarily) flux conserving. For consistency in scarlet,
127  all of the parents with only a single child (isolated sources)
128  need to be deblended. This creates a question: which type
129  of isolated source should we make measurements on, the
130  undeblended "parent" or the deblended child?
131  For that reason we distinguish between a DeblendedSource,
132  which is a source that has no children and uses the
133  isolated parents, and a DeblendedModelSource, which uses
134  the scarlet models for both isolated and blended sources.
135  In the case of meas_deblender, DeblendedModelSource is
136  `None` because it is not contained in the output catalog.
137 
138  Parameters
139  ----------
140  sources : `lsst.afw.table.SourceCatalog`
141  A sourceCatalog that has already been deblended using
142  either meas_extensions_scarlet or meas_deblender.
143 
144  Returns
145  -------
146  fromBlend : array-like of `bool`
147  True for each source modeled by the deblender from a `Peak`
148  in a parent footprint that contained at least one other `Peak`.
149  While these models can be approximated as isolated,
150  and measurements are made on them as if that's the case,
151  we know deblending to introduce biases in the shape and centroid
152  of objects and it is important to know that the sources that these
153  models are based on are all bleneded in the true image.
154  isIsolated : array-like of `bool`
155  True for isolated sources, regardless of whether or not they
156  were modeled by the deblender.
157  isDeblendedSource : array-like of `bool`
158  True for each source that is a "DeblendedSource" as defined above.
159  isDeblendedModelSource : array-like of `bool`
160  True for each source that is a "DeblendedSourceModel"
161  as defined above.
162  """
163  nChildKey = "deblend_nChild"
164  nChild = sources[nChildKey]
165  parent = sources["parent"]
166 
167  if "deblend_scarletFlux" in sources.schema:
168  # The number of peaks in the sources footprint.
169  # This (may be) different than nChild,
170  # the number of deblended sources in the catalog,
171  # because some peaks might have been culled during deblending.
172  nPeaks = sources["deblend_nPeaks"]
173  parentNChild = sources["deblend_parentNChild"]
174  # It is possible for a catalog to contain a hierarchy of sources,
175  # so we mark the leaves (end nodes of the hierarchy tree with no
176  # children).
177  isLeaf = nPeaks == 1
178  fromBlend = parentNChild > 1
179  isIsolated = isLeaf & ((parent == 0) | parentNChild == 1)
180  isDeblendedSource = (fromBlend & isLeaf) | (isIsolated & (parent == 0))
181  isDeblendedModelSource = (parent != 0) & isLeaf
182  else:
183  # Set the flags for meas_deblender
184  fromBlend = parent != 0
185  isIsolated = (nChild == 0) & (parent == 0)
186  isDeblendedSource = nChild == 0
187  isDeblendedModelSource = None
188  return fromBlend, isIsolated, isDeblendedSource, isDeblendedModelSource
189 
190 
192  nChildKeyName = Field(dtype=str, default="deprecated",
193  doc="Deprecated. This parameter is not used.")
194  pseudoFilterList = ListField(dtype=str, default=['sky'],
195  doc="Names of filters which should never be primary")
196 
197 
199  """Add isPrimaryKey to a given schema.
200 
201  Parameters
202  ----------
203  schema : `lsst.afw.table.Schema`
204  The input schema.
205  isSingleFrame : `bool`
206  Flag specifying if task is operating with single frame imaging.
207  includeDeblend : `bool`
208  Include deblend information in isPrimary and
209  add isDeblendedSource field?
210  kwargs :
211  Keyword arguments passed to the task.
212  """
213 
214  ConfigClass = SetPrimaryFlagsConfig
215 
216  def __init__(self, schema, isSingleFrame=False, **kwargs):
217  Task.__init__(self, **kwargs)
218  self.schemaschema = schema
219  self.isSingleFrameisSingleFrame = isSingleFrame
220  self.includeDeblendincludeDeblend = False
221  if not self.isSingleFrameisSingleFrame:
222  primaryDoc = ("true if source has no children and is in the inner region of a coadd patch "
223  "and is in the inner region of a coadd tract "
224  "and is not \"detected\" in a pseudo-filter (see config.pseudoFilterList)")
225  self.isPatchInnerKeyisPatchInnerKey = self.schemaschema.addField(
226  "detect_isPatchInner", type="Flag",
227  doc="true if source is in the inner region of a coadd patch",
228  )
229  self.isTractInnerKeyisTractInnerKey = self.schemaschema.addField(
230  "detect_isTractInner", type="Flag",
231  doc="true if source is in the inner region of a coadd tract",
232  )
233  else:
234  primaryDoc = "true if source has no children and is not a sky source"
235  self.isPrimaryKeyisPrimaryKey = self.schemaschema.addField(
236  "detect_isPrimary", type="Flag",
237  doc=primaryDoc,
238  )
239 
240  if "deblend_nChild" in schema.getNames():
241  self.includeDeblendincludeDeblend = True
242  self.isDeblendedSourceKeyisDeblendedSourceKey = self.schemaschema.addField(
243  "detect_isDeblendedSource", type="Flag",
244  doc=primaryDoc + " and is either an unblended isolated source or a "
245  "deblended child from a parent with 'deblend_nChild' > 1")
246  self.fromBlendKeyfromBlendKey = self.schemaschema.addField(
247  "detect_fromBlend", type="Flag",
248  doc="This source is deblended from a parent with more than one child."
249  )
250  self.isIsolatedKeyisIsolatedKey = self.schemaschema.addField(
251  "detect_isIsolated", type="Flag",
252  doc="This source is not a part of a blend."
253  )
254  if "deblend_scarletFlux" in schema.getNames():
255  self.isDeblendedModelKeyisDeblendedModelKey = self.schemaschema.addField(
256  "detect_isDeblendedModelSource", type="Flag",
257  doc=primaryDoc + " and is a deblended child")
258  else:
259  self.isDeblendedModelKeyisDeblendedModelKey = None
260 
261  def run(self, sources, skyMap=None, tractInfo=None, patchInfo=None):
262  """Set isPrimary and related flags on sources.
263 
264  For coadded imaging, the `isPrimary` flag returns True when an object
265  has no children, is in the inner region of a coadd patch, is in the
266  inner region of a coadd trach, and is not detected in a pseudo-filter
267  (e.g., a sky_object).
268  For single frame imaging, the isPrimary flag returns True when a
269  source has no children and is not a sky source.
270 
271  Parameters
272  ----------
273  sources : `lsst.afw.table.SourceCatalog`
274  A sourceTable. Reads in centroid fields and an nChild field.
275  Writes is-patch-inner, is-tract-inner, and is-primary flags.
276  skyMap : `lsst.skymap.BaseSkyMap`
277  Sky tessellation object
278  tractInfo : `lsst.skymap.TractInfo`
279  Tract object
280  patchInfo : `lsst.skymap.PatchInfo`
281  Patch object
282  """
283  # Mark whether sources are contained within the inner regions of the
284  # given tract/patch and are not "pseudo" (e.g. sky) sources.
285  if not self.isSingleFrameisSingleFrame:
286  isPatchInner = getPatchInner(sources, patchInfo)
287  isTractInner = getTractInner(sources, tractInfo, skyMap)
288  isPseudo = getPseudoSources(sources, self.config.pseudoFilterList, self.schemaschema, self.log)
289  isPrimary = isTractInner & isPatchInner & ~isPseudo
290 
291  sources[self.isPatchInnerKeyisPatchInnerKey] = isPatchInner
292  sources[self.isTractInnerKeyisTractInnerKey] = isTractInner
293  else:
294  # Mark all of the sky sources in SingleFrame images
295  # (if they were added)
296  if "sky_source" in sources.schema:
297  isSky = sources["sky_source"]
298  else:
299  isSky = np.zeros(len(sources), dtype=bool)
300  isPrimary = ~isSky
301 
302  if self.includeDeblendincludeDeblend:
303  result = getDeblendPrimaryFlags(sources)
304  fromBlend, isIsolated, isDeblendedSource, isDeblendedModelSource = result
305  sources[self.fromBlendKeyfromBlendKey] = fromBlend
306  sources[self.isIsolatedKeyisIsolatedKey] = isIsolated
307  sources[self.isDeblendedSourceKeyisDeblendedSourceKey] = isDeblendedSource
308  if self.isDeblendedModelKeyisDeblendedModelKey is not None:
309  sources[self.isDeblendedModelKeyisDeblendedModelKey] = isDeblendedModelSource
310  isPrimary = isPrimary & isDeblendedSource
311 
312  sources[self.isPrimaryKeyisPrimaryKey] = isPrimary
A floating-point coordinate rectangle geometry.
Definition: Box.h:413
def run(self, sources, skyMap=None, tractInfo=None, patchInfo=None)
def __init__(self, schema, isSingleFrame=False, **kwargs)
def getPatchInner(sources, patchInfo)
def getTractInner(sources, tractInfo, skyMap)
def getPseudoSources(sources, pseudoFilterList, schema, log)