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 LookupError: 

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

49 try: 

50 lsst.utils.getPackageDir('obs_cfht') 

51 except LookupError: 

52 raise unittest.SkipTest("obs_cfht not setup") 

53 

54 def setUp(self): 

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

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

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

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

59 

60 do_plot = False 

61 

62 # center of the cfht validation_data catalog 

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

64 radius = 3*lsst.geom.degrees 

65 

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

67 all_visits = [849375, 850587] 

68 

69 self.setUp_base(center, radius, 

70 input_dir=input_dir, 

71 all_visits=all_visits, 

72 do_plot=do_plot, 

73 log_level="DEBUG") 

74 

75 def test_jointcalTask_2_visits(self): 

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

77 output files also get created. 

78 """ 

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

80 self.config.astrometryModel = "simple" 

81 self.config.photometryModel = "simpleFlux" 

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

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

84 self.config.writeChi2FilesInitialFinal = True 

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

86 with tempfile.TemporaryDirectory() as tempdir: 

87 self.config.debugOutputPath = tempdir 

88 

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

90 dist_rms_relative = 11e-3*u.arcsecond 

91 pa1 = 0.014 

92 metrics = {'collected_astrometry_refStars': 1770, 

93 'collected_photometry_refStars': 1770, 

94 'selected_astrometry_refStars': 747, 

95 'selected_photometry_refStars': 747, 

96 'associated_astrometry_fittedStars': 2269, 

97 'associated_photometry_fittedStars': 2269, 

98 'selected_astrometry_fittedStars': 1408, 

99 'selected_photometry_fittedStars': 1408, 

100 'selected_astrometry_ccdImages': 12, 

101 'selected_photometry_ccdImages': 12, 

102 'astrometry_final_chi2': 1609.29, 

103 'astrometry_final_ndof': 3332, 

104 'photometry_final_chi2': 3632.26, 

105 'photometry_final_ndof': 1693 

106 } 

107 

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

109 

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

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

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

113 for partial in expected: 

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

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

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

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

118 

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

120 for name in expected: 

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

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

123 

124 def setup_jointcalTask_2_visits_constrainedAstrometry(self): 

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

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

127 """ 

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

129 self.config.astrometryModel = "constrained" 

130 self.config.doPhotometry = False 

131 self.jointcalStatistics.do_photometry = False 

132 

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

134 dist_rms_relative = 12e-3*u.arcsecond 

135 metrics = {'collected_astrometry_refStars': 1770, 

136 'selected_astrometry_refStars': 747, 

137 'associated_astrometry_fittedStars': 2269, 

138 'selected_astrometry_fittedStars': 1408, 

139 'selected_astrometry_ccdImages': 12, 

140 'astrometry_final_chi2': 1714.8, 

141 'astrometry_final_ndof': 3434, 

142 } 

143 

144 return dist_rms_relative, metrics 

145 

146 def test_jointcalTask_2_visits_constrainedAstrometry_no_photometry(self): 

147 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry() 

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

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

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

151 with tempfile.TemporaryDirectory() as tempdir: 

152 self.config.debugOutputPath = tempdir 

153 

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

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

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

157 

158 def test_jointcalTask_2_visits_constrainedAstrometry_no_rank_update(self): 

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

160 """ 

161 relative_error, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry() 

162 self.config.astrometryDoRankUpdate = False 

163 

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

165 

166 def test_jointcalTask_2_visits_constrainedAstrometry_4sigma_outliers(self): 

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

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

169 """ 

170 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry() 

171 self.config.outlierRejectSigma = 4 

172 metrics['astrometry_final_chi2'] = 1288.64 

173 metrics['astrometry_final_ndof'] = 3232 

174 

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

176 

177 def test_jointcalTask_2_visits_constrainedAstrometry_astrometryReferenceUncertainty_smaller(self): 

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

179 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry() 

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

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

182 self.configfiles.append(test_config) 

183 metrics['astrometry_final_chi2'] = 11522.9 

184 metrics['astrometry_final_ndof'] = 3406 

185 

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

187 

