Coverage for tests/test_spectraction.py: 98%

391 statements  

« prev     ^ index     » next       coverage.py v7.16.2, created at 2026-10-13 09:57 +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"""Test cases for the atmospec SpectractorShim.""" 

23 

24import importlib.resources 

25import os 

26import tempfile 

27import unittest 

28from unittest import mock 

29 

30import numpy as np 

31 

32import lsst.utils 

33import lsst.utils.tests 

34import lsst.afw.coord as afwCoord 

35import lsst.afw.image as afwImage 

36import lsst.geom as geom 

37from lsst.daf.base import DateTime 

38 

39from spectractor import parameters 

40from spectractor.extractor.images import Image 

41import lsst.atmospec.spectraction as spectractionModule 

42from lsst.atmospec.spectraction import SpectractorShim, Spectraction 

43 

44 

45def _makeExposure(width=20, height=24, physicalFilter="empty~holo4_003", 

46 weather=None, withMetadata=True): 

47 """Build a small ExposureF with a populated VisitInfo and filter.""" 

48 exp = afwImage.ExposureF(width, height) 

49 exp.image.array[:] = np.random.rand(height, width).astype(np.float32) + 10.0 

50 exp.variance.array[:] = 1.0 

51 

52 if weather is None: 

53 weather = afwCoord.Weather(10.0, 74300.0, 40.0) 

54 

55 vi = afwImage.VisitInfo( 

56 exposureTime=30.0, 

57 date=DateTime("2021-01-01T00:00:00Z", DateTime.UTC), 

58 boresightRaDec=geom.SpherePoint(30.0 * geom.degrees, -30.0 * geom.degrees), 

59 boresightAzAlt=geom.SpherePoint(0.0 * geom.degrees, 80.0 * geom.degrees), 

60 boresightAirmass=1.1, 

61 boresightRotAngle=45.0 * geom.degrees, 

62 weather=weather, 

63 ) 

64 exp.getInfo().setVisitInfo(vi) 

65 exp.setFilter(afwImage.FilterLabel(physical=physicalFilter, band="white")) 

66 if withMetadata: 66 ↛ 71line 66 didn't jump to line 71 because the condition on line 66 was always true

67 md = exp.getMetadata() 

68 md['GRATING'] = 'holo4_003' 

69 md['AIRMASS'] = 1.1 

70 md['DATE'] = '2021-01-01T00:00:00' 

71 return exp 

72 

73 

74def _makeSpectractorImage(shape=(10, 12)): 

75 """Make a real (blank) Spectractor Image usable by helper methods.""" 

76 image = Image(file_name='', target_label='', disperser_label='', filter_label='') 

77 image.data = np.ones(shape) 

78 return image 

79 

80 

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

82 """Tests for the pure/static helper methods.""" 

83 

84 def testImport(self): 

85 import lsst.atmospec.spectraction as s # noqa: F401 

86 

87 def test_flipImageLeftRight(self): 

88 image = mock.MagicMock() 

89 data = np.arange(12).reshape(3, 4) 

90 image.data = data.copy() 

91 outImage, xpos, ypos = SpectractorShim.flipImageLeftRight(image, 1.0, 2.0) 

92 # data flipped along axis 1 

93 np.testing.assert_array_equal(outImage.data, np.flip(data, 1)) 

94 self.assertEqual(xpos, data.shape[1] - 1.0) 

95 self.assertEqual(ypos, 2.0) 

96 

97 def test_transposeCentroid(self): 

98 image = mock.MagicMock() 

99 image.data = np.ones((5, 7)) # xSize, ySize = 5, 7 

100 newX, newY = SpectractorShim.transposeCentroid(2.0, 3.0, image) 

101 self.assertEqual(newX, 3.0) # dmYpos 

102 self.assertEqual(newY, 5 - 2.0) # xSize - dmXpos 

103 

104 def test_dumpParameters(self): 

105 # Should simply print without error. 

106 SpectractorShim.dumpParameters() 

107 

108 

109class SpectractionParameterTestCase(lsst.utils.tests.TestCase): 

