Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1# This file is part of jointcal. 

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 

23import os 

24import tempfile 

25 

26from astropy import units as u 

27 

28import lsst.geom 

29import lsst.utils 

30import lsst.pex.exceptions 

31import lsst.pex.config 

32 

33import jointcalTestBase 

34 

35 

36# for MemoryTestCase 

37def setup_module(module): 

38 lsst.utils.tests.init() 

39 

40 

41class JointcalTestCFHT(jointcalTestBase.JointcalTestBase, lsst.utils.tests.TestCase): 

42 

43 @classmethod 

44 def setUpClass(cls): 

45 try: 

46 cls.data_dir = lsst.utils.getPackageDir('testdata_jointcal') 

47 except lsst.pex.exceptions.NotFoundError: 

48 raise unittest.SkipTest("testdata_jointcal not setup") 

49 

50 def setUp(self): 

51 # We don't want the absolute astrometry to become significantly worse 

52 # than the single-epoch astrometry (about 0.040"). 

53 # See Readme for an explanation of this empirical value. 

54 self.dist_rms_absolute = 49e-3*u.arcsecond 

55 

56 do_plot = False 

57 

58 # center of the cfht validation_data catalog 

59 center = lsst.geom.SpherePoint(214.884832, 52.6622199, lsst.geom.degrees) 

60 radius = 3*lsst.geom.degrees 

61 

62 input_dir = os.path.join(self.data_dir, 'cfht') 

63 all_visits = [849375, 850587] 

64 

65 self.setUp_base(center, radius, 

66 input_dir=input_dir, 

67 all_visits=all_visits, 

68 do_plot=do_plot, 

69 log_level="DEBUG") 

70 

71 def test_jointcalTask_2_visits(self): 

72 """Test the simple models with two visits and check that some debug 

73 output files also get created. 

74 """ 

75 self.config = lsst.jointcal.jointcal.JointcalConfig() 

76 self.config.astrometryModel = "simple" 

77 self.config.photometryModel = "simpleFlux" 

78 self.config.writeInitialModel = True # write the initial models 

79 # to test whether we got the expected chi2 contribution files. 

80 self.config.writeChi2FilesInitialFinal = True 

81 # use a temporary directory for debug output, to prevent test collisions 

82 with tempfile.TemporaryDirectory() as tempdir: 

83 self.config.debugOutputPath = tempdir 

84 

85 # See Readme for an explanation of these empirical values. 

86 dist_rms_relative = 11e-3*u.arcsecond 

87 pa1 = 0.014 

88 metrics = {'collected_astrometry_refStars': 1770, 

89 'collected_photometry_refStars': 1770, 

90 'selected_astrometry_refStars': 747, 

91 'selected_photometry_refStars': 747, 

92 'associated_astrometry_fittedStars': 2269, 

93 'associated_photometry_fittedStars': 2269, 

94 'selected_astrometry_fittedStars': 1408, 

95 'selected_photometry_fittedStars': 1408, 

96 'selected_astrometry_ccdImages': 12, 

97 'selected_photometry_ccdImages': 12, 

98 'astrometry_final_chi2': 1609.29, 

99 'astrometry_final_ndof': 3332, 

100 'photometry_final_chi2': 3632.26, 

101 'photometry_final_ndof': 1693 

102 } 

103 

104 self._testJointcalTask(2, dist_rms_relative, self.dist_rms_absolute, pa1, metrics=metrics) 

105 

106 # Check for the existence of the chi2 contribution files. 

107 expected = ['photometry_initial_chi2-0_r', 'astrometry_initial_chi2-0_r', 

108 'photometry_final_chi2-0_r', 'astrometry_final_chi2-0_r'] 

109 for partial in expected: 

110 name = os.path.join(tempdir, partial+'-ref.csv') 

111 self.assertTrue(os.path.exists(name), msg="Did not find file %s"%name) 

112 name = os.path.join(tempdir, partial+'-meas.csv') 

113 self.assertTrue(os.path.exists(name), msg='Did not find file %s'%name) 

114 

115 expected = ["initialAstrometryModel.txt", "initialPhotometryModel.txt"] 

116 for name in expected: 

117 fullpath = os.path.join(tempdir, name) 

118 self.assertTrue(os.path.exists(fullpath), msg=f"Did not find file {fullpath}") 

119 

120 def setup_jointcalTask_2_visits_constrainedAstrometry(self): 

121 """Set default values for the constrainedAstrometry tests, and make 

122 the differences between each test and the defaults more obvious. 

123 """ 

124 self.config = lsst.jointcal.jointcal.JointcalConfig() 

125 self.config.astrometryModel = "constrained" 

126 self.config.doPhotometry = False 

127 self.jointcalStatistics.do_photometry = False 

128 

