Coverage for tests/test_gbdesAstrometricFit.py: 13%

296 statements  

« prev     ^ index     » next       coverage.py v7.4.0, created at 2024-01-26 11:22 +0000

1# This file is part of drp_tasks 

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 os.path 

23import unittest 

24 

25import astropy.units as u 

26import lsst.afw.geom as afwgeom 

27import lsst.afw.table as afwTable 

28import lsst.geom 

29import lsst.utils 

30import numpy as np 

31import pandas as pd 

32import wcsfit 

33import yaml 

34from lsst import sphgeom 

35from lsst.daf.base import PropertyList 

36from lsst.drp.tasks.gbdesAstrometricFit import GbdesAstrometricFitConfig, GbdesAstrometricFitTask 

37from lsst.meas.algorithms import ReferenceObjectLoader 

38from lsst.meas.algorithms.testUtils import MockRefcatDataId 

39from lsst.pipe.base import InMemoryDatasetHandle 

40 

41TESTDIR = os.path.abspath(os.path.dirname(__file__)) 

42 

43 

44class TestGbdesAstrometricFit(lsst.utils.tests.TestCase): 

45 @classmethod 

46 def setUpClass(cls): 

47 # Set random seed 

48 np.random.seed(1234) 

49 

50 # Fraction of simulated stars in the reference catalog and science 

51 # exposures 

52 inReferenceFraction = 1 

53 inScienceFraction = 1 

54 

55 # Make fake data 

56 cls.datadir = os.path.join(TESTDIR, "data") 

57 

58 cls.fieldNumber = 0 

59 cls.instrumentName = "HSC" 

60 cls.instrument = wcsfit.Instrument(cls.instrumentName) 

61 cls.refEpoch = 57205.5 

62 

63 # Make test inputVisitSummary. VisitSummaryTables are taken from 

64 # collection HSC/runs/RC2/w_2022_20/DM-34794 

65 cls.testVisits = [1176, 17900, 17930, 17934] 

66 cls.inputVisitSummary = [] 

67 for testVisit in cls.testVisits: 

68 visSum = afwTable.ExposureCatalog.readFits( 

69 os.path.join(cls.datadir, f"visitSummary_{testVisit}.fits") 

70 ) 

71 cls.inputVisitSummary.append(visSum) 

72 

73 cls.config = GbdesAstrometricFitConfig() 

74 cls.config.systematicError = 0 

75 cls.config.devicePolyOrder = 4 

76 cls.config.exposurePolyOrder = 6 

77 cls.config.fitReserveFraction = 0 

78 cls.config.fitReserveRandomSeed = 1234 

79 cls.config.saveModelParams = True 

80 cls.task = GbdesAstrometricFitTask(config=cls.config) 

81 

82 cls.exposureInfo, cls.exposuresHelper, cls.extensionInfo = cls.task._get_exposure_info( 

83 cls.inputVisitSummary, cls.instrument, refEpoch=cls.refEpoch 

84 ) 

85 

86 cls.fields, cls.fieldCenter, cls.fieldRadius = cls.task._prep_sky( 

87 cls.inputVisitSummary, cls.exposureInfo.medianEpoch 

88 ) 

89 

90 # Bounding box of observations: 

91 raMins, raMaxs = [], [] 

92 decMins, decMaxs = [], [] 

93 for visSum in cls.inputVisitSummary: 

94 raMins.append(visSum["raCorners"].min()) 

95 raMaxs.append(visSum["raCorners"].max()) 

96 decMins.append(visSum["decCorners"].min()) 

97 decMaxs.append(visSum["decCorners"].max()) 

98 raMin = min(raMins) 

99 raMax = max(raMaxs) 

100 decMin = min(decMins) 

101 decMax = max(decMaxs) 

102 

