Coverage for tests/test_jointcal.py: 22%

282 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-08-04 03:39 -0700

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 'ccd', 

121 ['Ixx', 'Iyy', 'Ixy'], 

122 self.config.sourceFluxType, 

123 self.jointcal.log) 

124 

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

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

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

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

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

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

131 

132 

133class JointcalTestBase: 

134 def setUp(self): 

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

136 self.ccdImageList = struct.ccdImageList 

137 # so that countStars() returns nonzero results 

138 for ccdImage in self.ccdImageList: 

139 ccdImage.resetCatalogForFit() 

140 

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

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

143 self.goodChi2.chi2 = 200.0 

144 self.goodChi2.ndof = 100 

145 

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

147 self.badChi2.chi2 = 600.0 

148 self.badChi2.ndof = 100 

149 

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

151 self.nanChi2.chi2 = np.nan 

152 self.nanChi2.ndof = 100 

153 

154 self.maxSteps = 20 

155 self.name = "testing" 

156 self.dataName = "fake" 

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

158 

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

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

161 

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

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

164 self.associations.getCcdImageList.return_value = self.ccdImageList 

165 

166 # a default config to be modified by individual tests 

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

168 

169 

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

171 def setUp(self): 

172 super().setUp() 

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

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

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

176 self.fitter.computeChi2.return_value = self.goodChi2 

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

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

179 

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

181 

182 def test_iterateFit_success(self): 

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

184 self.maxSteps, self.name, self.whatToFit) 

185 self.assertEqual(chi2, self.goodChi2) 

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

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

188 

189 def test_iterateFit_writeChi2Outer(self): 

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

191 self.maxSteps, self.name, self.whatToFit, 

192 dataName=self.dataName) 

193 self.assertEqual(chi2, self.goodChi2) 

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

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

196 # Default config should not call saveChi2Contributions 

197 self.fitter.saveChi2Contributions.assert_not_called() 

198 

199 def test_iterateFit_failed(self): 

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

201 

202 with self.assertRaises(RuntimeError): 

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

204 self.maxSteps, self.name, self.whatToFit) 

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

206 

207 def test_iterateFit_badFinalChi2(self): 

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

209 self.jointcal.log = log 

210 self.fitter.computeChi2.return_value = self.badChi2 

211 

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

213 self.maxSteps, self.name, self.whatToFit) 

214 self.assertEqual(chi2, self.badChi2) 

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

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

217 

218 def test_iterateFit_exceedMaxSteps(self): 

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

220 self.jointcal.log = log 

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

222 maxSteps = 3 

223 

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

225 maxSteps, self.name, self.whatToFit) 

226 self.assertEqual(chi2, self.goodChi2) 

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

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

229 

230 def test_moderate_chi2_increase(self): 

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

232 steps. 

233 """ 

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

235 chi2_1.chi2 = 100.0 

236 chi2_1.ndof = 100 

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

238 chi2_2.chi2 = 300.0 

239 chi2_2.ndof = 100 

240 

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

242 self.fitter.computeChi2.side_effect = chi2s 

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

244 MinimizeResult.Chi2Increased, 

245 MinimizeResult.Chi2Increased, 

246 MinimizeResult.Converged, 

247 MinimizeResult.Converged] 

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

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

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

251 self.maxSteps, self.name, self.whatToFit) 

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

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

254 

255 def test_large_chi2_increase_fails(self): 

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

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

258 chi2_1.chi2 = 1e11 

259 chi2_1.ndof = 100 

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

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

262 chi2_2.ndof = 100 

263 

264 chi2s = [chi2_1, chi2_1, chi2_2] 

265 self.fitter.computeChi2.side_effect = chi2s 

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

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

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

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

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

271 self.maxSteps, self.name, self.whatToFit) 

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

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

274 

275 def test_invalid_model(self): 

276 self.model.validate.return_value = False 

277 with(self.assertRaises(ValueError)): 

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

279 

280 def test_nonfinite_chi2(self): 

281 self.fitter.computeChi2.return_value = self.nanChi2 

282 with(self.assertRaises(FloatingPointError)): 

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

284 

285 def test_writeChi2(self): 

286 filename = "somefile" 

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

288 writeChi2Name=filename) 

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

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

291 

292 

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

294 

295 def _make_fake_refcat(self): 

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

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

298 flux = 10 

299 radius = 1 * lsst.geom.degrees 

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

301 

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

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

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

305 refObjLoader = mock.Mock(spec=ReferenceObjectLoader) 

306 refObjLoader.loadSkyCircle.return_value = returnStruct 

307 

308 return refObjLoader, center, radius, filter, fakeRefCat 

309 

310 def test_load_reference_catalog(self): 

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

312 

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

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

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

316 

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

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

319 refCat, fluxField = jointcal._load_reference_catalog(refObjLoader, 

320 jointcal.astrometryReferenceSelector, 

321 center, 

322 radius, 

323 filterLabel) 

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

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

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

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

328 self.assertEqual(r1, r2) 

329 

330 def test_load_reference_catalog_subselect(self): 

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

332 with a ridiculous S/N cut. 

333 """ 

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

