Coverage for tests/test_utils_extra.py: 99%

379 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-09 11:19 +0000

1#!/usr/bin/env python 

2# This file is part of atmospec. 

3# 

4# Developed for the LSST Data Management System. 

5# This product includes software developed by the LSST Project 

6# (https://www.lsst.org). 

7# See the COPYRIGHT file at the top-level directory of this distribution 

8# for details of code ownership. 

9# 

10# This program is free software: you can redistribute it and/or modify 

11# it under the terms of the GNU General Public License as published by 

12# the Free Software Foundation, either version 3 of the License, or 

13# (at your option) any later version. 

14# 

15# This program is distributed in the hope that it will be useful, 

16# but WITHOUT ANY WARRANTY; without even the implied warranty of 

17# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

18# GNU General Public License for more details. 

19# 

20# You should have received a copy of the GNU General Public License 

21# along with this program. If not, see <https://www.gnu.org/licenses/>. 

22"""Additional test cases for atmospec.utils, targeting full coverage.""" 

23 

24import unittest 

25from unittest import mock 

26 

27import numpy as np 

28import astropy.units as u 

29from astropy.table import Table 

30 

31import lsst.utils 

32import lsst.utils.tests 

33import lsst.afw.image as afwImage 

34import lsst.afw.geom as afwGeom 

35import lsst.afw.cameraGeom as cameraGeom 

36import lsst.geom as geom 

37import lsst.daf.base as dafBase 

38import lsst.daf.butler as dafButler 

39import lsst.pex.config as pexConfig 

40 

41from lsst.obs.lsst.translators.lsst import FILTER_DELIMITER 

42from lsst.atmospec.utils import ( 

43 makeGainFlat, 

44 isExposureTrimmed, 

45 getAmpReadNoiseFromRawExp, 

46 gainFromFlatPair, 

47 rotateExposure, 

48 getLinearStagePosition, 

49 getFilterAndDisperserFromExp, 

50 isDispersedExp, 

51 isDispersedDataId, 

52 simbadLocationForTarget, 

53 vizierLocationForTarget, 

54 getTargetCentroidFromWcs, 

55 runNotebook, 

56) 

57 

58 

59def makeTestDetector(numAmps=2, ampWidth=10, ampHeight=10, prescan=3, oscan=5): 

60 """Build a small camera detector with real trimmed and raw bounding boxes. 

61 

62 Each amp has a raw layout of prescan + ampWidth + oscan columns. 

63 """ 

64 camBuilder = cameraGeom.Camera.Builder('testCam') 

65 detBuilder = camBuilder.add('det0', 0) 

66 detBuilder.setSerial('det0-serial') 

67 detBuilder.setBBox(geom.Box2I(geom.Point2I(0, 0), 

68 geom.Extent2I(numAmps*ampWidth, ampHeight))) 

69 rawWidth = prescan + ampWidth + oscan 

70 for i in range(numAmps): 

71 ampBuilder = cameraGeom.Amplifier.Builder() 

72 ampBuilder.setName(f'amp{i}') 

73 ampBuilder.setBBox(geom.Box2I(geom.Point2I(i*ampWidth, 0), 

74 geom.Extent2I(ampWidth, ampHeight))) 

75 ampBuilder.setRawBBox(geom.Box2I(geom.Point2I(0, 0), 

76 geom.Extent2I(rawWidth, ampHeight))) 

77 ampBuilder.setRawDataBBox(geom.Box2I(geom.Point2I(prescan, 0), 

78 geom.Extent2I(ampWidth, ampHeight))) 

79 ampBuilder.setRawHorizontalOverscanBBox( 

80 geom.Box2I(geom.Point2I(prescan + ampWidth, 0), 

81 geom.Extent2I(oscan, ampHeight))) 

82 ampBuilder.setRawXYOffset(geom.Extent2I(i*rawWidth, 0)) 

83 ampBuilder.setGain(1.0) 

84 ampBuilder.setReadNoise(1.0) 

85 detBuilder.append(ampBuilder) 

86 cam = camBuilder.finish() 

87 return cam['det0'] 

88 

89 

