Coverage for python/lsst/fgcmcal/focalPlaneProjector.py: 25%

56 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-04-11 03:20 -0700

1# This file is part of fgcmcal. 

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"""A class to project the focal plane in arbitrary rotations for fgcm. 

22 

23This file contains a class used by fgcm ... 

24""" 

25from functools import lru_cache 

26import warnings 

27import numpy as np 

28 

29import lsst.afw.image as afwImage 

30import lsst.afw.cameraGeom as afwCameraGeom 

31import lsst.geom as geom 

32from lsst.obs.base import createInitialSkyWcs 

33 

34__all__ = ['FocalPlaneProjector'] 

35 

36 

37class FocalPlaneProjector(object): 

38 """ 

39 Class to project the focal plane onto the sky. 

40 

41 Parameters 

42 ---------- 

43 camera : `lsst.afw.cameraGeom.Camera` 

44 Camera from the butler. 

45 defaultOrientation : `int` 

46 Default camera orientation in degrees. This angle is the position 

47 angle of the focal plane +Y with respect to north. 

48 """ 

49 def __init__(self, camera, defaultOrientation): 

50 self.camera = camera 

51 

52 # Put the reference boresight at the equator to avoid cos(dec) problems. 

53 self.boresight = geom.SpherePoint(180.0*geom.degrees, 0.0*geom.degrees) 

54 self.flipX = False 

55 self.defaultOrientation = int(defaultOrientation) % 360 

56 

57 def _makeWcsDict(self, orientation): 

58 """ 

59 Make a dictionary of WCSs at the reference boresight position. 

60 

61 Parameters 

62 ---------- 

63 orientation : `int` 

64 Orientation in degrees. This angle is the position 

65 angle of the focal plane +Y with respect to north. 

66 

67 Returns 

68 ------- 

69 wcsDict : `dict` 

70 Dictionary of WCS, with the detector id as the key. 

71 """ 

72 _orientation = orientation*geom.degrees 

73 

74 visitInfo = afwImage.VisitInfo(boresightRaDec=self.boresight, 

75 boresightRotAngle=_orientation, 

76 rotType=afwImage.RotType.SKY) 

77 

78 wcsDict = {} 

79 

80 for detector in self.camera: 

81 detectorId = detector.getId() 

82 wcsDict[detectorId] = createInitialSkyWcs(visitInfo, detector, self.flipX) 

83 

84 return wcsDict 

85 

86 def __call__(self, orientation, nstep=100, use_cache=True): 

87 """ 

88 Make a focal plane projection mapping for use with fgcm. 

89 

90 Parameters 

91 ---------- 

92 orientation : `float` or `int` 

93 Camera orientation in degrees. This angle is the position 

94 angle of the focal plane +Y with respect to north. 

95 nstep : `int` 

96 Number of steps in x/y per detector for the mapping. 

97 use_cache : `bool`, optional 

98 Use integerized cached lookup. 

99 

100 Returns 

101 ------- 

102 projectionMapping : `np.ndarray` 

103 A projection mapping object with x, y, x_size, y_size, 

104 delta_ra_cent, delta_dec_cent, delta_ra, delta_dec for 

105 each detector id. 

106 """ 

107 if not np.isfinite(orientation): 

108 warnings.warn('Encountered non-finite orientation; using default.') 

109 _orientation = self.defaultOrientation 

110 else: 

111 _orientation = orientation % 360 

112 

113 if use_cache: 

114 _orientation = int(_orientation) 

115 

116 return self._compute_cached_projection(int(_orientation), nstep=nstep) 

117 else: 

118 return self._compute_projection(_orientation, nstep=nstep) 

119 

120 @lru_cache(maxsize=360) 

121 def _compute_cached_projection(self, orientation, nstep=50): 

122 """ 

123 Compute the focal plane projection, with caching. 

124 

125 Parameters 

126 ---------- 

127 orientation : `int` 

128 Camera orientation in degrees. This angle is the position 

129 angle of the focal plane +Y with respect to north. 

130 nstep : `int` 

131 Number of steps in x/y per detector for the mapping. 

132 

133 Returns 

134 ------- 

135 projectionMapping : `np.ndarray` 

136 A projection mapping object with x, y, x_size, y_size, 

137 delta_ra_cent, delta_dec_cent, delta_ra, delta_dec for 

138 each detector id. 

139 """ 

140 return self._compute_projection(orientation, nstep=nstep) 

141 

142 def _compute_projection(self, orientation, nstep=50): 

143 """ 

144 Compute the focal plane projection. 

145 

146 Parameters 

147 ---------- 

148 orientation : `float` or `int` 

149 Camera orientation in degrees. This angle is the position 

150 angle of the focal plane +Y with respect to north. 

151 nstep : `int` 

152 Number of steps in x/y per detector for the mapping. 

153 

154 Returns 

155 ------- 

156 projectionMapping : `np.ndarray` 

157 A projection mapping object with x, y, x_size, y_size, 

158 delta_ra_cent, delta_dec_cent, delta_ra, delta_dec for 

159 each detector id. 

160 """ 

161 wcsDict = self._makeWcsDict(orientation) 

162 

163 # Need something for the max detector ... 

164 deltaMapper = np.zeros(len(self.camera), dtype=[('id', 'i4'), 

165 ('x', 'f8', nstep**2), 

166 ('y', 'f8', nstep**2), 

167 ('x_size', 'i4'), 

168 ('y_size', 'i4'), 

169 ('delta_ra_cent', 'f8'), 

170 ('delta_dec_cent', 'f8'), 

171 ('delta_ra', 'f8', nstep**2), 

172 ('delta_dec', 'f8', nstep**2)]) 

173 

174 for detector in self.camera: 

175 detectorId = detector.getId() 

176 

177 deltaMapper['id'][detectorId] = detectorId 

178 

179 xSize = detector.getBBox().getMaxX() 

180 ySize = detector.getBBox().getMaxY() 

181 

182 xValues = np.linspace(0.0, xSize, nstep) 

183 yValues = np.linspace(0.0, ySize, nstep) 

184 

185 deltaMapper['x'][detectorId, :] = np.repeat(xValues, yValues.size) 

186 deltaMapper['y'][detectorId, :] = np.tile(yValues, xValues.size) 

187 deltaMapper['x_size'][detectorId] = xSize 

188 deltaMapper['y_size'][detectorId] = ySize 

189 

190 radec = wcsDict[detector.getId()].pixelToSkyArray(deltaMapper['x'][detectorId, :], 

191 deltaMapper['y'][detectorId, :], 

192 degrees=True) 

193 

194 deltaMapper['delta_ra'][detectorId, :] = radec[0] - self.boresight.getRa().asDegrees() 

195 deltaMapper['delta_dec'][detectorId, :] = radec[1] - self.boresight.getDec().asDegrees() 

196 

197 detCenter = wcsDict[detector.getId()].pixelToSky(detector.getCenter(afwCameraGeom.PIXELS)) 

198 deltaMapper['delta_ra_cent'][detectorId] = (detCenter.getRa() 

199 - self.boresight.getRa()).asDegrees() 

200 deltaMapper['delta_dec_cent'][detectorId] = (detCenter.getDec() 

201 - self.boresight.getDec()).asDegrees() 

202 

203 return deltaMapper