110 """Tests for the parameter (over/supplement/reset) machinery.""" 

111 

112 def setUp(self): 

113 self.shim = SpectractorShim() 

114 

115 def test_overrideParametersValid(self): 

116 original = parameters.VERBOSE 

117 try: 

118 self.shim.overrideParameters({'VERBOSE': True}) 

119 self.assertTrue(parameters.VERBOSE) 

120 finally: 

121 parameters.VERBOSE = original 

122 

123 def test_overrideParametersInvalidRaises(self): 

124 with self.assertRaises(RuntimeError): 

125 self.shim.overrideParameters({'THIS_PARAM_DOES_NOT_EXIST_XYZ': 1}) 

126 

127 def test_supplementParametersNew(self): 

128 name = 'MY_SUPPLEMENTARY_PARAM_XYZ' 

129 self.assertNotIn(name, dir(parameters)) 

130 try: 

131 self.shim.supplementParameters({name: 42}) 

132 self.assertEqual(getattr(parameters, name), 42) 

133 finally: 

134 if hasattr(parameters, name): 134 ↛ exitline 134 didn't return from function 'test_supplementParametersNew' because the condition on line 134 was always true

135 delattr(parameters, name) 

136 

137 def test_supplementParametersExistingWarns(self): 

138 # Existing key should NOT be modified, only a warning emitted. 

139 original = parameters.VERBOSE 

140 try: 

141 self.shim.supplementParameters({'VERBOSE': not original}) 

142 self.assertEqual(parameters.VERBOSE, original) 

143 finally: 

144 parameters.VERBOSE = original 

145 

146 def test_resetParameters(self): 

147 name = 'MY_RESET_PARAM_XYZ' 

148 try: 

149 self.shim.resetParameters({name: 7}) 

150 self.assertEqual(getattr(parameters, name), 7) 

151 # reset also works for existing attributes 

152 original = parameters.VERBOSE 

153 self.shim.resetParameters({'VERBOSE': not original}) 

154 self.assertEqual(parameters.VERBOSE, not original) 

155 parameters.VERBOSE = original 

156 finally: 

157 if hasattr(parameters, name): 157 ↛ exitline 157 didn't return from function 'test_resetParameters' because the condition on line 157 was always true

158 delattr(parameters, name) 

159 

160 

161class SpectractionInitTestCase(lsst.utils.tests.TestCase): 

162 """Tests for the SpectractorShim constructor.""" 

163 

164 @classmethod 

165 def setUpClass(cls): 

166 with importlib.resources.path("lsst.atmospec", "resources/config/auxtel.ini") as cfg: 

167 cls.configFile = str(cfg) 

168 

169 def test_initDefault(self): 

170 shim = SpectractorShim() 

171 self.assertIsNotNone(shim.log) 

172 

173 def test_initWithConfigFile(self): 

174 shim = SpectractorShim(configFile=self.configFile) 

175 self.assertIsNotNone(shim.log) 

176 

177 def test_initWithParamDicts(self): 

178 supName = 'INIT_SUPP_PARAM_XYZ' 

179 resName = 'INIT_RESET_PARAM_XYZ' 

180 original = parameters.VERBOSE 

181 try: 

182 shim = SpectractorShim( 

183 paramOverrides={'VERBOSE': True}, 

184 supplementaryParameters={supName: 1}, 

185 resetParameters={resName: 2}, 

186 ) 

187 self.assertIsNotNone(shim.log) 

188 self.assertTrue(parameters.VERBOSE) 

189 self.assertEqual(getattr(parameters, supName), 1) 

190 self.assertEqual(getattr(parameters, resName), 2) 

191 finally: 

192 parameters.VERBOSE = original 

193 for name in (supName, resName): 

194 if hasattr(parameters, name): 194 ↛ 193line 194 didn't jump to line 193 because the condition on line 194 was always true

195 delattr(parameters, name) 

196 

197 def test_initWithDebug(self): 

198 original = parameters.DEBUG 

199 try: 

200 parameters.DEBUG = True 

201 shim = SpectractorShim() 

202 self.assertIsNotNone(shim.log) 

