42 from typing
import List
45 """ WFS handler for compass simulation
48 wfs : (sutraWrap.Wfs) : SutraSensors instance
50 context : (carmaContext) : CarmaContext instance
52 config : (config module) : Parameters configuration structure module
54 sources : (List) : List of SutraSource instances used for raytracing
56 def __init__(self, context, config, tel):
57 """ Initialize a wfsCompass component for wfs related supervision
60 context : (carmaContext) : CarmaContext instance
62 config : (config module) : Parameters configuration structure module
64 tel : (TelescopeCompass) : A TelescopeCompass instance
72 self.
sources = [wfs.d_gs
for wfs
in self.
wfs.d_wfs]
75 """ Get an image from the WFS (wfs[0] by default), or from the centroider handling the WFS
76 to get the calibrated image
79 wfs_index : (int) : index of the WFS (or the centroider) to request an image
82 image : (np.ndarray) : WFS image
84 return np.array(self.
wfs.d_wfs[wfs_index].d_binimg)
87 *, weights: np.ndarray =
None) ->
None:
88 """ Set pyramid modulation positions
91 wfs_index : (int) : WFS index
93 cx : (np.ndarray) : X positions of the modulation points [arcsec]
95 cy : (np.ndarray) : Y positions of the modulation points [arcsec]
97 weights : (np.ndarray, optional) : Weights to apply on each modulation point contribution
100 pwfs = self.
config.p_wfss[wfs_index]
101 pwfs.set_pyr_npts(pyr_npts)
104 if weights
is not None:
108 cx, cy, weights, pyr_npts)
111 """ Set pyramid circular modulation amplitude value - in lambda/D units.
113 Compute new modulation points corresponding to the new amplitude value
115 /!\ WARNING : if you are using slopes-based centroider with the PWFS,
116 also update the centroider scale (rtc.set_scale) with the returned
120 wfs_index : (int) : WFS index
122 pyr_mod : (float) : new pyramid modulation amplitude value
125 scale : (float) : scale factor
127 p_wfs = self.
config.p_wfss[wfs_index]
129 cx, cy, scale, pyr_npts = wfs_util.comp_new_pyr_ampl(wfs_index, pyr_mod,
132 p_wfs.set_pyr_ampl(pyr_mod)
135 if (len(p_wfs._halfxy.shape) == 2):
136 print(
"PYR modulation set to: %f L/D using %d points" % (pyr_mod, pyr_npts))
137 elif (len(p_wfs._halfxy.shape) == 3):
138 newhalfxy = np.tile(p_wfs._halfxy[0, :, :], (pyr_npts, 1, 1))
139 print(
"Loading new modulation arrays")
140 self.
wfs.d_wfs[wfs_index].set_phalfxy(
141 np.exp(1j * newhalfxy).astype(np.complex64).T)
142 print(
"Done. PYR modulation set to: %f L/D using %d points" % (pyr_mod,
145 raise ValueError(
"Error unknown p_wfs._halfxy shape")
150 *, weights: List =
None, pyr_mod: float = 3.,
151 niters: int =
None) ->
None:
152 """ Sets the Pyramid modulation points with a multiple star system
155 wfs_index : (int) : WFS index
157 coords : (list) : list of couples of length n, coordinates of the n stars in lambda/D
159 weights : (list, optional) : list of weights to apply on each modulation points. Default is None
161 pyr_mod : (float, optional): modulation amplitude of the pyramid in lambda/D. Default is 3
163 niters : (int, optional) : number of iteration. Default is None
166 perim = pyr_mod * 2 * np.pi
167 niters = int((perim // 4 + 1) * 4)
169 scale_circ = self.
config.p_wfss[wfs_index]._pyr_scale_pos * pyr_mod
173 temp_cx.append(scale_circ * \
174 np.sin((np.arange(niters)) * 2. * np.pi / niters) + \
175 k[0] * self.
config.p_wfss[wfs_index]._pyr_scale_pos)
176 temp_cy.append(scale_circ * \
177 np.cos((np.arange(niters)) * 2. * np.pi / niters) + \
178 k[1] * self.
config.p_wfss[wfs_index]._pyr_scale_pos)
179 cx = np.concatenate(np.array(temp_cx))
180 cy = np.concatenate(np.array(temp_cy))
182 if weights
is not None:
186 weights = np.array(w)
190 """ Create disk object by packing PSF in a given radius, using hexagonal packing
191 and set it as modulation pattern
193 /!\ There is no modulation
196 wfs_index : (int) : WFS index
198 radius : (float) : radius of the disk object in lambda/D
201 gen_xp, gen_yp = np.array([1,
202 0.]), np.array([np.cos(np.pi / 3),
204 n = 1 + int(1.2 * radius)
206 for k
in range(-n, n):
207 for l
in range(-n, n):
208 coord = k * gen_xp + l * gen_yp
209 if np.sqrt(coord[0]**2 + coord[1]**2) <= radius:
210 mat_circ.append(coord)
211 mat_circ = np.array(mat_circ)
212 cx, cy = mat_circ[:, 0], mat_circ[:, 1]
216 def set_pyr_disk_source(self, wfs_index: int, radius: float, *, density: float = 1.) ->
None:
217 """ Create disk object by packing PSF in a given radius, using square packing
218 and set it as modulation pattern
220 /!\ There is no modulation
223 wfs_index : (int) : WFS index
225 radius : (float) : radius of the disk object in lambda/D
227 density : (float, optional) : Spacing between the packed PSF in the disk object, in lambda/D.
230 cx, cy = util.generate_circle(radius, density)
231 cx = cx.flatten() * self.
config.p_wfss[wfs_index]._pyr_scale_pos
232 cy = cy.flatten() * self.
config.p_wfss[wfs_index]._pyr_scale_pos
236 """ Create a square object by packing PSF in a given radius, using square packing
237 and set it as modulation pattern
239 /!\ There is no modulation
242 wfs_index : (int) : WFS index
244 radius : (float) : radius of the disk object in lambda/D
246 density : (float, optional) : Spacing between the packed PSF in the disk object, in lambda/D.
249 cx, cy = util.generate_square(radius, density)
250 cx = cx.flatten() * self.
config.p_wfss[wfs_index]._pyr_scale_pos
251 cy = cy.flatten() * self.
config.p_wfss[wfs_index]._pyr_scale_pos
256 additional_psf: int = 0, density: float = 1.) ->
None:
257 """ TODO : DESCRIPTION
260 wfs_index : (int) : WFS index
262 radius : (float) : TODO : DESCRIPTION
264 additional_psf : (int, optional) : TODO : DESCRIPTION
266 density : (float, optional) :TODO : DESCRIPTION
268 cx, cy, weights, _, _ = util.generate_pseudo_source(radius, additional_psf,
270 cx = cx.flatten() * self.
config.p_wfss[wfs_index]._pyr_scale_pos
271 cy = cy.flatten() * self.
config.p_wfss[wfs_index]._pyr_scale_pos
275 """ Set a mask in the Fourier Plane of the given WFS
278 wfs_index : (int, optional) : WFS index
280 new_mask : (ndarray) : mask to set
282 if new_mask.shape != self.
config.p_wfss[wfs_index].get_halfxy().shape:
283 print(
'Error : mask shape should be {}'.format(
284 self.
config.p_wfss[wfs_index].get_halfxy().shape))
286 self.
wfs.d_wfs[wfs_index].set_phalfxy(
287 np.exp(1j * np.fft.fftshift(new_mask)).astype(np.complex64).T)
289 def set_noise(self, wfs_index : int, noise: float, *, seed: int = 1234) ->
None:
290 """ Set noise value of WFS wfs_index
293 wfs_index : (int, optional) : WFS index
295 noise : (float) : readout noise value in e-
297 seed : (int, optional) : RNG seed. The seed used will be computed as seed + wfs_index
300 self.
wfs.d_wfs[wfs_index].
set_noise(noise, int(seed + wfs_index))
301 print(
"Noise set to: %f on WFS %d" % (noise, wfs_index))
303 def set_gs_mag(self, wfs_index : int, mag : float) ->
None:
304 """ Change the guide star magnitude for the given WFS
307 wfs_index : (int, optional) : WFS index
309 mag : (float) : New magnitude of the guide star
311 wfs = self.
wfs.d_wfs[wfs_index]
312 if (self.
config.p_wfs0.type ==
"pyrhr"):
313 r = wfs.comp_nphot(self.
config.p_loop.ittime,
314 self.
config.p_wfss[wfs_index].optthroughput,
316 self.
config.p_wfss[wfs_index].zerop, mag)
318 r = wfs.comp_nphot(self.
config.p_loop.ittime,
319 self.
config.p_wfss[wfs_index].optthroughput,
320 self.
config.p_tel.diam, self.
config.p_wfss[wfs_index].nxsub,
321 self.
config.p_wfss[wfs_index].zerop, mag)
323 print(
"GS magnitude is now %f on WFS %d" % (mag, wfs_index))
326 """ Computes the image produced by the WFS from its phase screen
329 wfs_index : (int): WFS index
331 noise : (bool, optional) : Flag to enable noise for image computation. Default is True
333 self.
wfs.d_wfs[wfs_index].comp_image(noise)
336 """ Reset all the WFS RNG to their original state
338 for wfs_index, p_wfs
in enumerate(self.
config.p_wfss):
339 self.
wfs.d_wfs[wfs_index].
set_noise(p_wfs.noise, 1234 + wfs_index)
342 """ Return the current NCPA phase screen of the WFS path
345 wfs_index : (int) : Index of the WFS
348 ncpa : (np.ndarray) : NCPA phase screen
350 return np.array(self.
wfs.d_wfs[wfs_index].d_gs.d_ncpa_phase)
353 """ Return the WFS phase screen of WFS number wfs_index
356 wfs_index : (int) : Index of the WFS
359 phase : (np.ndarray) : WFS phase screen
361 return np.array(self.
wfs.d_wfs[wfs_index].d_gs.d_phase)
364 """ Get an high resolution image from the PWFS
367 wfs_index : (int) : Index of the WFS
370 image : (np.ndarray) : PWFS high resolution image
373 return np.array(self.
wfs.d_wfs[wfs_index].d_hrimg)
375 def set_ncpa_wfs(self, wfs_index : int, ncpa: np.ndarray) ->
None:
376 """ Set the additional fixed NCPA phase in the WFS path.
377 ncpa must be of the same size of the mpupil support
380 wfs_index : (int) : WFS index
382 ncpa : (ndarray) : NCPA phase screen to set [µm]
384 self.
wfs.d_wfs[wfs_index].d_gs.set_ncpa(ncpa)
386 def set_wfs_phase(self, wfs_index : int, phase : np.ndarray) ->
None:
387 """ Set the phase screen seen by the WFS
390 wfs_index : (int) : WFS index
392 phase : (np.ndarray) : phase screen to set
394 self.
wfs.d_wfs[wfs_index].d_gs.set_phase(phase)
396 def set_wfs_pupil(self, wfs_index : int, pupil : np.ndarray) ->
None:
397 """ Set the pupil seen by the WFS
398 Other pupils remain unchanged, i.e. DM and target can see an other
399 pupil than the WFS after this call.
400 <pupil> must have the same shape than p_geom._mpupil support
403 wfs_index : (int) : WFS index
405 pupil : (np.ndarray) : new pupil to set
407 old_mpup = self.
config.p_geom._mpupil
408 dimx = old_mpup.shape[0]
409 dimy = old_mpup.shape[1]
410 if ((pupil.shape[0] != dimx)
or (pupil.shape[1] != dimy)):
411 print(
"Error pupil shape on wfs %d must be: (%d,%d)" % (wfs_index, dimx,
414 self.
wfs.d_wfs[wfs_index].set_pupil(pupil.copy())
417 """ Returns the psf on the top of the pyramid.
421 wfs_index : (int) : WFS index
424 focal_plane : (np.ndarray) : psf on the top of the pyramid
426 return np.fft.fftshift(np.array(self.
wfs.d_wfs[wfs_index].d_pyrfocalplane))