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 

29import astropy.time 

30 

31import lsst.log 

32import lsst.utils 

33 

34import lsst.afw.table 

35import lsst.daf.persistence 

36from lsst.daf.base import DateTime 

37import lsst.geom 

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

39import lsst.obs.base 

40import lsst.pipe.base 

41import lsst.jointcal 

42from lsst.jointcal import MinimizeResult 

43import lsst.jointcal.chi2 

44import lsst.jointcal.testUtils 

45 

46 

47# for MemoryTestCase 

48def setup_module(module): 

49 lsst.utils.tests.init() 

50 

51 

52def make_fake_refcat(center, flux, filterName): 

53 """Make a fake reference catalog.""" 

54 schema = LoadIndexedReferenceObjectsTask.makeMinimalSchema([filterName], 

55 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 config = lsst.jointcal.jointcal.JointcalConfig() 

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

94 config.sourceFluxType = "ApFlux_12_0" 

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

96 config.doAstrometry = False 

97 config.doPhotometry = False 

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

99 

100 def test_make_catalog_schema(self): 

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

102 the schema returned by _make_catalog_schema(). 

103 """ 

104 table = self.jointcal._make_schema_table() 

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

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

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

108 

109 def test_extract_detector_catalog_from_visit_catalog(self): 

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

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

112 is correct for each detectior. 

113 """ 

114 detectorId = 56 

115 table = self.jointcal._make_schema_table() 

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

117 

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

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

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

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

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

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

124 

125 

126class JointcalTestBase: 

127 def setUp(self): 

128 # Ensure that the filter list is reset for each test so that we avoid 

129 # confusion or contamination each time we create a cfht camera below. 

130 lsst.obs.base.FilterDefinitionCollection.reset() 

131 

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

133 self.ccdImageList = struct.ccdImageList 

134 # so that countStars() returns nonzero results 

135 for ccdImage in self.ccdImageList: 

136 ccdImage.resetCatalogForFit() 

137 

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

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

140 self.goodChi2.chi2 = 200.0 

141 self.goodChi2.ndof = 100 

142 

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

144 self.badChi2.chi2 = 600.0 

145 self.badChi2.ndof = 100 

146 

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

148 self.nanChi2.chi2 = np.nan 

149 self.nanChi2.ndof = 100 

150 

151 self.maxSteps = 20 

152 self.name = "testing" 

153 self.dataName = "fake" 

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

155 

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

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

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

159 

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

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

162 self.associations.getCcdImageList.return_value = self.ccdImageList 

163 

164 # a default config to be modified by individual tests 

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

166 

167 

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

169 def setUp(self): 

170 super().setUp() 

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

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

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

174 self.fitter.computeChi2.return_value = self.goodChi2 

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

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

177 

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

179 

180 def test_iterateFit_success(self): 

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

182 self.maxSteps, self.name, self.whatToFit) 

183 self.assertEqual(chi2, self.goodChi2) 

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

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

186 

187 def test_iterateFit_writeChi2Outer(self): 

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

189 self.maxSteps, self.name, self.whatToFit, 

190 dataName=self.dataName) 

191 self.assertEqual(chi2, self.goodChi2) 

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

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

194 # Default config should not call saveChi2Contributions 

195 self.fitter.saveChi2Contributions.assert_not_called() 

196 

197 def test_iterateFit_failed(self): 

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

199 

200 with self.assertRaises(RuntimeError): 

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

202 self.maxSteps, self.name, self.whatToFit) 

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

204 

205 def test_iterateFit_badFinalChi2(self): 

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

207 self.jointcal.log = log 

208 self.fitter.computeChi2.return_value = self.badChi2 

209 

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

211 self.maxSteps, self.name, self.whatToFit) 

212 self.assertEqual(chi2, self.badChi2) 

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

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

215 

216 def test_iterateFit_exceedMaxSteps(self): 

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

218 self.jointcal.log = log 

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

220 maxSteps = 3 

221 

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

223 maxSteps, self.name, self.whatToFit) 

224 self.assertEqual(chi2, self.goodChi2) 

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

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

227 

228 def test_moderate_chi2_increase(self): 

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

230 steps. 

