Coverage for python/lsst/obs/subaru/strayLight/rotatorAngle.py: 17%

141 statements  

« prev     ^ index     » next       coverage.py v6.5.0, created at 2023-01-13 04:05 -0800

1# Copyright (C) 2017 HSC Software Team 

2# Copyright (C) 2017 Satoshi Kawanomoto 

3# 

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

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

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

7# (at your option) any later version. 

8# 

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

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

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

12# GNU General Public License for more details. 

13# 

14# You should have received a copy of the GNU General Public License 

15# along with this program. If not, see <http://www.gnu.org/licenses/>. 

16 

17"""Module to calculate instrument rotator angle at start and end of observation 

18""" 

19 

20__all__ = ["inrStartEnd"] 

21 

22import numpy as np 

23from astropy.io import fits 

24import lsst.geom 

25 

26# fixed parameters 

27ltt_d = 19.82556 # dome latitude in degree 

28lng_d = -155.47611 # dome longitude in degree 

29mjd_J2000 = 51544.5 # mjd at J2000.0 (2000/01/01.5) 

30 

31# refraction index of air 

32# T=273.15[K], P=600[hPa], Pw=1.5[hPa], lambda=0.55[um] 

33air_idx = 1.0 + 1.7347e-04 

34 

35# scale height of air 

36air_sh = 0.00130 

37 

38 

39def _mjd2jc2000(mjd): 

40 """convert mjd to Julian century (J2000.0 origin)""" 

41 jc2000 = (mjd - mjd_J2000) / 36525.0 

42 return jc2000 

43 

44 

45def _precessionMatrix(jc2000): 

46 """create precession matrix at the given time in Julian century""" 

47 zeta_A = np.deg2rad((2306.2181*jc2000 + 0.30188*jc2000**2.0 + 0.017998*jc2000**3.0)/3600.0) 

48 z_A = np.deg2rad((2306.2181*jc2000 + 1.09468*jc2000**2.0 + 0.018203*jc2000**3.0)/3600.0) 

49 theta_A = np.deg2rad((2004.3109*jc2000 - 0.42665*jc2000**2.0 - 0.041833*jc2000**3.0)/3600.0) 

50 precMat = np.matrix([[+np.cos(zeta_A)*np.cos(theta_A)*np.cos(z_A) - np.sin(zeta_A)*np.sin(z_A), 

51 -np.sin(zeta_A)*np.cos(theta_A)*np.cos(z_A) - np.cos(zeta_A)*np.sin(z_A), 

52 -np.sin(theta_A)*np.cos(z_A)], 

53 [+np.cos(zeta_A)*np.cos(theta_A)*np.sin(z_A) + np.sin(zeta_A)*np.cos(z_A), 

54 -np.sin(zeta_A)*np.cos(theta_A)*np.sin(z_A) + np.cos(zeta_A)*np.cos(z_A), 

55 -np.sin(theta_A)*np.sin(z_A)], 

56 [+np.cos(zeta_A)*np.sin(theta_A), 

57 -np.sin(zeta_A)*np.sin(theta_A), 

58 +np.cos(theta_A)]]) 

59 return precMat 

60 

61 

62def _mjd2gmst(mjd): 

63 """convert mjd to GMST(Greenwich mean sidereal time)""" 

64 mjd_f = mjd % 1 

65 jc2000 = _mjd2jc2000(mjd) 

66 gmst_s = ((6.0*3600.0 + 41.0*60.0 + 50.54841) 

67 + 8640184.812866*jc2000 + 0.093104*jc2000**2.0 - 0.0000062*jc2000**3.0 

68 + mjd_f*86400.0) 

69 gmst_d = (gmst_s % 86400)/240.0 

70 return gmst_d 

71 

72 

73def _gmst2lmst(gmst_d): 

74 """convert GMST to LMST(mean local sidereal time)""" 

75 lmst_d = (gmst_d + lng_d) % 360 

76 return lmst_d 

77 

78 

79def _sph2vec(ra_d, de_d): 

80 """convert spherical coordinate to the Cartesian coordinates (vector)""" 

81 ra_r = np.deg2rad(ra_d) 

82 de_r = np.deg2rad(de_d) 

83 vec = np.array([[np.cos(ra_r)*np.cos(de_r)], 

84 [np.sin(ra_r)*np.cos(de_r)], 

85 [np.sin(de_r)]]) 

86 return vec 