90def makeTrimmedExp(det, fillValue=0.0): 

91 """Make a trimmed exposure with the given detector.""" 

92 exp = afwImage.ExposureF(det.getBBox()) 

93 exp.setDetector(det) 

94 if fillValue: 94 ↛ 95line 94 didn't jump to line 95 because the condition on line 94 was never true

95 exp.image.array[:, :] = fillValue 

96 return exp 

97 

98 

99def makeRawExp(det): 

100 """Make a raw (untrimmed) exposure with the given detector.""" 

101 rawWidth = sum(amp.getRawBBox().getWidth() for amp in det) 

102 rawHeight = det[0].getRawBBox().getHeight() 

103 exp = afwImage.ExposureF(geom.Box2I(geom.Point2I(0, 0), 

104 geom.Extent2I(rawWidth, rawHeight))) 

105 exp.setDetector(det) 

106 return exp 

107 

108 

109def makeWcs(crpix=(15, 15), crval=(30.0, 10.0)): 

110 crpixPoint = geom.Point2D(*crpix) 

111 crvalPoint = geom.SpherePoint(crval[0]*geom.degrees, crval[1]*geom.degrees) 

112 cdMatrix = afwGeom.makeCdMatrix(scale=0.2*geom.arcseconds) 

113 return afwGeom.makeSkyWcs(crpixPoint, crvalPoint, cdMatrix) 

114 

115 

116def makeHipTable(): 

117 """Build a table mimicking the Vizier Hipparcos2 ('I/311/hip2') result.""" 

118 star = Table() 

119 star['RArad'] = [30.0]*u.deg 

120 star['DErad'] = [10.0]*u.deg 

121 star['pmRA'] = [1.0]*(u.mas/u.yr) 

122 star['pmDE'] = [1.0]*(u.mas/u.yr) 

123 star['Plx'] = [10.0]*u.mas 

124 return star 

125 

126 

127class MakeGainFlatTestCase(lsst.utils.tests.TestCase): 

128 def setUp(self): 

129 self.det = makeTestDetector() 

130 self.exp = makeTrimmedExp(self.det) 

131 self.gainDict = {amp.getName(): float(i + 2) 

132 for i, amp in enumerate(self.det)} 

133 

134 def test_makeGainFlat_normal(self): 

135 flat = makeGainFlat(self.exp, self.gainDict, invertGains=False) 

136 for amp in self.det: 

137 vals = flat[amp.getBBox()].image.array 

138 self.assertTrue(np.all(vals == self.gainDict[amp.getName()])) 

139 self.assertTrue(np.all(flat.maskedImage.mask.array == 0)) 

140 self.assertTrue(np.all(flat.maskedImage.variance.array == 0.0)) 

141 

142 def test_makeGainFlat_inverted(self): 

143 flat = makeGainFlat(self.exp, self.gainDict, invertGains=True) 

144 for amp in self.det: 

145 vals = flat[amp.getBBox()].image.array 

146 expected = 1.0/self.gainDict[amp.getName()] 

147 self.assertTrue(np.allclose(vals, expected)) 

148 

149 def test_makeGainFlat_mismatchedKeys(self): 

150 badDict = {'notAnAmp': 1.0} 

151 with self.assertRaises(AssertionError): 

152 makeGainFlat(self.exp, badDict) 

153 

154 

155class IsExposureTrimmedTestCase(lsst.utils.tests.TestCase): 

156 def setUp(self): 

157 self.det = makeTestDetector() 

158 

159 def test_trimmed(self): 

160 exp = makeTrimmedExp(self.det) 

161 self.assertTrue(isExposureTrimmed(exp)) 

162 

163 def test_untrimmed(self): 

164 exp = makeRawExp(self.det) 

165 self.assertFalse(isExposureTrimmed(exp)) 

166 

167 

168class GetAmpReadNoiseTestCase(lsst.utils.tests.TestCase): 

169 def setUp(self): 

170 self.det = makeTestDetector() 

171 

172 def test_raisesOnTrimmed(self): 

173 exp = makeTrimmedExp(self.det) 

174 with self.assertRaises(RuntimeError): 

