Coverage for tests/test_calibrate.py: 29%

116 statements  

« prev     ^ index     » next       coverage.py v7.4.4, created at 2024-04-20 11:05 +0000

1# This file is part of pipe_tasks. 

2# 

3# Developed for the LSST Data Management System. 

4# This product includes software developed by the LSST Project 

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

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

7# for details of code ownership. 

8# 

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

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

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

12# (at your option) any later version. 

13# 

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

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

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

17# GNU General Public License for more details. 

18# 

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

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

21 

22"""Test ProcessCcdTask and its immediate subtasks. 

23""" 

24import logging 

25import os 

26import shutil 

27import tempfile 

28import unittest 

29 

30import lsst.utils.tests 

31import lsst.afw.image 

32import lsst.afw.math 

33import lsst.afw.table 

34import lsst.daf.butler.tests as butlerTests 

35from lsst.utils import getPackageDir 

36from lsst.pipe.base import testUtils 

37from lsst.pipe.tasks.calibrate import CalibrateTask, CalibrateConfig 

38from lsst.pipe.tasks.characterizeImage import CharacterizeImageTask, CharacterizeImageConfig 

39import lsst.meas.extensions.piff.piffPsfDeterminer 

40 

41 

42class CalibrateTaskTestCaseWithButler(lsst.utils.tests.TestCase): 

43 

44 @classmethod 

45 def _makeTestRepo(cls, root): 

46 """Create a repository with the metadata assumed by CalibrateTask. 

47 """ 

48 # In-memory for performance 

49 config = lsst.daf.butler.Config() 

50 config["datastore", "cls"] = "lsst.daf.butler.datastores.inMemoryDatastore.InMemoryDatastore" 

51 config["datastore", "checksum"] = False 

52 config["registry", "db"] = "sqlite:///:memory:" 

53 

54 butler = lsst.daf.butler.Butler(lsst.daf.butler.Butler.makeRepo(root, config=config), writeable=True) 

55 butler.registry.insertDimensionData( 

56 "instrument", 

57 {"name": "notACam", "visit_max": 256, "exposure_max": 256, "detector_max": 64}) 

58 butler.registry.insertDimensionData( 

59 "physical_filter", 

60 {"instrument": "notACam", "name": "r", "band": "r"}, 

61 ) 

62 if "day_obs" in butler.dimensions: 

63 butler.registry.insertDimensionData( 

64 "day_obs", 

65 {"id": 20240201, "instrument": "notACam"}, 

66 ) 

67 butler.registry.insertDimensionData( 

68 "visit", 

69 {"instrument": "notACam", "id": 101, "name": "101", "physical_filter": "r", "day_obs": 20240201}, 

70 ) 

71 butler.registry.insertDimensionData("detector", 

72 {"instrument": "notACam", "id": 42, "full_name": "42"}) 

73 return butler 

74 

75 @classmethod 

76 def setUpClass(cls): 

77 super().setUpClass() 

78 

79 cls.root = tempfile.mkdtemp() 

80 cls.repo = cls._makeTestRepo(cls.root) 

81 

82 butlerTests.addDatasetType( 

83 cls.repo, "icExp", {"instrument", "visit", "detector"}, 

84 "ExposureF") 

85 butlerTests.addDatasetType( 

86 cls.repo, "icExpBackground", {"instrument", "visit", "detector"}, 

87 "Background") 

88 butlerTests.addDatasetType( 

89 cls.repo, "icSrc", {"instrument", "visit", "detector"}, 

90 "SourceCatalog") 

91 butlerTests.addDatasetType( 

92 cls.repo, "cal_ref_cat", {"htm7"}, 

93 "SimpleCatalog") 

94 butlerTests.addDatasetType( 

95 cls.repo, "calexp", {"instrument", "visit", "detector"}, 

96 "ExposureF") 

97 butlerTests.addDatasetType( 

98 cls.repo, "src", {"instrument", "visit", "detector"}, 

99 "SourceCatalog") 

100 butlerTests.addDatasetType( 

101 cls.repo, "calexpBackground", {"instrument", "visit", "detector"}, 

102 "Background") 

103 butlerTests.addDatasetType( 

104 cls.repo, "srcMatch", {"instrument", "visit", "detector"}, 

105 "Catalog") 

106 butlerTests.addDatasetType( 

107 cls.repo, "srcMatchFull", {"instrument", "visit", "detector"}, 

108 "Catalog") 

109 

110 @classmethod 

111 def tearDownClass(cls): 

112 shutil.rmtree(cls.root, ignore_errors=True) 

113 super().tearDownClass() 

114 

115 def setUp(self): 

116 super().setUp() 

117 self.butler = butlerTests.makeTestCollection(self.repo, uniqueId=self.id()) 

118 

119 self.dataId = {"instrument": "notACam", "visit": 101, "detector": 42} 

120 # CalibrateTask absolutely requires an ExpandedDataCoordinate 

121 self.dataId = self.butler.registry.expandDataId(self.dataId) 

122 self.refcatId = {"htm7": 189584} 

123 

124 # Tests do no processing, so we don't need real data 

125 self.exposure = lsst.afw.image.ExposureF(10, 10) 

126 background = lsst.afw.math.BackgroundMI(self.exposure.getBBox(), self.exposure.getMaskedImage()) 

