Coverage for python/lsst/obs/subaru/strayLight/rotatorAngle.py: 17%
141 statements
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-25 13:10 +0000
« prev ^ index » next coverage.py v7.4.0, created at 2024-01-25 13:10 +0000
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/>.
17"""Module to calculate instrument rotator angle at start and end of observation
18"""
20__all__ = ["inrStartEnd"]
22import numpy as np
23from astropy.io import fits
24import lsst.geom
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)
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
35# scale height of air
36air_sh = 0.00130
39def _mjd2jc2000(mjd):
40 """convert mjd to Julian century (J2000.0 origin)"""
41 jc2000 = (mjd - mjd_J2000) / 36525.0
42 return jc2000
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
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
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
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
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
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
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
118def _air_idx():
119 """return the air refraction index"""
120 return air_idx
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)
138def _mal2aal(mal_d):
139 """convert mean altitude to apparent altitude"""
140 aal_d = mal_d + _atm_ref(mal_d)
141 return aal_d
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)
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))
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
162 return ad_d, pa_d
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
174def _gsCPposNorth(ra_t_d, de_t_d, inr_d, mjd):
175 jc2000 = _mjd2jc2000(mjd)
176 pm = _precessionMatrix(jc2000)
178 vt = _sph2vec(ra_t_d, de_t_d)
179 vt_mean = np.dot(pm, vt)
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
185 gmst_d = _mjd2gmst(mjd)
186 lmst_d = _gmst2lmst(gmst_d)
188 mean_ha_t_d = _ra2ha(mean_ra_t_d, lmst_d)
189 mean_ha_s_d = _ra2ha(mean_ra_s_d, lmst_d)
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)
194 apparent_al_t_d = _mal2aal(mean_al_t_d)
195 apparent_al_s_d = _mal2aal(mean_al_s_d)
197 delta_az_d = mean_az_s_d - mean_az_t_d
199 (ang_dist_d, p_ang_d) = _pos2adt(apparent_al_t_d, apparent_al_s_d, delta_az_d)
201 (x, y) = _addpad2xy(ang_dist_d, p_ang_d, inr_d)
202 return x, y
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
214def _minorArc(angle1, angle2):
215 """e.g. input (-179, 179) -> output (-179, -181)"""
217 angle1 = (angle1 + 180.0) % 360 - 180.0
218 angle2 = (angle2 + 180.0) % 360 - 180.0
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
227 # Try to place [angle1, angle2] within [-270, +270]
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
236 return angle1, angle2
239def inrStartEnd(visitInfo):
240 """Calculate instrument rotator angle for start and end of exposure
242 Parameters
243 ----------
244 visitInfo : `lsst.afw.image.VisitInfo`
245 Visit info for the exposure to calculate correction.
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 """
255 inst_pa = 270.0 - visitInfo.getBoresightRotAngle().asAngularUnits(lsst.geom.degrees)
256 ra_t_sp, de_t_sp = visitInfo.getBoresightRaDec()
258 ra_t_d = ra_t_sp.asAngularUnits(lsst.geom.degrees)
259 de_t_d = de_t_sp.asAngularUnits(lsst.geom.degrees)
261 mjd_str = visitInfo.getDate().get() - 0.5*visitInfo.getExposureTime()/86400.0
262 mjd_end = visitInfo.getDate().get() + 0.5*visitInfo.getExposureTime()/86400.0
264 inr_d = 0.00
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
271 return _minorArc(x_inr_str, x_inr_end)