175 getAmpReadNoiseFromRawExp(exp, 0) 

176 

177 def test_noise_noBorder(self): 

178 raw = makeRawExp(self.det) 

179 rng = np.random.RandomState(1234) 

180 raw.image.array[:, :] = rng.normal(0, 5, raw.image.array.shape) 

181 noise = getAmpReadNoiseFromRawExp(raw, 0, nOscanBorderPix=0) 

182 self.assertGreater(noise, 0.0) 

183 

184 def test_noise_withBorder(self): 

185 # need a large enough overscan so that cropping the border leaves data 

186 det = makeTestDetector(ampWidth=10, ampHeight=20, oscan=12) 

187 raw = makeRawExp(det) 

188 rng = np.random.RandomState(1234) 

189 raw.image.array[:, :] = rng.normal(0, 5, raw.image.array.shape) 

190 noise = getAmpReadNoiseFromRawExp(raw, 0, nOscanBorderPix=2) 

191 self.assertGreater(noise, 0.0) 

192 

193 

194class GainFromFlatPairTestCase(lsst.utils.tests.TestCase): 

195 def setUp(self): 

196 self.det = makeTestDetector() 

197 self.flat1 = makeTrimmedExp(self.det) 

198 self.flat2 = makeTrimmedExp(self.det) 

199 rng = np.random.RandomState(42) 

200 self.flat1.image.array[:, :] = 1000.0 + rng.normal(0, 5, self.flat1.image.array.shape) 

201 self.flat2.image.array[:, :] = 1000.0 + rng.normal(0, 5, self.flat2.image.array.shape) 

202 self.raw = makeRawExp(self.det) 

203 self.raw.image.array[:, :] = rng.normal(0, 5, self.raw.image.array.shape) 

204 

205 def test_noCorrection(self): 

206 gains = gainFromFlatPair(self.flat1, self.flat2, correctionType=None) 

207 self.assertEqual(set(gains.keys()), 

208 set(a.getName() for a in self.det)) 

209 for v in gains.values(): 

210 self.assertGreater(v, 0.0) 

211 

212 def test_simpleCorrection(self): 

213 gains = gainFromFlatPair(self.flat1, self.flat2, correctionType='simple', 

214 rawExpForNoiseCalc=self.raw) 

215 self.assertEqual(len(gains), len(list(self.det))) 

216 

217 def test_fullCorrection(self): 

218 gains = gainFromFlatPair(self.flat1, self.flat2, correctionType='full', 

219 rawExpForNoiseCalc=self.raw) 

220 self.assertEqual(len(gains), len(list(self.det))) 

221 

222 def test_unknownCorrectionType(self): 

223 with self.assertRaises(RuntimeError): 

224 gainFromFlatPair(self.flat1, self.flat2, correctionType='bogus') 

225 

226 def test_correctionWithoutRawExp(self): 

227 with self.assertRaises(RuntimeError): 

228 gainFromFlatPair(self.flat1, self.flat2, correctionType='simple') 

229 

230 

231class RotateExposureTestCase(lsst.utils.tests.TestCase): 

232 def test_noWcs(self): 

233 exp = afwImage.ExposureF(10, 10) 

234 rotated = rotateExposure(exp, 30) 

235 self.assertEqual(rotated.getDimensions(), exp.getDimensions()) 

236 # confirm it is a copy, not the same object 

237 self.assertIsNot(rotated, exp) 

238 

239 def test_noWcs_customLogger(self): 

240 import logging 

241 exp = afwImage.ExposureF(10, 10) 

242 logger = logging.getLogger('test.rotate') 

243 rotated = rotateExposure(exp, 30, logger=logger) 

244 self.assertEqual(rotated.getDimensions(), exp.getDimensions()) 

245 

246 def test_rotateExposureF(self): 

247 exp = afwImage.ExposureF(30, 30) 

248 exp.setWcs(makeWcs()) 

249 rotated = rotateExposure(exp, 90) 

250 self.assertIsNotNone(rotated.getWcs()) 

251 

252 def test_rotateExposureU(self): 

253 exp = afwImage.ExposureU(30, 30) 

254 exp.setWcs(makeWcs()) 

