Coverage for tests/test_jointcal.py: 19%

282 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2022-12-06 03:07 -0800

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 itertools 

23import os.path 

24import unittest 

25from unittest import mock 

26 

27import numpy as np 

28import pyarrow.parquet 

29import astropy.time 

30 

31import lsst.log 

32import lsst.utils 

33 

34import lsst.afw.table 

35import lsst.daf.butler 

36from lsst.daf.base import DateTime 

37import lsst.geom 

38from lsst.meas.algorithms import getRefFluxField, ReferenceObjectLoader 

39import lsst.obs.base 

40import lsst.pipe.base 

41import lsst.jointcal 

42from lsst.jointcal.jointcal import make_schema_table, extract_detector_catalog_from_visit_catalog 

43from lsst.jointcal import MinimizeResult 

44import lsst.jointcal.chi2 

45import lsst.jointcal.testUtils 

46 

47 

48# for MemoryTestCase 

49def setup_module(module): 

50 lsst.utils.tests.init() 

51 

52 

53def make_fake_refcat(center, flux, filterName): 

54 """Make a fake reference catalog.""" 

55 schema = ReferenceObjectLoader.makeMinimalSchema([filterName], addProperMotion=True) 

56 catalog = lsst.afw.table.SimpleCatalog(schema) 

57 record = catalog.addNew() 

58 record.setCoord(center) 

59 record[filterName + '_flux'] = flux 

60 record[filterName + '_fluxErr'] = flux*0.1 

61 record['pm_ra'] = lsst.geom.Angle(1) 

62 record['pm_dec'] = lsst.geom.Angle(2) 

63 record['epoch'] = 65432.1 

64 return catalog 

65 

66 

67def make_fake_wcs(): 

68 """Return two simple SkyWcs objects, with slightly different sky positions. 

69 

70 Use the same pixel origins as the cfht_minimal data, but put the sky origin 

71 at RA=0 

72 """ 

73 crpix = lsst.geom.Point2D(931.517869, 2438.572109) 

74 cd = np.array([[5.19513851e-05, -2.81124812e-07], 

75 [-3.25186974e-07, -5.19112119e-05]]) 

76 crval1 = lsst.geom.SpherePoint(0.01, -0.01, lsst.geom.degrees) 

77 crval2 = lsst.geom.SpherePoint(-0.01, 0.01, lsst.geom.degrees) 

78 wcs1 = lsst.afw.geom.makeSkyWcs(crpix, crval1, cd) 

79 wcs2 = lsst.afw.geom.makeSkyWcs(crpix, crval2, cd) 

80 return wcs1, wcs2 

81 

82 

83class TestJointcalVisitCatalog(lsst.utils.tests.TestCase): 

84 """Tests of jointcal's sourceTable_visit parquet ->single detector afw 

85 table catalog unrolling. 

86 """ 

87 def setUp(self): 

88 filename = os.path.join(os.path.dirname(__file__), 

89 "data/subselected-sourceTable-0034690.parq") 

90 file = pyarrow.parquet.ParquetFile(filename) 

91 self.data = file.read(use_pandas_metadata=True).to_pandas() 

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

93 # NOTE: This parquet file is older and uses the earlier 

94 # "capitalize first letter" naming convention for these fields. 

95 self.config.sourceFluxType = "ApFlux_12_0" 

96 # we don't actually need either fitter to run for these tests 

97 self.config.doAstrometry = False 

98 self.config.doPhotometry = False 

99 self.jointcal = lsst.jointcal.JointcalTask(config=self.config) 

100 

101 def test_make_catalog_schema(self): 

102 """Check that the slot fields required by CcdImage::loadCatalog are in 

103 the schema returned by _make_catalog_schema(). 

104 """ 

105 table = make_schema_table() 

106 self.assertTrue(table.getCentroidSlot().getMeasKey().isValid()) 

107 self.assertTrue(table.getCentroidSlot().getErrKey().isValid()) 

108 self.assertTrue(table.getShapeSlot().getMeasKey().isValid()) 

109 

110 def test_extract_detector_catalog_from_visit_catalog(self): 

111 """Spot check a value output by the script that generated the test 

112 parquet catalog and check that the size of the returned catalog 

113 is correct for each detectior. 

114 """ 

115 detectorId = 56 

116 table = make_schema_table() 

