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 itertools 

23import os.path 

24import unittest 

25from unittest import mock 

26 

27import numpy as np 

28import pyarrow.parquet 

29 

30import lsst.log 

31import lsst.utils 

32 

33import lsst.afw.table 

34import lsst.daf.persistence 

35from lsst.daf.base import DateTime 

36import lsst.geom 

37from lsst.meas.algorithms import getRefFluxField, LoadIndexedReferenceObjectsTask, DatasetConfig 

38import lsst.pipe.base 

39import lsst.jointcal 

40from lsst.jointcal import MinimizeResult 

41import lsst.jointcal.chi2 

42import lsst.jointcal.testUtils 

43 

44 

45# for MemoryTestCase 

46def setup_module(module): 

47 lsst.utils.tests.init() 

48 

49 

50def make_fake_refcat(center, flux, filterName): 

51 """Make a fake reference catalog.""" 

52 schema = LoadIndexedReferenceObjectsTask.makeMinimalSchema([filterName], 

53 addProperMotion=True) 

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

55 record = catalog.addNew() 

56 record.setCoord(center) 

57 record[filterName + '_flux'] = flux 

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

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

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

61 record['epoch'] = 65432.1 

62 return catalog 

63 

64 

65def make_fake_wcs(): 

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

67 

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

69 at RA=0 

70 """ 

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

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

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

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

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

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

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

78 return wcs1, wcs2 

79 

80 

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

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

83 table catalog unrolling. 

84 """ 

85 def setUp(self): 

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

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

88 file = pyarrow.parquet.ParquetFile(filename) 

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

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

91 # TODO DM-29008: Remove this (to use the new gen3 default) before gen2 removal. 

92 config.sourceFluxType = "ApFlux_12_0" 

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

94 config.doAstrometry = False 

95 config.doPhotometry = False 

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

97 

98 def test_make_catalog_schema(self): 

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

100 the schema returned by _make_catalog_schema(). 

101 """ 

102 table = self.jointcal._make_schema_table() 

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

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

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

106 

107 def test_extract_detector_catalog_from_visit_catalog(self): 

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

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

110 is correct for each detectior. 

111 """ 

112 detectorId = 56 

113 table = self.jointcal._make_schema_table() 

114 catalog = self.jointcal._extract_detector_catalog_from_visit_catalog(table, self.data, detectorId) 

115 

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

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

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

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

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

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

122 

123 

124class JointcalTestBase: 

125 def setUp(self): 

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

127 self.ccdImageList = struct.ccdImageList 

128 # so that countStars() returns nonzero results 

129 for ccdImage in self.ccdImageList: 

130 ccdImage.resetCatalogForFit() 

131 

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

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

134 self.goodChi2.chi2 = 200.0 

135 self.goodChi2.ndof = 100 

136 

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

138 self.badChi2.chi2 = 600.0 

139 self.badChi2.ndof = 100 

140 

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

142 self.nanChi2.chi2 = np.nan 

143 self.nanChi2.ndof = 100 

144 

145 self.maxSteps = 20 

146 self.name = "testing" 

147 self.dataName = "fake" 

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

149 

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

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

152 self.butler.get.return_value.indexer = DatasetConfig().indexer 

153 

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

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

156 self.associations.getCcdImageList.return_value = self.ccdImageList 

157 

158 # a default config to be modified by individual tests 

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

160 

161 

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

163 def setUp(self): 

164 super().setUp() 

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

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

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

168 self.fitter.computeChi2.return_value = self.goodChi2 

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

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

171 

172 self.jointcal = lsst.jointcal.JointcalTask(config=self.config, butler=self.butler) 

173 

174 def test_iterateFit_success(self): 

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

176 self.maxSteps, self.name, self.whatToFit) 

177 self.assertEqual(chi2, self.goodChi2) 

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

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

180 

181 def test_iterateFit_writeChi2Outer(self): 

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

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

184 dataName=self.dataName) 

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 # Default config should not call saveChi2Contributions 

189 self.fitter.saveChi2Contributions.assert_not_called() 

190 

191 def test_iterateFit_failed(self): 

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

193 

194 with self.assertRaises(RuntimeError): 

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

196 self.maxSteps, self.name, self.whatToFit) 

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

198 

199 def test_iterateFit_badFinalChi2(self): 

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

201 self.jointcal.log = log 