103 corners = [ 

104 lsst.geom.SpherePoint(raMin, decMin, lsst.geom.degrees).getVector(), 

105 lsst.geom.SpherePoint(raMax, decMin, lsst.geom.degrees).getVector(), 

106 lsst.geom.SpherePoint(raMax, decMax, lsst.geom.degrees).getVector(), 

107 lsst.geom.SpherePoint(raMin, decMax, lsst.geom.degrees).getVector(), 

108 ] 

109 cls.boundingPolygon = sphgeom.ConvexPolygon(corners) 

110 

111 # Make random set of data in a bounding box determined by input visits 

112 # Make wcs objects for the "true" model 

113 cls.nStars = 10000 

114 starIds = np.arange(cls.nStars) 

115 starRAs = np.random.random(cls.nStars) * (raMax - raMin) + raMin 

116 starDecs = np.random.random(cls.nStars) * (decMax - decMin) + decMin 

117 

118 # Make a reference catalog and load it into ReferenceObjectLoader 

119 refDataId, deferredRefCat = cls._make_refCat(starIds, starRAs, starDecs, inReferenceFraction) 

120 cls.refObjectLoader = ReferenceObjectLoader([refDataId], [deferredRefCat]) 

121 cls.refObjectLoader.config.requireProperMotion = False 

122 cls.refObjectLoader.config.anyFilterMapsToThis = "test_filter" 

123 

124 cls.task.refObjectLoader = cls.refObjectLoader 

125 

126 # Get True WCS for stars: 

127 with open(os.path.join(cls.datadir, "sample_wcs.yaml"), "r") as f: 

128 cls.trueModel = yaml.load(f, Loader=yaml.Loader) 

129 

130 trueWCSs = cls._make_wcs(cls.trueModel, cls.inputVisitSummary) 

131 

132 # Make source catalogs: 

133 cls.inputCatalogRefs = cls._make_sourceCat(starIds, starRAs, starDecs, trueWCSs, inScienceFraction) 

134 

135 cls.outputs = cls.task.run( 

136 cls.inputCatalogRefs, 

137 cls.inputVisitSummary, 

138 instrumentName=cls.instrumentName, 

139 refEpoch=cls.refEpoch, 

140 refObjectLoader=cls.refObjectLoader, 

141 ) 

142 

143 @classmethod 

144 def _make_refCat(cls, starIds, starRas, starDecs, inReferenceFraction): 

145 """Make reference catalog from a subset of the simulated data 

146 

147 Parameters 

148 ---------- 

149 starIds : `np.ndarray` of `int` 

150 Source ids for the simulated stars 

151 starRas : `np.ndarray` of `float` 

152 RAs of the simulated stars 

153 starDecs : `np.ndarray` of `float` 

154 Decs of the simulated stars 

155 inReferenceFraction : float 

156 Percentage of simulated stars to include in reference catalog 

157 

158 Returns 

159 ------- 

160 refDataId : `lsst.meas.algorithms.testUtils.MockRefcatDataId` 

161 Object that replicates the functionality of a dataId. 

162 deferredRefCat : `lsst.pipe.base.InMemoryDatasetHandle` 

163 Dataset handle for reference catalog. 

164 """ 

165 nRefs = int(cls.nStars * inReferenceFraction) 

166 refStarIndices = np.random.choice(cls.nStars, nRefs, replace=False) 

167 # Make simpleCatalog to hold data, create datasetRef with `region` 

168 # determined by bounding box used in above simulate. 

169 refSchema = afwTable.SimpleTable.makeMinimalSchema() 

170 idKey = refSchema.addField("sourceId", type="I") 

171 fluxKey = refSchema.addField("test_filter_flux", units="nJy", type=np.float64) 

172 raErrKey = refSchema.addField("coord_raErr", type=np.float64) 

173 decErrKey = refSchema.addField("coord_decErr", type=np.float64) 

174 pmraErrKey = refSchema.addField("pm_raErr", type=np.float64) 

175 pmdecErrKey = refSchema.addField("pm_decErr", type=np.float64) 