117 catalog = extract_detector_catalog_from_visit_catalog(table, 

118 self.data, 

119 detectorId, 

120 ['Ixx', 'Iyy', 'Ixy'], 

121 self.config.sourceFluxType, 

122 self.jointcal.log) 

123 

124 # The test catalog has a number of elements for each detector equal to the detector id. 

125 self.assertEqual(len(catalog), detectorId) 

126 self.assertIn(29798723617816629, catalog['id']) 

127 matched = catalog[29798723617816629 == catalog['id']] 

128 self.assertEqual(1715.734359473175, matched['slot_Centroid_x']) 

129 self.assertEqual(89.06076509964362, matched['slot_Centroid_y']) 

130 

131 

132class JointcalTestBase: 

133 def setUp(self): 

134 struct = lsst.jointcal.testUtils.createTwoFakeCcdImages(100, 100) 

135 self.ccdImageList = struct.ccdImageList 

136 # so that countStars() returns nonzero results 

137 for ccdImage in self.ccdImageList: 

138 ccdImage.resetCatalogForFit() 

139 

140 self.goodChi2 = lsst.jointcal.chi2.Chi2Statistic() 

141 # chi2/ndof == 2.0 should be non-bad 

142 self.goodChi2.chi2 = 200.0 

143 self.goodChi2.ndof = 100 

144 

145 self.badChi2 = lsst.jointcal.chi2.Chi2Statistic() 

146 self.badChi2.chi2 = 600.0 

147 self.badChi2.ndof = 100 

148 

149 self.nanChi2 = lsst.jointcal.chi2.Chi2Statistic() 

150 self.nanChi2.chi2 = np.nan 

151 self.nanChi2.ndof = 100 

152 

153 self.maxSteps = 20 

154 self.name = "testing" 

155 self.dataName = "fake" 

156 self.whatToFit = "" # unneeded, since we're mocking the fitter 

157 

158 # Mock a Butler so the refObjLoaders have something to call `get()` on. 

159 self.butler = unittest.mock.Mock(spec=lsst.daf.butler.Butler) 

160 

161 # Mock the association manager and give it access to the ccd list above. 

162 self.associations = mock.Mock(spec=lsst.jointcal.Associations) 

163 self.associations.getCcdImageList.return_value = self.ccdImageList 

164 

165 # a default config to be modified by individual tests 

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

167 

168 

169class TestJointcalIterateFit(JointcalTestBase, lsst.utils.tests.TestCase): 

170 def setUp(self): 

171 super().setUp() 

172 # Mock the fitter and model, so we can force particular 

173 # return values/exceptions. Default to "good" return values. 

174 self.fitter = mock.Mock(spec=lsst.jointcal.PhotometryFit) 

175 self.fitter.computeChi2.return_value = self.goodChi2 

176 self.fitter.minimize.return_value = MinimizeResult.Converged 

177 self.model = mock.Mock(spec=lsst.jointcal.SimpleFluxModel) 

178 

179 self.jointcal = lsst.jointcal.JointcalTask(config=self.config) 

180 

181 def test_iterateFit_success(self): 

182 chi2 = self.jointcal._iterate_fit(self.associations, self.fitter, 

183 self.maxSteps, self.name, self.whatToFit) 

184 self.assertEqual(chi2, self.goodChi2) 

185 # Once for the for loop, the second time for the rank update. 

186 self.assertEqual(self.fitter.minimize.call_count, 2) 

187 

188 def test_iterateFit_writeChi2Outer(self): 

189 chi2 = self.jointcal._iterate_fit(self.associations, self.fitter, 

190 self.maxSteps, self.name, self.whatToFit, 

191 dataName=self.dataName) 

192 self.assertEqual(chi2, self.goodChi2) 

193 # Once for the for loop, the second time for the rank update. 

194 self.assertEqual(self.fitter.minimize.call_count, 2) 

195 # Default config should not call saveChi2Contributions 

196 self.fitter.saveChi2Contributions.assert_not_called() 

197 

198 def test_iterateFit_failed(self): 

199 self.fitter.minimize.return_value = MinimizeResult.Failed 

200 

201 with self.assertRaises(RuntimeError): 

202 self.jointcal._iterate_fit(self.associations, self.fitter, 

203 self.maxSteps, self.name, self.whatToFit) 

204 self.assertEqual(self.fitter.minimize.call_count, 1) 

205 