255 rotated = rotateExposure(exp, 90) 

256 # gets converted to ExposureF internally 

257 self.assertIsInstance(rotated, afwImage.ExposureF) 

258 

259 

260class GetLinearStagePositionTestCase(lsst.utils.tests.TestCase): 

261 def test_withPosition(self): 

262 exp = afwImage.ExposureF(10, 10) 

263 md = dafBase.PropertyList() 

264 md['LINSPOS'] = 5.0 

265 exp.setMetadata(md) 

266 self.assertEqual(getLinearStagePosition(exp), 120.0) 

267 

268 def test_withNonePosition(self): 

269 exp = afwImage.ExposureF(10, 10) 

270 md = dafBase.PropertyList() 

271 md.set('LINSPOS', None) 

272 exp.setMetadata(md) 

273 self.assertEqual(getLinearStagePosition(exp), 115) 

274 

275 def test_withoutPosition(self): 

276 exp = afwImage.ExposureF(10, 10) 

277 exp.setMetadata(dafBase.PropertyList()) 

278 self.assertEqual(getLinearStagePosition(exp), 115) 

279 

280 

281class GetFilterAndDisperserTestCase(lsst.utils.tests.TestCase): 

282 def test_withDelimiter(self): 

283 exp = afwImage.ExposureF(10, 10) 

284 exp.setFilter(afwImage.FilterLabel( 

285 band='r', physical='SDSSr' + FILTER_DELIMITER + 'ronchi170lpmm')) 

286 filt, grating = getFilterAndDisperserFromExp(exp) 

287 self.assertEqual(filt, 'SDSSr') 

288 self.assertEqual(grating, 'ronchi170lpmm') 

289 

290 def test_withoutDelimiter(self): 

291 exp = afwImage.ExposureF(10, 10) 

292 exp.setFilter(afwImage.FilterLabel(band='r', physical='SDSSr')) 

293 md = dafBase.PropertyList() 

294 md['GRATING'] = 'ronchi170lpmm' 

295 exp.setMetadata(md) 

296 filt, grating = getFilterAndDisperserFromExp(exp) 

297 self.assertEqual(filt, 'SDSSr') 

298 self.assertEqual(grating, 'ronchi170lpmm') 

299 

300 

301class IsDispersedExpTestCase(lsst.utils.tests.TestCase): 

302 def _makeExp(self, physical): 

303 exp = afwImage.ExposureF(10, 10) 

304 exp.setFilter(afwImage.FilterLabel(band='r', physical=physical)) 

305 return exp 

306 

307 def test_dispersed(self): 

308 exp = self._makeExp('SDSSr' + FILTER_DELIMITER + 'ronchi170lpmm') 

309 self.assertTrue(isDispersedExp(exp)) 

310 

311 def test_notDispersed(self): 

312 exp = self._makeExp('SDSSr' + FILTER_DELIMITER + 'empty') 

313 self.assertFalse(isDispersedExp(exp)) 

314 

315 def test_noDelimiter(self): 

316 exp = self._makeExp('SDSSr') 

317 with self.assertRaises(RuntimeError): 

318 isDispersedExp(exp) 

319 

320 

321class IsDispersedDataIdTestCase(lsst.utils.tests.TestCase): 

322 def _makeButler(self, physicalFilter, nRecords=1): 

323 butler = mock.MagicMock(spec=dafButler.Butler) 

324 records = [] 

325 for _ in range(nRecords): 

326 rec = mock.MagicMock() 

327 rec.physical_filter = physicalFilter 

328 records.append(rec) 

329 butler.registry.queryDimensionRecords.return_value = records 

330 return butler 

331 

332 def test_notAButler(self): 

333 with self.assertRaises(RuntimeError): 

334 isDispersedDataId({'day_obs': 1, 'seq_num': 1}, object()) 

335 

336 def test_dispersed(self): 

337 butler = self._makeButler('SDSSr' + FILTER_DELIMITER + 'ronchi170lpmm') 

338 self.assertTrue(isDispersedDataId({'day_obs': 20200101, 'seq_num': 5}, butler)) 

339 

340 def test_empty(self): 