176 refCat = afwTable.SimpleCatalog(refSchema) 

177 ref_md = PropertyList() 

178 ref_md.set("REFCAT_FORMAT_VERSION", 1) 

179 refCat.table.setMetadata(ref_md) 

180 for i in refStarIndices: 

181 record = refCat.addNew() 

182 record.set(idKey, starIds[i]) 

183 record.setRa(lsst.geom.Angle(starRas[i], lsst.geom.degrees)) 

184 record.setDec(lsst.geom.Angle(starDecs[i], lsst.geom.degrees)) 

185 record.set(fluxKey, 1) 

186 record.set(raErrKey, 0.00001) 

187 record.set(decErrKey, 0.00001) 

188 record.set(pmraErrKey, 1e-9) 

189 record.set(pmdecErrKey, 1e-9) 

190 refDataId = MockRefcatDataId(cls.boundingPolygon) 

191 deferredRefCat = InMemoryDatasetHandle(refCat, storageClass="SourceCatalog", htm7="mockRefCat") 

192 

193 return refDataId, deferredRefCat 

194 

195 @classmethod 

196 def _make_sourceCat(cls, starIds, starRas, starDecs, trueWCSs, inScienceFraction): 

197 """Make a `pd.DataFrame` catalog with the columns needed for the 

198 object selector. 

199 

200 Parameters 

201 ---------- 

202 starIds : `np.ndarray` of `int` 

203 Source ids for the simulated stars 

204 starRas : `np.ndarray` of `float` 

205 RAs of the simulated stars 

206 starDecs : `np.ndarray` of `float` 

207 Decs of the simulated stars 

208 trueWCSs : `list` of `lsst.afw.geom.SkyWcs` 

209 WCS with which to simulate the source pixel coordinates 

210 inReferenceFraction : float 

211 Percentage of simulated stars to include in reference catalog 

212 

213 Returns 

214 ------- 

215 sourceCat : `list` of `lsst.pipe.base.InMemoryDatasetHandle` 

216 List of reference to source catalogs. 

217 """ 

218 inputCatalogRefs = [] 

219 # Take a subset of the simulated data 

220 # Use true wcs objects to put simulated data into ccds 

221 bbox = lsst.geom.BoxD( 

222 lsst.geom.Point2D( 

223 cls.inputVisitSummary[0][0]["bbox_min_x"], cls.inputVisitSummary[0][0]["bbox_min_y"] 

224 ), 

225 lsst.geom.Point2D( 

226 cls.inputVisitSummary[0][0]["bbox_max_x"], cls.inputVisitSummary[0][0]["bbox_max_y"] 

227 ), 

228 ) 

229 bboxCorners = bbox.getCorners() 

230 cls.inputCatalogRefs = [] 

231 for v, visit in enumerate(cls.testVisits): 

232 nVisStars = int(cls.nStars * inScienceFraction) 

233 visitStarIndices = np.random.choice(cls.nStars, nVisStars, replace=False) 

234 visitStarIds = starIds[visitStarIndices] 

235 visitStarRas = starRas[visitStarIndices] 

236 visitStarDecs = starDecs[visitStarIndices] 

237 sourceCats = [] 

238 for detector in trueWCSs[visit]: 

239 detWcs = detector.getWcs() 

240 detectorId = detector["id"] 

241 radecCorners = detWcs.pixelToSky(bboxCorners) 

242 detectorFootprint = sphgeom.ConvexPolygon([rd.getVector() for rd in radecCorners]) 

243 detectorIndices = detectorFootprint.contains( 

244 (visitStarRas * u.degree).to(u.radian), (visitStarDecs * u.degree).to(u.radian) 

245 ) 

246 nDetectorStars = detectorIndices.sum() 

247 detectorArray = np.ones(nDetectorStars, dtype=bool) * detector["id"] 

248 

249 ones_like = np.ones(nDetectorStars) 

