Coverage for tests/surveyPropertyMapsTestUtils.py: 22%

87 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-02-18 01:31 -0800

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"""Utilities for HealSparsePropertyMapTask and others.""" 

23import numpy as np 

24 

25import lsst.daf.butler 

26import lsst.geom as geom 

27from lsst.daf.base import DateTime 

28from lsst.afw.coord import Observatory 

29from lsst.pipe.tasks.postprocess import ConsolidateVisitSummaryTask 

30import lsst.afw.table as afwTable 

31import lsst.afw.image as afwImage 

32import lsst.afw.geom as afwGeom 

33from lsst.afw.detection import GaussianPsf 

34 

35 

36__all__ = ['makeMockVisitSummary', 'MockVisitSummaryReference', 'MockCoaddReference', 

37 'MockInputMapReference'] 

38 

39 

40def makeMockVisitSummary(visit, 

41 ra_center=0.0, 

42 dec_center=-45.0, 

43 physical_filter='TEST-I', 

44 band='i', 

45 mjd=59234.7083333334, 

46 psf_sigma=3.0, 

47 zenith_distance=45.0, 

48 zero_point=30.0, 

49 sky_background=100.0, 

50 sky_noise=10.0, 

51 mean_var=100.0, 

52 exposure_time=100.0, 

53 detector_size=200, 

54 pixel_scale=0.2): 

55 """Make a mock visit summary catalog. 

56 

57 This will contain two square detectors with the same metadata, 

58 with a small (20 pixel) gap between the detectors. There is no 

59 rotation, as each detector is simply offset in RA from the 

60 specified boresight. 

61 

62 Parameters 

63 ---------- 

64 visit : `int` 

65 Visit number. 

66 ra_center : `float` 

67 Right ascension of the center of the "camera" boresight (degrees). 

68 dec_center : `float` 

69 Declination of the center of the "camera" boresight (degrees). 

70 physical_filter : `str` 

71 Arbitrary name for the physical filter. 

72 band : `str` 

73 Name of the associated band. 

74 mjd : `float` 

75 Modified Julian Date. 

76 psf_sigma : `float` 

77 Sigma width of Gaussian psf. 

78 zenith_distance : `float` 

79 Distance from zenith of the visit (degrees). 

80 zero_point : `float` 

81 Constant zero point for the visit (magnitudes). 

82 sky_background : `float` 

83 Background level for the visit (counts). 

84 sky_noise : `float` 

85 Noise level for the background of the visit (counts). 

86 mean_var : `float` 

87 Mean of the variance plane of the visit (counts). 

88 exposure_time : `float` 

89 Exposure time of the visit (seconds). 

90 detector_size : `int` 

91 Size of each square detector in the visit (pixels). 

92 pixel_scale : `float` 

93 Size of the pixel in arcseconds per pixel. 

94 

95 Returns 

96 ------- 

97 visit_summary : `lsst.afw.table.ExposureCatalog` 

98 """ 

99 # We are making a 2 detector "camera" 

100 n_detector = 2 

101 

102 schema = ConsolidateVisitSummaryTask().schema 

103 visit_summary = afwTable.ExposureCatalog(schema) 

104 visit_summary.resize(n_detector) 

105 

106 bbox = geom.Box2I(x=geom.IntervalI(min=0, max=detector_size - 1), 

107 y=geom.IntervalI(min=0, max=detector_size - 1)) 

108 

109 for detector_id in range(n_detector): 

110 row = visit_summary[detector_id] 

111 

112 row['id'] = detector_id 

113 row.setBBox(bbox) 

114 row['visit'] = visit 

115 row['physical_filter'] = physical_filter 

116 row['band'] = band 

117 row['zenithDistance'] = zenith_distance 

118 row['zeroPoint'] = zero_point 

119 row['skyBg'] = sky_background 

120 row['skyNoise'] = sky_noise 

121 row['meanVar'] = mean_var 

122 

123 # Generate a photocalib 

124 instFluxMag0 = 10.**(zero_point/2.5) 

125 row.setPhotoCalib(afwImage.makePhotoCalibFromCalibZeroPoint(instFluxMag0)) 

126 

127 # Generate a WCS and set values accordingly 

128 crpix = geom.Point2D(detector_size/2., detector_size/2.) 

129 # Create a 20 pixel gap between the two detectors (each offset 10 pixels). 

130 if detector_id == 0: 

131 delta_ra = -1.0*((detector_size + 10)*pixel_scale/3600.)/np.cos(np.deg2rad(dec_center)) 

132 delta_dec = 0.0 

133 elif detector_id == 1: 

134 delta_ra = ((detector_size + 10)*pixel_scale/3600.)/np.cos(np.deg2rad(dec_center)) 

135 delta_dec = 0.0 

136 crval = geom.SpherePoint(ra_center + delta_ra, dec_center + delta_dec, geom.degrees) 

137 cd_matrix = afwGeom.makeCdMatrix(scale=pixel_scale*geom.arcseconds, orientation=0.0*geom.degrees) 

138 wcs = afwGeom.makeSkyWcs(crpix=crpix, crval=crval, cdMatrix=cd_matrix) 

139 row.setWcs(wcs) 

140 

141 sph_pts = wcs.pixelToSky(geom.Box2D(bbox).getCorners()) 