335 

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

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

338 config.astrometryReferenceSelector.doSignalToNoise = True 

339 config.astrometryReferenceSelector.signalToNoise.minimum = 1e10 

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

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

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

343 

344 refCat, fluxField = jointcal._load_reference_catalog(refObjLoader, 

345 jointcal.astrometryReferenceSelector, 

346 center, 

347 radius, 

348 filterLabel) 

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

350 

351 

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

353 def test_fit_photometry_writeChi2(self): 

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

355 self.config.photometryModel = "constrainedFlux" 

356 self.config.writeChi2FilesOuterLoop = True 

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

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

359 

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

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

362 fitPatch.return_value.computeChi2.return_value = self.goodChi2 

363 fitPatch.return_value.minimize.return_value = MinimizeResult.Converged 

364 

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

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

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

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

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

370 fitPatch.return_value.saveChi2Contributions.assert_has_calls(expected) 

371 

372 def test_fit_astrometry_writeChi2(self): 

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

374 self.config.astrometryModel = "constrained" 

375 self.config.writeChi2FilesOuterLoop = True 

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

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

378 

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

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

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

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

383 with fitPatch as fit, projectorPatch as projector: 

384 fit.return_value.computeChi2.return_value = self.goodChi2 

385 fit.return_value.minimize.return_value = MinimizeResult.Converged 

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

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

388 

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

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

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

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

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

394 fit.return_value.saveChi2Contributions.assert_has_calls(expected) 

395 

396 

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

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

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

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

401 

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

403 (or slightly less than) radius. 

404 """ 

405 maxSeparation = 0*lsst.geom.degrees 

406 for point in points: 

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

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

409 self.assertLess(maxSeparation, radius) 

410 

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

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

413 bounding circle for the input data. 

414 

415 Parameters 

416 ---------- 

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

418 The CcdImages to add to the Associations object. 

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

420 The WCS of each of the above images. 

421 bbox : `lsst.geom.Box2D` 

422 The ccd bounding box of both images. 

423 """ 

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

425 associations = lsst.jointcal.Associations() 

426 associations.addCcdImage(ccdImage1) 

427 associations.addCcdImage(ccdImage2) 

428 associations.computeCommonTangentPoint() 

429 

430 circle = associations.computeBoundingCircle() 

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

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

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

434 for x in bbox.getCorners()] 

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

436 for x in bbox.getCorners()]) 

437 self._checkPointsInCircle(points, center, radius) 

438 

439 def testPoints(self): 

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

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

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

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

444 

445 def testPointsRA0(self): 

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

447 the bug described in DM-19802. 

448 """ 

449 wcs1, wcs2 = make_fake_wcs() 

450 

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

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

453 date=DateTime(date=65321.1), 

454 boresightRaDec=wcs1.getSkyOrigin()) 

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

456 date=DateTime(date=65322.1), 

457 boresightRaDec=wcs1.getSkyOrigin()) 

458 

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

460 fakeVisitInfos=[visitInfo1, visitInfo2]) 

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

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

463 

464 

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

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

467 def test_compute_proper_motion_epoch(self): 

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

469 

470 wcs1, wcs2 = make_fake_wcs() 

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

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

473 boresightRaDec=wcs1.getSkyOrigin()) 

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

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

476 boresightRaDec=wcs2.getSkyOrigin()) 

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

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

479 boresightRaDec=wcs1.getSkyOrigin()) 

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

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

482 boresightRaDec=wcs2.getSkyOrigin()) 

483 

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

485 fakeVisitInfos=[visitInfo1, visitInfo2]) 

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

487 fakeVisitInfos=[visitInfo3, visitInfo4]) 

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

489 associations = lsst.jointcal.Associations() 

490 for ccdImage in ccdImageList: 

491 associations.addCcdImage(ccdImage) 

492 associations.computeCommonTangentPoint() 

493 

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

495 result = jointcal._compute_proper_motion_epoch(ccdImageList) 

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

497 

498 

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

500 pass 

501 

502 

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

504 lsst.utils.tests.init() 

505 unittest.main()