206 def test_iterateFit_badFinalChi2(self): 

207 log = mock.Mock(spec=lsst.log.Log) 

208 self.jointcal.log = log 

209 self.fitter.computeChi2.return_value = self.badChi2 

210 

211 chi2 = self.jointcal._iterate_fit(self.associations, self.fitter, 

212 self.maxSteps, self.name, self.whatToFit) 

213 self.assertEqual(chi2, self.badChi2) 

214 log.info.assert_called_with("%s %s", "Fit completed", self.badChi2) 

215 log.error.assert_called_with("Potentially bad fit: High chi-squared/ndof.") 

216 

217 def test_iterateFit_exceedMaxSteps(self): 

218 log = mock.Mock(spec=lsst.log.Log) 

219 self.jointcal.log = log 

220 self.fitter.minimize.return_value = MinimizeResult.Chi2Increased 

221 maxSteps = 3 

222 

223 chi2 = self.jointcal._iterate_fit(self.associations, self.fitter, 

224 maxSteps, self.name, self.whatToFit) 

225 self.assertEqual(chi2, self.goodChi2) 

226 self.assertEqual(self.fitter.minimize.call_count, maxSteps) 

227 log.error.assert_called_with("testing failed to converge after %s steps" % maxSteps) 

228 

229 def test_moderate_chi2_increase(self): 

230 """DM-25159: warn, but don't fail, on moderate chi2 increases between 

231 steps. 

232 """ 

233 chi2_1 = lsst.jointcal.chi2.Chi2Statistic() 

234 chi2_1.chi2 = 100.0 

235 chi2_1.ndof = 100 

236 chi2_2 = lsst.jointcal.chi2.Chi2Statistic() 

237 chi2_2.chi2 = 300.0 

238 chi2_2.ndof = 100 

239 

240 chi2s = [self.goodChi2, chi2_1, chi2_2, self.goodChi2, self.goodChi2] 

241 self.fitter.computeChi2.side_effect = chi2s 

242 self.fitter.minimize.side_effect = [MinimizeResult.Chi2Increased, 

243 MinimizeResult.Chi2Increased, 

244 MinimizeResult.Chi2Increased, 

245 MinimizeResult.Converged, 

246 MinimizeResult.Converged] 

247 with lsst.log.UsePythonLogging(): # so that assertLogs works with lsst.log 

248 with self.assertLogs("lsst.jointcal", level="WARNING") as logger: 

249 self.jointcal._iterate_fit(self.associations, self.fitter, 

250 self.maxSteps, self.name, self.whatToFit) 

251 msg = "Significant chi2 increase by a factor of 300 / 100 = 3" 

252 self.assertIn(msg, [rec.message for rec in logger.records]) 

253 

254 def test_large_chi2_increase_fails(self): 

255 """DM-25159: fail on large chi2 increases between steps.""" 

256 chi2_1 = lsst.jointcal.chi2.Chi2Statistic() 

257 chi2_1.chi2 = 1e11 

258 chi2_1.ndof = 100 

259 chi2_2 = lsst.jointcal.chi2.Chi2Statistic() 

260 chi2_2.chi2 = 1.123456e13 # to check floating point formatting 

261 chi2_2.ndof = 100 

262 

263 chi2s = [chi2_1, chi2_1, chi2_2] 

264 self.fitter.computeChi2.side_effect = chi2s 

265 self.fitter.minimize.return_value = MinimizeResult.Chi2Increased 

266 with lsst.log.UsePythonLogging(): # so that assertLogs works with lsst.log 

267 with self.assertLogs("lsst.jointcal", level="WARNING") as logger: 

268 with(self.assertRaisesRegex(RuntimeError, "Large chi2 increase")): 

269 self.jointcal._iterate_fit(self.associations, self.fitter, 

270 self.maxSteps, self.name, self.whatToFit) 

271 msg = "Significant chi2 increase by a factor of 1.123e+13 / 1e+11 = 112.3" 

272 self.assertIn(msg, [rec.message for rec in logger.records]) 

273 

274 def test_invalid_model(self): 

275 self.model.validate.return_value = False 

276 with(self.assertRaises(ValueError)): 

277 self.jointcal._logChi2AndValidate(self.associations, self.fitter, self.model, "invalid") 

278 

279 def test_nonfinite_chi2(self): 

280 self.fitter.computeChi2.return_value = self.nanChi2 

