Coverage for tests/test_makeSurveyPropertyMaps.py: 18%

167 statements  

« prev     ^ index     » next       coverage.py v7.3.2, created at 2023-10-20 11:05 +0000

1# This file is part of pipe_tasks. 

2# 

3# LSST Data Management System 

4# This product includes software developed by the 

5# LSST Project (http://www.lsst.org/). 

6# See COPYRIGHT file at the top of the source tree. 

7# 

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

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

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

11# (at your option) any later version. 

12# 

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

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

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

16# GNU General Public License for more details. 

17# 

18# You should have received a copy of the LSST License Statement and 

19# the GNU General Public License along with this program. If not, 

20# see <https://www.lsstcorp.org/LegalNotices/>. 

21# 

22"""Test HealSparsePropertyMapTask. 

23""" 

24import unittest 

25import numpy as np 

26import healsparse as hsp 

27import esutil 

28import warnings 

29import logging 

30from astropy import units 

31 

32import lsst.utils.tests 

33import lsst.daf.butler 

34import lsst.afw.table as afwTable 

35import lsst.afw.geom as afwGeom 

36import lsst.afw.image as afwImage 

37from lsst.skymap.discreteSkyMap import DiscreteSkyMap 

38import lsst.geom as geom 

39 

40from lsst.pipe.base import InMemoryDatasetHandle 

41from lsst.pipe.tasks.healSparseMapping import HealSparsePropertyMapTask 

42from lsst.pipe.tasks.healSparseMappingProperties import (register_property_map, 

43 BasePropertyMap) 

44 

45from surveyPropertyMapsTestUtils import makeMockVisitSummary 

46 

47 

48# Test creation of an arbitrary new property map by registering it here 

49# and using it in the test class. 

50@register_property_map("dist_times_psfarea") 

51class DistTimesPsfAreaPropertyMap(BasePropertyMap): 

52 """Property map to compute the distance from the boresight center 

53 by the psf area. Do not try this at home.""" 

54 description = "Distance times psf area" 

55 unit = "deg*pixel**2" 

56 requires_psf = True 

57 

58 def _compute(self, row, ra, dec, scalings, psf_array=None): 

59 boresight = row.getVisitInfo().getBoresightRaDec() 

60 dist = esutil.coords.sphdist(ra, dec, 

61 boresight.getRa().asDegrees(), boresight.getDec().asDegrees()) 

62 return np.deg2rad(dist)*psf_array['psf_area'] 

63 

64 

65class HealSparsePropertyMapTaskTestCase(lsst.utils.tests.TestCase): 

66 """Test of HealSparsePropertyMapTask. 

67 

68 These tests bypass the middleware used for accessing data and 

69 managing Task execution. 

70 """ 

71 def setUp(self): 

72 tract = 0 

73 band = 'r' 

74 patch = 0 

75 visits = [100, 101] 

76 # Good to test crossing 0. 

77 ra_center = 0.0 

78 dec_center = -45.0 

79 pixel_scale = 0.2 

80 coadd_zp = 27.0 

81 

82 # Generate a mock skymap with one patch 

83 config = DiscreteSkyMap.ConfigClass() 

84 config.raList = [ra_center] 

85 config.decList = [dec_center] 

86 config.radiusList = [150*pixel_scale/3600.] 

87 config.patchInnerDimensions = (350, 350) 

88 config.patchBorder = 50 

89 config.tractOverlap = 0.0 

90 config.pixelScale = pixel_scale 

91 sky_map = DiscreteSkyMap(config) 

92 

93 visit_summaries = [makeMockVisitSummary(visit, 

94 ra_center=ra_center, 

95 dec_center=dec_center) 

96 for visit in visits] 

97 visit_summary_refs = [InMemoryDatasetHandle(visit_summary, visit=visit) 

98 for visit_summary, visit in zip(visit_summaries, visits)] 

99 self.visit_summary_dict = {visit: ref.get() 

100 for ref, visit in zip(visit_summary_refs, visits)} 

101 

102 # Generate an input map. Note that this does not need to be consistent 

103 # with the visit_summary projections, we're just tracking values. 

104 with warnings.catch_warnings(): 

105 # Healsparse will emit a warning if nside coverage is greater than 

106 # 128. In the case of generating patch input maps, and not global 

107 # maps, high nside coverage works fine, so we can suppress this 