250 zeros_like = np.zeros(nDetectorStars, dtype=bool) 

251 

252 x, y = detWcs.skyToPixelArray( 

253 visitStarRas[detectorIndices], visitStarDecs[detectorIndices], degrees=True 

254 ) 

255 

256 origWcs = (cls.inputVisitSummary[v][cls.inputVisitSummary[v]["id"] == detectorId])[0].getWcs() 

257 inputRa, inputDec = origWcs.pixelToSkyArray(x, y, degrees=True) 

258 

259 sourceDict = {} 

260 sourceDict["detector"] = detectorArray 

261 sourceDict["sourceId"] = visitStarIds[detectorIndices] 

262 sourceDict["x"] = x 

263 sourceDict["y"] = y 

264 sourceDict["xErr"] = 1e-3 * ones_like 

265 sourceDict["yErr"] = 1e-3 * ones_like 

266 sourceDict["inputRA"] = inputRa 

267 sourceDict["inputDec"] = inputDec 

268 sourceDict["trueRA"] = visitStarRas[detectorIndices] 

269 sourceDict["trueDec"] = visitStarDecs[detectorIndices] 

270 for key in ["apFlux_12_0_flux", "apFlux_12_0_instFlux", "ixx", "iyy"]: 

271 sourceDict[key] = ones_like 

272 for key in [ 

273 "pixelFlags_edge", 

274 "pixelFlags_saturated", 

275 "pixelFlags_interpolatedCenter", 

276 "pixelFlags_interpolated", 

277 "pixelFlags_crCenter", 

278 "pixelFlags_bad", 

279 "hsmPsfMoments_flag", 

280 "apFlux_12_0_flag", 

281 "extendedness", 

282 "parentSourceId", 

283 "deblend_nChild", 

284 "ixy", 

285 ]: 

286 sourceDict[key] = zeros_like 

287 sourceDict["apFlux_12_0_instFluxErr"] = 1e-3 * ones_like 

288 sourceDict["detect_isPrimary"] = ones_like.astype(bool) 

289 

290 sourceCat = pd.DataFrame(sourceDict) 

291 sourceCats.append(sourceCat) 

292 

293 visitSourceTable = pd.concat(sourceCats) 

294 

295 inputCatalogRef = InMemoryDatasetHandle( 

296 visitSourceTable, storageClass="DataFrame", dataId={"visit": visit} 

297 ) 

298 

299 inputCatalogRefs.append(inputCatalogRef) 

300 

301 return inputCatalogRefs 

302 

303 @classmethod 

304 def _make_wcs(cls, model, inputVisitSummaries): 

305 """Make a `lsst.afw.geom.SkyWcs` from given model parameters 

306 

307 Parameters 

308 ---------- 

309 model : `dict` 

310 Dictionary with WCS model parameters 

311 inputVisitSummaries : `list` of `lsst.afw.table.ExposureCatalog` 

312 Visit summary catalogs 

313 Returns 

314 ------- 

315 catalogs : `dict` of `lsst.afw.table.ExposureCatalog` 

316 Visit summary catalogs with WCS set to input model 

317 """ 

318 

319 # Pixels will need to be rescaled before going into the mappings 

320 xscale = inputVisitSummaries[0][0]["bbox_max_x"] - inputVisitSummaries[0][0]["bbox_min_x"] 

321 yscale = inputVisitSummaries[0][0]["bbox_max_y"] - inputVisitSummaries[0][0]["bbox_min_y"] 

322 

323 catalogs = {} 

324 schema = lsst.afw.table.ExposureTable.makeMinimalSchema() 

325 schema.addField("visit", type="L", doc="Visit number") 

326 for visitSum in inputVisitSummaries: 

327 visit = visitSum[0]["visit"] 

328 visitMapName = f"{visit}/poly" 

329 visitModel = model[visitMapName] 

330 

331 catalog = lsst.afw.table.ExposureCatalog(schema) 