142 row['raCorners'] = np.array([float(sph.getRa().asDegrees()) for sph in sph_pts]) 

143 row['decCorners'] = np.array([float(sph.getDec().asDegrees()) for sph in sph_pts]) 

144 sph_pt = wcs.pixelToSky(bbox.getCenter()) 

145 row['ra'] = sph_pt.getRa().asDegrees() 

146 row['decl'] = sph_pt.getDec().asDegrees() 

147 

148 # Generate a visitInfo. 

149 # This does not need to be consistent with the zenith angle in the table, 

150 # it just needs to be valid and have sufficient information to compute 

151 # exposure time and parallactic angle. 

152 date = DateTime(date=mjd, system=DateTime.DateSystem.MJD) 

153 visit_info = afwImage.VisitInfo(exposureId=visit, 

154 exposureTime=exposure_time, 

155 date=date, 

156 darkTime=0.0, 

157 boresightRaDec=geom.SpherePoint(ra_center, 

158 dec_center, 

159 geom.degrees), 

160 era=45.1*geom.degrees, 

161 observatory=Observatory( 

162 11.1*geom.degrees, 

163 0.0*geom.degrees, 

164 0.333), 

165 boresightRotAngle=0.0*geom.degrees, 

166 rotType=afwImage.RotType.SKY) 

167 row.setVisitInfo(visit_info) 

168 

169 # Generate a PSF and set values accordingly 

170 psf = GaussianPsf(15, 15, psf_sigma) 

171 row.setPsf(psf) 

172 psfAvgPos = psf.getAveragePosition() 

173 shape = psf.computeShape(psfAvgPos) 

174 row['psfSigma'] = psf.getSigma() 

175 row['psfIxx'] = shape.getIxx() 

176 row['psfIyy'] = shape.getIyy() 

177 row['psfIxy'] = shape.getIxy() 

178 row['psfArea'] = shape.getArea() 

179 

180 return visit_summary 

181 

182 

183class MockVisitSummaryReference(lsst.daf.butler.DeferredDatasetHandle): 

184 """Very simple object that looks like a Gen3 data reference to 

185 a visit summary. 

186 

187 Parameters 

188 ---------- 

189 visit_summary : `lsst.afw.table.ExposureCatalog` 

190 Visit summary catalog. 

191 visit : `int` 

192 Visit number. 

193 """ 

194 def __init__(self, visit_summary, visit): 

195 self.visit_summary = visit_summary 

196 self.visit = visit 

197 

198 def get(self): 

199 """Retrieve the specified dataset using the API of the Gen3 Butler. 

200 

201 Returns 

202 ------- 

203 visit_summary : `lsst.afw.table.ExposureCatalog` 

204 """ 

205 return self.visit_summary 

206 

207 

208class MockCoaddReference(lsst.daf.butler.DeferredDatasetHandle): 

209 """Very simple object that looks like a Gen3 data reference to 

210 a coadd. 

211 

212 Parameters 

213 ---------- 

214 exposure : `lsst.afw.image.Exposure` 

215 The exposure to be retrieved by the data reference. 

216 coaddName : `str` 

217 The type of coadd produced. Typically "deep". 

218 patch : `int` 

219 Unique identifier for a subdivision of a tract. 

220 tract : `int` 

221 Unique identifier for a tract of a skyMap 

222 """ 

223 def __init__(self, exposure, coaddName="deep", patch=0, tract=0): 

224 self.coaddName = coaddName 

225 self.exposure = exposure 

226 self.tract = tract 

227 self.patch = patch 

228 

229 def get(self, component=None): 

230 """Retrieve the specified dataset using the API of the Gen 3 Butler. 

231 

232 Parameters 

233 ---------- 

234 component : `str`, optional 

235 If supplied, return the named metadata of the exposure. Allowed 

236 components are "photoCalib" or "coaddInputs". 

237 

238 Returns 

239 ------- 

240 `lsst.afw.image.Exposure` ('component=None') or 

241 `lsst.afw.image.PhotoCalib` ('component="photoCalib") or 

242 `lsst.afw.image.CoaddInputs` ('component="coaddInputs") 

243 """ 

244 if component == "photoCalib": 

245 return self.exposure.getPhotoCalib() 

246 elif component == "coaddInputs": 

247 return self.exposure.getInfo().getCoaddInputs() 

248 

249 return self.exposure.clone() 

250 

251 

252class MockInputMapReference(lsst.daf.butler.DeferredDatasetHandle): 

253 """Very simple object that looks like a Gen3 data reference to 

254 an input map. 

255 

256 Parameters 

257 ---------- 

258 input_map : `healsparse.HealSparseMap` 

259 Bitwise input map. 

260 patch : `int` 

261 Patch number. 

262 tract : `int` 

263 Tract number. 

264 """ 

265 def __init__(self, input_map, patch=0, tract=0): 

266 self.input_map = input_map 

267 self.tract = tract 

268 self.patch = patch 

269 

270 def get(self): 

271 """ 

272 Retrieve the specified dataset using the API of the Gen 3 Butler. 

273 

274 Returns 

275 ------- 

276 input_map : `healsparse.HealSparseMap` 

277 """ 

278 return self.input_map