127 self.backgroundlist = lsst.afw.math.BackgroundList( 

128 (background, lsst.afw.math.Interpolate.UNKNOWN, lsst.afw.math.UndersampleStyle.THROW_EXCEPTION, 

129 lsst.afw.math.ApproximateControl.UNKNOWN, 0, 0, 1)) 

130 self.icSrc = lsst.afw.table.SourceCatalog() 

131 self.refcat = lsst.afw.table.SimpleCatalog() 

132 

133 self.butler.put(self.exposure, "icExp", self.dataId) 

134 self.butler.put(self.backgroundlist, "icExpBackground", self.dataId) 

135 self.butler.put(self.icSrc, "icSrc", self.dataId) 

136 self.butler.put(self.refcat, "cal_ref_cat", self.refcatId) 

137 

138 def testDoAstrometry(self): 

139 """Ensure correct inputs passed to run whether or not doAstrometry 

140 is set. 

141 """ 

142 allIds = {key: self.dataId for key in { 

143 "exposure", "background", "icSourceCat", "outputExposure", "outputCat", "outputBackground", 

144 "matches", "matchesDenormalized" 

145 }} 

146 allIds.update({key: [self.refcatId] for key in {"astromRefCat", "photoRefCat"}}) 

147 

148 self._checkDoRefcats(doAstrometry=True, doPhotoCal=True, ids=allIds) 

149 self._checkDoRefcats(doAstrometry=False, doPhotoCal=True, ids=allIds) 

150 

151 def testDoPhotoCal(self): 

152 """Ensure correct inputs passed to run whether or not doPhotoCal 

153 is set. 

154 """ 

155 allIds = {key: self.dataId for key in { 

156 "exposure", "background", "icSourceCat", "outputExposure", "outputCat", "outputBackground", 

157 "matches", "matchesDenormalized" 

158 }} 

159 allIds.update({key: [self.refcatId] for key in {"astromRefCat", "photoRefCat"}}) 

160 

161 self._checkDoRefcats(doAstrometry=True, doPhotoCal=True, ids=allIds) 

162 self._checkDoRefcats(doAstrometry=True, doPhotoCal=False, ids=allIds) 

163 

164 def _checkDoRefcats(self, doAstrometry, doPhotoCal, ids): 

165 """Test whether run is called with the correct arguments. 

166 

167 In the case of `CalibrateTask`, the inputs should not depend on the 

168 task configuration. 

169 

170 Parameters 

171 ---------- 

172 doAstrometry, doPhotoCal : `bool` 

173 Values of the config flags of the same name. 

174 ids : `dict` [`str`] 

175 A mapping from the input dataset type to the data ID of the 

176 dataset to process. 

177 """ 

178 config = CalibrateConfig() 

179 config.doWriteMatches = False # no real output to write 

180 config.doAstrometry = doAstrometry 

181 config.doPhotoCal = doPhotoCal 

182 config.connections.photoRefCat = "cal_ref_cat" 

183 config.connections.astromRefCat = "cal_ref_cat" 

184 config.idGenerator.packer.name = "observation" 

185 task = CalibrateTask(config=config) 

186 quantumId = ids["exposure"] 

187 

188 quantum = testUtils.makeQuantum(task, self.butler, quantumId, ids) 

189 run = testUtils.runTestQuantum(task, self.butler, quantum) 

190 

191 run.assert_called_once() 

192 self.assertEqual(run.call_args[0], ()) 

193 # Some arguments unprintable because we don't have a full environment 

194 # So just check which ones were passed in 

195 self.assertEqual(run.call_args[1].keys(), 

196 {"exposure", "idGenerator", "background", "icSourceCat"}) 

197 

198 def testNoAperCorrMap(self): 

199 expPath = os.path.join(getPackageDir("pipe_tasks"), "tests", "data", "v695833-e0-c000-a00.sci.fits") 

200 exposure = lsst.afw.image.ExposureF(expPath) 

201 

202 charImConfig = CharacterizeImageConfig() 

203 charImConfig.measurePsf.psfDeterminer = 'piff' 

204 charImConfig.measurePsf.psfDeterminer['piff'].spatialOrder = 0 

205 charImConfig.measureApCorr.sourceSelector["science"].doSignalToNoise = False 

206 charImTask = CharacterizeImageTask(config=charImConfig) 

207 charImResults = charImTask.run(exposure) 

208 calibConfig = CalibrateConfig() 

209 calibConfig.doAstrometry = False 

210 calibConfig.doPhotoCal = False 

211 calibConfig.doSkySources = False 

212 calibConfig.doComputeSummaryStats = False 

213 

214 # Force the aperture correction map to None (DM-39626) 

215 exposure.info.setApCorrMap(None) 

216 calibTask = CalibrateTask(config=calibConfig) 

217 with self.assertLogs(level=logging.WARNING) as cm: 

218 _ = calibTask.run(charImResults.exposure) 

219 # Other warnings may also be emitted. 

220 warnings = '\n'.join(cm.output) 

221 self.assertIn("Image does not have valid aperture correction map", warnings) 

222 

223 

224def setup_module(module): 

225 lsst.utils.tests.init() 

226 

227 

228class MemoryTestCase(lsst.utils.tests.MemoryTestCase): 

229 pass 

230 

231 

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

233 lsst.utils.tests.init() 

234 unittest.main()