341 butler = self._makeButler('SDSSr' + FILTER_DELIMITER + 'empty') 

342 self.assertFalse(isDispersedDataId({'day_obs': 20200101, 'seq_num': 5}, butler)) 

343 

344 def test_exposureDottedKeys(self): 

345 butler = self._makeButler('SDSSr' + FILTER_DELIMITER + 'ronchi170lpmm') 

346 dataId = {'exposure.day_obs': 20200101, 'exposure.seq_num': 5} 

347 self.assertTrue(isDispersedDataId(dataId, butler)) 

348 

349 def test_missingDayObs(self): 

350 butler = self._makeButler('SDSSr' + FILTER_DELIMITER + 'empty') 

351 with self.assertRaises(AssertionError): 

352 isDispersedDataId({'seq_num': 5}, butler) 

353 

354 def test_missingSeqNum(self): 

355 butler = self._makeButler('SDSSr' + FILTER_DELIMITER + 'empty') 

356 with self.assertRaises(AssertionError): 

357 isDispersedDataId({'day_obs': 20200101}, butler) 

358 

359 def test_multipleRecords(self): 

360 butler = self._makeButler('SDSSr' + FILTER_DELIMITER + 'empty', nRecords=2) 

361 with self.assertRaises(AssertionError): 

362 isDispersedDataId({'day_obs': 20200101, 'seq_num': 5}, butler) 

363 

364 def test_noDelimiter(self): 

365 butler = self._makeButler('SDSSr') 

366 with self.assertRaises(RuntimeError): 

367 isDispersedDataId({'day_obs': 20200101, 'seq_num': 5}, butler) 

368 

369 

370class SimbadLocationTestCase(lsst.utils.tests.TestCase): 

371 def test_found(self): 

372 with mock.patch('astroquery.simbad.Simbad') as mockSimbad: 

373 mockSimbad.query_object.return_value = [ 

374 {'RA': '02 00 00', 'DEC': '+10 00 00'}] 

375 loc = simbadLocationForTarget('HD 1') 

376 self.assertIsInstance(loc, geom.SpherePoint) 

377 

378 def test_notFound(self): 

379 with mock.patch('astroquery.simbad.Simbad') as mockSimbad: 

380 mockSimbad.query_object.return_value = None 

381 with self.assertRaises(ValueError): 

382 simbadLocationForTarget('HD 1') 

383 

384 def test_multipleFound(self): 

385 with mock.patch('astroquery.simbad.Simbad') as mockSimbad: 

386 mockSimbad.query_object.return_value = [ 

387 {'RA': '02 00 00', 'DEC': '+10 00 00'}, 

388 {'RA': '03 00 00', 'DEC': '+20 00 00'}] 

389 with self.assertRaises(ValueError): 

390 simbadLocationForTarget('HD 1') 

391 

392 

393class VizierLocationTestCase(lsst.utils.tests.TestCase): 

394 def _makeExpWithDate(self): 

395 exp = afwImage.ExposureF(30, 30) 

396 exp.setWcs(makeWcs()) 

397 date = dafBase.DateTime(60000.0, dafBase.DateTime.MJD, dafBase.DateTime.TAI) 

398 exp.getInfo().setVisitInfo(afwImage.VisitInfo(date=date)) 

399 return exp 

400 

401 def test_found_withMotionCorrection(self): 

402 exp = self._makeExpWithDate() 

403 with mock.patch('astroquery.vizier.Vizier') as mockVizier: 

404 mockVizier.query_object.return_value = {'I/311/hip2': makeHipTable()} 

405 loc = vizierLocationForTarget(exp, 'HD 1', doMotionCorrection=True) 

406 self.assertIsInstance(loc, geom.SpherePoint) 

407 

408 def test_found_noMotionCorrection(self): 

409 exp = self._makeExpWithDate() 

410 with mock.patch('astroquery.vizier.Vizier') as mockVizier: 

411 mockVizier.query_object.return_value = {'I/311/hip2': makeHipTable()} 

412 loc = vizierLocationForTarget(exp, 'HD 1', doMotionCorrection=False) 

413 self.assertIsInstance(loc, geom.SpherePoint) 