87 

88 

89def _vec2sph(vec): 

90 """convert the Cartesian coordinates vector to shperical coordinates""" 

91 ra_r = np.arctan2(vec[1, 0], vec[0, 0]) 

92 de_r = np.arcsin(vec[2, 0]) 

93 ra_d = np.rad2deg(ra_r) 

94 de_d = np.rad2deg(de_r) 

95 return ra_d, de_d 

96 

97 

98def _ra2ha(ra_d, lst_d): 

99 """convert right ascension to hour angle at given LST""" 

100 ha_d = (lst_d - ra_d)%360 

101 return ha_d 

102 

103 

104def _eq2hz(ha_d, de_d): 

105 """convert equatorial coordinates to the horizontal coordinates""" 

106 ltt_r = np.deg2rad(ltt_d) 

107 ha_r = np.deg2rad(ha_d) 

108 de_r = np.deg2rad(de_d) 

109 zd_r = np.arccos(+np.sin(ltt_r)*np.sin(de_r) + np.cos(ltt_r)*np.cos(de_r)*np.cos(ha_r)) 

110 az_r = np.arctan2(+np.cos(de_r)*np.sin(ha_r), 

111 -np.cos(ltt_r)*np.sin(de_r) + np.sin(ltt_r)*np.cos(de_r)*np.cos(ha_r)) 

112 zd_d = np.rad2deg(zd_r) 

113 az_d = np.rad2deg(az_r) 

114 al_d = 90.0 - zd_d 

115 return al_d, az_d 

116 

117 

118def _air_idx(): 

119 """return the air refraction index""" 

120 return air_idx 

121 

122 

123def _atm_ref(al_d): 

124 """return the atmospheric refraction at given altitude""" 

125 if al_d > 20.0: 

126 zd_r = np.deg2rad(90.0 - al_d) 

127 else: 

128 zd_r = np.deg2rad(70.0) 

129 r0 = _air_idx()-1.0 

130 sh = air_sh 

131 R0 = (1.0 - sh)*r0 - sh*r0**2/2.0 + sh**2*r0*2.0 

132 R1 = r0**2/2.0 + r0**3/6.0 - sh*r0 - sh*r0**2*11.0/4.0 + sh**2*r0*5.0 

133 R2 = r0**3 - sh*r0**2*9.0/4.0 + sh**2*r0*3.0 

134 R = R0*np.tan(zd_r) + R1*(np.tan(zd_r))**3 + R2*(np.tan(zd_r))**5 

135 return np.rad2deg(R) 

136 

137 

138def _mal2aal(mal_d): 

139 """convert mean altitude to apparent altitude""" 

140 aal_d = mal_d + _atm_ref(mal_d) 

141 return aal_d 

142 

143 

144def _pos2adt(al_t_d, al_s_d, delta_az_d): 

145 """convert altitudes of telescope and star and relative azimuth to angular 

146 distance and position angle""" 

147 zd_t_r = np.deg2rad(90.0-al_t_d) 

148 zd_s_r = np.deg2rad(90.0-al_s_d) 

149 daz_r = np.deg2rad(delta_az_d) 

150 

151 ad_r = np.arccos(np.cos(zd_t_r)*np.cos(zd_s_r) + np.sin(zd_t_r)*np.sin(zd_s_r)*np.cos(daz_r)) 

152 

153 if ad_r > 0.0: 

154 pa_r = np.arcsin(np.sin(zd_s_r)*np.sin(daz_r)/np.sin(ad_r)) 

155 else: 

156 pa_r = 0.0 

157 ad_d = np.rad2deg(ad_r) 

158 pa_d = np.rad2deg(pa_r) 

159 if (zd_t_r < zd_s_r): 

160 pa_d = 180.0 - pa_d 

161 

162 return ad_d, pa_d 

163 

164 

165def _addpad2xy(ang_dist_d, p_ang_d, inr_d): 

166 """convert angular distance, position angle, and instrument rotator angle 

167 to position on the cold plate""" 

168 t = 90.0-(p_ang_d-inr_d) 

169 x = np.cos(np.deg2rad(t)) 

170 y = np.sin(np.deg2rad(t)) 

171 return x, y 

172 

173 

174def _gsCPposNorth(ra_t_d, de_t_d, inr_d, mjd): 

175 jc2000 = _mjd2jc2000(mjd) 

176 pm = _precessionMatrix(jc2000) 