203 finally: 

204 parameters.DEBUG = original 

205 

206 

207class SpectractionHelperTestCase(lsst.utils.tests.TestCase): 

208 """Tests for the small IO / image helper methods.""" 

209 

210 def setUp(self): 

211 self.shim = SpectractorShim() 

212 

213 def test_makePathPlotting(self): 

214 with tempfile.TemporaryDirectory() as tmp: 

215 target = os.path.join(tmp, 'sub') 

216 self.shim._makePath(target, plotting=True) 

217 self.assertTrue(os.path.exists(os.path.join(target, 'plots'))) 

218 

219 def test_makePathNoPlotting(self): 

220 with tempfile.TemporaryDirectory() as tmp: 

221 target = os.path.join(tmp, 'sub2') 

222 self.shim._makePath(target, plotting=False) 

223 self.assertTrue(os.path.exists(target)) 

224 # Calling again when it already exists should be a no-op. 

225 self.shim._makePath(target, plotting=False) 

226 self.assertTrue(os.path.exists(target)) 

227 

228 def test_ensureFitsHeaderAdds(self): 

229 obj = _makeSpectractorImage() 

230 if 'SIMPLE' in obj.header: 230 ↛ 231line 230 didn't jump to line 231 because the condition on line 230 was never true

231 del obj.header['SIMPLE'] 

232 self.shim._ensureFitsHeader(obj) 

233 self.assertIn('SIMPLE', obj.header) 

234 

235 def test_ensureFitsHeaderAlreadyPresent(self): 

236 obj = _makeSpectractorImage() 

237 obj.header['SIMPLE'] = True 

238 self.shim._ensureFitsHeader(obj) 

239 self.assertIn('SIMPLE', obj.header) 

240 

241 def test_getImageData(self): 

242 exp = _makeExposure() 

243 data = self.shim._getImageData(exp) 

244 # transposed relative to exp.image.array 

245 self.assertEqual(data.shape, exp.image.array.T.shape) 

246 

247 def test_getImageDataTrimToSquare(self): 

248 exp = _makeExposure() 

249 data = self.shim._getImageData(exp, trimToSquare=True) 

250 # image is smaller than 4000, so trimming leaves it unchanged (then .T) 

251 self.assertEqual(data.shape, exp.image.array.T.shape) 

252 

253 def test_setReadNoiseFromExpConst(self): 

254 image = _makeSpectractorImage() 

255 exp = _makeExposure() 

256 self.shim._setReadNoiseFromExp(image, exp, constValue=3.0) 

257 self.assertEqual(image.read_out_noise.shape, image.data.shape) 

258 self.assertTrue(np.all(image.read_out_noise == 3.0)) 

259 

260 def test_setReadNoiseFromExpNoneRaises(self): 

261 image = _makeSpectractorImage() 

262 exp = _makeExposure() 

263 with self.assertRaises(NotImplementedError): 

264 self.shim._setReadNoiseFromExp(image, exp, constValue=None) 

265 

266 def test_setReadNoiseToNone(self): 

267 image = _makeSpectractorImage() 

268 self.shim._setReadNoiseToNone(image) 

269 self.assertIsNone(image.read_out_noise) 

270 

271 def test_setGainFromExpConst(self): 

272 image = _makeSpectractorImage() 

273 exp = _makeExposure() 

274 gain = self.shim._setGainFromExp(image, exp, constValue=0.85) 

275 self.assertEqual(gain.shape, image.data.shape) 

276 self.assertTrue(np.all(gain == 0.85)) 

277 

278 def test_setGainFromExpDefault(self): 

279 image = _makeSpectractorImage() 

280 exp = _makeExposure() 

281 gain = self.shim._setGainFromExp(image, exp, constValue=None) 

282 self.assertTrue(np.all(gain == 1.0)) 

283 

284 def test_setStatErrorInImageComputed(self): 

285 image = _makeSpectractorImage() 

286 exp = _makeExposure() 

287 image.read_out_noise = np.ones_like(image.data) 

288 image.gain = np.ones_like(image.data) 