332 catalog.resize(len(visitSum)) 

333 catalog["visit"] = visit 

334 

335 raDec = visitSum[0].getVisitInfo().getBoresightRaDec() 

336 

337 visitMapType = visitModel["Type"] 

338 visitDict = {"Type": visitMapType} 

339 if visitMapType == "Poly": 

340 mapCoefficients = visitModel["XPoly"]["Coefficients"] + visitModel["YPoly"]["Coefficients"] 

341 visitDict["Coefficients"] = mapCoefficients 

342 

343 for d, detector in enumerate(visitSum): 

344 detectorId = detector["id"] 

345 detectorMapName = f"HSC/{detectorId}/poly" 

346 detectorModel = model[detectorMapName] 

347 

348 detectorMapType = detectorModel["Type"] 

349 mapDict = {detectorMapName: {"Type": detectorMapType}, visitMapName: visitDict} 

350 if detectorMapType == "Poly": 

351 mapCoefficients = ( 

352 detectorModel["XPoly"]["Coefficients"] + detectorModel["YPoly"]["Coefficients"] 

353 ) 

354 mapDict[detectorMapName]["Coefficients"] = mapCoefficients 

355 

356 outWCS = cls.task._make_afw_wcs( 

357 mapDict, 

358 raDec.getRa(), 

359 raDec.getDec(), 

360 doNormalizePixels=True, 

361 xScale=xscale, 

362 yScale=yscale, 

363 ) 

364 catalog[d].setId(detectorId) 

365 catalog[d].setWcs(outWCS) 

366 

367 catalog.sort() 

368 catalogs[visit] = catalog 

369 

370 return catalogs 

371 

372 def test_get_exposure_info(self): 

373 """Test that information for input exposures is as expected and that 

374 the WCS in the class object gives approximately the same results as the 

375 input `lsst.afw.geom.SkyWcs`. 

376 """ 

377 

378 # The total number of extensions is the number of detectors for each 

379 # visit plus one for the reference catalog 

380 totalExtensions = sum([len(visSum) for visSum in self.inputVisitSummary]) + 1 

381 

382 self.assertEqual(totalExtensions, len(self.extensionInfo.visit)) 

383 

384 taskVisits = set(self.extensionInfo.visit) 

385 self.assertEqual(taskVisits, set(self.testVisits + [-1])) 

386 

387 xx = np.linspace(0, 2000, 3) 

388 yy = np.linspace(0, 4000, 6) 

389 xgrid, ygrid = np.meshgrid(xx, yy) 

390 for visSum in self.inputVisitSummary: 

391 visit = visSum[0]["visit"] 

392 for detectorInfo in visSum: 

393 detector = detectorInfo["id"] 

394 extensionIndex = np.flatnonzero( 

395 (self.extensionInfo.visit == visit) & (self.extensionInfo.detector == detector) 

396 )[0] 

397 fitWcs = self.extensionInfo.wcs[extensionIndex] 

398 calexpWcs = detectorInfo.getWcs() 

399 

400 tanPlaneXY = np.array([fitWcs.toWorld(x, y) for (x, y) in zip(xgrid.ravel(), ygrid.ravel())]) 

401 

402 calexpra, calexpdec = calexpWcs.pixelToSkyArray(xgrid.ravel(), ygrid.ravel(), degrees=True) 

403 

404 tangentPoint = calexpWcs.pixelToSky( 

405 calexpWcs.getPixelOrigin().getX(), calexpWcs.getPixelOrigin().getY() 

406 ) 

407 cdMatrix = afwgeom.makeCdMatrix(1.0 * lsst.geom.degrees, 0 * lsst.geom.degrees, True) 

408 iwcToSkyWcs = afwgeom.makeSkyWcs(lsst.geom.Point2D(0, 0), tangentPoint, cdMatrix) 

409 newRAdeg, newDecdeg = iwcToSkyWcs.pixelToSkyArray( 

410 tanPlaneXY[:, 0], tanPlaneXY[:, 1], degrees=True 

411 ) 

