43 from .
import utilities
as util
45 from typing
import List, Union
49 """ Compute the DM support dimensions
53 cent : (float): center of the pupil
55 extent: (float): size of the DM support
57 ssize: (int): size of ipupil support
59 n1 = np.floor(cent - extent / 2)
60 n2 = np.ceil(cent + extent / 2)
66 return int(n1), int(n2)
69 def dim_dm_patch(pupdiam: int, diam: float, type: bytes, alt: float,
70 xpos_wfs: List[float], ypos_wfs: List[float]):
71 """ compute patchDiam for DM
75 pupdiam: (int) : pupil diameter
77 diam: (float) : telescope diameter
79 type: (bytes) : type of dm
81 alt: (float) : altitude of dm
83 xpos_wfs: (list) : list of wfs xpos
85 ypos_wfs: (list) : list of wfs ypos
88 if len(xpos_wfs) == 0:
92 np.linalg.norm([xpos_wfs[w], ypos_wfs[w]])
for w
in range(len(xpos_wfs))
94 if ((type == scons.DmType.PZT)
or (type == scons.DmType.TT)):
96 elif (type == scons.DmType.KL):
99 raise TypeError(
"This type of DM doesn't exist ")
101 patchDiam = int(pupdiam + 2 * np.max(norms) * CONST.ARCSEC2RAD * np.abs(alt) / (pp))
107 Creates a list of M=nxact^2 actuator positions spread over an square grid.
108 Coordinates are centred around (0,0).
112 pitch: (float) : distance in pixels between 2 adjacent actus
114 nxact: (int) : number of actu across the pupil diameter
118 xy: (np.ndarray(dims=2,dtype=np.float32)) : xy[M,2] list of coodinates
121 xy = np.tile(np.arange(nxact) - (nxact - 1.) / 2., (nxact, 1)).astype(np.float32)
122 xy = np.array([xy.flatten(), xy.T.flatten()]) * pitch
129 Creates a list of M actuator positions spread over an hexagonal grid.
130 The number M is the number of points of this grid, it cannot be
131 known before the procedure is called.
132 Coordinates are centred around (0,0).
133 The support that limits the grid is a square [-supportSize/2, supportSize/2].
137 pitch: (float) : distance in pixels between 2 adjacent actus
139 supportSize: (int) : size in pixels of the support over which the coordinate list
144 xy: (np.ndarray(dims=2,dtype=np.float32)) : xy[2,M] list of coordinates
147 nx = int(np.ceil((supportSize / 2.0) / pitch) + 1)
148 x = pitch * (np.arange(2 * nx + 1, dtype=np.float32) - nx)
150 ny = int(np.ceil((supportSize / 2.0) / pitch / V3) + 1)
151 y = (V3 * pitch) * (np.arange(2 * ny + 1, dtype=np.float32) - ny)
153 x = np.tile(x, (Ny, 1)).flatten()
154 y = np.tile(y, (Nx, 1)).T.flatten()
155 x = np.append(x, x + pitch / 2.)
156 y = np.append(y, y + pitch * V3 / 2.)
157 xy = np.float32(np.array([y, x]))
163 Creates a list of M actuator positions spread over an hexagonal grid.
164 The number M is the number of points of this grid, it cannot be
165 known before the procedure is called.
166 Coordinates are centred around (0,0).
167 The support of the grid is a square [-supportSize/2,vsupportSize/2].
171 pitch: (float) : distance in pixels between 2 adjacent actus
172 supportSize: (int) : size in pixels of the support over which the coordinate list
174 pupAngleDegree: (float) : Rotation angle of the DM
178 xy: (np.ndarray(dims=2,dtype=np.float32)) : xy[2,M] list of coodinates
184 ny = int(np.ceil((supportSize / 2.0) / pitch) + 1)
185 y = pitch * (np.arange(2 * ny + 1, dtype=np.float32) - ny)
186 nx = int(np.ceil((supportSize / 2.0) / pitch / V3) + 1)
187 x = (V3 * pitch) * (np.arange(2 * nx + 1, dtype=np.float32) - nx)
190 x, y = np.meshgrid(x, y, indexing=
'ij')
193 x = np.append(x, x + pitch * V3 / 2.)
194 y = np.append(y, y + pitch / 2.)
203 th = np.arctan2(y, x)
204 nn = np.where(((th < pi / 6) & (th > -pi / 6)))
213 xx = np.cos(k * pi / 3) * x + np.sin(k * pi / 3) * y
214 yy = -np.sin(k * pi / 3) * x + np.cos(k * pi / 3) * y
219 rot = pupAngleDegree * np.pi / 180.0
220 mrot = np.array([[np.cos(rot), -np.sin(rot)], [np.sin(rot), np.cos(rot)]])
221 XY = np.dot(mrot, [X, Y])
222 return np.float32(XY)
226 threshold: float) -> np.ndarray:
228 Select actuators based on their distance to the nearest pupil pixel
229 The implementation proposed here is easy but limits it precision to
230 an integer roundoff of the threshold
232 actuPos: 2 x nActu np.array[float]: actuator position list - pupil pixel units
233 pupil: nPup x nPup np.ndarray[bool]: pupil mask
234 threshold: float: max allowed distance - pupil pixel units
238 from scipy.ndimage.morphology
import binary_dilation
239 k = np.ceil(threshold)
240 i, j = np.meshgrid(np.arange(2 * k + 1), np.arange(2 * k + 1), indexing=
'ij')
241 disk = ((i - k)**2 + (j - k)**2)**.5 <= k
242 dilatedPupil = binary_dilation(pupil, disk)
243 actuIsIn = dilatedPupil[(np.round(actuPos[0]).astype(np.int32),
244 np.round(actuPos[1]).astype(np.int32))]
245 return actuPos[:, actuIsIn]
248 def select_actuators(xc: np.ndarray, yc: np.ndarray, nxact: int, pitch: int, cobs: float,
249 margin_in: float, margin_out: float, N=
None):
251 Select the "valid" actuators according to the system geometry
255 xc: actuators x positions (origine in center of mirror)
257 yc: actuators y positions (origine in center of mirror)
273 liste_fin: actuator indice selection for xpos/ypos
279 dis = np.sqrt(xc**2 + yc**2)
282 rad_in = (((nxact - 1) / 2) * cobs - margin_in) * pitch
285 if (margin_out
is None):
287 rad_out = ((nxact - 1.) / 2. + margin_out) * pitch
289 valid_actus = np.where((dis <= rad_out) * (dis >= rad_in))[0]
292 valid_actus = np.where(dis >= rad_in)[0]
293 indsort = np.argsort(dis[valid_actus])
295 if (N > valid_actus.size):
296 print(
'Too many actuators wanted, restricted to ', valid_actus.size)
298 valid_actus = np.sort(indsort[:N])
303 def make_zernike(nzer: int, size: int, diameter: int, xc=-1., yc=-1., ext=0):
304 """Compute the zernike modes
308 nzer: (int) : number of modes
310 size: (int) : size of the screen
312 diameter: (int) : pupil diameter
314 xc: (float) : (optional) x-position of the center
316 yc: (float) : (optional) y-position of the center
318 ext: (int) : (optional) extension
322 z : (np.ndarray(ndims=3,dtype=np.float64)) : zernikes modes
332 radius = (diameter + 1.) / 2.
333 zr = util.dist(size, xc, yc).astype(np.float32).T / radius
334 zmask = np.zeros((zr.shape[0], zr.shape[1], nzer), dtype=np.float32)
335 zmaskmod = np.zeros((zr.shape[0], zr.shape[1], nzer), dtype=np.float32)
337 zmask[:, :, 0] = (zr <= 1).astype(np.float32)
338 zmaskmod[:, :, 0] = (zr <= 1.2).astype(np.float32)
340 for i
in range(1, nzer):
341 zmask[:, :, i] = zmask[:, :, 0]
342 zmaskmod[:, :, i] = zmaskmod[:, :, 0]
344 zrmod = zr * zmaskmod[:, :, 0]
346 zr = zr * zmask[:, :, 0]
348 x = np.tile(np.linspace(1, size, size).astype(np.float32), (size, 1))
349 zteta = np.arctan2(x - yc, x.T - xc).astype(np.float32)
351 z = np.zeros((size, size, nzer), dtype=np.float32)
353 for zn
in range(nzer):
357 for i
in range((n - m) // 2 + 1):
358 z[:, :, zn] = z[:, :, zn] + (-1.) ** i * zrmod ** (n - 2. * i) * float(np.math.factorial(n - i)) / \
359 float(np.math.factorial(i) * np.math.factorial((n + m) / 2 - i) *
360 np.math.factorial((n - m) / 2 - i))
362 for i
in range((n - m) // 2 + 1):
363 z[:, :, zn] = z[:, :, zn] + (-1.) ** i * zr ** (n - 2. * i) * float(np.math.factorial(n - i)) / \
364 float(np.math.factorial(i) * np.math.factorial((n + m) / 2 - i) *
365 np.math.factorial((n - m) / 2 - i))
367 if ((zn + 1) % 2 == 1):
369 z[:, :, zn] = z[:, :, zn] * np.sqrt(n + 1.)
371 z[:, :, zn] = z[:, :, zn] * \
372 np.sqrt(2. * (n + 1)) * np.sin(m * zteta)
375 z[:, :, zn] = z[:, :, zn] * np.sqrt(n + 1.)
377 z[:, :, zn] = z[:, :, zn] * \
378 np.sqrt(2. * (n + 1)) * np.cos(m * zteta)
388 Returns the radial degree and the azimuthal number of zernike
389 number zn, according to Noll numbering (Noll, JOSA, 1976)
393 zn: (int) : zernike number
397 rd: (int) : radial degrees
399 an: (int) : azimuthal numbers
404 for m
in range(n + 1):
405 if ((n - m) % 2 == 0):