289 self.shim._setStatErrorInImage(image, exp, useExpVariance=False) 

290 self.assertIsNotNone(image.err) 

291 

292 def test_setStatErrorInImageFromVariance(self): 

293 # useExpVariance=True must write to Image.err (renamed from the old 

294 # Image.stat_errors, which no longer exists in Spectractor). 

295 image = _makeSpectractorImage(shape=(24, 20)) 

296 exp = _makeExposure() 

297 self.assertFalse(hasattr(Image, "stat_errors")) 

298 self.shim._setStatErrorInImage(image, exp, useExpVariance=True) 

299 np.testing.assert_array_equal(image.err, exp.maskedImage.variance.array) 

300 

301 def test_debugPrintTargetCentroidValue(self): 

302 image = mock.MagicMock() 

303 image.target_guess = (2.0, 3.0) 

304 image.data = np.ones((10, 10)) 

305 # Should run without error. 

306 self.shim.debugPrintTargetCentroidValue(image) 

307 

308 def test_setImageAndHeaderInfoVisitInfo(self): 

309 image = _makeSpectractorImage() 

310 exp = _makeExposure() 

311 self.shim._setImageAndHeaderInfo(image, exp, useVisitInfo=True) 

312 self.assertEqual(image.expo, 30.0) 

313 self.assertEqual(image.header.filter, 'empty') 

314 

315 def test_setImageAndHeaderInfoMetadata(self): 

316 image = _makeSpectractorImage() 

317 exp = _makeExposure() 

318 self.shim._setImageAndHeaderInfo(image, exp, useVisitInfo=False) 

319 self.assertEqual(image.airmass, 1.1) 

320 self.assertEqual(image.date_obs, '2021-01-01T00:00:00') 

321 

322 def test_setImageAndHeaderInfoException(self): 

323 # Missing AIRMASS in the metadata triggers the exception branch, 

324 # falling back to a default airmass of 1. 

325 image = _makeSpectractorImage() 

326 exp = _makeExposure() 

327 exp.getMetadata().remove('AIRMASS') 

328 self.shim._setImageAndHeaderInfo(image, exp, useVisitInfo=False) 

329 self.assertEqual(image.header.airmass, 1.) 

330 

331 def test_displayImage(self): 

332 image = mock.MagicMock() 

333 image.data = np.ones((5, 6)) 

334 with mock.patch("lsst.afw.display.Display") as mockDisp: 

335 self.shim.displayImage(image, centroid=(1, 2)) 

336 mockDisp.assert_called_once() 

337 self.assertTrue(mockDisp.return_value.mtv.called) 

338 self.assertTrue(mockDisp.return_value.dot.called) 

339 

340 def test_displayImageNoCentroid(self): 

341 image = mock.MagicMock() 

342 image.data = np.ones((5, 6)) 

343 with mock.patch("lsst.afw.display.Display") as mockDisp: 

344 self.shim.displayImage(image, centroid=None) 

345 self.assertFalse(mockDisp.return_value.dot.called) 

346 

347 

348class SpectractionAdrTestCase(lsst.utils.tests.TestCase): 

349 """Tests for setAdrParameters.""" 

350 

351 def setUp(self): 

352 self.shim = SpectractorShim() 

353 

354 def test_setAdrParametersValidWeather(self): 

355 exp = _makeExposure(weather=afwCoord.Weather(10.0, 74300.0, 40.0)) 

356 spectrum = mock.MagicMock() 

357 self.shim.setAdrParameters(spectrum, exp) 

358 # pressure converted Pa -> hPa 

359 self.assertAlmostEqual(spectrum.pressure, 743.0) 

360 self.assertEqual(spectrum.temperature, 10.0) 

361 self.assertEqual(spectrum.humidity, 40.0) 

362 self.assertEqual(len(spectrum.adr_params), 6) 

363 

364 def test_setAdrParametersNanWeather(self): 

365 nan = float('nan') 

366 exp = _makeExposure(weather=afwCoord.Weather(nan, nan, nan)) 

367 spectrum = mock.MagicMock() 

368 self.shim.setAdrParameters(spectrum, exp) 

