Coverage for tests / test_catalogMatch.py: 24%

93 statements  

« prev     ^ index     » next       coverage.py v7.13.5, created at 2026-05-07 08:53 +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 

22import unittest 

23 

24import astropy.units as u 

25import numpy as np 

26import pandas as pd 

27from astropy.table import Table 

28 

29import lsst.afw.table as afwTable 

30import lsst.geom 

31import lsst.skymap 

32from lsst.analysis.tools.tasks import AstrometricCatalogMatchConfig, AstrometricCatalogMatchTask 

33from lsst.daf.base import PropertyList 

34from lsst.meas.algorithms.testUtils import MockRefcatDataId 

35from lsst.pipe.base import InMemoryDatasetHandle 

36from lsst.pipe.tasks.loadReferenceCatalog import LoadReferenceCatalogTask 

37 

38 

39class TestCatalogMatch(unittest.TestCase): 

40 """Test AstrometricCatalogMatchTask""" 

41 

42 def setUp(self): 

43 config = AstrometricCatalogMatchConfig() 

44 config.bands = ["g", "r", "i", "z", "y"] 

45 self.task = AstrometricCatalogMatchTask(config=config) 

46 self.task.config.extraColumns.append("sourceId") 

47 self.task.config.referenceCatalogLoader.refObjLoader.requireProperMotion = False 

48 

49 self.rng = np.random.default_rng(12345) 

50 

51 self.skymap = self._make_skymap() 

52 self.tract = 9813 

53 

54 tract = self.skymap.generateTract(self.tract) 

55 self.tractPoly = tract.getOuterSkyPolygon() 

56 self.tractBbox = self.tractPoly.getBoundingBox() 

57 

58 self.nStars = 1000 

59 starIds = np.arange(self.nStars) 

60 starRas = ( 

61 self.rng.random(self.nStars) * self.tractBbox.getWidth().asDegrees() 

62 + self.tractBbox.getLon().getA().asDegrees() 

63 ) 

64 starDecs = ( 

65 self.rng.random(self.nStars) * self.tractBbox.getHeight().asDegrees() 

66 + self.tractBbox.getLat().getA().asDegrees() 

67 ) 

68 

69 refDataId, deferredRefCat = self._make_refCat(starIds, starRas, starDecs, self.tractPoly) 

70 

71 loaderTask = LoadReferenceCatalogTask( 

72 config=config.referenceCatalogLoader, 

73 dataIds=[refDataId], 

74 name="gaia_dr3_20230707", 

75 refCats=[deferredRefCat], 

76 ) 

77 self.loadedRefCat = self.task._loadRefCat(loaderTask, tract) 

78 self.loadedRefCat["sourceId"] = np.arange(0, len(self.loadedRefCat)) 

79 

80 self.objectTable = self._make_objectCat(starIds, starRas, starDecs) 

81 

82 def _make_skymap(self): 

83 """Make a testing skymap. 

84 

85 Returns 

86 ------- 

87 `lsst.skymap.ringsSkyMap.RingsSkyMap` 

88 Skymap that mimics the "hsc_rings_v1" skymap 

89 """ 

90 skymap_config = lsst.skymap.ringsSkyMap.RingsSkyMapConfig() 

91 skymap_config.numRings = 120 

92 skymap_config.projection = "TAN" 

93 skymap_config.tractOverlap = 1.0 / 60 

94 skymap_config.pixelScale = 0.168 

95 return lsst.skymap.ringsSkyMap.RingsSkyMap(skymap_config) 

96 

97 def _make_refCat(self, starIds, starRas, starDecs, poly): 

98 """Make a mock `deferredDatasetReference` and 

99 `DeferredDatasetHandle.dataId for a reference catalog. 

100 

101 Parameters 

102 ---------- 

103 starIds : `np.ndarray` of `int` 

104 Source ids for the simulated stars 

105 starRas : `np.ndarray` of `float` 

106 RAs of the simulated stars 

107 starDecs : `np.ndarray` of `float` 

108 Decs of the simulated stars 

109 poly : `lsst.sphgeom._sphgeom.ConvexPolygon` 

110 Bounding polygon containing the simulated stars 

111 

112 Returns 

113 ------- 

114 refDataId : `lsst.meas.algorithms.testUtils.MockRefcatDataId` 

115 Object that replicates the functionality of a dataId 

116 deferredRefCat : InMemoryDatasetHandle 

117 Object that replicates the functionality of a `DeferredDatasetRef` 

118 """ 

119 refSchema = afwTable.SimpleTable.makeMinimalSchema() 

120 idKey = refSchema.addField("sourceId", type="I") 

121 fluxKey = refSchema.addField("phot_g_mean_flux", units="nJy", type=np.float64) 

122 fluxErrKey = refSchema.addField("phot_g_mean_fluxErr", units="nJy", type=np.float64) 

123 refCat = afwTable.SimpleCatalog(refSchema) 

124 ref_md = PropertyList() 

125 ref_md.set("REFCAT_FORMAT_VERSION", 1) 