281 with(self.assertRaises(FloatingPointError)): 

282 self.jointcal._logChi2AndValidate(self.associations, self.fitter, self.model, "nonfinite") 

283 

284 def test_writeChi2(self): 

285 filename = "somefile" 

286 self.jointcal._logChi2AndValidate(self.associations, self.fitter, self.model, "writeCh2", 

287 writeChi2Name=filename) 

288 # logChi2AndValidate prepends `config.debugOutputPath` to the filename 

289 self.fitter.saveChi2Contributions.assert_called_with("./"+filename+"{type}") 

290 

291 

292class TestJointcalLoadRefCat(JointcalTestBase, lsst.utils.tests.TestCase): 

293 

294 def _make_fake_refcat(self): 

295 """Mock a fake reference catalog and the bits necessary to use it.""" 

296 center = lsst.geom.SpherePoint(30, -30, lsst.geom.degrees) 

297 flux = 10 

298 radius = 1 * lsst.geom.degrees 

299 filter = lsst.afw.image.FilterLabel(band='fake', physical="fake-filter") 

300 

301 fakeRefCat = make_fake_refcat(center, flux, filter.bandLabel) 

302 fluxField = getRefFluxField(fakeRefCat.schema, filter.bandLabel) 

303 returnStruct = lsst.pipe.base.Struct(refCat=fakeRefCat, fluxField=fluxField) 

304 refObjLoader = mock.Mock(spec=ReferenceObjectLoader) 

305 refObjLoader.loadSkyCircle.return_value = returnStruct 

306 

307 return refObjLoader, center, radius, filter, fakeRefCat 

308 

309 def test_load_reference_catalog(self): 

310 refObjLoader, center, radius, filterLabel, fakeRefCat = self._make_fake_refcat() 

311 

312 config = lsst.jointcal.jointcal.JointcalConfig() 

313 config.astrometryReferenceErr = 0.1 # our test refcats don't have coord errors 

314 jointcal = lsst.jointcal.JointcalTask(config=config) 

315 

316 # NOTE: we cannot test application of proper motion here, because we 

317 # mock the refObjLoader, so the real loader is never called. 

318 refCat, fluxField = jointcal._load_reference_catalog(refObjLoader, 

319 jointcal.astrometryReferenceSelector, 

320 center, 

321 radius, 

322 filterLabel) 

323 # operator== isn't implemented for Catalogs, so we have to check like 

324 # this, in case the records are copied during load. 

325 self.assertEqual(len(refCat), len(fakeRefCat)) 

326 for r1, r2 in zip(refCat, fakeRefCat): 

327 self.assertEqual(r1, r2) 

328 

329 def test_load_reference_catalog_subselect(self): 

330 """Test that we can select out the one source in the fake refcat 

331 with a ridiculous S/N cut. 

332 """ 

333 refObjLoader, center, radius, filterLabel, fakeRefCat = self._make_fake_refcat() 

334 

335 config = lsst.jointcal.jointcal.JointcalConfig() 

336 config.astrometryReferenceErr = 0.1 # our test refcats don't have coord errors 

337 config.astrometryReferenceSelector.doSignalToNoise = True 

338 config.astrometryReferenceSelector.signalToNoise.minimum = 1e10 

339 config.astrometryReferenceSelector.signalToNoise.fluxField = "fake_flux" 

340 config.astrometryReferenceSelector.signalToNoise.errField = "fake_fluxErr" 

341 jointcal = lsst.jointcal.JointcalTask(config=config) 

342 

343 refCat, fluxField = jointcal._load_reference_catalog(refObjLoader, 

344 jointcal.astrometryReferenceSelector, 

345 center, 

346 radius, 

347 filterLabel) 

348 self.assertEqual(len(refCat), 0) 

349 

350 

351class TestJointcalFitModel(JointcalTestBase, lsst.utils.tests.TestCase): 

352 def test_fit_photometry_writeChi2(self): 

353 """Test that we are calling saveChi2 with appropriate file prefixes.""" 

354 self.config.photometryModel = "constrainedFlux" 

355 self.config.writeChi2FilesOuterLoop = True 

356 jointcal = lsst.jointcal.JointcalTask(config=self.config) 

357 jointcal.focalPlaneBBox = lsst.geom.Box2D() 

358 

359 # Mock the fitter, so we can pretend it found a good fit 

