Coverage for python/lsst/afw/cameraGeom/pupil.py: 29%

49 statements  

« prev     ^ index     » next       coverage.py v7.2.7, created at 2023-06-27 02:52 -0700

1# This file is part of afw. 

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__all__ = ['PupilFactory', 'Pupil'] 

23 

24import numpy as np 

25 

26 

27class Pupil: 

28 """Pupil obscuration function. 

29 

30 Parameters 

31 ---------- 

32 illuminated : `numpy.ndarray`, (Nx, Ny) 

33 2D numpy array indicating which parts of the pupil plane are 

34 illuminated. 

35 size : `float` 

36 Size of pupil plane array in meters. Note that this may be larger 

37 than the actual diameter of the illuminated pupil to accommodate 

38 zero-padding. 

39 scale : `float` 

40 Sampling interval of pupil plane array in meters. 

41 """ 

42 

43 def __init__(self, illuminated, size, scale): 

44 self.illuminated = illuminated 

45 self.size = size 

46 self.scale = scale 

47 

48 

49class PupilFactory: 

50 """Pupil obscuration function factory for use with Fourier optics. 

51 

52 Parameters 

53 ---------- 

54 visitInfo : `lsst.afw.image.VisitInfo` 

55 Visit information for a particular exposure. 

56 pupilSize : `float` 

57 Size in meters of constructed Pupil array. 

58 Note that this may be larger than the actual diameter of the 

59 illuminated pupil to accommodate zero-padding. 

60 npix : `int` 

61 Constructed Pupils will be npix x npix. 

62 """ 

63 

64 def __init__(self, visitInfo, pupilSize, npix): 

65 self.visitInfo = visitInfo 

66 self.pupilSize = pupilSize 

67 self.npix = npix 

68 self.pupilScale = pupilSize/npix 

69 u = (np.arange(npix, dtype=np.float64) - (npix - 1)/2) * self.pupilScale 

70 self.u, self.v = np.meshgrid(u, u) 

71 

72 def getPupil(self, point): 

73 """Calculate a Pupil at a given point in the focal plane. 

74 

75 Parameters 

76 ---------- 

77 point : `lsst.geom.Point2D` 

78 The focal plane coordinates. 

79 

80 Returns 

81 ------- 

82 pupil : `Pupil` 

83 The Pupil at ``point``. 

84 """ 

85 raise NotImplementedError( 

86 "PupilFactory not implemented for this camera") 

87 

88 @staticmethod 

89 def _pointLineDistance(p0, p1, p2): 

90 """Compute the right-angle distance between the points given by `p0` 

91 and the line that passes through `p1` and `p2`. 

92 

93 Parameters 

94 ---------- 

95 p0 : `tuple` of `numpy.ndarray` 

96 2-tuple of numpy arrays (x, y focal plane coordinates) 

97 p1 : ``pair`` of `float` 

98 x,y focal plane coordinates 

99 p2 : ``pair`` of `float` 

100 x,y focal plane coordinates 

101 

102 Returns 

103 ------- 

104 distances : `numpy.ndarray` 

105 Numpy array of distances; shape congruent to p0[0]. 

106 """ 

107 x0, y0 = p0 

108 x1, y1 = p1 

109 x2, y2 = p2 

110 dy21 = y2 - y1 

111 dx21 = x2 - x1 

112 return np.abs(dy21*x0 - dx21*y0 + x2*y1 - y2*x1)/np.hypot(dy21, dx21) 

113 

114 def _fullPupil(self): 

115 """Make a fully-illuminated Pupil. 

116 

117 Returns 

118 ------- 

119 pupil : `Pupil` 

120 The illuminated pupil. 

121 """ 

122 illuminated = np.ones(self.u.shape, dtype=bool) 

123 return Pupil(illuminated, self.pupilSize, self.pupilScale) 

124 

125 def _cutCircleInterior(self, pupil, p0, r): 

126 """Cut out the interior of a circular region from a Pupil. 

127 

128 Parameters 

129 ---------- 

130 pupil : `Pupil` 

131 Pupil to modify in place. 

132 p0 : `pair`` of `float` 

133 2-tuple indicating region center. 

134 r : `float` 

135 Circular region radius. 

136 """ 

137 r2 = (self.u - p0[0])**2 + (self.v - p0[1])**2 

138 pupil.illuminated[r2 < r**2] = False 

139 

140 def _cutCircleExterior(self, pupil, p0, r): 

141 """Cut out the exterior of a circular region from a Pupil. 

142 

143 Parameters 

144 ---------- 

145 pupil : `Pupil` 

146 Pupil to modify in place 

147 p0 : `pair`` of `float` 

148 2-tuple indicating region center. 

149 r : `float` 

150 Circular region radius. 

151 """ 

152 r2 = (self.u - p0[0])**2 + (self.v - p0[1])**2 

153 pupil.illuminated[r2 > r**2] = False 

154 

155 def _cutRay(self, pupil, p0, angle, thickness): 

156 """Cut out a ray from a Pupil. 

157 

158 Parameters 

159 ---------- 

160 pupil : `Pupil` 

161 Pupil to modify in place. 

162 p0 : `pair`` of `float` 

163 2-tuple indicating ray starting point. 

164 angle : `pair` of `float` 

165 Ray angle measured CCW from +x. 

166 thickness : `float` 

167 Thickness of cutout. 

168 """ 

169 angleRad = angle.asRadians() 

170 # the 1 is arbitrary, just need something to define another point on 

171 # the line 

172 p1 = (p0[0] + 1, p0[1] + np.tan(angleRad)) 

173 d = PupilFactory._pointLineDistance((self.u, self.v), p0, p1) 

174 pupil.illuminated[(d < 0.5*thickness) 

175 & ((self.u - p0[0])*np.cos(angleRad) 

176 + (self.v - p0[1])*np.sin(angleRad) >= 0)] = False 

177 

178 def _centerPupil(self, pupil): 

179 """Center the illuminated portion of the pupil in array. 

180 

181 Parameters 

182 ---------- 

183 pupil : `Pupil` 

184 Pupil to modify in place 

185 """ 

186 def center(arr, axis): 

187 smash = np.sum(arr, axis=axis) 

188 w = np.where(smash)[0] 

189 return int(0.5*(np.min(w)+np.max(w))) 

190 ycenter = center(pupil.illuminated, 0) 

191 xcenter = center(pupil.illuminated, 1) 

192 ytarget = pupil.illuminated.shape[0]//2 

193 xtarget = pupil.illuminated.shape[1]//2 

194 pupil.illuminated = np.roll(np.roll(pupil.illuminated, 

195 xtarget-xcenter, 

196 axis=0), 

197 ytarget-ycenter, 

198 axis=1)