369 # falls back to nominal values 

370 self.assertEqual(spectrum.temperature, 10) 

371 self.assertEqual(spectrum.pressure, 743) 

372 self.assertIsNone(spectrum.humidity) 

373 

374 def test_setAdrParametersLowPressure(self): 

375 # pressure already in hPa (< 10000) should not be divided. 

376 exp = _makeExposure(weather=afwCoord.Weather(10.0, 743.0, 40.0)) 

377 spectrum = mock.MagicMock() 

378 self.shim.setAdrParameters(spectrum, exp) 

379 self.assertAlmostEqual(spectrum.pressure, 743.0) 

380 

381 

382class SpectractionImageBuildTestCase(lsst.utils.tests.TestCase): 

383 """Tests for spectractorImageFromLsstExposure.""" 

384 

385 @classmethod 

386 def setUpClass(cls): 

387 with importlib.resources.path("lsst.atmospec", "resources/config/auxtel.ini") as cfg: 

388 cls.shim = SpectractorShim(configFile=str(cfg)) 

389 

390 def test_spectractorImageFromLsstExposure(self): 

391 exp = _makeExposure() 

392 with mock.patch.object(spectractionModule, "Hologram"), \ 

393 mock.patch.object(Image, "compute_parallactic_angle"): 

394 image = self.shim.spectractorImageFromLsstExposure( 

395 exp, 5.0, 6.0, target_label='HD1', 

396 disperser_label='holo4_003', filter_label='empty') 

397 self.assertEqual(image.units, "ADU/s") 

398 self.assertEqual(image.expo, 30.0) 

399 # target_guess is the (y, x) translation of the input centroid 

400 self.assertEqual(tuple(image.target_guess), (6.0, 5.0)) 

401 

402 def test_hologramConstructedWithoutDeadKwargs(self): 

403 # Spectractor's Disperser/Hologram no longer accept ``D`` or 

404 # ``verbose``; the shim must call Hologram with only the supported 

405 # kwargs (currently just data_dir). 

406 exp = _makeExposure() 

407 with mock.patch.object(spectractionModule, "Hologram") as mockHologram, \ 

408 mock.patch.object(Image, "compute_parallactic_angle"): 

409 self.shim.spectractorImageFromLsstExposure( 

410 exp, 5.0, 6.0, target_label='HD1', 

411 disperser_label='holo4_003', filter_label='empty') 

412 mockHologram.assert_called_once() 

413 _, kwargs = mockHologram.call_args 

414 self.assertNotIn('D', kwargs) 

415 self.assertNotIn('verbose', kwargs) 

416 

417 def test_hologramSignatureRejectsDeadKwargs(self): 

418 # Guard against the upstream signature regressing: the real Hologram 

419 # must not accept ``D`` or ``verbose``. 

420 import inspect 

421 from spectractor.extractor.dispersers import Disperser, Hologram 

422 params = set(inspect.signature(Hologram.__init__).parameters) 

423 params |= set(inspect.signature(Disperser.__init__).parameters) 

424 self.assertNotIn('D', params) 

425 self.assertNotIn('verbose', params) 

426 

427 def test_spectractorImageFromLsstExposureDebug(self): 

428 exp = _makeExposure() 

429 original = parameters.DEBUG 

430 try: 

431 parameters.DEBUG = True 

432 with mock.patch.object(spectractionModule, "Hologram"), \ 

433 mock.patch.object(Image, "compute_parallactic_angle"): 

434 image = self.shim.spectractorImageFromLsstExposure( 

435 exp, 5.0, 6.0, target_label='HD1', 

436 disperser_label='holo4_003', filter_label='empty') 

437 self.assertIsNotNone(image) 

438 finally: 

439 parameters.DEBUG = original 

440 

441 

442class SpectractionRunTestCase(lsst.utils.tests.TestCase): 

443 """Tests for the run() pipeline, with heavy mocking of Spectractor.""" 

444 

445 @classmethod 

446 def setUpClass(cls): 

447 with importlib.resources.path("lsst.atmospec", "resources/config/auxtel.ini") as cfg: 