129 # See Readme for an explanation of these empirical values. 

130 dist_rms_relative = 12e-3*u.arcsecond 

131 metrics = {'collected_astrometry_refStars': 1770, 

132 'selected_astrometry_refStars': 747, 

133 'associated_astrometry_fittedStars': 2269, 

134 'selected_astrometry_fittedStars': 1408, 

135 'selected_astrometry_ccdImages': 12, 

136 'astrometry_final_chi2': 1714.8, 

137 'astrometry_final_ndof': 3434, 

138 } 

139 

140 return dist_rms_relative, metrics 

141 

142 def test_jointcalTask_2_visits_constrainedAstrometry_no_photometry(self): 

143 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry() 

144 self.config.writeInitialModel = True # write the initial models 

145 # use a temporary directory for debug output, to prevent test collisions 

146 # use a temporary directory for debug output, to prevent test collisions 

147 with tempfile.TemporaryDirectory() as tempdir: 

148 self.config.debugOutputPath = tempdir 

149 

150 self._testJointcalTask(2, dist_rms_relative, self.dist_rms_absolute, None, metrics=metrics) 

151 filename = os.path.join(tempdir, "initialAstrometryModel.txt") 

152 self.assertTrue(os.path.exists(filename), msg=f"Did not find file {filename}") 

153 

154 def test_jointcalTask_2_visits_constrainedAstrometry_no_rank_update(self): 

155 """Demonstrate that skipping the rank update doesn't substantially affect astrometry. 

156 """ 

157 relative_error, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry() 

158 self.config.astrometryDoRankUpdate = False 

159 

160 self._testJointcalTask(2, relative_error, self.dist_rms_absolute, None, metrics=metrics) 

161 

162 def test_jointcalTask_2_visits_constrainedAstrometry_4sigma_outliers(self): 

163 """4 sigma outlier rejection means fewer available sources after the 

164 fitter converges, resulting in a smaller ndof and chi2. 

165 """ 

166 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry() 

167 self.config.outlierRejectSigma = 4 

168 metrics['astrometry_final_chi2'] = 1288.64 

169 metrics['astrometry_final_ndof'] = 3232 

170 

171 self._testJointcalTask(2, dist_rms_relative, self.dist_rms_absolute, None, metrics=metrics) 

172 

173 def test_jointcalTask_2_visits_constrainedAstrometry_astrometryReferenceUncertainty_smaller(self): 

174 """Test with a smaller fake reference uncertainty: chi2 will be higher.""" 

175 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry() 

176 test_config = os.path.join(lsst.utils.getPackageDir('jointcal'), 

177 'tests/config/astrometryReferenceErr-config.py') 

178 self.configfiles.append(test_config) 

179 metrics['astrometry_final_chi2'] = 11522.9 

180 metrics['astrometry_final_ndof'] = 3406 

181 

182 self._testJointcalTask(2, dist_rms_relative, self.dist_rms_absolute, None, metrics=metrics) 

183 

184 def test_jointcalTask_2_visits_constrainedAstrometry_astrometryReferenceUncertainty_None_fails(self): 

185 """The default `None` should fail for the existing refcats that have no position errors.""" 

186 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry() 

187 # This is the default, but we override it in tests/config/config.py, 

188 # because none of the existing test refcats have errors. So we have to 

189 # re-override it here. 

190 test_config = os.path.join(lsst.utils.getPackageDir('jointcal'), 

191 'tests/config/astrometryReferenceErr-None-config.py') 

192 self.configfiles.append(test_config) 

193 with self.assertRaises(lsst.pex.config.FieldValidationError): 

194 self._testJointcalTask(2, None, None, None) 

195 

196 def setup_jointcalTask_2_visits_constrainedPhotometry(self): 

197 """Set default values for the constrainedPhotometry tests, and make 

198 the differences between each test and the defaults more obvious. 

199 """ 

200 self.config = lsst.jointcal.jointcal.JointcalConfig() 

201 self.config.photometryModel = "constrainedFlux" 

202 self.config.doAstrometry = False 

203 self.jointcalStatistics.do_astrometry = False 

204 

205 # See Readme for an explanation of these empirical values. 

206 pa1 = 0.017 

207 metrics = {'collected_photometry_refStars': 1770, 

208 'selected_photometry_refStars': 747, 

209 'associated_photometry_fittedStars': 2269, 

210 'selected_photometry_fittedStars': 1408, 

211 'selected_photometry_ccdImages': 12, 

212 'photometry_final_chi2': 3292.08, 

213 'photometry_final_ndof': 1622 

214 } 

215 return pa1, metrics 

216 

217 def test_jointcalTask_2_visits_constrainedPhotometry_no_astrometry(self): 

218 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry() 

219 self.config.writeInitialModel = True # write the initial models 