412 

413 np.testing.assert_allclose(calexpra, newRAdeg) 

414 np.testing.assert_allclose(calexpdec, newDecdeg) 

415 

416 def test_refCatLoader(self): 

417 """Test that we can load objects from refCat""" 

418 

419 tmpAssociations = wcsfit.FoFClass( 

420 self.fields, 

421 [self.instrument], 

422 self.exposuresHelper, 

423 [self.fieldRadius.asDegrees()], 

424 (self.task.config.matchRadius * u.arcsec).to(u.degree).value, 

425 ) 

426 

427 self.task._load_refcat( 

428 tmpAssociations, 

429 self.refObjectLoader, 

430 self.fieldCenter, 

431 self.fieldRadius, 

432 self.extensionInfo, 

433 epoch=2015, 

434 ) 

435 

436 # We have only loaded one catalog, so getting the 'matches' should just 

437 # return the same objects we put in, except some random objects that 

438 # are too close together. 

439 tmpAssociations.sortMatches(self.fieldNumber, minMatches=1) 

440 

441 nMatches = (np.array(tmpAssociations.sequence) == 0).sum() 

442 

443 self.assertLessEqual(nMatches, self.nStars) 

444 self.assertGreater(nMatches, self.nStars * 0.9) 

445 

446 def test_load_catalogs_and_associate(self): 

447 tmpAssociations = wcsfit.FoFClass( 

448 self.fields, 

449 [self.instrument], 

450 self.exposuresHelper, 

451 [self.fieldRadius.asDegrees()], 

452 (self.task.config.matchRadius * u.arcsec).to(u.degree).value, 

453 ) 

454 self.task._load_catalogs_and_associate(tmpAssociations, self.inputCatalogRefs, self.extensionInfo) 

455 

456 tmpAssociations.sortMatches(self.fieldNumber, minMatches=2) 

457 

458 matchIds = [] 

459 correctMatches = [] 

460 for s, e, o in zip(tmpAssociations.sequence, tmpAssociations.extn, tmpAssociations.obj): 

461 objVisitInd = self.extensionInfo.visitIndex[e] 

462 objDet = self.extensionInfo.detector[e] 

463 ExtnInds = self.inputCatalogRefs[objVisitInd].get()["detector"] == objDet 

464 objInfo = self.inputCatalogRefs[objVisitInd].get()[ExtnInds].iloc[o] 

465 if s == 0: 

466 if len(matchIds) > 0: 

467 correctMatches.append(len(set(matchIds)) == 1) 

468 matchIds = [] 

469 

470 matchIds.append(objInfo["sourceId"]) 

471 

472 # A few matches may incorrectly associate sources because of the random 

473 # positions 

474 self.assertGreater(sum(correctMatches), len(correctMatches) * 0.95) 

475 

476 def test_make_outputs(self): 

477 """Test that the run method recovers the input model parameters.""" 

478 for v, visit in enumerate(self.testVisits): 

479 visitSummary = self.inputVisitSummary[v] 

480 outputWcsCatalog = self.outputs.outputWCSs[visit] 

481 visitSources = self.inputCatalogRefs[v].get() 

482 for d, detectorRow in enumerate(visitSummary): 

483 detectorId = detectorRow["id"] 

484 fitwcs = outputWcsCatalog[d].getWcs() 

485 detSources = visitSources[visitSources["detector"] == detectorId] 

486 fitRA, fitDec = fitwcs.pixelToSkyArray(detSources["x"], detSources["y"], degrees=True) 

487 dRA = fitRA - detSources["trueRA"] 

488 dDec = fitDec - detSources["trueDec"] 

489 # Check that input coordinates match the output coordinates 

490 self.assertAlmostEqual(np.mean(dRA), 0) 

491 self.assertAlmostEqual(np.std(dRA), 0) 