177 

178 vt = _sph2vec(ra_t_d, de_t_d) 

179 vt_mean = np.dot(pm, vt) 

180 

181 (mean_ra_t_d, mean_de_t_d) = _vec2sph(vt_mean) 

182 mean_ra_s_d = mean_ra_t_d 

183 mean_de_s_d = mean_de_t_d+0.75 

184 

185 gmst_d = _mjd2gmst(mjd) 

186 lmst_d = _gmst2lmst(gmst_d) 

187 

188 mean_ha_t_d = _ra2ha(mean_ra_t_d, lmst_d) 

189 mean_ha_s_d = _ra2ha(mean_ra_s_d, lmst_d) 

190 

191 (mean_al_t_d, mean_az_t_d) = _eq2hz(mean_ha_t_d, mean_de_t_d) 

192 (mean_al_s_d, mean_az_s_d) = _eq2hz(mean_ha_s_d, mean_de_s_d) 

193 

194 apparent_al_t_d = _mal2aal(mean_al_t_d) 

195 apparent_al_s_d = _mal2aal(mean_al_s_d) 

196 

197 delta_az_d = mean_az_s_d - mean_az_t_d 

198 

199 (ang_dist_d, p_ang_d) = _pos2adt(apparent_al_t_d, apparent_al_s_d, delta_az_d) 

200 

201 (x, y) = _addpad2xy(ang_dist_d, p_ang_d, inr_d) 

202 return x, y 

203 

204 

205def _getDataArrayFromFITSFileWithHeader(pathToFITSFile): 

206 """return array of pixel data""" 

207 fitsfile = fits.open(pathToFITSFile) 

208 dataArray = fitsfile[0].data 

209 fitsHeader = fitsfile[0].header 

210 fitsfile.close() 

211 return dataArray, fitsHeader 

212 

213 

214def _minorArc(angle1, angle2): 

215 """e.g. input (-179, 179) -> output (-179, -181)""" 

216 

217 angle1 = (angle1 + 180.0) % 360 - 180.0 

218 angle2 = (angle2 + 180.0) % 360 - 180.0 

219 

220 if angle1 < angle2: 

221 if angle2 - angle1 > 180.0: 

222 angle2 -= 360.0 

223 elif angle2 < angle1: 

224 if angle1 - angle2 > 180.0: 

225 angle1 -= 360.0 

226 

227 # Try to place [angle1, angle2] within [-270, +270] 

228 

229 if min(angle1, angle2) < -270.0: 

230 angle1 += 360.0 

231 angle2 += 360.0 

232 if max(angle1, angle2) > 270.0: 

233 angle1 -= 360.0 

234 angle2 -= 360.0 

235 

236 return angle1, angle2 

237 

238 

239def inrStartEnd(visitInfo): 

240 """Calculate instrument rotator angle for start and end of exposure 

241 

242 Parameters 

243 ---------- 

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

245 Visit info for the exposure to calculate correction. 

246 

247 Returns 

248 ------- 

249 start : `float` 

250 Instrument rotator angle at start of exposure, degrees. 

251 end : `float` 

252 Instrument rotator angle at end of exposure, degrees. 

253 """ 

254 

255 inst_pa = 270.0 - visitInfo.getBoresightRotAngle().asAngularUnits(lsst.geom.degrees) 

256 ra_t_sp, de_t_sp = visitInfo.getBoresightRaDec() 

257 

258 ra_t_d = ra_t_sp.asAngularUnits(lsst.geom.degrees) 

259 de_t_d = de_t_sp.asAngularUnits(lsst.geom.degrees) 

260 

261 mjd_str = visitInfo.getDate().get() - 0.5*visitInfo.getExposureTime()/86400.0 

262 mjd_end = visitInfo.getDate().get() + 0.5*visitInfo.getExposureTime()/86400.0 

263 

264 inr_d = 0.00 

265 

266 (x, y) = _gsCPposNorth(ra_t_d, de_t_d, inr_d, mjd_str) 

267 x_inr_str = 90.0 - np.rad2deg(np.arctan2(y, x)) + inst_pa 

268 (x, y) = _gsCPposNorth(ra_t_d, de_t_d, inr_d, mjd_end) 

269 x_inr_end = 90.0 - np.rad2deg(np.arctan2(y, x)) + inst_pa 

270 

271 return _minorArc(x_inr_str, x_inr_end)