220 # use a temporary directory for debug output, to prevent test collisions 

221 with tempfile.TemporaryDirectory() as tempdir: 

222 self.config.debugOutputPath = tempdir 

223 

224 self._testJointcalTask(2, None, None, pa1, metrics=metrics) 

225 filename = os.path.join(tempdir, "initialPhotometryModel.txt") 

226 self.assertTrue(os.path.exists(filename), msg=f"Did not find file {filename}") 

227 

228 def test_jointcalTask_2_visits_constrainedPhotometry_no_rank_update(self): 

229 """Demonstrate that skipping the rank update doesn't substantially affect photometry. 

230 """ 

231 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry() 

232 self.config.photometryDoRankUpdate = False 

233 

234 # The constrainedPhotometry model is not purely linear, so a small 

235 # change in final chi2 is possible. 

236 metrics['photometry_final_chi2'] = 3297.34 

237 

238 self._testJointcalTask(2, None, None, pa1, metrics=metrics) 

239 

240 def test_jointcalTask_2_visits_constrainedPhotometry_lineSearch(self): 

241 """Activating the line search should only slightly change the chi2. 

242 """ 

243 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry() 

244 self.config.allowLineSearch = True 

245 

246 # Activating line search for constrainedPhotometry should result in 

247 # nearly the same final fit (the system is somewhat non-linear, so it 

248 # may not be exactly the same: check the "Line search scale factor" 

249 # lines in the DEBUG log for values that are not ~1 for proof). 

250 metrics['photometry_final_chi2'] = 3324.2 

251 metrics['photometry_final_ndof'] = 1625 

252 

253 self._testJointcalTask(2, None, None, pa1, metrics=metrics) 

254 

255 def test_jointcalTask_2_visits_constrainedPhotometry_flagged(self): 

256 """Test the use of the FlaggedSourceSelector.""" 

257 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry() 

258 test_config = os.path.join(lsst.utils.getPackageDir('jointcal'), 

259 'tests/config/cfht-flagged-config.py') 

260 self.configfiles.append(test_config) 

261 # Reduce warnings due to flaggedSourceSelector having fewer sources than astrometrySourceSelector. 

262 self.config.minMeasuredStarsPerCcd = 30 

263 self.config.minRefStarsPerCcd = 20 

264 

265 pa1 = 0.026 

266 metrics['selected_photometry_refStars'] = 214 

267 metrics['associated_photometry_fittedStars'] = 270 

268 metrics['selected_photometry_fittedStars'] = 245 

269 metrics['photometry_final_chi2'] = 373.141 

270 metrics['photometry_final_ndof'] = 254 

271 

272 self._testJointcalTask(2, None, None, pa1, metrics=metrics) 

273 

274 def test_jointcalTask_2_visits_constrainedMagnitude_no_astrometry(self): 

275 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry() 

276 self.config.photometryModel = "constrainedMagnitude" 

277 

278 # The resulting fit should be close to the constrainedFlux model: 

279 # there are few CCDs and 2 visits, so there's not a lot of complexity 

280 # in this case to distinguish the flux vs. magnitude models. 

281 metrics['photometry_final_chi2'] = 3332.76 

282 

283 self._testJointcalTask(2, None, None, pa1, metrics=metrics) 

284 

285 def test_jointcalTask_2_visits_constrainedFlux_pedestal(self): 

286 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry() 

287 self.config.photometryErrorPedestal = 0.02 

288 

289 # We're allowing more error in the fit, so PA1 may be worse. 

290 pa1 = 0.021 

291 # Final chi2 is much lower, because all sources contribute more error. 

292 metrics['photometry_final_chi2'] = 2246.58 

293 # ndof shouldn't change much; slightly different likelihood contours 

294 metrics['photometry_final_ndof'] = 1624 

295 

296 self._testJointcalTask(2, None, None, pa1, metrics=metrics) 

297 

298 def test_jointcalTask_2_visits_constrainedMagnitude_pedestal(self): 

299 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry() 

300 self.config.photometryModel = "constrainedMagnitude" 

301 self.config.photometryErrorPedestal = 0.02 

302 

303 # We're allowing more error in the fit, so PA1 may be worse. 

304 pa1 = 0.024 

305 # Final chi2 is much lower, because all sources contribute more error. 

306 metrics['photometry_final_chi2'] = 2243.56 

307 # ndof shouldn't change much; slightly different likelihood contours 

308 metrics['photometry_final_ndof'] = 1617 

309 

310 self._testJointcalTask(2, None, None, pa1, metrics=metrics) 

311 

312 

313class MemoryTester(lsst.utils.tests.MemoryTestCase): 

314 pass 

315 

316 

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

318 lsst.utils.tests.init() 

319 unittest.main()