414 

415 def test_notFound(self): 

416 exp = self._makeExpWithDate() 

417 with mock.patch('astroquery.vizier.Vizier') as mockVizier: 

418 # empty TableList -> indexing by str raises TypeError -> ValueError 

419 mockVizier.query_object.return_value = [] 

420 with self.assertRaises(ValueError): 

421 vizierLocationForTarget(exp, 'HD 1', doMotionCorrection=False) 

422 

423 

424class GetTargetCentroidTestCase(lsst.utils.tests.TestCase): 

425 def _makeExpWithDate(self): 

426 exp = afwImage.ExposureF(30, 30) 

427 exp.setWcs(makeWcs()) 

428 date = dafBase.DateTime(60000.0, dafBase.DateTime.MJD, dafBase.DateTime.TAI) 

429 exp.getInfo().setVisitInfo(afwImage.VisitInfo(date=date)) 

430 return exp 

431 

432 def test_vizierSuccess(self): 

433 exp = self._makeExpWithDate() 

434 with mock.patch('astroquery.vizier.Vizier') as mockVizier: 

435 mockVizier.query_object.return_value = {'I/311/hip2': makeHipTable()} 

436 pixCoord = getTargetCentroidFromWcs(exp, 'HD 1', doMotionCorrection=True) 

437 self.assertIsNotNone(pixCoord) 

438 

439 def test_vizierFail_simbadSuccess(self): 

440 exp = self._makeExpWithDate() 

441 with mock.patch('astroquery.vizier.Vizier') as mockVizier, \ 

442 mock.patch('astroquery.simbad.Simbad') as mockSimbad: 

443 mockVizier.query_object.return_value = [] # forces ValueError 

444 mockSimbad.query_object.return_value = [ 

445 {'RA': '02 00 00', 'DEC': '+10 00 00'}] 

446 # doMotionCorrection True + simbad triggers the warning branch too 

447 pixCoord = getTargetCentroidFromWcs(exp, 'HD 1', doMotionCorrection=True) 

448 self.assertIsNotNone(pixCoord) 

449 

450 def test_bothFail_returnsNone(self): 

451 exp = self._makeExpWithDate() 

452 with mock.patch('astroquery.vizier.Vizier') as mockVizier, \ 

453 mock.patch('astroquery.simbad.Simbad') as mockSimbad: 

454 mockVizier.query_object.return_value = [] 

455 mockSimbad.query_object.return_value = None # ValueError in simbad 

456 pixCoord = getTargetCentroidFromWcs(exp, 'HD 1', doMotionCorrection=True) 

457 self.assertIsNone(pixCoord) 

458 

459 def test_falsyTargetLocation_returnsNone(self): 

460 # Defensive branch: the lookup succeeds (no exception) but returns a 

461 # falsy location. Patch vizierLocationForTarget to return None. 

462 exp = self._makeExpWithDate() 

463 with mock.patch('lsst.atmospec.utils.vizierLocationForTarget', 

464 return_value=None): 

465 pixCoord = getTargetCentroidFromWcs(exp, 'HD 1', doMotionCorrection=False) 

466 self.assertIsNone(pixCoord) 

467 

468 def test_customLogger_noMotionCorrection(self): 

469 import logging 

470 exp = self._makeExpWithDate() 

471 logger = logging.getLogger('test.centroid') 

472 with mock.patch('astroquery.vizier.Vizier') as mockVizier: 

473 mockVizier.query_object.return_value = {'I/311/hip2': makeHipTable()} 

474 pixCoord = getTargetCentroidFromWcs(exp, 'HD 1', 

475 doMotionCorrection=False, logger=logger) 

476 self.assertIsNotNone(pixCoord) 

477 

478 

479class RunNotebookTestCase(lsst.utils.tests.TestCase): 

480 """Tests for runNotebook, with all butler/pipeline machinery mocked.""" 

481 

482 def _runWith(self, dataId, **kwargs): 

483 patchButler = mock.patch('lsst.atmospec.utils.dafButler') 

484 patchPipeline = mock.patch('lsst.atmospec.utils.Pipeline') 

