Coverage for tests/surveyPropertyMapsTestUtils.py: 26%

86 statements  

« prev     ^ index     » next       coverage.py v6.4.2, created at 2022-07-17 08:57 +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"""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()._makeVisitSummarySchema() 

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 shape = psf.computeShape() 

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

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

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

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

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

178 

179 return visit_summary 

180 

181 

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

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

184 a visit summary. 

185 

186 Parameters 

187 ---------- 

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

189 Visit summary catalog. 

190 visit : `int` 

191 Visit number. 

192 """ 

193 def __init__(self, visit_summary, visit): 

194 self.visit_summary = visit_summary 

195 self.visit = visit 

196 

197 def get(self, **kwargs): 

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

199 

200 Parameters 

201 ---------- 

202 **kwargs : 

203 Additional keyword arguments such as `immediate=True` that would 

204 control internal butler behavior. 

205 

206 Returns 

207 ------- 

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

209 """ 

210 return self.visit_summary 

211 

212 

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

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

215 a coadd. 

216 

217 Parameters 

218 ---------- 

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

220 The exposure to be retrieved by the data reference. 

221 coaddName : `str` 

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

223 patch : `int` 

224 Unique identifier for a subdivision of a tract. 

225 tract : `int` 

226 Unique identifier for a tract of a skyMap 

227 """ 

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

229 self.coaddName = coaddName 

230 self.exposure = exposure 

231 self.tract = tract 

232 self.patch = patch 

233 

234 def get(self, component=None, **kwargs): 

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

236 

237 Parameters 

238 ---------- 

239 component : `str`, optional 

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

241 components are "photoCalib" or "coaddInputs". 

242 **kwargs 

243 Additional keyword arguments such as `immediate=True` that would 

244 control internal butler behavior. 

245 

246 Returns 

247 ------- 

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

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

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

251 """ 

252 if component == "photoCalib": 

253 return self.exposure.getPhotoCalib() 

254 elif component == "coaddInputs": 

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

256 

257 return self.exposure.clone() 

258 

259 

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

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

262 an input map. 

263 

264 Parameters 

265 ---------- 

266 input_map : `healsparse.HealSparseMap` 

267 Bitwise input map. 

268 patch : `int` 

269 Patch number. 

270 tract : `int` 

271 Tract number. 

272 """ 

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

274 self.input_map = input_map 

275 self.tract = tract 

276 self.patch = patch 

277 

278 def get(self, **kwargs): 

279 """ 

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

281 

282 Parameters 

283 ---------- 

284 **kwargs 

285 Additional keyword arguments such as `immediate=True` that would 

286 control internal butler behavior. 

287 

288 Returns 

289 ------- 

290 input_map : `healsparse.HealSparseMap` 

291 """ 

292 return self.input_map