492 self.assertAlmostEqual(np.mean(dDec), 0) 

493 self.assertAlmostEqual(np.std(dDec), 0) 

494 

495 def test_compute_model_params(self): 

496 """Test the optional model parameters and covariance output.""" 

497 modelParams = pd.DataFrame(self.outputs.modelParams) 

498 # Check that DataFrame is the expected size. 

499 shape = modelParams.shape 

500 self.assertEqual(shape[0] + 4, shape[1]) 

501 # Check that covariance matrix is symmetric. 

502 covariance = (modelParams.iloc[:, 4:]).to_numpy() 

503 np.testing.assert_allclose(covariance, covariance.T, atol=1e-18) 

504 

505 def test_run(self): 

506 """Test that run method recovers the input model parameters""" 

507 outputMaps = self.outputs.fitModel.mapCollection.getParamDict() 

508 

509 for v, visit in enumerate(self.testVisits): 

510 visitSummary = self.inputVisitSummary[v] 

511 visitMapName = f"{visit}/poly" 

512 

513 origModel = self.trueModel[visitMapName] 

514 if origModel["Type"] != "Identity": 

515 fitModel = outputMaps[visitMapName] 

516 origXPoly = origModel["XPoly"]["Coefficients"] 

517 origYPoly = origModel["YPoly"]["Coefficients"] 

518 fitXPoly = fitModel[: len(origXPoly)] 

519 fitYPoly = fitModel[len(origXPoly) :] 

520 

521 absDiffX = abs(fitXPoly - origXPoly) 

522 absDiffY = abs(fitYPoly - origYPoly) 

523 # Check that input visit model matches fit 

524 np.testing.assert_array_less(absDiffX, 1e-6) 

525 np.testing.assert_array_less(absDiffY, 1e-6) 

526 for d, detectorRow in enumerate(visitSummary): 

527 detectorId = detectorRow["id"] 

528 detectorMapName = f"HSC/{detectorId}/poly" 

529 origModel = self.trueModel[detectorMapName] 

530 if (origModel["Type"] != "Identity") and (v == 0): 

531 fitModel = outputMaps[detectorMapName] 

532 origXPoly = origModel["XPoly"]["Coefficients"] 

533 origYPoly = origModel["YPoly"]["Coefficients"] 

534 fitXPoly = fitModel[: len(origXPoly)] 

535 fitYPoly = fitModel[len(origXPoly) :] 

536 absDiffX = abs(fitXPoly - origXPoly) 

537 absDiffY = abs(fitYPoly - origYPoly) 

538 # Check that input detector model matches fit 

539 np.testing.assert_array_less(absDiffX, 1e-7) 

540 np.testing.assert_array_less(absDiffY, 1e-7) 

541 

542 def test_missingWcs(self): 

543 """Test that task does not fail when the input WCS is None for one 

544 extension and that the fit WCS for that extension returns a finite 

545 result. 

546 """ 

547 inputVisitSummary = self.inputVisitSummary.copy() 

548 # Set one WCS to be None 

549 testVisit = 0 

550 testDetector = 20 

551 inputVisitSummary[testVisit][testDetector].setWcs(None) 

552 

553 outputs = self.task.run( 

554 self.inputCatalogRefs, 

555 inputVisitSummary, 

556 instrumentName=self.instrumentName, 

557 refEpoch=self.refEpoch, 

558 refObjectLoader=self.refObjectLoader, 

559 ) 

560 

561 # Check that the fit WCS for the extension with input WCS=None returns 

562 # finite sky values. 

563 testWcs = outputs.outputWCSs[self.testVisits[testVisit]][testDetector].getWcs() 

564 testSky = testWcs.pixelToSky(0, 0) 

565 self.assertTrue(testSky.isFinite()) 

566 

567 

568def setup_module(module): 

569 lsst.utils.tests.init() 

570 

571 

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

573 lsst.utils.tests.init() 

574 unittest.main()