360 with mock.patch("lsst.jointcal.PhotometryFit", autospec=True) as fitPatch: 

361 fitPatch.return_value.computeChi2.return_value = self.goodChi2 

362 fitPatch.return_value.minimize.return_value = MinimizeResult.Converged 

363 

364 # config.debugOutputPath is prepended to the filenames that go into saveChi2Contributions 

365 expected = ["./photometry_init-ModelVisit_chi2", "./photometry_init-Model_chi2", 

366 "./photometry_init-Fluxes_chi2", "./photometry_init-ModelFluxes_chi2"] 

367 expected = [mock.call(x+"-fake{type}") for x in expected] 

368 jointcal._fit_photometry(self.associations, dataName=self.dataName) 

369 fitPatch.return_value.saveChi2Contributions.assert_has_calls(expected) 

370 

371 def test_fit_astrometry_writeChi2(self): 

372 """Test that we are calling saveChi2 with appropriate file prefixes.""" 

373 self.config.astrometryModel = "constrained" 

374 self.config.writeChi2FilesOuterLoop = True 

375 jointcal = lsst.jointcal.JointcalTask(config=self.config) 

376 jointcal.focalPlaneBBox = lsst.geom.Box2D() 

377 

378 # Mock the fitter, so we can pretend it found a good fit 

379 fitPatch = mock.patch("lsst.jointcal.AstrometryFit") 

380 # Mock the projection handler so we don't segfault due to not-fully initialized ccdImages 

381 projectorPatch = mock.patch("lsst.jointcal.OneTPPerVisitHandler") 

382 with fitPatch as fit, projectorPatch as projector: 

383 fit.return_value.computeChi2.return_value = self.goodChi2 

384 fit.return_value.minimize.return_value = MinimizeResult.Converged 

385 # return a real ProjectionHandler to keep ConstrainedAstrometryModel() happy 

386 projector.return_value = lsst.jointcal.IdentityProjectionHandler() 

387 

388 # config.debugOutputPath is prepended to the filenames that go into saveChi2Contributions 

389 expected = ["./astrometry_init-DistortionsVisit_chi2", "./astrometry_init-Distortions_chi2", 

390 "./astrometry_init-Positions_chi2", "./astrometry_init-DistortionsPositions_chi2"] 

391 expected = [mock.call(x+"-fake{type}") for x in expected] 

392 jointcal._fit_astrometry(self.associations, dataName=self.dataName) 

393 fit.return_value.saveChi2Contributions.assert_has_calls(expected) 

394 

395 

396class TestComputeBoundingCircle(lsst.utils.tests.TestCase): 

397 """Tests of Associations.computeBoundingCircle()""" 

398 def _checkPointsInCircle(self, points, center, radius): 

399 """Check that all points are within the (center, radius) circle. 

400 

401 The test is whether the max(points - center) separation is equal to 

402 (or slightly less than) radius. 

403 """ 

404 maxSeparation = 0*lsst.geom.degrees 

405 for point in points: 

406 maxSeparation = max(maxSeparation, center.separation(point)) 

407 self.assertAnglesAlmostEqual(maxSeparation, radius, maxDiff=3*lsst.geom.arcseconds) 

408 self.assertLess(maxSeparation, radius) 

409 

410 def _testPoints(self, ccdImage1, ccdImage2, skyWcs1, skyWcs2, bbox): 

411 """Fill an Associations object and test that it computes the correct 

412 bounding circle for the input data. 

413 

414 Parameters 

415 ---------- 

416 ccdImage1, ccdImage2 : `lsst.jointcal.CcdImage` 

417 The CcdImages to add to the Associations object. 

418 skyWcs1, skyWcs2 : `lsst.afw.geom.SkyWcs` 

419 The WCS of each of the above images. 

420 bbox : `lsst.geom.Box2D` 

421 The ccd bounding box of both images. 

422 """ 

423 lsst.log.setLevel('jointcal', lsst.log.DEBUG) 

424 associations = lsst.jointcal.Associations() 

425 associations.addCcdImage(ccdImage1) 

426 associations.addCcdImage(ccdImage2) 

427 associations.computeCommonTangentPoint() 

428 

429 circle = associations.computeBoundingCircle() 

430 center = lsst.geom.SpherePoint(circle.getCenter()) 

431 radius = lsst.geom.Angle(circle.getOpeningAngle().asRadians(), lsst.geom.radians) 