108 # warning. 

109 warnings.simplefilter("ignore") 

110 input_map = hsp.HealSparseMap.make_empty(nside_coverage=256, 

111 nside_sparse=32768, 

112 dtype=hsp.WIDE_MASK, 

113 wide_mask_maxbits=len(visits)*2) 

114 

115 patch_poly = afwGeom.Polygon(geom.Box2D(sky_map[tract][patch].getOuterBBox())) 

116 sph_pts = sky_map[tract].getWcs().pixelToSky(patch_poly.convexHull().getVertices()) 

117 patch_poly_radec = np.array([(sph.getRa().asDegrees(), sph.getDec().asDegrees()) 

118 for sph in sph_pts]) 

119 poly = hsp.Polygon(ra=patch_poly_radec[: -1, 0], 

120 dec=patch_poly_radec[: -1, 1], 

121 value=[0]) 

122 poly_pixels = poly.get_pixels(nside=input_map.nside_sparse) 

123 # The input map has full coverage for bits 0 and 1 

124 input_map.set_bits_pix(poly_pixels, [0]) 

125 input_map.set_bits_pix(poly_pixels, [1]) 

126 

127 input_map_ref = InMemoryDatasetHandle(input_map, patch=patch, tract=tract) 

128 self.input_map_dict = {patch: input_map_ref} 

129 

130 coadd = afwImage.ExposureF(sky_map[tract][patch].getOuterBBox(), 

131 sky_map[tract].getWcs()) 

132 instFluxMag0 = 10.**(coadd_zp/2.5) 

133 pc = afwImage.makePhotoCalibFromCalibZeroPoint(instFluxMag0) 

134 coadd.setPhotoCalib(pc) 

135 

136 # Mock the coadd input ccd table 

137 schema = afwTable.ExposureTable.makeMinimalSchema() 

138 schema.addField("ccd", type="I") 

139 schema.addField("visit", type="I") 

140 schema.addField("weight", type="F") 

141 ccds = afwTable.ExposureCatalog(schema) 

142 ccds.resize(2) 

143 ccds['id'] = np.arange(2) 

144 ccds['visit'][0] = visits[0] 

145 ccds['visit'][1] = visits[1] 

146 ccds['ccd'][0] = 0 

147 ccds['ccd'][1] = 1 

148 ccds['weight'] = 10.0 

149 for ccd_row in ccds: 

150 summary = self.visit_summary_dict[ccd_row['visit']].find(ccd_row['ccd']) 

151 ccd_row.setWcs(summary.getWcs()) 

152 ccd_row.setPsf(summary.getPsf()) 

153 ccd_row.setBBox(summary.getBBox()) 

154 ccd_row.setPhotoCalib(summary.getPhotoCalib()) 

155 

156 inputs = afwImage.CoaddInputs() 

157 inputs.ccds = ccds 

158 coadd.getInfo().setCoaddInputs(inputs) 

159 

160 coadd_ref = InMemoryDatasetHandle(coadd, patch=patch, tract=tract, storageClass="ExposureF") 

161 self.coadd_dict = {patch: coadd_ref} 

162 

163 self.tract = tract 

164 self.band = band 

165 self.sky_map = sky_map 

166 self.input_map = input_map 

167 

168 def testPropertyMapCreation(self): 

169 """Test creation of property maps.""" 

170 config = HealSparsePropertyMapTask.ConfigClass() 

171 

172 # Add our new test map to the set of maps 

173 config.property_maps.names |= ['dist_times_psfarea'] 

174 config.property_maps['dist_times_psfarea'].do_min = True 

175 config.property_maps['dist_times_psfarea'].do_max = True 

176 config.property_maps['dist_times_psfarea'].do_mean = True 

177 

178 property_task = HealSparsePropertyMapTask(config=config) 

179 

180 property_task.run(self.sky_map, 

181 self.tract, 

182 self.band, 

183 self.coadd_dict, 

184 self.input_map_dict, 

185 self.visit_summary_dict) 

186 

187 valid_pixels = self.input_map.valid_pixels 

188 

189 # Verify each map exists and has the correct pixels set. 

190 for name, map_config, PropertyMapClass in config.property_maps.apply(): 

191 self.assertTrue(name in property_task.property_maps) 

192 property_map = property_task.property_maps[name] 

193 if map_config.do_min: 