485 patchExecutor = mock.patch('lsst.atmospec.utils.SeparablePipelineExecutor') 

486 patchDefaults = mock.patch('lsst.atmospec.utils.RegistryDefaults') 

487 with patchButler as mockDafButler, patchPipeline as mockPipeline, \ 

488 patchExecutor as mockExecutor, patchDefaults: 

489 butlerInstance = mockDafButler.Butler.return_value 

490 butlerInstance.get.return_value = 'theSpectrum' 

491 pipelineInstance = mockPipeline.from_uri.return_value 

492 result = runNotebook(dataId, 'testOutputCollection', **kwargs) 

493 return result, butlerInstance, pipelineInstance, mockExecutor 

494 

495 def test_basic(self): 

496 result, butler, pipeline, executor = self._runWith( 

497 {'day_obs': 20200101, 'seq_num': 5}) 

498 self.assertEqual(result, 'theSpectrum') 

499 butler.get.assert_called_once() 

500 

501 def test_dottedDataIdAndExtraInputs(self): 

502 result, butler, pipeline, executor = self._runWith( 

503 {'exposure.day_obs': 20200101, 'exposure.seq_num': 5}, 

504 extraInputCollections='extra/collection', embargo=True) 

505 self.assertEqual(result, 'theSpectrum') 

506 

507 def test_extraInputsList(self): 

508 result, butler, pipeline, executor = self._runWith( 

509 {'day_obs': 20200101, 'seq_num': 5}, 

510 extraInputCollections=['extra/one', 'extra/two']) 

511 self.assertEqual(result, 'theSpectrum') 

512 

513 def test_taskConfigs_connectionsAndPlainOption(self): 

514 # Build a real PipelineTaskConfig so ConnectionsConfigClass exists. 

515 # Iterating its .items() yields the connections config (exercising the 

516 # ConnectionsConfigClass isinstance branch and its inner loop) plus a 

517 # plain scalar field (exercising the addConfigOverride branch). 

518 import lsst.pipe.base as pipeBase 

519 import lsst.pipe.base.connectionTypes as cT 

520 

521 class Connections(pipeBase.PipelineTaskConnections, 

522 dimensions=("instrument",)): 

523 output = cT.Output(name="anOutput", doc="an output", 

524 storageClass="Exposure", 

525 dimensions=("instrument",)) 

526 

527 class MyConfig(pipeBase.PipelineTaskConfig, 

528 pipelineConnections=Connections): 

529 val = pexConfig.Field(dtype=float, default=1.0, doc='val') 

530 

531 config = MyConfig() 

532 result, butler, pipeline, executor = self._runWith( 

533 {'day_obs': 20200101, 'seq_num': 5}, 

534 taskConfigs={'myTask': config}) 

535 self.assertEqual(result, 'theSpectrum') 

536 # the plain 'val' option is applied 

537 appliedOptions = [c.args[1] for c in pipeline.addConfigOverride.call_args_list] 

538 self.assertIn('val', appliedOptions) 

539 

540 def test_configOptions_plainAndConfigurable(self): 

541 class SubConfig(pexConfig.Config): 

542 x = pexConfig.Field(dtype=int, default=1, doc='x') 

543 

544 class MainConfig(pexConfig.Config): 

545 sub = pexConfig.ConfigurableField(doc='c', target=object, 

546 ConfigClass=SubConfig) 

547 

548 configurable = MainConfig().sub 

549 result, butler, pipeline, executor = self._runWith( 

550 {'day_obs': 20200101, 'seq_num': 5}, 

551 configOptions={'myTask': {'threshold': 5.0, 

552 'configurableThing': configurable}}) 

553 appliedOptions = [c.args[1] for c in pipeline.addConfigOverride.call_args_list] 

554 self.assertIn('threshold', appliedOptions) 

555 self.assertNotIn('configurableThing', appliedOptions) 

556 

557 

558class TestMemory(lsst.utils.tests.MemoryTestCase): 

559 pass 

560 

561 

562def setup_module(module): 

563 lsst.utils.tests.init() 

564 

565 

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

567 lsst.utils.tests.init() 

568 unittest.main()