Coverage for python/lsst/analysis/tools/tasks/photometricCatalogMatch.py: 25%

88 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 13:15 +0000

1# This file is part of analysis_tools. 

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__ = ("PhotometricCatalogMatchConfig", "PhotometricCatalogMatchTask") 

23 

24 

25import lsst.geom 

26import lsst.pipe.base as pipeBase 

27import numpy as np 

28from astropy.table import Table 

29from astropy.time import Time 

30from lsst.pipe.tasks.loadReferenceCatalog import LoadReferenceCatalogTask 

31 

32from ..actions.vector import VisitPlotFlagSelector 

33from ..tasks.catalogMatch import CatalogMatchConfig, CatalogMatchConnections, CatalogMatchTask 

34 

35 

36class PhotometricCatalogMatchConnections(CatalogMatchConnections): 

37 pass 

38 

39 

40class PhotometricCatalogMatchConfig( 

41 CatalogMatchConfig, pipelineConnections=PhotometricCatalogMatchConnections 

42): 

43 def setDefaults(self): 

44 super().setDefaults() 

45 self.referenceCatalogLoader.doReferenceSelection = False 

46 self.referenceCatalogLoader.doApplyColorTerms = True 

47 

48 

49class PhotometricCatalogMatchTask(CatalogMatchTask): 

50 """A wrapper task to provide the information that 

51 is specific to the photometric reference catalog. 

52 """ 

53 

54 ConfigClass = PhotometricCatalogMatchConfig 

55 _DefaultName = "analysisToolsPhotometricCatalogMatch" 

56 

57 def runQuantum(self, butlerQC, inputRefs, outputRefs): 

58 """Run the matching to the photometric reference 

59 catalog. 

60 

61 Parameters 

62 ---------- 

63 `butlerQC` : lsst.pipe.base.butlerQuantumContext.ButlerQuantumContext 

64 `inputRefs` : lsst.pipe.base.connections.InputQuantizedConnection 

65 `outputRefs` : lsst.pipe.base.connections.OutputQuantizedConnection 

66 

67 """ 

68 

69 inputs = butlerQC.get(inputRefs) 

70 bands = [] 

71 for filterName in self.config.filterNames: 

72 bands.append(self.config.referenceCatalogLoader.refObjLoader.filterMap[filterName]) 

73 

74 # For some reason the imsim filterMaps don't work the same way as 

75 # the HSC ones do, this is a bit hacky but fixes this 

76 if "sim" in bands[0] or "smeared" in bands[0]: 

77 bands = self.config.filterNames 

78 

79 columns = self.prepColumns(bands) 

80 table = inputs["catalog"].get(parameters={"columns": columns}) 

81 

82 tract = butlerQC.quantum.dataId["tract"] 

83 

84 loaderTask = LoadReferenceCatalogTask( 

85 config=self.config.referenceCatalogLoader, 

86 dataIds=[ref.dataId for ref in inputRefs.refCat], 

87 name=inputs["refCat"][0].ref.datasetType.name, 

88 refCats=inputs["refCat"], 

89 ) 

90 

91 skymap = inputs.pop("skymap") 

92 loadedRefCat = self._loadRefCat(loaderTask, skymap[tract]) 

93 outputs = self.run(catalog=table, loadedRefCat=loadedRefCat, bands=bands) 

94 

95 butlerQC.put(outputs, outputRefs) 

96 

97 

98class PhotometricCatalogMatchVisitConnections( 

99 pipeBase.PipelineTaskConnections, 

100 dimensions=("visit",), 

101 defaultTemplates={"targetCatalog": "sourceTable_visit", "refCatalog": "ps1_pv3_3pi_20170110"}, 

102): 

103 catalog = pipeBase.connectionTypes.Input( 

104 doc="The visit-wide catalog to make plots from.", 

105 storageClass="ArrowAstropy", 

106 name="{targetCatalog}", 

107 dimensions=("visit",), 

108 deferLoad=True, 

109 ) 

110 

111 refCat = pipeBase.connectionTypes.PrerequisiteInput( 

112 doc="The photometric reference catalog to match to.", 

113 name="{refCatalog}", 

114 storageClass="SimpleCatalog", 

115 dimensions=("skypix",), 

116 deferLoad=True, 

117 multiple=True, 

118 ) 

119 

120 visitSummaryTable = pipeBase.connectionTypes.Input( 

121 doc="A summary table of the ccds in the visit", 

122 storageClass="ExposureCatalog", 

123 name="visitSummary", 

124 dimensions=("visit",), 

125 ) 

126 

127 matchedCatalog = pipeBase.connectionTypes.Output( 

128 doc="Catalog with matched target and reference objects with separations", 

129 name="{targetCatalog}_{refCatalog}_match", 

130 storageClass="ArrowAstropy", 

131 dimensions=("visit",), 

132 ) 

133 

134 

135class PhotometricCatalogMatchVisitConfig( 

136 PhotometricCatalogMatchConfig, pipelineConnections=PhotometricCatalogMatchVisitConnections 

137): 

138 def setDefaults(self): 

139 self.filterNames = [] 