202 self.fitter.computeChi2.return_value = self.badChi2 

203 

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

205 self.maxSteps, self.name, self.whatToFit) 

206 self.assertEqual(chi2, self.badChi2) 

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

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

209 

210 def test_iterateFit_exceedMaxSteps(self): 

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

212 self.jointcal.log = log 

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

214 maxSteps = 3 

215 

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

217 maxSteps, self.name, self.whatToFit) 

218 self.assertEqual(chi2, self.goodChi2) 

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

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

221 

222 def test_moderate_chi2_increase(self): 

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

224 steps. 

225 """ 

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

227 chi2_1.chi2 = 100.0 

228 chi2_1.ndof = 100 

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

230 chi2_2.chi2 = 300.0 

231 chi2_2.ndof = 100 

232 

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

234 self.fitter.computeChi2.side_effect = chi2s 

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

236 MinimizeResult.Chi2Increased, 

237 MinimizeResult.Chi2Increased, 

238 MinimizeResult.Converged, 

239 MinimizeResult.Converged] 

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

241 with self.assertLogs("jointcal", level="WARN") as logger: 

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

243 self.maxSteps, self.name, self.whatToFit) 

244 msg = "WARNING:jointcal:Significant chi2 increase by a factor of 300 / 100 = 3" 

245 self.assertIn(msg, logger.output) 

246 

247 def test_large_chi2_increase_fails(self): 

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

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

250 chi2_1.chi2 = 1e11 

251 chi2_1.ndof = 100 

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

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

254 chi2_2.ndof = 100 

255 

256 chi2s = [chi2_1, chi2_1, chi2_2] 

257 self.fitter.computeChi2.side_effect = chi2s 

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

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

260 with self.assertLogs("jointcal", level="WARN") as logger: 

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

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

263 self.maxSteps, self.name, self.whatToFit) 

264 msg = "WARNING:jointcal:Significant chi2 increase by a factor of 1.123e+13 / 1e+11 = 112.3" 

265 self.assertIn(msg, logger.output) 

266 

267 def test_invalid_model(self): 

268 self.model.validate.return_value = False 

269 with(self.assertRaises(ValueError)): 

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

271 

272 def test_nonfinite_chi2(self): 

273 self.fitter.computeChi2.return_value = self.nanChi2 

274 with(self.assertRaises(FloatingPointError)): 

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

276 

277 def test_writeChi2(self): 

278 filename = "somefile" 

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

280 writeChi2Name=filename) 

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

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

283 

284 

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

286 

287 def _make_fake_refcat(self): 

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

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

290 flux = 10 

291 radius = 1 * lsst.geom.degrees 

292 filterName = 'fake' 

293 

294 fakeRefCat = make_fake_refcat(center, flux, filterName) 

295 fluxField = getRefFluxField(fakeRefCat.schema, filterName) 

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

297 refObjLoader = mock.Mock(spec=LoadIndexedReferenceObjectsTask) 

298 refObjLoader.loadSkyCircle.return_value = returnStruct 

299 

300 return refObjLoader, center, radius, filterName, fakeRefCat 

301 

302 def test_load_reference_catalog(self): 

303 refObjLoader, center, radius, filterName, fakeRefCat = self._make_fake_refcat() 

304 

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

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

307 jointcal = lsst.jointcal.JointcalTask(config=config, butler=self.butler) 

308 

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

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

311 refCat, fluxField = jointcal._load_reference_catalog(refObjLoader, 

312 jointcal.astrometryReferenceSelector, 

313 center, 

314 radius, 

315 filterName) 

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

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

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

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

320 self.assertEqual(r1, r2) 

321 

322 def test_load_reference_catalog_subselect(self): 

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

324 with a ridiculous S/N cut. 

325 """ 

326 refObjLoader, center, radius, filterName, fakeRefCat = self._make_fake_refcat() 

327 

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

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

330 config.astrometryReferenceSelector.doSignalToNoise = True 

331 config.astrometryReferenceSelector.signalToNoise.minimum = 1e10 

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

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

334 jointcal = lsst.jointcal.JointcalTask(config=config, butler=self.butler) 

335 

336 refCat, fluxField = jointcal._load_reference_catalog(refObjLoader, 

337 jointcal.astrometryReferenceSelector, 

338 center, 

339 radius, 

340 filterName) 

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