188 def test_jointcalTask_2_visits_constrainedAstrometry_astrometryReferenceUncertainty_None_fails(self): 

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

190 dist_rms_relative, metrics = self.setup_jointcalTask_2_visits_constrainedAstrometry() 

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

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

193 # re-override it here. 

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

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

196 self.configfiles.append(test_config) 

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

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

199 

200 def setup_jointcalTask_2_visits_constrainedPhotometry(self): 

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

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

203 """ 

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

205 self.config.photometryModel = "constrainedFlux" 

206 self.config.doAstrometry = False 

207 self.jointcalStatistics.do_astrometry = False 

208 

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

210 pa1 = 0.017 

211 metrics = {'collected_photometry_refStars': 1770, 

212 'selected_photometry_refStars': 747, 

213 'associated_photometry_fittedStars': 2269, 

214 'selected_photometry_fittedStars': 1408, 

215 'selected_photometry_ccdImages': 12, 

216 'photometry_final_chi2': 3292.08, 

217 'photometry_final_ndof': 1622 

218 } 

219 return pa1, metrics 

220 

221 def test_jointcalTask_2_visits_constrainedPhotometry_no_astrometry(self): 

222 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry() 

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

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

225 with tempfile.TemporaryDirectory() as tempdir: 

226 self.config.debugOutputPath = tempdir 

227 

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

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

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

231 

232 def test_jointcalTask_2_visits_constrainedPhotometry_no_rank_update(self): 

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

234 """ 

235 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry() 

236 self.config.photometryDoRankUpdate = False 

237 

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

239 # change in final chi2 is possible. 

240 metrics['photometry_final_chi2'] = 3297.34 

241 

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

243 

244 def test_jointcalTask_2_visits_constrainedPhotometry_lineSearch(self): 

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

246 """ 

247 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry() 

248 self.config.allowLineSearch = True 

249 

250 # Activating line search for constrainedPhotometry should result in 

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

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

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

254 metrics['photometry_final_chi2'] = 3324.2 

255 metrics['photometry_final_ndof'] = 1625 

256 

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

258 

259 def test_jointcalTask_2_visits_constrainedPhotometry_flagged(self): 

260 """Test the use of the FlaggedSourceSelector.""" 

261 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry() 

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

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

264 self.configfiles.append(test_config) 

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

266 self.config.minMeasuredStarsPerCcd = 30 

267 self.config.minRefStarsPerCcd = 20 

268 

269 pa1 = 0.026 

270 metrics['selected_photometry_refStars'] = 214 

271 metrics['associated_photometry_fittedStars'] = 270 

272 metrics['selected_photometry_fittedStars'] = 245 

273 metrics['photometry_final_chi2'] = 373.141 

274 metrics['photometry_final_ndof'] = 254 

275 

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

277 

278 def test_jointcalTask_2_visits_constrainedMagnitude_no_astrometry(self): 

279 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry() 

280 self.config.photometryModel = "constrainedMagnitude" 

281 

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

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

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

285 metrics['photometry_final_chi2'] = 3332.76 

286 

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

288 

289 def test_jointcalTask_2_visits_constrainedFlux_pedestal(self): 

290 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry() 

291 self.config.photometryErrorPedestal = 0.02 

292 

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

294 pa1 = 0.021 

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

296 metrics['photometry_final_chi2'] = 2246.58 

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

298 metrics['photometry_final_ndof'] = 1624 

299 

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

301 

302 def test_jointcalTask_2_visits_constrainedMagnitude_pedestal(self): 

303 pa1, metrics = self.setup_jointcalTask_2_visits_constrainedPhotometry() 

304 self.config.photometryModel = "constrainedMagnitude" 

305 self.config.photometryErrorPedestal = 0.02 

306 

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

308 pa1 = 0.024 

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

310 metrics['photometry_final_chi2'] = 2243.56 

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

312 metrics['photometry_final_ndof'] = 1617 

313 

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

315 

316 

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

318 pass 

319 

320 

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

322 lsst.utils.tests.init() 

323 unittest.main()