448 cls.shim = SpectractorShim(configFile=str(cfg)) 

449 

450 def setUp(self): 

451 self._saved = {name: getattr(parameters, name) for name in ( 

452 'DEBUG', 'VERBOSE', 'CCD_REBIN', 'SPECTRACTOR_DECONVOLUTION_PSF2D', 

453 'SPECTRACTOR_DECONVOLUTION_FFM', 'OBS_OBJECT_TYPE', 'DISPLAY')} 

454 

455 def tearDown(self): 

456 for name, value in self._saved.items(): 

457 setattr(parameters, name, value) 

458 

459 def _runWithMocks(self, **runKwargs): 

460 exp = _makeExposure() 

461 spectrum = mock.MagicMock() 

462 spectrum.order = 1 

463 spectrum.lambdas = np.zeros(10) 

464 image = mock.MagicMock() 

465 image.data = np.ones((20, 24)) 

466 image.target_guess = (5.0, 6.0) 

467 

468 patches = { 

469 "spectractorImageFromLsstExposure": mock.patch.object( 

470 SpectractorShim, "spectractorImageFromLsstExposure", return_value=image), 

471 "setAdrParameters": mock.patch.object(SpectractorShim, "setAdrParameters"), 

472 "find_target": mock.patch.object(spectractionModule, "find_target"), 

473 "turn_image": mock.patch.object(spectractionModule, "turn_image"), 

474 "Spectrum": mock.patch.object(spectractionModule, "Spectrum", return_value=spectrum), 

475 "extract": mock.patch.object( 

476 spectractionModule, "extract_spectrum_from_image", 

477 return_value=(mock.MagicMock(), mock.MagicMock())), 

478 "psf2d": mock.patch.object(spectractionModule, "run_spectrogram_deconvolution_psf2d"), 

479 "calibrate": mock.patch.object(spectractionModule, "calibrate_spectrum"), 

480 "ffmWs": mock.patch.object(spectractionModule, "FullForwardModelFitWorkspace"), 

481 "ffmRun": mock.patch.object( 

482 spectractionModule, "run_ffm_minimisation", return_value=spectrum), 

483 "specWs": mock.patch.object(spectractionModule, "SpectrumFitWorkspace"), 

484 "specRun": mock.patch.object(spectractionModule, "run_spectrum_minimisation"), 

485 "sgramWs": mock.patch.object(spectractionModule, "SpectrogramFitWorkspace"), 

486 "sgramRun": mock.patch.object(spectractionModule, "run_spectrogram_minimisation"), 

487 "rebin": mock.patch.object(spectractionModule, "apply_rebinning_to_parameters"), 

488 } 

489 started = {k: p.start() for k, p in patches.items()} 

490 self.addCleanup(mock.patch.stopall) 

491 result = self.shim.run(exp, 5.0, 6.0, "HD1", **runKwargs) 

492 return result, spectrum, image, started 

493 

494 def test_runAllBranchesOn(self): 

495 parameters.CCD_REBIN = 1 

496 parameters.VERBOSE = False 

497 parameters.DEBUG = False 

498 parameters.SPECTRACTOR_DECONVOLUTION_PSF2D = True 

499 parameters.SPECTRACTOR_DECONVOLUTION_FFM = True 

500 parameters.OBS_OBJECT_TYPE = "STAR" 

501 with tempfile.TemporaryDirectory() as tmp: 

502 result, spectrum, image, started = self._runWithMocks( 

503 doFitAtmosphere=True, doFitAtmosphereOnSpectrogram=True, 

504 outputRoot=tmp, plotting=True) 

505 self.assertIsInstance(result, Spectraction) 

506 self.assertIs(result.spectrum, spectrum) 

507 self.assertIs(result.image, image) 

508 self.assertIsNotNone(result.spectrumForwardModelFitParameters) 

509 self.assertIsNotNone(result.spectrumLibradtranFitParameters) 

510 self.assertIsNotNone(result.spectrogramLibradtranFitParameters) 

511 self.assertTrue(started["psf2d"].called) 