432 points = [lsst.geom.SpherePoint(skyWcs1.pixelToSky(lsst.geom.Point2D(x))) 

433 for x in bbox.getCorners()] 

434 points.extend([lsst.geom.SpherePoint(skyWcs2.pixelToSky(lsst.geom.Point2D(x))) 

435 for x in bbox.getCorners()]) 

436 self._checkPointsInCircle(points, center, radius) 

437 

438 def testPoints(self): 

439 """Test for points in an "easy" area, far from RA=0 or the poles.""" 

440 struct = lsst.jointcal.testUtils.createTwoFakeCcdImages() 

441 self._testPoints(struct.ccdImageList[0], struct.ccdImageList[1], 

442 struct.skyWcs[0], struct.skyWcs[1], struct.bbox) 

443 

444 def testPointsRA0(self): 

445 """Test for CcdImages crossing RA=0; this demonstrates a fix for 

446 the bug described in DM-19802. 

447 """ 

448 wcs1, wcs2 = make_fake_wcs() 

449 

450 # Put the visit boresights at the WCS origin, for consistency 

451 visitInfo1 = lsst.afw.image.VisitInfo(exposureId=30577512, 

452 date=DateTime(date=65321.1), 

453 boresightRaDec=wcs1.getSkyOrigin()) 

454 visitInfo2 = lsst.afw.image.VisitInfo(exposureId=30621144, 

455 date=DateTime(date=65322.1), 

456 boresightRaDec=wcs1.getSkyOrigin()) 

457 

458 struct = lsst.jointcal.testUtils.createTwoFakeCcdImages(fakeWcses=[wcs1, wcs2], 

459 fakeVisitInfos=[visitInfo1, visitInfo2]) 

460 self._testPoints(struct.ccdImageList[0], struct.ccdImageList[1], 

461 struct.skyWcs[0], struct.skyWcs[1], struct.bbox) 

462 

463 

464class TestJointcalComputePMDate(JointcalTestBase, lsst.utils.tests.TestCase): 

465 """Tests of jointcal._compute_proper_motion_epoch(), using fake dates.""" 

466 def test_compute_proper_motion_epoch(self): 

467 mjds = np.array((65432.1, 66666.0, 55555.0, 44322.2)) 

468 

469 wcs1, wcs2 = make_fake_wcs() 

470 visitInfo1 = lsst.afw.image.VisitInfo(exposureId=30577512, 

471 date=DateTime(date=mjds[0]), 

472 boresightRaDec=wcs1.getSkyOrigin()) 

473 visitInfo2 = lsst.afw.image.VisitInfo(exposureId=30621144, 

474 date=DateTime(date=mjds[1]), 

475 boresightRaDec=wcs2.getSkyOrigin()) 

476 visitInfo3 = lsst.afw.image.VisitInfo(exposureId=30577513, 

477 date=DateTime(date=mjds[2]), 

478 boresightRaDec=wcs1.getSkyOrigin()) 

479 visitInfo4 = lsst.afw.image.VisitInfo(exposureId=30621145, 

480 date=DateTime(date=mjds[3]), 

481 boresightRaDec=wcs2.getSkyOrigin()) 

482 

483 struct1 = lsst.jointcal.testUtils.createTwoFakeCcdImages(fakeWcses=[wcs1, wcs2], 

484 fakeVisitInfos=[visitInfo1, visitInfo2]) 

485 struct2 = lsst.jointcal.testUtils.createTwoFakeCcdImages(fakeWcses=[wcs1, wcs2], 

486 fakeVisitInfos=[visitInfo3, visitInfo4]) 

487 ccdImageList = list(itertools.chain(struct1.ccdImageList, struct2.ccdImageList)) 

488 associations = lsst.jointcal.Associations() 

489 for ccdImage in ccdImageList: 

490 associations.addCcdImage(ccdImage) 

491 associations.computeCommonTangentPoint() 

492 

493 jointcal = lsst.jointcal.JointcalTask(config=self.config) 

494 result = jointcal._compute_proper_motion_epoch(ccdImageList) 

495 self.assertEqual(result.jyear, (astropy.time.Time(mjds, format="mjd", scale="tai").jyear).mean()) 

496 

497 

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

499 pass 

500 

501 

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

503 lsst.utils.tests.init() 

504 unittest.main()