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

88 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-10 14:08 +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 catalog. 

59 

60 Parameters 

61 ---------- 

62 butlerQC : `lsst.pipe.base.QuantumContext` 

63 inputRefs : `lsst.pipe.base.InputQuantizedConnection` 

64 outputRefs : `lsst.pipe.base.OutputQuantizedConnection` 

65 """ 

66 

67 inputs = butlerQC.get(inputRefs) 

68 bands = [] 

69 for filterName in self.config.filterNames: 

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

71 

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

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

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

75 bands = self.config.filterNames 

76 

77 columns = self.prepColumns(bands) 

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

79 

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

81 

82 loaderTask = LoadReferenceCatalogTask( 

83 config=self.config.referenceCatalogLoader, 

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

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

86 refCats=inputs["refCat"], 

87 ) 

88 

89 skymap = inputs.pop("skymap") 

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

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

92 

93 butlerQC.put(outputs, outputRefs) 

94 

95 

96class PhotometricCatalogMatchVisitConnections( 

97 pipeBase.PipelineTaskConnections, 

98 dimensions=("visit",), 

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

100): 

101 catalog = pipeBase.connectionTypes.Input( 

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

103 storageClass="ArrowAstropy", 

104 name="{targetCatalog}", 

105 dimensions=("visit",), 

106 deferLoad=True, 

107 ) 

108 

109 refCat = pipeBase.connectionTypes.PrerequisiteInput( 

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

111 name="{refCatalog}", 

112 storageClass="SimpleCatalog", 

113 dimensions=("skypix",), 

114 deferLoad=True, 

115 multiple=True, 

116 ) 

117 

118 visitSummaryTable = pipeBase.connectionTypes.Input( 

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

120 storageClass="ExposureCatalog", 

121 name="visitSummary", 

122 dimensions=("visit",), 

123 ) 

124 

125 matchedCatalog = pipeBase.connectionTypes.Output( 

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

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

128 storageClass="ArrowAstropy", 

129 dimensions=("visit",), 

130 ) 

131 

132 

133class PhotometricCatalogMatchVisitConfig( 

134 PhotometricCatalogMatchConfig, pipelineConnections=PhotometricCatalogMatchVisitConnections 

135): 

136 def setDefaults(self): 

137 self.filterNames = [] 

138 self.extraPerBandColumns = [] 

139 self.patchColumn = "" 

140 self.selectorBands = [] 

141 self.selectorActions.flagSelector = VisitPlotFlagSelector 

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

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

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

145 

146 

147class PhotometricCatalogMatchVisitTask(PhotometricCatalogMatchTask): 

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

149 is specific to the photometric reference catalog. 

150 """ 

151 

152 ConfigClass = PhotometricCatalogMatchVisitConfig 

153 _DefaultName = "analysisToolsPhotometricCatalogMatchVisit" 

154 

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

156 """Run the matching to the photometric reference catalog. 

157 

158 Parameters 

159 ---------- 

160 butlerQC : `lsst.pipe.base.QuantumContext` 

161 inputRefs : `lsst.pipe.base.InputQuantizedConnection` 

162 outputRefs : `lsst.pipe.base.OutputQuantizedConnection` 

163 """ 

164 

165 inputs = butlerQC.get(inputRefs) 

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

167 

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

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

170 if "sim" in physicalFilter: 

171 physicalFilter = physicalFilter[0] 

172 bands = [physicalFilter] 

173 else: 

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

175 # No bands needed for visit tables 

176 # but we do need them later for the matching 

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

178 for selectorAction in [ 

179 self.config.selectorActions, 

180 self.config.sourceSelectorActions, 

181 self.config.extraColumnSelectors, 

182 ]: 

183 for selector in selectorAction: 

184 selectorSchema = selector.getFormattedInputSchema() 

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

186 

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

188 

189 loaderTask = LoadReferenceCatalogTask( 

190 config=self.config.referenceCatalogLoader, 

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

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

193 refCats=inputs["refCat"], 

194 ) 

195 

196 visitSummaryTable = inputs.pop("visitSummaryTable") 

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

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

199 

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

201 # but the visit plots aren't expecting it 

202 cols = list(outputs.matchedCatalog.columns) 

203 for col in cols: 

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

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

206 

207 butlerQC.put(outputs, outputRefs) 

208 

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

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

211 

212 Parameters 

213 ---------- 

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

215 The table of visit information 

216 """ 

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

218 corners = [] 

219 for visSum in visitSummaryTable: 

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

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

222 # because it crashes later 

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

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

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

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

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

228 radius = visitBoundingCircle.getOpeningAngle() 

229 

230 # Get the observation date of the visit 

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

232 epoch = Time(obsDate.toPython()) 

233 

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

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

236 # dataframe 

237 

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

239 

240 return Table(loadedRefCat)