512 self.assertTrue(started["specRun"].called) 

513 self.assertTrue(started["sgramRun"].called) 

514 

515 def test_runAllBranchesOff(self): 

516 parameters.CCD_REBIN = 1 

517 parameters.VERBOSE = False 

518 parameters.DEBUG = False 

519 parameters.SPECTRACTOR_DECONVOLUTION_PSF2D = False 

520 parameters.SPECTRACTOR_DECONVOLUTION_FFM = False 

521 parameters.OBS_OBJECT_TYPE = "HG-AR" # triggers with_adr = False 

522 result, spectrum, image, started = self._runWithMocks( 

523 doFitAtmosphere=False, doFitAtmosphereOnSpectrogram=False, 

524 outputRoot=None, plotting=True) 

525 self.assertIsInstance(result, Spectraction) 

526 self.assertIsNone(result.spectrumForwardModelFitParameters) 

527 self.assertIsNone(result.spectrumLibradtranFitParameters) 

528 self.assertIsNone(result.spectrogramLibradtranFitParameters) 

529 self.assertFalse(started["psf2d"].called) 

530 self.assertFalse(started["ffmRun"].called) 

531 self.assertFalse(started["specRun"].called) 

532 self.assertFalse(started["sgramRun"].called) 

533 

534 def test_extractSpectrumCalledWithoutRightEdge(self): 

535 # Spectractor's extract_spectrum_from_image no longer accepts 

536 # ``right_edge`` (it computes it internally from data_rotated), so the 

537 # shim must not pass it. 

538 parameters.CCD_REBIN = 1 

539 parameters.VERBOSE = False 

540 parameters.DEBUG = False 

541 parameters.SPECTRACTOR_DECONVOLUTION_PSF2D = False 

542 parameters.SPECTRACTOR_DECONVOLUTION_FFM = False 

543 parameters.OBS_OBJECT_TYPE = "STAR" 

544 result, spectrum, image, started = self._runWithMocks( 

545 doFitAtmosphere=False, doFitAtmosphereOnSpectrogram=False, 

546 outputRoot=None, plotting=False) 

547 self.assertTrue(started["extract"].called) 

548 _, kwargs = started["extract"].call_args 

549 self.assertNotIn('right_edge', kwargs) 

550 

551 def test_extractSpectrumSignatureRejectsRightEdge(self): 

552 # Guard against the upstream signature regressing: the real 

553 # extract_spectrum_from_image must not accept ``right_edge``. 

554 import inspect 

555 from spectractor.extractor.extractor import extract_spectrum_from_image 

556 params = inspect.signature(extract_spectrum_from_image).parameters 

557 self.assertNotIn('right_edge', params) 

558 

559 def test_runDebugAndRebin(self): 

560 parameters.CCD_REBIN = 2 

561 parameters.VERBOSE = True 

562 parameters.DEBUG = True 

563 parameters.SPECTRACTOR_DECONVOLUTION_PSF2D = True 

564 parameters.SPECTRACTOR_DECONVOLUTION_FFM = True 

565 parameters.OBS_OBJECT_TYPE = "STAR" 

566 result, spectrum, image, started = self._runWithMocks( 

567 doFitAtmosphere=True, doFitAtmosphereOnSpectrogram=True, 

568 outputRoot=None, plotting=True) 

569 self.assertIsInstance(result, Spectraction) 

570 # rebinning path was exercised 

571 self.assertTrue(started["rebin"].called) 

572 self.assertTrue(image.rebin.called) 

573 

574 

575class SpectractionContainerTestCase(lsst.utils.tests.TestCase): 

576 """Tests for the Spectraction data container.""" 

577 

578 def test_instantiate(self): 

579 result = Spectraction() 

580 result.spectrum = "spectrum" 

581 result.image = "image" 

582 self.assertEqual(result.spectrum, "spectrum") 

583 self.assertEqual(result.image, "image") 

584 

585 

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

587 pass 

588 

589 

590def setup_module(module): 

591 lsst.utils.tests.init() 

592 

593 

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

595 lsst.utils.tests.init() 

596 unittest.main()