Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

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__all__ = ["inrStartEnd"] 

20 

21import numpy as np 

22from astropy.io import fits 

23import lsst.geom 

24 

25# fixed parameters 

26ltt_d = 19.82556 # dome latitude in degree 

27lng_d = -155.47611 # dome longitude in degree 

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

29 

30# refraction index of air 

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

32air_idx = 1.0 + 1.7347e-04 

33 

34# scale height of air 

35air_sh = 0.00130 

36 

37 

38def _mjd2jc2000(mjd): 

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

40 jc2000 = (mjd - mjd_J2000) / 36525.0 

41 return jc2000 

42 

43 

44def _precessionMatrix(jc2000): 

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

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

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

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

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

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

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

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

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

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

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

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

57 +np.cos(theta_A)]]) 

58 return precMat 

59 

60 

61def _mjd2gmst(mjd): 

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

63 mjd_f = mjd % 1 

64 jc2000 = _mjd2jc2000(mjd) 

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

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

67 mjd_f*86400.0) 

68 gmst_d = (gmst_s % 86400)/240.0 

69 return gmst_d 

70 

71 

72def _gmst2lmst(gmst_d): 

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

74 lmst_d = (gmst_d + lng_d) % 360 

75 return lmst_d 

76 

77 

78def _sph2vec(ra_d, de_d): 

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

80 ra_r = np.deg2rad(ra_d) 

81 de_r = np.deg2rad(de_d) 

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

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

84 [np.sin(de_r)]]) 

85 return vec 

86 

87 

88def _vec2sph(vec): 

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

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

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

92 ra_d = np.rad2deg(ra_r) 

93 de_d = np.rad2deg(de_r) 

94 return ra_d, de_d 

95 

96 

97def _ra2ha(ra_d, lst_d): 

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

99 ha_d = (lst_d - ra_d)%360 

100 return ha_d 

101 

102 

103def _eq2hz(ha_d, de_d): 

104 """convert equatorial coordinates to the horizontal coordinates""" 

105 ltt_r = np.deg2rad(ltt_d) 

106 ha_r = np.deg2rad(ha_d) 

107 de_r = np.deg2rad(de_d) 

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

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

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

111 zd_d = np.rad2deg(zd_r) 

112 az_d = np.rad2deg(az_r) 

113 al_d = 90.0 - zd_d 

114 return al_d, az_d 

115 

116 

117def _air_idx(): 

118 """return the air refraction index""" 

119 return air_idx 

120 

121 

122def _atm_ref(al_d): 

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

124 if al_d > 20.0: 

125 zd_r = np.deg2rad(90.0 - al_d) 

126 else: 

127 zd_r = np.deg2rad(70.0) 

128 r0 = _air_idx()-1.0 

129 sh = air_sh 

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

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

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

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

134 return np.rad2deg(R) 

135 

136 

137def _mal2aal(mal_d): 

138 """convert mean altitude to apparent altitude""" 

139 aal_d = mal_d + _atm_ref(mal_d) 

140 return aal_d 

141 

142 

143def _pos2adt(al_t_d, al_s_d, delta_az_d): 

144 """convert altitudes of telescope and star and relative azimuth to angular distance and position angle""" 

145 zd_t_r = np.deg2rad(90.0-al_t_d) 

146 zd_s_r = np.deg2rad(90.0-al_s_d) 

147 daz_r = np.deg2rad(delta_az_d) 

148 

149 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)) 

150 

151 if ad_r > 0.0: 

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

153 else: 

154 pa_r = 0.0 

155 ad_d = np.rad2deg(ad_r) 

156 pa_d = np.rad2deg(pa_r) 

157 if (zd_t_r < zd_s_r): 

158 pa_d = 180.0 - pa_d 

159 

160 return ad_d, pa_d 

161 

162 

163def _addpad2xy(ang_dist_d, p_ang_d, inr_d): 

164 """convert angular distance, position angle, and instrument rotator angle to position on the cold plate""" 

165 t = 90.0-(p_ang_d-inr_d) 

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

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

168 return x, y 

169 

170 

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

172 jc2000 = _mjd2jc2000(mjd) 

173 pm = _precessionMatrix(jc2000) 

174 

175 vt = _sph2vec(ra_t_d, de_t_d) 

176 vt_mean = np.dot(pm, vt) 

177 

178 (mean_ra_t_d, mean_de_t_d) = _vec2sph(vt_mean) 

179 mean_ra_s_d = mean_ra_t_d 

180 mean_de_s_d = mean_de_t_d+0.75 

181 

182 gmst_d = _mjd2gmst(mjd) 

183 lmst_d = _gmst2lmst(gmst_d) 

184 

185 mean_ha_t_d = _ra2ha(mean_ra_t_d, lmst_d) 

186 mean_ha_s_d = _ra2ha(mean_ra_s_d, lmst_d) 

187 

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

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

190 

191 apparent_al_t_d = _mal2aal(mean_al_t_d) 

192 apparent_al_s_d = _mal2aal(mean_al_s_d) 

193 

194 delta_az_d = mean_az_s_d - mean_az_t_d 

195 

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

197 

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

199 return x, y 

200 

201 

202def _getDataArrayFromFITSFileWithHeader(pathToFITSFile): 

203 """return array of pixel data""" 

204 fitsfile = fits.open(pathToFITSFile) 

205 dataArray = fitsfile[0].data 

206 fitsHeader = fitsfile[0].header 

207 fitsfile.close() 

208 return dataArray, fitsHeader 

209 

210 

211def _minorArc(angle1, angle2): 

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

213 

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

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

216 

217 if angle1 < angle2: 

218 if angle2 - angle1 > 180.0: 

219 angle2 -= 360.0 

220 elif angle2 < angle1: 

221 if angle1 - angle2 > 180.0: 

222 angle1 -= 360.0 

223 

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

225 

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

227 angle1 += 360.0 

228 angle2 += 360.0 

229 if max(angle1, angle2) > 270.0: 

230 angle1 -= 360.0 

231 angle2 -= 360.0 

232 

233 return angle1, angle2 

234 

235 

236def inrStartEnd(visitInfo): 

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

238 

239 Parameters 

240 ---------- 

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

242 Visit info for the exposure to calculate correction. 

243 

244 Returns 

245 ------- 

246 start : `float` 

247 Instrument rotator angle at start of exposure, degrees. 

248 end : `float` 

249 Instrument rotator angle at end of exposure, degrees. 

250 """ 

251 

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

253 ra_t_sp, de_t_sp = visitInfo.getBoresightRaDec() 

254 

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

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

257 

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

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

260 

261 inr_d = 0.00 

262 

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

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

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

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

267 

268 return _minorArc(x_inr_str, x_inr_end)