140 self.extraPerBandColumns = [] 

141 self.patchColumn = "" 

142 self.selectorBands = [] 

143 self.selectorActions.flagSelector = VisitPlotFlagSelector 

144 self.sourceSelectorActions.sourceSelector.vectorKey = "extendedness" 

145 self.extraColumnSelectors.selector1.fluxType = "psfFlux" 

146 self.extraColumnSelectors.selector2.vectorKey = "extendedness" 

147 

148 

149class PhotometricCatalogMatchVisitTask(PhotometricCatalogMatchTask): 

150 """A wrapper task to provide the information that 

151 is specific to the photometric reference catalog. 

152 """ 

153 

154 ConfigClass = PhotometricCatalogMatchVisitConfig 

155 _DefaultName = "analysisToolsPhotometricCatalogMatchVisit" 

156 

157 def runQuantum(self, butlerQC, inputRefs, outputRefs): 

158 """Run the matching to the photometric reference 

159 catalog. 

160 

161 Parameters 

162 ---------- 

163 `butlerQC` : lsst.pipe.base.butlerQuantumContext.ButlerQuantumContext 

164 `inputRefs` : lsst.pipe.base.connections.InputQuantizedConnection 

165 `outputRefs` : lsst.pipe.base.connections.OutputQuantizedConnection 

166 

167 """ 

168 

169 inputs = butlerQC.get(inputRefs) 

170 physicalFilter = inputs["catalog"].dataId["physical_filter"] 

171 

172 # For some reason the imsim filterMaps don't work the same way as 

173 # the HSC ones do, this is a bit hacky but fixes this 

174 if "sim" in physicalFilter: 

175 physicalFilter = physicalFilter[0] 

176 bands = [physicalFilter] 

177 else: 

178 bands = [self.config.referenceCatalogLoader.refObjLoader.filterMap[physicalFilter]] 

179 # No bands needed for visit tables 

180 # but we do need them later for the matching 

181 columns = ["coord_ra", "coord_dec", "detector"] + self.config.extraColumns.list() 

182 for selectorAction in [ 

183 self.config.selectorActions, 

184 self.config.sourceSelectorActions, 

185 self.config.extraColumnSelectors, 

186 ]: 

187 for selector in selectorAction: 

188 selectorSchema = selector.getFormattedInputSchema() 

189 columns += [s[0] for s in selectorSchema] 

190 

191 table = inputs["catalog"].get(parameters={"columns": columns}) 

192 

193 loaderTask = LoadReferenceCatalogTask( 

194 config=self.config.referenceCatalogLoader, 

195 dataIds=[ref.dataId for ref in inputRefs.refCat], 

196 name=inputs["refCat"][0].ref.datasetType.name, 

197 refCats=inputs["refCat"], 

198 ) 

199 

200 visitSummaryTable = inputs.pop("visitSummaryTable") 

201 loadedRefCat = self._loadRefCat(loaderTask, visitSummaryTable, physicalFilter) 

202 outputs = self.run(catalog=table, loadedRefCat=loadedRefCat, bands=bands) 

203 

204 # The matcher adds the band to the front of the columns 

205 # but the visit plots aren't expecting it 

206 cols = list(outputs.matchedCatalog.columns) 

207 for col in cols: 

208 if col[:2] == bands[0] + "_": 

209 outputs.matchedCatalog.rename_column(col, col[2:]) 

210 

211 butlerQC.put(outputs, outputRefs) 

212 

213 def _loadRefCat(self, loaderTask, visitSummaryTable, physicalFilter): 

214 """Make a reference catalog with coordinates in degrees 

215 

216 Parameters 

217 ---------- 

218 visitSummaryTable : `lsst.afw.table.ExposureCatalog` 

219 The table of visit information 

220 """ 

221 # Get convex hull around the detectors, then get its center and radius 

222 corners = [] 

223 for visSum in visitSummaryTable: 

224 for ra, dec in zip(visSum["raCorners"], visSum["decCorners"]): 

225 # If the coordinates are nan then don't keep going 

226 # because it crashes later 

227 if not np.isfinite(ra) or not np.isfinite(dec): 

228 raise pipeBase.NoWorkFound("Visit summary corners not finite") 

229 corners.append(lsst.geom.SpherePoint(ra, dec, units=lsst.geom.degrees).getVector()) 

230 visitBoundingCircle = lsst.sphgeom.ConvexPolygon.convexHull(corners).getBoundingCircle() 

231 center = lsst.geom.SpherePoint(visitBoundingCircle.getCenter()) 

232 radius = visitBoundingCircle.getOpeningAngle() 

233 

234 # Get the observation date of the visit 

235 obsDate = visSum.getVisitInfo().getDate() 

236 epoch = Time(obsDate.toPython()) 

237 

238 # Load the reference catalog in the skyCircle of the detectors, then 

239 # convert the coordinates to degrees and convert the catalog to a 

240 # dataframe 

241 

242 loadedRefCat = loaderTask.getSkyCircleCatalog(center, radius, [physicalFilter], epoch=epoch) 

243 

244 return Table(loadedRefCat)