Coverage for python/lsst/pipe/tasks/setPrimaryFlags.py: 17%

91 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-07-13 12:29 +0000

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# 

23import numpy as np 

24from lsst.pex.config import Config, Field, ListField 

25from lsst.pipe.base import Task 

26from lsst.geom import Box2D 

27 

28 

29def 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 

67def 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 

90def 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 

122def getDeblendPrimaryFlags(sources): 

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 

191class SetPrimaryFlagsConfig(Config): 

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 

198class SetPrimaryFlagsTask(Task): 

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.schema = schema 

219 self.isSingleFrame = isSingleFrame 

220 self.includeDeblend = False 

221 if not self.isSingleFrame: 

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.isPatchInnerKey = self.schema.addField( 

226 "detect_isPatchInner", type="Flag", 

227 doc="true if source is in the inner region of a coadd patch", 

228 ) 

229 self.isTractInnerKey = self.schema.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.isPrimaryKey = self.schema.addField( 

236 "detect_isPrimary", type="Flag", 

237 doc=primaryDoc, 

238 ) 

239 

240 if "deblend_nChild" in schema.getNames(): 

241 self.includeDeblend = True 

242 self.isDeblendedSourceKey = self.schema.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.fromBlendKey = self.schema.addField( 

247 "detect_fromBlend", type="Flag", 

248 doc="This source is deblended from a parent with more than one child." 

249 ) 

250 self.isIsolatedKey = self.schema.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.isDeblendedModelKey = self.schema.addField( 

256 "detect_isDeblendedModelSource", type="Flag", 

257 doc=primaryDoc + " and is a deblended child") 

258 else: 

259 self.isDeblendedModelKey = 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.isSingleFrame: 

286 isPatchInner = getPatchInner(sources, patchInfo) 

287 isTractInner = getTractInner(sources, tractInfo, skyMap) 

288 isPseudo = getPseudoSources(sources, self.config.pseudoFilterList, self.schema, self.log) 

289 isPrimary = isTractInner & isPatchInner & ~isPseudo 

290 

291 sources[self.isPatchInnerKey] = isPatchInner 

292 sources[self.isTractInnerKey] = 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.includeDeblend: 

303 result = getDeblendPrimaryFlags(sources) 

304 fromBlend, isIsolated, isDeblendedSource, isDeblendedModelSource = result 

305 sources[self.fromBlendKey] = fromBlend 

306 sources[self.isIsolatedKey] = isIsolated 

307 sources[self.isDeblendedSourceKey] = isDeblendedSource 

308 if self.isDeblendedModelKey is not None: 

309 sources[self.isDeblendedModelKey] = isDeblendedModelSource 

310 isPrimary = isPrimary & isDeblendedSource 

311 

312 sources[self.isPrimaryKey] = isPrimary