194 self.assertTrue(hasattr(property_map, 'min_map')) 

195 np.testing.assert_array_equal(property_map.min_map.valid_pixels, valid_pixels) 

196 metadata = property_map.min_map.metadata 

197 self.assertIsNotNone(metadata["DESCRIPTION"]) 

198 self.assertEqual(metadata["OPERATION"], "minimum") 

199 unit = units.Unit(metadata["UNIT"]) 

200 self.assertIsNotNone(unit) 

201 else: 

202 self.assertFalse(hasattr(property_map, 'min_map')) 

203 if map_config.do_max: 

204 self.assertTrue(hasattr(property_map, 'max_map')) 

205 np.testing.assert_array_equal(property_map.max_map.valid_pixels, valid_pixels) 

206 metadata = property_map.max_map.metadata 

207 self.assertIsNotNone(metadata["DESCRIPTION"]) 

208 self.assertEqual(metadata["OPERATION"], "maximum") 

209 unit = units.Unit(metadata["UNIT"]) 

210 self.assertIsNotNone(unit) 

211 else: 

212 self.assertFalse(hasattr(property_map, 'max_map')) 

213 if map_config.do_mean: 

214 self.assertTrue(hasattr(property_map, 'mean_map')) 

215 np.testing.assert_array_equal(property_map.mean_map.valid_pixels, valid_pixels) 

216 metadata = property_map.mean_map.metadata 

217 self.assertIsNotNone(metadata["DESCRIPTION"]) 

218 self.assertEqual(metadata["OPERATION"], "mean") 

219 unit = units.Unit(metadata["UNIT"]) 

220 self.assertIsNotNone(unit) 

221 else: 

222 self.assertFalse(hasattr(property_map, 'mean_map')) 

223 if map_config.do_weighted_mean: 

224 self.assertTrue(hasattr(property_map, 'weighted_mean_map')) 

225 np.testing.assert_array_equal(property_map.weighted_mean_map.valid_pixels, valid_pixels) 

226 metadata = property_map.weighted_mean_map.metadata 

227 self.assertIsNotNone(metadata["DESCRIPTION"]) 

228 self.assertEqual(metadata["OPERATION"], "weighted mean") 

229 unit = units.Unit(metadata["UNIT"]) 

230 self.assertIsNotNone(unit) 

231 else: 

232 self.assertFalse(hasattr(property_map, 'weighted_mean_map')) 

233 if map_config.do_sum: 

234 self.assertTrue(hasattr(property_map, 'sum_map')) 

235 np.testing.assert_array_equal(property_map.sum_map.valid_pixels, valid_pixels) 

236 metadata = property_map.sum_map.metadata 

237 self.assertIsNotNone(metadata["DESCRIPTION"]) 

238 self.assertEqual(metadata["OPERATION"], "sum") 

239 unit = units.Unit(metadata["UNIT"]) 

240 self.assertIsNotNone(unit) 

241 else: 

242 self.assertFalse(hasattr(property_map, 'sum_map')) 

243 

244 def testPropertyMapCreationEmptyInputMap(self): 

245 """Test creation of maps with an empty input map (DM-37837).""" 

246 # Replace the input map with an empty one. 

247 with warnings.catch_warnings(): 

248 warnings.simplefilter("ignore") 

249 self.input_map_dict[0] = InMemoryDatasetHandle(hsp.HealSparseMap.make_empty_like( 

250 self.input_map_dict[0].inMemoryDataset 

251 ), **self.input_map_dict[0].dataId) 

252 

253 config = HealSparsePropertyMapTask.ConfigClass() 

254 

255 property_task = HealSparsePropertyMapTask(config=config) 

256 

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

258 property_task.run(self.sky_map, 

259 self.tract, 

260 self.band, 

261 self.coadd_dict, 

262 self.input_map_dict, 

263 self.visit_summary_dict) 

264 self.assertIn("No valid pixels", cm.output[0]) 

265 

266 # Ensure we have no valid pixels output. 

267 self.assertEqual(property_task.property_maps["exposure_time"].sum_map.valid_pixels.size, 0) 

268 

269 

270class MyMemoryTestCase(lsst.utils.tests.MemoryTestCase): 

271 pass 

272 

273 

274def setup_module(module): 

275 lsst.utils.tests.init() 

276 

277 

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

279 lsst.utils.tests.init() 

280 unittest.main()