342 

343 

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

345 def test_fit_photometry_writeChi2(self): 

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

347 self.config.photometryModel = "constrainedFlux" 

348 self.config.writeChi2FilesOuterLoop = True 

349 jointcal = lsst.jointcal.JointcalTask(config=self.config, butler=self.butler) 

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

351 

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

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

354 fitPatch.return_value.computeChi2.return_value = self.goodChi2 

355 fitPatch.return_value.minimize.return_value = MinimizeResult.Converged 

356 

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

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

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

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

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

362 fitPatch.return_value.saveChi2Contributions.assert_has_calls(expected) 

363 

364 def test_fit_astrometry_writeChi2(self): 

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

366 self.config.astrometryModel = "constrained" 

367 self.config.writeChi2FilesOuterLoop = True 

368 jointcal = lsst.jointcal.JointcalTask(config=self.config, butler=self.butler) 

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

370 

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

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

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

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

375 with fitPatch as fit, projectorPatch as projector: 

376 fit.return_value.computeChi2.return_value = self.goodChi2 

377 fit.return_value.minimize.return_value = MinimizeResult.Converged 

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

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

380 

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

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

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

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

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

386 fit.return_value.saveChi2Contributions.assert_has_calls(expected) 

387 

388 

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

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

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

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

393 

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

395 (or slightly less than) radius. 

396 """ 

397 maxSeparation = 0*lsst.geom.degrees 

398 for point in points: 

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

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

401 self.assertLess(maxSeparation, radius) 

402 

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

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

405 bounding circle for the input data. 

406 

407 Parameters 

408 ---------- 

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

410 The CcdImages to add to the Associations object. 

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

412 The WCS of each of the above images. 

413 bbox : `lsst.geom.Box2D` 

414 The ccd bounding box of both images. 

415 """ 

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

417 associations = lsst.jointcal.Associations() 

418 associations.addCcdImage(ccdImage1) 

419 associations.addCcdImage(ccdImage2) 

420 associations.computeCommonTangentPoint() 

421 

422 circle = associations.computeBoundingCircle() 

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

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

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

426 for x in bbox.getCorners()] 

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

428 for x in bbox.getCorners()]) 

429 self._checkPointsInCircle(points, center, radius) 

430 

431 def testPoints(self): 

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

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

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

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

436 

437 def testPointsRA0(self): 

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

439 the bug described in DM-19802. 

440 """ 

441 wcs1, wcs2 = make_fake_wcs() 

442 

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

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

445 date=DateTime(65321.1), 

446 boresightRaDec=wcs1.getSkyOrigin()) 

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

448 date=DateTime(65322.1), 

449 boresightRaDec=wcs1.getSkyOrigin()) 

450 

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

452 fakeVisitInfos=[visitInfo1, visitInfo2]) 

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

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

455 

456 

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

458 """Tests of jointcal._compute_proper_motion_epoch()""" 

459 def test_compute_proper_motion_epoch(self): 

460 mjds = np.array((65432.1, 66666, 65555, 64322.2)) 

461 

462 wcs1, wcs2 = make_fake_wcs() 

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

464 date=DateTime(mjds[0]), 

465 boresightRaDec=wcs1.getSkyOrigin()) 

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

467 date=DateTime(mjds[1]), 

468 boresightRaDec=wcs2.getSkyOrigin()) 

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

470 date=DateTime(mjds[2]), 

471 boresightRaDec=wcs1.getSkyOrigin()) 

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

473 date=DateTime(mjds[3]), 

474 boresightRaDec=wcs2.getSkyOrigin()) 

475 

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

477 fakeVisitInfos=[visitInfo1, visitInfo2]) 

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

479 fakeVisitInfos=[visitInfo3, visitInfo4]) 

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

481 associations = lsst.jointcal.Associations() 

482 for ccdImage in ccdImageList: 

483 associations.addCcdImage(ccdImage) 

484 associations.computeCommonTangentPoint() 

485 

486 jointcal = lsst.jointcal.JointcalTask(config=self.config, butler=self.butler) 

487 result = jointcal._compute_proper_motion_epoch(ccdImageList) 

488 self.assertEqual(result.mjd, mjds.mean()) 

489 

490 

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

492 pass 

493 

494 

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

496 lsst.utils.tests.init() 

497 unittest.main()