Coverage for python/lsst/analysis/tools/tasks/astrometricCatalogMatch.py: 35%

78 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-12-08 13:17 +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__ = ( 

23 "AstrometricCatalogMatchConfig", 

24 "AstrometricCatalogMatchTask", 

25 "AstrometricCatalogMatchVisitConfig", 

26 "AstrometricCatalogMatchVisitTask", 

27) 

28 

29import lsst.geom 

30import lsst.pex.config as pexConfig 

31import lsst.pipe.base as pipeBase 

32import numpy as np 

33from astropy.table import Table 

34from astropy.time import Time 

35from lsst.pipe.tasks.configurableActions import ConfigurableActionStructField 

36from lsst.pipe.tasks.loadReferenceCatalog import LoadReferenceCatalogTask 

37 

38from ..actions.vector import VisitPlotFlagSelector 

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

40 

41 

42class AstrometricCatalogMatchConfig(CatalogMatchConfig, pipelineConnections=CatalogMatchConnections): 

43 bands = pexConfig.ListField[str]( 

44 doc="The bands to persist downstream", 

45 default=["u", "g", "r", "i", "z", "y"], 

46 ) 

47 

48 def setDefaults(self): 

49 super().setDefaults() 

50 self.referenceCatalogLoader.doApplyColorTerms = False 

51 self.referenceCatalogLoader.refObjLoader.requireProperMotion = True 

52 self.referenceCatalogLoader.refObjLoader.anyFilterMapsToThis = "phot_g_mean" 

53 

54 

55class AstrometricCatalogMatchTask(CatalogMatchTask): 

56 """Match a tract-level catalog to a reference catalog""" 

57 

58 ConfigClass = AstrometricCatalogMatchConfig 

59 _DefaultName = "analysisToolsAstrometricCatalogMatch" 

60 

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

62 # Docs inherited from base class 

63 

64 inputs = butlerQC.get(inputRefs) 

65 columns = self.prepColumns(self.config.bands) 

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

67 

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

69 

70 loaderTask = LoadReferenceCatalogTask( 

71 config=self.config.referenceCatalogLoader, 

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

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

74 refCats=inputs["refCat"], 

75 ) 

76 

77 skymap = inputs.pop("skymap") 

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

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

80 

81 butlerQC.put(outputs, outputRefs) 

82 

83 

84class AstrometricCatalogMatchVisitConnections( 

85 pipeBase.PipelineTaskConnections, 

86 dimensions=("visit",), 

87 defaultTemplates={"targetCatalog": "sourceTable_visit", "refCatalog": "gaia_dr2_20200414"}, 

88): 

89 catalog = pipeBase.connectionTypes.Input( 

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

91 storageClass="ArrowAstropy", 

92 name="sourceTable_visit", 

93 dimensions=("visit",), 

94 deferLoad=True, 

95 ) 

96 

97 refCat = pipeBase.connectionTypes.PrerequisiteInput( 

98 doc="The astrometry reference catalog to match to loaded input catalog sources.", 

99 name="gaia_dr2_20200414", 

100 storageClass="SimpleCatalog", 

101 dimensions=("skypix",), 

102 deferLoad=True, 

103 multiple=True, 

104 ) 

105 

106 visitSummaryTable = pipeBase.connectionTypes.Input( 

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

108 storageClass="ExposureCatalog", 

109 name="visitSummary", 

110 dimensions=("visit",), 

111 ) 

112 

113 matchedCatalog = pipeBase.connectionTypes.Output( 

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

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

116 storageClass="ArrowAstropy", 

117 dimensions=("visit",), 

118 ) 

119 

120 

121class AstrometricCatalogMatchVisitConfig( 

122 AstrometricCatalogMatchConfig, pipelineConnections=AstrometricCatalogMatchVisitConnections 

123): 

124 selectorActions = ConfigurableActionStructField( 

125 doc="Which selectors to use to narrow down the data for QA plotting.", 

126 default={"flagSelector": VisitPlotFlagSelector}, 

127 ) 

128 

129 extraColumns = pexConfig.ListField[str]( 

130 doc="Other catalog columns to persist to downstream tasks", 

131 default=["psfFlux", "psfFluxErr"], 

132 ) 

133 

134 bands = pexConfig.ListField[str]( 

135 doc="The bands to persist downstream", 

136 default=[], 

137 ) 

138 

139 def setDefaults(self): 

140 # sourceSelectorActions.sourceSelector is StarSelector 

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

142 # extraColumnSelectors.selector1 is SnSelector 

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

144 # extraColumnSelectors.selector2 is GalaxySelector 

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

146 self.referenceCatalogLoader.doApplyColorTerms = False 

147 self.referenceCatalogLoader.refObjLoader.requireProperMotion = False 

148 self.referenceCatalogLoader.refObjLoader.anyFilterMapsToThis = "phot_g_mean" 

149 

150 

151class AstrometricCatalogMatchVisitTask(AstrometricCatalogMatchTask): 

152 """Match a visit-level catalog to a reference catalog""" 

153 

154 ConfigClass = AstrometricCatalogMatchVisitConfig 

155 _DefaultName = "analysisToolsAstrometricCatalogMatchVisit" 

156 

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

158 # Docs inherited from base class 

159 

160 inputs = butlerQC.get(inputRefs) 

161 

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

163 for selectorAction in [ 

164 self.config.selectorActions, 

165 self.config.sourceSelectorActions, 

166 self.config.extraColumnSelectors, 

167 ]: 

168 for selector in selectorAction: 

169 selectorSchema = selector.getFormattedInputSchema() 

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

171 

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

173 

174 loaderTask = LoadReferenceCatalogTask( 

175 config=self.config.referenceCatalogLoader, 

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

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

178 refCats=inputs["refCat"], 

179 ) 

180 

181 visitSummaryTable = inputs.pop("visitSummaryTable") 

182 loadedRefCat = self._loadRefCat(loaderTask, visitSummaryTable) 

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

184 

185 butlerQC.put(outputs, outputRefs) 

186 

187 def _loadRefCat(self, loaderTask, visitSummaryTable): 

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

189 

190 Parameters 

191 ---------- 

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

193 The table of visit information 

194 """ 

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

196 corners = [] 

197 for visSum in visitSummaryTable: 

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

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

200 # because it crashes later 

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

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

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

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

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

206 radius = visitBoundingCircle.getOpeningAngle() 

207 

208 # Get the observation date of the visit 

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

210 epoch = Time(obsDate.toPython()) 

211 

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

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

214 # dataframe 

215 

216 filterName = self.config.referenceCatalogLoader.refObjLoader.anyFilterMapsToThis 

217 loadedRefCat = loaderTask.getSkyCircleCatalog(center, radius, filterName, epoch=epoch) 

218 

219 return Table(loadedRefCat)