126 refCat.table.setMetadata(ref_md) 

127 for i in range(len(starIds)): 

128 record = refCat.addNew() 

129 record.set(idKey, starIds[i]) 

130 record.setRa(lsst.geom.Angle(starRas[i], lsst.geom.degrees)) 

131 record.setDec(lsst.geom.Angle(starDecs[i], lsst.geom.degrees)) 

132 record.set(fluxKey, 1) 

133 record.set(fluxErrKey, 0.01) 

134 refDataId = MockRefcatDataId(poly) 

135 deferredRefCat = InMemoryDatasetHandle(refCat, storageClass="SimpleCatalog", htm7="mockRefCat") 

136 return refDataId, deferredRefCat 

137 

138 def _make_objectCat(self, starIds, starRas, starDecs): 

139 """Make a `pd.DataFrame` catalog with the columns needed for the 

140 object selector. 

141 

142 Parameters 

143 ---------- 

144 starIds : `np.ndarray` of `int` 

145 Source ids for the simulated stars 

146 starRas : `np.ndarray` of `float` 

147 RAs of the simulated stars 

148 starDecs : `np.ndarray` of `float` 

149 Decs of the simulated stars 

150 poly : `lsst.sphgeom._sphgeom.ConvexPolygon` 

151 Bounding polygon containing the simulated stars 

152 

153 Returns 

154 ------- 

155 sourceCat : `pd.DataFrame` 

156 Catalog containing the simulated stars 

157 """ 

158 x = self.rng.random(self.nStars) * 4000 

159 y = self.rng.random(self.nStars) * 4000 

160 radecErr = 1.0 / (3600 * 10) # Let random scatter be about 1/10 arcsecond 

161 sourceDict = { 

162 "sourceId": starIds, 

163 "coord_ra": starRas + self.rng.standard_normal(self.nStars) * radecErr, 

164 "coord_dec": starDecs + self.rng.standard_normal(self.nStars) * radecErr, 

165 "x": x, 

166 "y": y, 

167 } 

168 

169 for key in [ 

170 "r_psfFlux_flag", 

171 "y_extendedness_flag", 

172 "i_pixelFlags_saturatedCenter", 

173 "r_extendedness_flag", 

174 "y_extendedness", 

175 "g_extendedness_flag", 

176 "z_extendedness", 

177 "i_extendedness", 

178 "z_pixelFlags_saturatedCenter", 

179 "i_psfFlux_flag", 

180 "r_pixelFlags_saturatedCenter", 

181 "coord_flag", 

182 "r_extendedness", 

183 "y_pixelFlags_saturatedCenter", 

184 "i_extendedness_flag", 

185 "patch", 

186 "g_psfFlux_flag", 

187 "y_psfFlux_flag", 

188 "z_psfFlux_flag", 

189 "g_pixelFlags_saturatedCenter", 

190 "z_extendedness_flag", 

191 "g_extendedness", 

192 ]: 

193 sourceDict[key] = 0 

194 for key in ["detect_isPatchInner", "detect_isDeblendedSource"]: 

195 sourceDict[key] = 1 

196 for key in ["i_psfFlux", "g_psfFlux", "r_psfFlux", "y_psfFlux", "z_psfFlux"]: 

197 sourceDict[key] = 1000 

198 for key in ["z_psfFluxErr", "i_psfFluxErr", "r_psfFluxErr", "g_psfFluxErr", "y_psfFluxErr"]: 

199 sourceDict[key] = 1 

200 sourceCat = Table.from_pandas(pd.DataFrame(sourceDict)) 

201 return sourceCat 

202 

203 def test_setRefCat(self): 

204 """Test whether the objects in the reference catalog are in the 

205 expected footprint and that we get as many as expected 

206 """ 

207 coord_ra = (self.loadedRefCat["ra"] * u.degree).to(u.radian).value 

208 coord_dec = (self.loadedRefCat["dec"] * u.degree).to(u.radian).value 

209 inFootprint = self.tractBbox.contains(coord_ra, coord_dec) 

210 self.assertTrue(inFootprint.all()) 

211 self.assertEqual(len(self.loadedRefCat), self.nStars) 

212 

213 def test_run(self): 

214 """Test whether `CatalogMatchTask` correctly associates the target and 

215 reference catalog. 

216 """ 

217 output = self.task.run( 

218 targetCatalog=self.objectTable, refCatalog=self.loadedRefCat, bands=self.task.config.bands 

219 ) 

220 

221 self.assertEqual(len(output.matchedCatalog), self.nStars) 

222 self.assertListEqual( 

223 list(output.matchedCatalog["sourceId_target"]), 

224 list(output.matchedCatalog["sourceId_ref"]), 

225 ) 

226 

227 

228class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase): 

229 pass 

230 

231 

232def setup_module(module): 

233 lsst.utils.tests.init() 

234 

235 

236if __name__ == "__main__": 236 ↛ 237line 236 didn't jump to line 237 because the condition on line 236 was never true

237 lsst.utils.tests.init() 

238 unittest.main()