COMPASS  5.4.4
End-to-end AO simulation tool using GPU acceleration
coronagraph_utils.py
1 import numpy as np
2 
3 
4 # ------------------------ #
5 # Generic pupils #
6 # ------------------------ #
7 
8 def roundpupil(dim_pp, prad, center_pos='b'):
9  """Create a circular pupil.
10 
11  AUTHORS : Axel Pottier, Johan Mazoyer
12  7/9/22 Modified by J Mazoyer to remove the pixel crenellation with rebin and add a better center option
13 
14  Parameters
15  ----------
16  dim_pp : int
17  Size of the image (in pixels)
18  prad : float
19  Size of the pupil radius (in pixels)
20  center_pos : string (optional, default 'b')
21  Option for the center pixel.
22  If 'p', center on the pixel dim_pp//2.
23  If 'b', center in between pixels dim_pp//2 -1 and dim_pp//2, for 'dim_pp' odd or even.
24 
25  Returns
26  -------
27  pupilnormal : 2D array
28  Output circular pupil
29  """
30 
31  xx, yy = np.meshgrid(np.arange(dim_pp) - dim_pp // 2, np.arange(dim_pp) - dim_pp // 2)
32 
33  if center_pos.lower() == 'b':
34  offset = 1 / 2
35  elif center_pos.lower() == 'p':
36  offset = 0
37  else:
38  raise ValueError("center_pos must be 'p' (centered on pixel) or 'b' (centered in between 4 pixels)")
39 
40  rr = np.hypot(yy + offset, xx + offset)
41  pupilnormal = np.zeros((dim_pp, dim_pp))
42  pupilnormal[rr <= prad] = 1.0
43 
44  return pupilnormal
45 
46 def classical_lyot_fpm(rad_lyot_fpm, dim_fpm, Lyot_fpm_sampling, wav_vec):
47  """Create a classical Lyot coronagraph of radius rad_LyotFP.
48 
49  AUTHOR : Johan Mazoyer
50 
51  Returns
52  -------
53  ClassicalLyotFPM_allwl : list of 2D numpy arrays
54  The FP masks at all wavelengths.
55  """
56 
57  rad_LyotFP_pix = rad_lyot_fpm * Lyot_fpm_sampling
58  ClassicalLyotFPM = 1. - roundpupil(dim_fpm, rad_LyotFP_pix)
59 
60  ClassicalLyotFPM_allwl = []
61  for wav in wav_vec:
62  ClassicalLyotFPM_allwl.append(ClassicalLyotFPM)
63 
64  return ClassicalLyotFPM_allwl
65 
66 
67 # ------------------------------------ #
68 # SPHERE/VLT specific pupils #
69 # ------------------------------------ #
70 
71 def make_VLT_pupil(pupdiam, centralobs = 0.14, spiders = 0.00625, spiders_bool = True, centralobs_bool = True):
72  """
73  Return a VLT pupil
74  based on make_VLT function from shesha/shesha/util/make_pupil.py
75 
76  Args :
77  pupdiam (int) [pixel] : pupil diameter
78 
79  centralobs (float, optional) [fraction of diameter] : central obtruction diameter, default = 0.14
80 
81  spiders (float, optional) [fraction of diameter] : spider diameter, default = 0.00625
82 
83  spiders_bool (bool, optional) : if False, return the VLT pupil without spiders; default = True
84 
85  centralobs_bool (bool, optional) : if False, return the VLT pupil without central obstruction; default = True
86 
87  Returns :
88  VLT_pupil (2D array) : VLT transmission pupil of shape (pupdiam, pupdiam), filled with 0 and 1
89  """
90  range = (0.5 * (1) - 0.25 / pupdiam)
91  X = np.tile(np.linspace(-range, range, pupdiam, dtype=np.float32), (pupdiam, 1))
92  R = np.sqrt(X**2 + (X.T)**2)
93 
94  if centralobs_bool :
95  VLT_pupil = ((R < 0.5) & (R > (centralobs / 2))).astype(np.float32)
96  elif centralobs_bool == False :
97  VLT_pupil = (R < 0.5).astype(np.float32)
98 
99  if spiders_bool:
100  angle = 50.5 * np.pi / 180. # 50.5 degrees = angle between spiders
101 
102  if pupdiam % 2 == 0 :
103  spiders_map = (
104  (X.T >
105  (X - centralobs / 2 + spiders / np.sin(angle)) * np.tan(angle)) +
106  (X.T < (X - centralobs / 2) * np.tan(angle))) * (X > 0) * (X.T > 0)
107  elif pupdiam % 2 == 1 :
108  spiders_map = (
109  (X.T >
110  (X - centralobs / 2 + spiders / np.sin(angle)) * np.tan(angle)) +
111  (X.T < (X - centralobs / 2) * np.tan(angle))) * (X >= 0) * (X.T >= 0)
112  spiders_map += np.fliplr(spiders_map)
113  spiders_map += np.flipud(spiders_map)
114  VLT_pupil = VLT_pupil * spiders_map
115 
116  return VLT_pupil
117 
119  """
120  Compute the transmission radial profile of the SPHERE APO1 apodizer.
121  x is the radial coordinate inside the pupil
122  x = 0 at the center of the pupil and x = 1 on the outer edge
123  This profile has been estimated with a five order polynomial fit.
124  Don't go inside the central obstruction, namely x < 0.14,
125  as the fit is no longer reliable.
126 
127  Parameters
128  ----------
129  x : float or array
130  distance to the pupil center, in fraction of the pupil radius
131 
132  Returns
133  -------
134  profile : float or array
135  corresponding transmission
136  """
137  a = 0.16544446129778326
138  b = 4.840243632913415
139  c = -12.02291052479871
140  d = 7.549499000031292
141  e = -1.031115714037546
142  f = 0.8227341447351052
143  profile = a * x**5 + b * x**4 + c * x**3 + d * x**2 + e * x + f
144  return profile
145 
146 def make_sphere_apodizer(pupdiam, centralobs = 0.14, radial_profile = sphere_apodizer_radial_profile):
147  """
148  Return the SPHERE APLC apodizer APO1, based on its radial transmission profile
149 
150  can be used with any profile, assuming that the radial coordinates is 0 at center
151  and 1 at the outer edge pixel
152 
153  Args :
154  pupdiam (int) [pixel] : pupil diameter
155 
156  centralobs (float, optional) [fraction of diameter] : central obtruction diameter
157 
158  radialProfile (function, optional) : apodizer radial transmission; default is SPHERE APO1 apodizer
159 
160  Returns :
161  apodizer (2D array) : apodizer transmission pupil
162  """
163  # creating VLT pup without spiders
164  pup = make_VLT_pupil(pupdiam, centralobs = centralobs, spiders = 0, spiders_bool = False, centralobs_bool = True)
165 
166  # applying apodizer radial profile
167  X = np.tile(np.linspace(-1, 1, pupdiam, dtype=np.float32), (pupdiam, 1)) # !
168  R = np.sqrt(X**2 + (X.T)**2)
169  apodizer = pup*radial_profile(R)
170  return apodizer
171 
172 def make_sphere_lyot_stop(pupdiam,
173  centralobs = 0.14,
174  spiders = 0.00625,
175  add_centralobs = 7.3/100,
176  add_spiders = 3.12/100,
177  add_outer_edge_obs = 1.8/100):
178  """
179  Return the SPHERE Lyot stop
180 
181  default values of additional central obstruction, spiders size and
182  outer edge obstruction have been estimated by eye on the real lyot stop
183  WARNING : this lyot stop does not feature the dead actuators patches
184 
185  Args :
186  pupdiam (int) [pixel] : pupil diameter
187 
188  centralobs (float, optional) [fraction of diameter] : central obstruction diameter
189 
190  spiders (float, optional) [fraction of diameter] : spider diameter
191 
192  add_centralobs (float, optional) [fraction of diameter] : additional diameter of central obstruction
193 
194  add_spiders (float, optional) [fraction of diameter] : additional diameter of spiders obstruction
195 
196  add_outer_edge_obs (float, optional) [fraction of diameter] : outer edge obstruction size
197 
198  Returns :
199  lyotStop (2D array) : Sphere lyot Stop transmission pupil of shape (pupdiam, pupdiam), filled with 0 and 1
200  """
201  lyotCentralObs = centralobs + add_centralobs
202 
203  range = 0.5 # !
204  X = np.tile(np.linspace(-range, range, pupdiam, dtype=np.float32), (pupdiam, 1))
205  R = np.sqrt(X**2 + (X.T)**2)
206  lyotCentralMap = ((R < 0.5) & (R > (lyotCentralObs / 2))).astype(np.float32)
207 
208  angle = 50.5 * np.pi / 180. # 50.5 degrees = angle between spiders
209  if pupdiam % 2 == 0 :
210  lyotSpidersMap = (
211  (X.T > (X - centralobs / 2 + (spiders + add_spiders / 2) / np.sin(angle)) * np.tan(angle)) +
212  (X.T < (X - centralobs / 2 - add_spiders / 2 / np.sin(angle)) * np.tan(angle))
213  ) * (X > 0) * (X.T > 0)
214  elif pupdiam % 2 == 1 :
215  lyotSpidersMap = (
216  (X.T > (X - centralobs / 2 + (spiders + add_spiders / 2) / np.sin(angle)) * np.tan(angle)) +
217  (X.T < (X - centralobs / 2 - add_spiders / 2 / np.sin(angle)) * np.tan(angle))
218  ) * (X >= 0) * (X.T >= 0)
219  lyotSpidersMap += np.fliplr(lyotSpidersMap)
220  lyotSpidersMap += np.flipud(lyotSpidersMap)
221 
222  X = np.tile(np.linspace(-range, range, pupdiam, dtype=np.float32), (pupdiam, 1))
223  R = np.sqrt(X**2 + (X.T)**2)
224  lyotOuterEdge = (R < 0.5 - add_outer_edge_obs)
225 
226  lyotStop = lyotCentralMap*lyotSpidersMap*lyotOuterEdge
227  return lyotStop
228 
229 
230 # --------------------------------------------------- #
231 # Useful functions for contrast computation #
232 # --------------------------------------------------- #
233 
234 def ring(center, radius_min, radius_max, shape):
235  """ Returns a ring mask
236 
237  Args:
238  center: (float or (float, float)): center of the ring in pixel
239 
240  radius_min: (float): internal radius in pixel
241 
242  radius_max: (float): external radius in pixel
243 
244  shape: (int or (int, int)): shape of the image in pixel
245 
246  Return:
247  mask (boolean 2D array) : image of boolean, filled
248  with False outside the ring and True inside
249  """
250  if np.isscalar(shape):
251  shape = (shape, shape)
252  if np.isscalar(center):
253  center = (center, center)
254  mask = np.full(shape, False)
255  xx, yy = np.meshgrid(np.arange(shape[0]) - center[0], np.arange(shape[1]) - center[1])
256  rr = np.hypot(xx, yy)
257  mask = np.logical_and((radius_min <= rr), (rr <= radius_max))
258  return mask
259 
260 def compute_contrast(image, center, r_min, r_max, width):
261  """ Computes average, standard deviation, minimum and maximum
262  over rings at several distances from the center of the image.
263 
264  A ring includes the pixels between the following radiuses :
265  r_min + k * width - width / 2 and r_min + k * width + width / 2,
266  with k = 0, 1, 2... until r_min + k * width > r_max (excluded).
267 
268  Args:
269  image: (2D array): the coronagraphic image
270 
271  center: (float or (float, float)): center of the coronagraphic image in pixel
272 
273  r_min: (float): radius of the first ring in pixel
274 
275  r_max: (float): maximum radius in pixel
276 
277  width: (float): width of one ring in pixel
278 
279  Returns:
280  distances: (1D array): distances in pixel
281 
282  mean: (1D array): corresponding averages
283 
284  std: (1D array): corresponding standard deviations
285 
286  mini: (1D array): corresponding minimums
287 
288  maxi: (1D array): corresponding maximums
289  """
290 
291  distances = np.arange(r_min, r_max, width)
292  mean = []
293  std = []
294  mini = []
295  maxi = []
296  for radius in distances:
297  mask = ring(center, radius - width / 2, radius + width / 2, image.shape)
298  values = image[mask]
299  mean.append(np.mean(values))
300  std.append(np.std(values))
301  mini.append(np.min(values))
302  maxi.append(np.max(values))
303  mean = np.array(mean)
304  std = np.array(std)
305  mini = np.array(mini)
306  maxi = np.array(maxi)
307  return distances, mean, std, mini, maxi
def compute_contrast(image, center, r_min, r_max, width)
Computes average, standard deviation, minimum and maximum over rings at several distances from the ce...
def sphere_apodizer_radial_profile(x)
Compute the transmission radial profile of the SPHERE APO1 apodizer.
def make_VLT_pupil(pupdiam, centralobs=0.14, spiders=0.00625, spiders_bool=True, centralobs_bool=True)
Return a VLT pupil based on make_VLT function from shesha/shesha/util/make_pupil.py.
def make_sphere_apodizer(pupdiam, centralobs=0.14, radial_profile=sphere_apodizer_radial_profile)
Return the SPHERE APLC apodizer APO1, based on its radial transmission profile.
def make_sphere_lyot_stop(pupdiam, centralobs=0.14, spiders=0.00625, add_centralobs=7.3/100, add_spiders=3.12/100, add_outer_edge_obs=1.8/100)
Return the SPHERE Lyot stop.
def classical_lyot_fpm(rad_lyot_fpm, dim_fpm, Lyot_fpm_sampling, wav_vec)
Create a classical Lyot coronagraph of radius rad_LyotFP.
def roundpupil(dim_pp, prad, center_pos='b')
Create a circular pupil.
def ring(center, radius_min, radius_max, shape)
Returns a ring mask.