Coverage for tests/test_utils_extra.py: 99%
379 statements
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-10 09:55 +0000
« prev ^ index » next coverage.py v7.16.2, created at 2026-10-10 09:55 +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."""
24import unittest
25from unittest import mock
27import numpy as np
28import astropy.units as u
29from astropy.table import Table
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
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)
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.
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']
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
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
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)
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
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)}
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))
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))
149 def test_makeGainFlat_mismatchedKeys(self):
150 badDict = {'notAnAmp': 1.0}
151 with self.assertRaises(AssertionError):
152 makeGainFlat(self.exp, badDict)
155class IsExposureTrimmedTestCase(lsst.utils.tests.TestCase):
156 def setUp(self):
157 self.det = makeTestDetector()
159 def test_trimmed(self):
160 exp = makeTrimmedExp(self.det)
161 self.assertTrue(isExposureTrimmed(exp))
163 def test_untrimmed(self):
164 exp = makeRawExp(self.det)
165 self.assertFalse(isExposureTrimmed(exp))
168class GetAmpReadNoiseTestCase(lsst.utils.tests.TestCase):
169 def setUp(self):
170 self.det = makeTestDetector()
172 def test_raisesOnTrimmed(self):
173 exp = makeTrimmedExp(self.det)
174 with self.assertRaises(RuntimeError):
175 getAmpReadNoiseFromRawExp(exp, 0)
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)
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)
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)
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)
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)))
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)))
222 def test_unknownCorrectionType(self):
223 with self.assertRaises(RuntimeError):
224 gainFromFlatPair(self.flat1, self.flat2, correctionType='bogus')
226 def test_correctionWithoutRawExp(self):
227 with self.assertRaises(RuntimeError):
228 gainFromFlatPair(self.flat1, self.flat2, correctionType='simple')
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)
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())
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())
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)
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)
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)
275 def test_withoutPosition(self):
276 exp = afwImage.ExposureF(10, 10)
277 exp.setMetadata(dafBase.PropertyList())
278 self.assertEqual(getLinearStagePosition(exp), 115)
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')
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')
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
307 def test_dispersed(self):
308 exp = self._makeExp('SDSSr' + FILTER_DELIMITER + 'ronchi170lpmm')
309 self.assertTrue(isDispersedExp(exp))
311 def test_notDispersed(self):
312 exp = self._makeExp('SDSSr' + FILTER_DELIMITER + 'empty')
313 self.assertFalse(isDispersedExp(exp))
315 def test_noDelimiter(self):
316 exp = self._makeExp('SDSSr')
317 with self.assertRaises(RuntimeError):
318 isDispersedExp(exp)
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
332 def test_notAButler(self):
333 with self.assertRaises(RuntimeError):
334 isDispersedDataId({'day_obs': 1, 'seq_num': 1}, object())
336 def test_dispersed(self):
337 butler = self._makeButler('SDSSr' + FILTER_DELIMITER + 'ronchi170lpmm')
338 self.assertTrue(isDispersedDataId({'day_obs': 20200101, 'seq_num': 5}, butler))
340 def test_empty(self):
341 butler = self._makeButler('SDSSr' + FILTER_DELIMITER + 'empty')
342 self.assertFalse(isDispersedDataId({'day_obs': 20200101, 'seq_num': 5}, butler))
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))
349 def test_missingDayObs(self):
350 butler = self._makeButler('SDSSr' + FILTER_DELIMITER + 'empty')
351 with self.assertRaises(AssertionError):
352 isDispersedDataId({'seq_num': 5}, butler)
354 def test_missingSeqNum(self):
355 butler = self._makeButler('SDSSr' + FILTER_DELIMITER + 'empty')
356 with self.assertRaises(AssertionError):
357 isDispersedDataId({'day_obs': 20200101}, butler)
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)
364 def test_noDelimiter(self):
365 butler = self._makeButler('SDSSr')
366 with self.assertRaises(RuntimeError):
367 isDispersedDataId({'day_obs': 20200101, 'seq_num': 5}, butler)
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)
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')
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')
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
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)
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)
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)
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
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)
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)
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)
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)
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)
479class RunNotebookTestCase(lsst.utils.tests.TestCase):
480 """Tests for runNotebook, with all butler/pipeline machinery mocked."""
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
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()
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')
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')
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
521 class Connections(pipeBase.PipelineTaskConnections,
522 dimensions=("instrument",)):
523 output = cT.Output(name="anOutput", doc="an output",
524 storageClass="Exposure",
525 dimensions=("instrument",))
527 class MyConfig(pipeBase.PipelineTaskConfig,
528 pipelineConnections=Connections):
529 val = pexConfig.Field(dtype=float, default=1.0, doc='val')
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)
540 def test_configOptions_plainAndConfigurable(self):
541 class SubConfig(pexConfig.Config):
542 x = pexConfig.Field(dtype=int, default=1, doc='x')
544 class MainConfig(pexConfig.Config):
545 sub = pexConfig.ConfigurableField(doc='c', target=object,
546 ConfigClass=SubConfig)
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)
558class TestMemory(lsst.utils.tests.MemoryTestCase):
559 pass
562def setup_module(module):
563 lsst.utils.tests.init()
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()