231 """ 

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

233 chi2_1.chi2 = 100.0 

234 chi2_1.ndof = 100 

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

236 chi2_2.chi2 = 300.0 

237 chi2_2.ndof = 100 

238 

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

240 self.fitter.computeChi2.side_effect = chi2s 

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

242 MinimizeResult.Chi2Increased, 

243 MinimizeResult.Chi2Increased, 

244 MinimizeResult.Converged, 

245 MinimizeResult.Converged] 

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

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

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

249 self.maxSteps, self.name, self.whatToFit) 

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

251 self.assertIn(msg, logger.output) 

252 

253 def test_large_chi2_increase_fails(self): 

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

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

256 chi2_1.chi2 = 1e11 

257 chi2_1.ndof = 100 

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

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

260 chi2_2.ndof = 100 

261 

262 chi2s = [chi2_1, chi2_1, chi2_2] 

263 self.fitter.computeChi2.side_effect = chi2s 

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

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

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

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

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

269 self.maxSteps, self.name, self.whatToFit) 

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

271 self.assertIn(msg, logger.output) 

272 

273 def test_invalid_model(self): 

274 self.model.validate.return_value = False 

275 with(self.assertRaises(ValueError)): 

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

277 

278 def test_nonfinite_chi2(self): 

279 self.fitter.computeChi2.return_value = self.nanChi2 

280 with(self.assertRaises(FloatingPointError)): 

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

282 

283 def test_writeChi2(self): 

284 filename = "somefile" 

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

286 writeChi2Name=filename) 

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

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

289 

290 

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

292 

293 def _make_fake_refcat(self): 

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

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

296 flux = 10 

297 radius = 1 * lsst.geom.degrees 

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

299 

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

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

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

303 refObjLoader = mock.Mock(spec=LoadIndexedReferenceObjectsTask) 

304 refObjLoader.loadSkyCircle.return_value = returnStruct 

305 

306 return refObjLoader, center, radius, filter, fakeRefCat 

307 

308 def test_load_reference_catalog(self): 

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

310 

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

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

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

314 

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

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

317 refCat, fluxField = jointcal._load_reference_catalog(refObjLoader, 

318 jointcal.astrometryReferenceSelector, 

319 center, 

320 radius, 

321 filterLabel) 

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

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

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

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

326 self.assertEqual(r1, r2) 

327 

328 def test_load_reference_catalog_subselect(self): 

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

330 with a ridiculous S/N cut. 

331 """ 

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

333 

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

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

336 config.astrometryReferenceSelector.doSignalToNoise = True 

337 config.astrometryReferenceSelector.signalToNoise.minimum = 1e10 

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

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

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

341 

342 refCat, fluxField = jointcal._load_reference_catalog(refObjLoader, 

343 jointcal.astrometryReferenceSelector, 

344 center, 

345 radius, 

346 filterLabel) 

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

348 

349 

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

351 def test_fit_photometry_writeChi2(self): 

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

353 self.config.photometryModel = "constrainedFlux" 

354 self.config.writeChi2FilesOuterLoop = True 

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

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

357 

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

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

360 fitPatch.return_value.computeChi2.return_value = self.goodChi2 

361 fitPatch.return_value.minimize.return_value = MinimizeResult.Converged 

362 

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

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

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

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

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

368 fitPatch.return_value.saveChi2Contributions.assert_has_calls(expected) 

369 

370 def test_fit_astrometry_writeChi2(self): 

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

372 self.config.astrometryModel = "constrained" 

373 self.config.writeChi2FilesOuterLoop = True 

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

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

376 

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

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

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

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

381 with fitPatch as fit, projectorPatch as projector: 

382 fit.return_value.computeChi2.return_value = self.goodChi2 

383 fit.return_value.minimize.return_value = MinimizeResult.Converged 

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

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

386 

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

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

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

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

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

392 fit.return_value.saveChi2Contributions.assert_has_calls(expected) 

393 

394 

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

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

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

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

399 

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

401 (or slightly less than) radius. 

402 """ 

403 maxSeparation = 0*lsst.geom.degrees 

404 for point in points: 

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

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

407 self.assertLess(maxSeparation, radius) 

408 

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

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

411 bounding circle for the input data. 

412 

413 Parameters 

414 ---------- 

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

416 The CcdImages to add to the Associations object. 

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

418 The WCS of each of the above images. 

419 bbox : `lsst.geom.Box2D` 

420 The ccd bounding box of both images. 

421 """ 

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

423 associations = lsst.jointcal.Associations() 

424 associations.addCcdImage(ccdImage1) 

425 associations.addCcdImage(ccdImage2) 

426 associations.computeCommonTangentPoint() 

427 

428 circle = associations.computeBoundingCircle() 

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

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

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

432 for x in bbox.getCorners()] 

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

434 for x in bbox.getCorners()]) 

435 self._checkPointsInCircle(points, center, radius) 

436 

437 def testPoints(self): 

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

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

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

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

442 

443 def testPointsRA0(self): 

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

445 the bug described in DM-19802. 

446 """ 

447 wcs1, wcs2 = make_fake_wcs() 

448 

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

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

451 date=DateTime(date=65321.1), 

452 boresightRaDec=wcs1.getSkyOrigin()) 

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

454 date=DateTime(date=65322.1), 

455 boresightRaDec=wcs1.getSkyOrigin()) 

456 

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

458 fakeVisitInfos=[visitInfo1, visitInfo2]) 

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

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

461 

462 

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

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

465 def test_compute_proper_motion_epoch(self): 

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

467 

468 wcs1, wcs2 = make_fake_wcs() 

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

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

471 boresightRaDec=wcs1.getSkyOrigin()) 

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

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

474 boresightRaDec=wcs2.getSkyOrigin()) 

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

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

477 boresightRaDec=wcs1.getSkyOrigin()) 

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

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

480 boresightRaDec=wcs2.getSkyOrigin()) 

481 

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

483 fakeVisitInfos=[visitInfo1, visitInfo2]) 

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

485 fakeVisitInfos=[visitInfo3, visitInfo4]) 

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

487 associations = lsst.jointcal.Associations() 

488 for ccdImage in ccdImageList: 

489 associations.addCcdImage(ccdImage) 

490 associations.computeCommonTangentPoint() 

491 

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

493 result = jointcal._compute_proper_motion_epoch(ccdImageList) 

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

495 

496 

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

498 pass 

499 

500 

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

502 lsst.utils.tests.init() 

503 unittest.main()