Coverage for tests/test_spectraction.py: 98%
407 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-02 02:15 -0800
« prev ^ index » next coverage.py v7.16.2, created at 2026-11-02 02:15 -0800
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."""
24import functools
25import importlib.resources
26import os
27import shutil
28import tempfile
29import unittest
30from unittest import mock
32import astropy.config
33import numpy as np
35import lsst.utils
36import lsst.utils.tests
37import lsst.afw.coord as afwCoord
38import lsst.afw.image as afwImage
39import lsst.geom as geom
40from lsst.daf.base import DateTime
42from spectractor import parameters
43from spectractor.extractor.images import Image
44import lsst.atmospec.spectraction as spectractionModule
45from lsst.atmospec.spectraction import SpectractorShim, Spectraction
47HD1_SIMBAD_TABLE = os.path.join(os.path.dirname(__file__), "data", "HD1.ecsv")
50def withSimbadCache(func):
51 """Serve the HD1 Simbad lookup from a committed table, without network.
53 Spectractor caches Simbad results as ``<label>.ecsv`` files under
54 ``astropy.config.get_cache_dir()/astroquery/Simbad`` and skips the query
55 when that file exists. The astropy cache dir is pointed at a temporary
56 directory seeded with ``tests/data/HD1.ecsv``, so the test neither queries
57 Simbad nor reads or writes the user's real cache.
58 """
59 @functools.wraps(func)
60 def wrapper(self, *args, **kwargs):
61 with tempfile.TemporaryDirectory() as tmpDir, astropy.config.set_temp_cache(tmpDir):
62 simbadDir = os.path.join(astropy.config.get_cache_dir(), "astroquery", "Simbad")
63 os.makedirs(simbadDir)
64 shutil.copy(HD1_SIMBAD_TABLE, simbadDir)
65 return func(self, *args, **kwargs)
66 return wrapper
69def _makeExposure(width=20, height=24, physicalFilter="empty~holo4_003",
70 weather=None, withMetadata=True):
71 """Build a small ExposureF with a populated VisitInfo and filter."""
72 exp = afwImage.ExposureF(width, height)
73 exp.image.array[:] = np.random.rand(height, width).astype(np.float32) + 10.0
74 exp.variance.array[:] = 1.0
76 if weather is None:
77 weather = afwCoord.Weather(10.0, 74300.0, 40.0)
79 vi = afwImage.VisitInfo(
80 exposureTime=30.0,
81 date=DateTime("2021-01-01T00:00:00Z", DateTime.UTC),
82 boresightRaDec=geom.SpherePoint(30.0 * geom.degrees, -30.0 * geom.degrees),
83 boresightAzAlt=geom.SpherePoint(0.0 * geom.degrees, 80.0 * geom.degrees),
84 boresightAirmass=1.1,
85 boresightRotAngle=45.0 * geom.degrees,
86 weather=weather,
87 )
88 exp.getInfo().setVisitInfo(vi)
89 exp.setFilter(afwImage.FilterLabel(physical=physicalFilter, band="white"))
90 if withMetadata: 90 ↛ 95line 90 didn't jump to line 95 because the condition on line 90 was always true
91 md = exp.getMetadata()
92 md['GRATING'] = 'holo4_003'
93 md['AIRMASS'] = 1.1
94 md['DATE'] = '2021-01-01T00:00:00'
95 return exp
98def _makeSpectractorImage(shape=(10, 12)):
99 """Make a real (blank) Spectractor Image usable by helper methods."""
100 image = Image(file_name='', target_label='', disperser_label='', filter_label='')
101 image.data = np.ones(shape)
102 return image
105class SpectractionStaticTestCase(lsst.utils.tests.TestCase):
106 """Tests for the pure/static helper methods."""
108 def testImport(self):
109 import lsst.atmospec.spectraction as s # noqa: F401
111 def test_flipImageLeftRight(self):
112 image = mock.MagicMock()
113 data = np.arange(12).reshape(3, 4)
114 image.data = data.copy()
115 outImage, xpos, ypos = SpectractorShim.flipImageLeftRight(image, 1.0, 2.0)
116 # data flipped along axis 1
117 np.testing.assert_array_equal(outImage.data, np.flip(data, 1))
118 self.assertEqual(xpos, data.shape[1] - 1.0)
119 self.assertEqual(ypos, 2.0)
121 def test_transposeCentroid(self):
122 image = mock.MagicMock()
123 image.data = np.ones((5, 7)) # xSize, ySize = 5, 7
124 newX, newY = SpectractorShim.transposeCentroid(2.0, 3.0, image)
125 self.assertEqual(newX, 3.0) # dmYpos
126 self.assertEqual(newY, 5 - 2.0) # xSize - dmXpos
128 def test_dumpParameters(self):
129 # Should simply print without error.
130 SpectractorShim.dumpParameters()
133class SpectractionParameterTestCase(lsst.utils.tests.TestCase):
134 """Tests for the parameter (over/supplement/reset) machinery."""
136 def setUp(self):
137 self.shim = SpectractorShim()
139 def test_overrideParametersValid(self):
140 original = parameters.VERBOSE
141 try:
142 self.shim.overrideParameters({'VERBOSE': True})
143 self.assertTrue(parameters.VERBOSE)
144 finally:
145 parameters.VERBOSE = original
147 def test_overrideParametersInvalidRaises(self):
148 with self.assertRaises(RuntimeError):
149 self.shim.overrideParameters({'THIS_PARAM_DOES_NOT_EXIST_XYZ': 1})
151 def test_supplementParametersNew(self):
152 name = 'MY_SUPPLEMENTARY_PARAM_XYZ'
153 self.assertNotIn(name, dir(parameters))
154 try:
155 self.shim.supplementParameters({name: 42})
156 self.assertEqual(getattr(parameters, name), 42)
157 finally:
158 if hasattr(parameters, name): 158 ↛ exitline 158 didn't return from function 'test_supplementParametersNew' because the condition on line 158 was always true
159 delattr(parameters, name)
161 def test_supplementParametersExistingWarns(self):
162 # Existing key should NOT be modified, only a warning emitted.
163 original = parameters.VERBOSE
164 try:
165 self.shim.supplementParameters({'VERBOSE': not original})
166 self.assertEqual(parameters.VERBOSE, original)
167 finally:
168 parameters.VERBOSE = original
170 def test_resetParameters(self):
171 name = 'MY_RESET_PARAM_XYZ'
172 try:
173 self.shim.resetParameters({name: 7})
174 self.assertEqual(getattr(parameters, name), 7)
175 # reset also works for existing attributes
176 original = parameters.VERBOSE
177 self.shim.resetParameters({'VERBOSE': not original})
178 self.assertEqual(parameters.VERBOSE, not original)
179 parameters.VERBOSE = original
180 finally:
181 if hasattr(parameters, name): 181 ↛ exitline 181 didn't return from function 'test_resetParameters' because the condition on line 181 was always true
182 delattr(parameters, name)
185class SpectractionInitTestCase(lsst.utils.tests.TestCase):
186 """Tests for the SpectractorShim constructor."""
188 @classmethod
189 def setUpClass(cls):
190 with importlib.resources.path("lsst.atmospec", "resources/config/auxtel.ini") as cfg:
191 cls.configFile = str(cfg)
193 def test_initDefault(self):
194 shim = SpectractorShim()
195 self.assertIsNotNone(shim.log)
197 def test_initWithConfigFile(self):
198 shim = SpectractorShim(configFile=self.configFile)
199 self.assertIsNotNone(shim.log)
201 def test_initWithParamDicts(self):
202 supName = 'INIT_SUPP_PARAM_XYZ'
203 resName = 'INIT_RESET_PARAM_XYZ'
204 original = parameters.VERBOSE
205 try:
206 shim = SpectractorShim(
207 paramOverrides={'VERBOSE': True},
208 supplementaryParameters={supName: 1},
209 resetParameters={resName: 2},
210 )
211 self.assertIsNotNone(shim.log)
212 self.assertTrue(parameters.VERBOSE)
213 self.assertEqual(getattr(parameters, supName), 1)
214 self.assertEqual(getattr(parameters, resName), 2)
215 finally:
216 parameters.VERBOSE = original
217 for name in (supName, resName):
218 if hasattr(parameters, name): 218 ↛ 217line 218 didn't jump to line 217 because the condition on line 218 was always true
219 delattr(parameters, name)
221 def test_initWithDebug(self):
222 original = parameters.DEBUG
223 try:
224 parameters.DEBUG = True
225 shim = SpectractorShim()
226 self.assertIsNotNone(shim.log)
227 finally:
228 parameters.DEBUG = original
231class SpectractionHelperTestCase(lsst.utils.tests.TestCase):
232 """Tests for the small IO / image helper methods."""
234 def setUp(self):
235 self.shim = SpectractorShim()
237 def test_makePathPlotting(self):
238 with tempfile.TemporaryDirectory() as tmp:
239 target = os.path.join(tmp, 'sub')
240 self.shim._makePath(target, plotting=True)
241 self.assertTrue(os.path.exists(os.path.join(target, 'plots')))
243 def test_makePathNoPlotting(self):
244 with tempfile.TemporaryDirectory() as tmp:
245 target = os.path.join(tmp, 'sub2')
246 self.shim._makePath(target, plotting=False)
247 self.assertTrue(os.path.exists(target))
248 # Calling again when it already exists should be a no-op.
249 self.shim._makePath(target, plotting=False)
250 self.assertTrue(os.path.exists(target))
252 def test_ensureFitsHeaderAdds(self):
253 obj = _makeSpectractorImage()
254 if 'SIMPLE' in obj.header: 254 ↛ 255line 254 didn't jump to line 255 because the condition on line 254 was never true
255 del obj.header['SIMPLE']
256 self.shim._ensureFitsHeader(obj)
257 self.assertIn('SIMPLE', obj.header)
259 def test_ensureFitsHeaderAlreadyPresent(self):
260 obj = _makeSpectractorImage()
261 obj.header['SIMPLE'] = True
262 self.shim._ensureFitsHeader(obj)
263 self.assertIn('SIMPLE', obj.header)
265 def test_getImageData(self):
266 exp = _makeExposure()
267 data = self.shim._getImageData(exp)
268 # transposed relative to exp.image.array
269 self.assertEqual(data.shape, exp.image.array.T.shape)
271 def test_getImageDataTrimToSquare(self):
272 exp = _makeExposure()
273 data = self.shim._getImageData(exp, trimToSquare=True)
274 # image is smaller than 4000, so trimming leaves it unchanged (then .T)
275 self.assertEqual(data.shape, exp.image.array.T.shape)
277 def test_setReadNoiseFromExpConst(self):
278 image = _makeSpectractorImage()
279 exp = _makeExposure()
280 self.shim._setReadNoiseFromExp(image, exp, constValue=3.0)
281 self.assertEqual(image.read_out_noise.shape, image.data.shape)
282 self.assertTrue(np.all(image.read_out_noise == 3.0))
284 def test_setReadNoiseFromExpNoneRaises(self):
285 image = _makeSpectractorImage()
286 exp = _makeExposure()
287 with self.assertRaises(NotImplementedError):
288 self.shim._setReadNoiseFromExp(image, exp, constValue=None)
290 def test_setReadNoiseToNone(self):
291 image = _makeSpectractorImage()
292 self.shim._setReadNoiseToNone(image)
293 self.assertIsNone(image.read_out_noise)
295 def test_setGainFromExpConst(self):
296 image = _makeSpectractorImage()
297 exp = _makeExposure()
298 gain = self.shim._setGainFromExp(image, exp, constValue=0.85)
299 self.assertEqual(gain.shape, image.data.shape)
300 self.assertTrue(np.all(gain == 0.85))
302 def test_setGainFromExpDefault(self):
303 image = _makeSpectractorImage()
304 exp = _makeExposure()
305 gain = self.shim._setGainFromExp(image, exp, constValue=None)
306 self.assertTrue(np.all(gain == 1.0))
308 def test_setStatErrorInImageComputed(self):
309 image = _makeSpectractorImage()
310 exp = _makeExposure()
311 image.read_out_noise = np.ones_like(image.data)
312 image.gain = np.ones_like(image.data)
313 self.shim._setStatErrorInImage(image, exp, useExpVariance=False)
314 self.assertIsNotNone(image.err)
316 def test_setStatErrorInImageFromVariance(self):
317 # useExpVariance=True must write to Image.err (renamed from the old
318 # Image.stat_errors, which no longer exists in Spectractor).
319 image = _makeSpectractorImage(shape=(24, 20))
320 exp = _makeExposure()
321 self.assertFalse(hasattr(Image, "stat_errors"))
322 self.shim._setStatErrorInImage(image, exp, useExpVariance=True)
323 np.testing.assert_array_equal(image.err, exp.maskedImage.variance.array)
325 def test_debugPrintTargetCentroidValue(self):
326 image = mock.MagicMock()
327 image.target_guess = (2.0, 3.0)
328 image.data = np.ones((10, 10))
329 # Should run without error.
330 self.shim.debugPrintTargetCentroidValue(image)
332 def test_setImageAndHeaderInfoVisitInfo(self):
333 image = _makeSpectractorImage()
334 exp = _makeExposure()
335 self.shim._setImageAndHeaderInfo(image, exp, useVisitInfo=True)
336 self.assertEqual(image.expo, 30.0)
337 self.assertEqual(image.header.filter, 'empty')
339 def test_setImageAndHeaderInfoMetadata(self):
340 image = _makeSpectractorImage()
341 exp = _makeExposure()
342 self.shim._setImageAndHeaderInfo(image, exp, useVisitInfo=False)
343 self.assertEqual(image.airmass, 1.1)
344 self.assertEqual(image.date_obs, '2021-01-01T00:00:00')
346 def test_setImageAndHeaderInfoException(self):
347 # Missing AIRMASS in the metadata triggers the exception branch,
348 # falling back to a default airmass of 1.
349 image = _makeSpectractorImage()
350 exp = _makeExposure()
351 exp.getMetadata().remove('AIRMASS')
352 self.shim._setImageAndHeaderInfo(image, exp, useVisitInfo=False)
353 self.assertEqual(image.header.airmass, 1.)
355 def test_displayImage(self):
356 image = mock.MagicMock()
357 image.data = np.ones((5, 6))
358 with mock.patch("lsst.afw.display.Display") as mockDisp:
359 self.shim.displayImage(image, centroid=(1, 2))
360 mockDisp.assert_called_once()
361 self.assertTrue(mockDisp.return_value.mtv.called)
362 self.assertTrue(mockDisp.return_value.dot.called)
364 def test_displayImageNoCentroid(self):
365 image = mock.MagicMock()
366 image.data = np.ones((5, 6))
367 with mock.patch("lsst.afw.display.Display") as mockDisp:
368 self.shim.displayImage(image, centroid=None)
369 self.assertFalse(mockDisp.return_value.dot.called)
372class SpectractionAdrTestCase(lsst.utils.tests.TestCase):
373 """Tests for setAdrParameters."""
375 def setUp(self):
376 self.shim = SpectractorShim()
378 def test_setAdrParametersValidWeather(self):
379 exp = _makeExposure(weather=afwCoord.Weather(10.0, 74300.0, 40.0))
380 spectrum = mock.MagicMock()
381 self.shim.setAdrParameters(spectrum, exp)
382 # pressure converted Pa -> hPa
383 self.assertAlmostEqual(spectrum.pressure, 743.0)
384 self.assertEqual(spectrum.temperature, 10.0)
385 self.assertEqual(spectrum.humidity, 40.0)
386 self.assertEqual(len(spectrum.adr_params), 6)
388 def test_setAdrParametersNanWeather(self):
389 nan = float('nan')
390 exp = _makeExposure(weather=afwCoord.Weather(nan, nan, nan))
391 spectrum = mock.MagicMock()
392 self.shim.setAdrParameters(spectrum, exp)
393 # falls back to nominal values
394 self.assertEqual(spectrum.temperature, 10)
395 self.assertEqual(spectrum.pressure, 743)
396 self.assertIsNone(spectrum.humidity)
398 def test_setAdrParametersLowPressure(self):
399 # pressure already in hPa (< 10000) should not be divided.
400 exp = _makeExposure(weather=afwCoord.Weather(10.0, 743.0, 40.0))
401 spectrum = mock.MagicMock()
402 self.shim.setAdrParameters(spectrum, exp)
403 self.assertAlmostEqual(spectrum.pressure, 743.0)
406class SpectractionImageBuildTestCase(lsst.utils.tests.TestCase):
407 """Tests for spectractorImageFromLsstExposure."""
409 @classmethod
410 def setUpClass(cls):
411 with importlib.resources.path("lsst.atmospec", "resources/config/auxtel.ini") as cfg:
412 cls.shim = SpectractorShim(configFile=str(cfg))
414 @withSimbadCache
415 def test_spectractorImageFromLsstExposure(self):
416 exp = _makeExposure()
417 with mock.patch.object(spectractionModule, "Hologram"), \
418 mock.patch.object(Image, "compute_parallactic_angle"):
419 image = self.shim.spectractorImageFromLsstExposure(
420 exp, 5.0, 6.0, target_label='HD1',
421 disperser_label='holo4_003', filter_label='empty')
422 self.assertEqual(image.units, "ADU/s")
423 self.assertEqual(image.expo, 30.0)
424 # target_guess is the (y, x) translation of the input centroid
425 self.assertEqual(tuple(image.target_guess), (6.0, 5.0))
427 @withSimbadCache
428 def test_hologramConstructedWithoutDeadKwargs(self):
429 # Spectractor's Disperser/Hologram no longer accept ``D`` or
430 # ``verbose``; the shim must call Hologram with only the supported
431 # kwargs (currently just data_dir).
432 exp = _makeExposure()
433 with mock.patch.object(spectractionModule, "Hologram") as mockHologram, \
434 mock.patch.object(Image, "compute_parallactic_angle"):
435 self.shim.spectractorImageFromLsstExposure(
436 exp, 5.0, 6.0, target_label='HD1',
437 disperser_label='holo4_003', filter_label='empty')
438 mockHologram.assert_called_once()
439 _, kwargs = mockHologram.call_args
440 self.assertNotIn('D', kwargs)
441 self.assertNotIn('verbose', kwargs)
443 def test_hologramSignatureRejectsDeadKwargs(self):
444 # Guard against the upstream signature regressing: the real Hologram
445 # must not accept ``D`` or ``verbose``.
446 import inspect
447 from spectractor.extractor.dispersers import Disperser, Hologram
448 params = set(inspect.signature(Hologram.__init__).parameters)
449 params |= set(inspect.signature(Disperser.__init__).parameters)
450 self.assertNotIn('D', params)
451 self.assertNotIn('verbose', params)
453 @withSimbadCache
454 def test_spectractorImageFromLsstExposureDebug(self):
455 exp = _makeExposure()
456 original = parameters.DEBUG
457 try:
458 parameters.DEBUG = True
459 with mock.patch.object(spectractionModule, "Hologram"), \
460 mock.patch.object(Image, "compute_parallactic_angle"):
461 image = self.shim.spectractorImageFromLsstExposure(
462 exp, 5.0, 6.0, target_label='HD1',
463 disperser_label='holo4_003', filter_label='empty')
464 self.assertIsNotNone(image)
465 finally:
466 parameters.DEBUG = original
469class SpectractionRunTestCase(lsst.utils.tests.TestCase):
470 """Tests for the run() pipeline, with heavy mocking of Spectractor."""
472 @classmethod
473 def setUpClass(cls):
474 with importlib.resources.path("lsst.atmospec", "resources/config/auxtel.ini") as cfg:
475 cls.shim = SpectractorShim(configFile=str(cfg))
477 def setUp(self):
478 self._saved = {name: getattr(parameters, name) for name in (
479 'DEBUG', 'VERBOSE', 'CCD_REBIN', 'SPECTRACTOR_DECONVOLUTION_PSF2D',
480 'SPECTRACTOR_DECONVOLUTION_FFM', 'OBS_OBJECT_TYPE', 'DISPLAY')}
482 def tearDown(self):
483 for name, value in self._saved.items():
484 setattr(parameters, name, value)
486 def _runWithMocks(self, **runKwargs):
487 exp = _makeExposure()
488 spectrum = mock.MagicMock()
489 spectrum.order = 1
490 spectrum.lambdas = np.zeros(10)
491 image = mock.MagicMock()
492 image.data = np.ones((20, 24))
493 image.target_guess = (5.0, 6.0)
495 patches = {
496 "spectractorImageFromLsstExposure": mock.patch.object(
497 SpectractorShim, "spectractorImageFromLsstExposure", return_value=image),
498 "setAdrParameters": mock.patch.object(SpectractorShim, "setAdrParameters"),
499 "find_target": mock.patch.object(spectractionModule, "find_target"),
500 "turn_image": mock.patch.object(spectractionModule, "turn_image"),
501 "Spectrum": mock.patch.object(spectractionModule, "Spectrum", return_value=spectrum),
502 "extract": mock.patch.object(
503 spectractionModule, "extract_spectrum_from_image",
504 return_value=(mock.MagicMock(), mock.MagicMock())),
505 "psf2d": mock.patch.object(spectractionModule, "run_spectrogram_deconvolution_psf2d"),
506 "calibrate": mock.patch.object(spectractionModule, "calibrate_spectrum"),
507 "ffmWs": mock.patch.object(spectractionModule, "FullForwardModelFitWorkspace"),
508 "ffmRun": mock.patch.object(
509 spectractionModule, "run_ffm_minimisation", return_value=spectrum),
510 "specWs": mock.patch.object(spectractionModule, "SpectrumFitWorkspace"),
511 "specRun": mock.patch.object(spectractionModule, "run_spectrum_minimisation"),
512 "sgramWs": mock.patch.object(spectractionModule, "SpectrogramFitWorkspace"),
513 "sgramRun": mock.patch.object(spectractionModule, "run_spectrogram_minimisation"),
514 "rebin": mock.patch.object(spectractionModule, "apply_rebinning_to_parameters"),
515 }
516 started = {k: p.start() for k, p in patches.items()}
517 self.addCleanup(mock.patch.stopall)
518 result = self.shim.run(exp, 5.0, 6.0, "HD1", **runKwargs)
519 return result, spectrum, image, started
521 def test_runAllBranchesOn(self):
522 parameters.CCD_REBIN = 1
523 parameters.VERBOSE = False
524 parameters.DEBUG = False
525 parameters.SPECTRACTOR_DECONVOLUTION_PSF2D = True
526 parameters.SPECTRACTOR_DECONVOLUTION_FFM = True
527 parameters.OBS_OBJECT_TYPE = "STAR"
528 with tempfile.TemporaryDirectory() as tmp:
529 result, spectrum, image, started = self._runWithMocks(
530 doFitAtmosphere=True, doFitAtmosphereOnSpectrogram=True,
531 outputRoot=tmp, plotting=True)
532 self.assertIsInstance(result, Spectraction)
533 self.assertIs(result.spectrum, spectrum)
534 self.assertIs(result.image, image)
535 self.assertIsNotNone(result.spectrumForwardModelFitParameters)
536 self.assertIsNotNone(result.spectrumLibradtranFitParameters)
537 self.assertIsNotNone(result.spectrogramLibradtranFitParameters)
538 self.assertTrue(started["psf2d"].called)
539 self.assertTrue(started["specRun"].called)
540 self.assertTrue(started["sgramRun"].called)
542 def test_runAllBranchesOff(self):
543 parameters.CCD_REBIN = 1
544 parameters.VERBOSE = False
545 parameters.DEBUG = False
546 parameters.SPECTRACTOR_DECONVOLUTION_PSF2D = False
547 parameters.SPECTRACTOR_DECONVOLUTION_FFM = False
548 parameters.OBS_OBJECT_TYPE = "HG-AR" # triggers with_adr = False
549 result, spectrum, image, started = self._runWithMocks(
550 doFitAtmosphere=False, doFitAtmosphereOnSpectrogram=False,
551 outputRoot=None, plotting=True)
552 self.assertIsInstance(result, Spectraction)
553 self.assertIsNone(result.spectrumForwardModelFitParameters)
554 self.assertIsNone(result.spectrumLibradtranFitParameters)
555 self.assertIsNone(result.spectrogramLibradtranFitParameters)
556 self.assertFalse(started["psf2d"].called)
557 self.assertFalse(started["ffmRun"].called)
558 self.assertFalse(started["specRun"].called)
559 self.assertFalse(started["sgramRun"].called)
561 def test_extractSpectrumCalledWithoutRightEdge(self):
562 # Spectractor's extract_spectrum_from_image no longer accepts
563 # ``right_edge`` (it computes it internally from data_rotated), so the
564 # shim must not pass it.
565 parameters.CCD_REBIN = 1
566 parameters.VERBOSE = False
567 parameters.DEBUG = False
568 parameters.SPECTRACTOR_DECONVOLUTION_PSF2D = False
569 parameters.SPECTRACTOR_DECONVOLUTION_FFM = False
570 parameters.OBS_OBJECT_TYPE = "STAR"
571 result, spectrum, image, started = self._runWithMocks(
572 doFitAtmosphere=False, doFitAtmosphereOnSpectrogram=False,
573 outputRoot=None, plotting=False)
574 self.assertTrue(started["extract"].called)
575 _, kwargs = started["extract"].call_args
576 self.assertNotIn('right_edge', kwargs)
578 def test_extractSpectrumSignatureRejectsRightEdge(self):
579 # Guard against the upstream signature regressing: the real
580 # extract_spectrum_from_image must not accept ``right_edge``.
581 import inspect
582 from spectractor.extractor.extractor import extract_spectrum_from_image
583 params = inspect.signature(extract_spectrum_from_image).parameters
584 self.assertNotIn('right_edge', params)
586 def test_runDebugAndRebin(self):
587 parameters.CCD_REBIN = 2
588 parameters.VERBOSE = True
589 parameters.DEBUG = True
590 parameters.SPECTRACTOR_DECONVOLUTION_PSF2D = True
591 parameters.SPECTRACTOR_DECONVOLUTION_FFM = True
592 parameters.OBS_OBJECT_TYPE = "STAR"
593 result, spectrum, image, started = self._runWithMocks(
594 doFitAtmosphere=True, doFitAtmosphereOnSpectrogram=True,
595 outputRoot=None, plotting=True)
596 self.assertIsInstance(result, Spectraction)
597 # rebinning path was exercised
598 self.assertTrue(started["rebin"].called)
599 self.assertTrue(image.rebin.called)
602class SpectractionContainerTestCase(lsst.utils.tests.TestCase):
603 """Tests for the Spectraction data container."""
605 def test_instantiate(self):
606 result = Spectraction()
607 result.spectrum = "spectrum"
608 result.image = "image"
609 self.assertEqual(result.spectrum, "spectrum")
610 self.assertEqual(result.image, "image")
613class TestMemory(lsst.utils.tests.MemoryTestCase):
614 pass
617def setup_module(module):
618 lsst.utils.tests.init()
621if __name__ == "__main__": 621 ↛ 622line 621 didn't jump to line 622 because the condition on line 621 was never true
622 lsst.utils.tests.init()
623 unittest.main()