Coverage for python/lsst/sims/featureScheduler/detailers/dither_detailer.py : 13%

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
1import numpy as np
2from lsst.sims.featureScheduler.detailers import Base_detailer
3from lsst.sims.utils import _approx_RaDec2AltAz, _approx_altaz2pa
6__all__ = ["Dither_detailer", "Camera_rot_detailer", "Euclid_dither_detailer"]
9def gnomonic_project_toxy(ra, dec, raCen, decCen):
10 """Calculate x/y projection of RA1/Dec1 in system with center at RAcen, Deccenp.
11 Input radians. Returns x/y."""
12 # also used in Global Telescope Network website
13 if (len(ra) != len(dec)):
14 raise Exception("Expect RA and Dec arrays input to gnomonic projection to be same length.")
15 cosc = np.sin(decCen) * np.sin(dec) + np.cos(decCen) * np.cos(dec) * np.cos(ra-raCen)
16 x = np.cos(dec) * np.sin(ra-raCen) / cosc
17 y = (np.cos(decCen)*np.sin(dec) - np.sin(decCen)*np.cos(dec)*np.cos(ra-raCen)) / cosc
18 return x, y
21def gnomonic_project_tosky(x, y, raCen, decCen):
22 """Calculate RA/Dec on sky of object with x/y and RA/Cen of field of view.
23 Returns Ra/Dec in radians."""
24 denom = np.cos(decCen) - y * np.sin(decCen)
25 ra = raCen + np.arctan2(x, denom)
26 dec = np.arctan2(np.sin(decCen) + y * np.cos(decCen), np.sqrt(x*x + denom*denom))
27 return ra, dec
30class Dither_detailer(Base_detailer):
31 """
32 make a uniform dither pattern. Offset by a maximum radius in a random direction.
33 Mostly intended for DDF pointings, the BaseMarkovDF_survey class includes dithering
34 for large areas.
36 Parameters
37 ----------
38 max_dither : float (0.7)
39 The maximum dither size to use (degrees).
40 per_night : bool (True)
41 If true, us the same dither offset for an entire night
44 """
45 def __init__(self, max_dither=0.7, seed=42, per_night=True):
46 self.survey_features = {}
48 self.current_night = -1
49 self.max_dither = np.radians(max_dither)
50 self.per_night = per_night
51 np.random.seed(seed=seed)
52 self.offset = None
54 def _generate_offsets(self, n_offsets, night):
55 if self.per_night:
56 if night != self.current_night:
57 self.current_night = night
58 self.offset = (np.random.random((1, 2))-0.5) * 2.*self.max_dither
59 angle = np.random.random(1)*2*np.pi
60 radius = self.max_dither * np.sqrt(np.random.random(1))
61 self.offset = np.array([radius*np.cos(angle), radius*np.sin(angle)])
62 offsets = np.tile(self.offset, (n_offsets, 1))
63 else:
64 angle = np.random.random(n_offsets)*2*np.pi
65 radius = self.max_dither * np.sqrt(np.random.random(n_offsets))
66 offsets = np.array([radius*np.cos(angle), radius*np.sin(angle)])
68 return offsets
70 def __call__(self, observation_list, conditions):
72 # Generate offsets in RA and Dec
73 offsets = self._generate_offsets(len(observation_list), conditions.night)
75 obs_array = np.concatenate(observation_list)
76 newRA, newDec = gnomonic_project_tosky(offsets[0, :], offsets[1, :], obs_array['RA'], obs_array['dec'])
77 for i, obs in enumerate(observation_list):
78 observation_list[i]['RA'] = newRA[i]
79 observation_list[i]['dec'] = newDec[i]
80 return observation_list
83def bearing(lon1, lat1, lon2, lat2):
84 """Bearing between two points
85 """
87 delta_l = lon2 - lon1
88 X = np.cos(lat2) * np.sin(delta_l)
89 Y = np.cos(lat1) * np.sin(lat2) - np.sin(lat1) * np.cos(lat2) * np.cos(delta_l)
90 theta = np.arctan2(X, Y)
92 return theta
95def dest(dist, bearing, lat1, lon1):
97 lat2 = np.arcsin(np.sin(lat1)*np.cos(dist)+np.cos(lat1)*np.sin(dist)*np.cos(bearing))
98 lon2 = lon1 + np.arctan2(np.sin(bearing)*np.sin(dist)*np.cos(lat1), np.cos(dist)-np.sin(lat1)*np.sin(lat2))
99 return lat2, lon2
102class Euclid_dither_detailer(Base_detailer):
103 """Directional dithering for Euclid DDFs
105 Parameters
106 ----------
107 XXX--fill in docstring
109 """
110 def __init__(self, dither_bearing_dir=[-0.25, 1], dither_bearing_perp=[-0.25, 0.25],
111 seed=42, per_night=True, ra_a=58.90,
112 dec_a=-49.315, ra_b=63.6, dec_b=-47.60):
113 self.survey_features = {}
115 self.ra_a = np.radians(ra_a)
116 self.ra_b = np.radians(ra_b)
117 self.dec_a = np.radians(dec_a)
118 self.dec_b = np.radians(dec_b)
120 self.dither_bearing_dir = np.radians(dither_bearing_dir)
121 self.dither_bearing_perp = np.radians(dither_bearing_perp)
123 self.bearing_atob = bearing(self.ra_a, self.dec_a, self.ra_b, self.dec_b)
124 self.bearing_btoa = bearing(self.ra_b, self.dec_b, self.ra_a, self.dec_a)
126 self.current_night = -1
128 self.per_night = per_night
129 np.random.seed(seed=seed)
130 self.shifted_ra_a = None
131 self.shifted_dec_a = None
132 self.shifted_ra_b = None
133 self.shifted_dec_b = None
135 def _generate_offsets(self, n_offsets, night):
136 if self.per_night:
137 if night != self.current_night:
138 self.current_night = night
139 bearing_mag = np.random.uniform(low=self.dither_bearing_dir.min(), high=self.dither_bearing_dir.max())
140 perp_mag = np.random.uniform(low=self.dither_bearing_perp.min(), high=self.dither_bearing_perp.max())
141 # Move point a along the bearings
142 self.shifted_dec_a, self.shifted_ra_a = dest(bearing_mag, self.bearing_atob, self.dec_a, self.ra_a)
143 self.shifted_dec_a, self.shifted_ra_a = dest(perp_mag, self.bearing_atob+np.pi/2.,
144 self.shifted_dec_a, self.shifted_ra_a)
146 # Shift the second position
147 bearing_mag = np.random.uniform(low=self.dither_bearing_dir.min(), high=self.dither_bearing_dir.max())
148 perp_mag = np.random.uniform(low=self.dither_bearing_perp.min(), high=self.dither_bearing_perp.max())
150 self.shifted_dec_b, self.shifted_ra_b = dest(bearing_mag, self.bearing_btoa, self.dec_b, self.ra_b)
151 self.shifted_dec_b, self.shifted_ra_b = dest(perp_mag, self.bearing_btoa+np.pi/2.,
152 self.shifted_dec_b, self.shifted_ra_b)
153 else:
154 # XXX--not implamented
155 ValueError('not implamented')
157 return self.shifted_ra_a, self.shifted_dec_a, self.shifted_ra_b, self.shifted_dec_b
159 def __call__(self, observation_list, conditions):
160 # Generate offsets in RA and Dec
161 ra_a, dec_a, ra_b, dec_b = self._generate_offsets(len(observation_list), conditions.night)
163 for i, obs in enumerate(observation_list):
164 if obs[0]['note'][-1] == 'a':
165 observation_list[i]['RA'] = ra_a
166 observation_list[i]['dec'] = dec_a
167 elif obs[0]['note'][-1] == 'b':
168 observation_list[i]['RA'] = ra_b
169 observation_list[i]['dec'] = dec_b
170 else:
171 ValueError('observation note does not end in a or b.')
172 return observation_list
175class Camera_rot_detailer(Base_detailer):
176 """
177 Randomly set the camera rotation, either for each exposure, or per night.
179 Parameters
180 ----------
181 max_rot : float (90.)
182 The maximum amount to offset the camera (degrees)
183 min_rot : float (90)
184 The minimum to offset the camera (degrees)
185 per_night : bool (True)
186 If True, only set a new offset per night. If False, randomly rotates every observation.
187 """
188 def __init__(self, max_rot=90., min_rot=-90., per_night=True, seed=42):
189 self.survey_features = {}
191 self.current_night = -1
192 self.max_rot = np.radians(max_rot)
193 self.min_rot = np.radians(min_rot)
194 self.range = self.max_rot - self.min_rot
195 self.per_night = per_night
196 np.random.seed(seed=seed)
197 self.offset = None
199 def _generate_offsets(self, n_offsets, night):
200 if self.per_night:
201 if night != self.current_night:
202 self.current_night = night
203 self.offset = np.random.random(1) * self.range + self.min_rot
204 offsets = np.ones(n_offsets) * self.offset
205 else:
206 offsets = np.random.random(n_offsets) * self.range + self.min_rot
208 offsets = offsets % (2.*np.pi)
210 return offsets
212 def __call__(self, observation_list, conditions):
214 # Generate offsets in camamera rotator
215 offsets = self._generate_offsets(len(observation_list), conditions.night)
217 for i, obs in enumerate(observation_list):
218 alt, az = _approx_RaDec2AltAz(obs['RA'], obs['dec'], conditions.site.latitude_rad,
219 conditions.site.longitude_rad, conditions.mjd)
220 obs_pa = _approx_altaz2pa(alt, az, conditions.site.latitude_rad)
221 obs['rotSkyPos'] = (offsets[i] - obs_pa) % (2.*np.pi)
222 obs['rotTelPos'] = offsets[i]
224 return observation_list