ModuleDefects
Provides classes for different deformations of optical surfaces.
Created on Fri Mar 10 2023
@author: Semptum + Stefan Haessler
1""" 2Provides classes for different deformations of optical surfaces. 3 4 5Created on Fri Mar 10 2023 6 7@author: Semptum + Stefan Haessler 8""" 9# %% Modules 10 11import numpy as np 12#import cupy as cp 13from abc import ABC, abstractmethod 14import scipy 15import math 16from ARTcore.ModuleZernike import zernike_gradient 17import logging 18 19logger = logging.getLogger(__name__) 20 21# %% Abstract class 22 23 24class Defect(ABC): 25 """ 26 Abstract class representing a defect on the optical surface. 27 """ 28 29 @abstractmethod 30 def RMS(self): 31 pass 32 33 @abstractmethod 34 def PV(self): 35 pass 36 37class MeasuredMap(Defect): 38 """ 39 Class describing a defect map measured experimentally (for instance using a Hartmann wavefront sensor). Note that the provided image should cover the entire support. 40 """ 41 def __init__(self, Support, Map): 42 self.deformation = Map 43 self. Support = Support 44 rect = Support._CircumRect() # TODO fix this so it just uses a rectangle with known size 45 X = np.linspace(-rect[0], rect[0], num=self.deformation.shape[0]) 46 Y = np.linspace(-rect[1], rect[1], num=self.deformation.shape[1]) 47 self.DerivX, self.DerivY = np.gradient(self.deformation, rect/self.deformation.shape) 48 DerivInterpX = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(self.DerivX), method="linear") 49 DerivInterpY = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(self.DerivY), method="linear") 50 SurfInterp = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(self.deformation), method="linear") 51 52 self.rms = np.std(self.deformation) 53 self.DerivInterp = lambda x: np.array([DerivInterpX(x[:2]), DerivInterpY(x[:2])]) 54 self.SurfInterp = lambda x: SurfInterp(x[:2]) 55 def get_normal(self, Point): 56 Grad = self.DerivInterp(Point) 57 dX, dY = Grad.flatten() 58 norm = np.linalg.norm([dX, dY, 1]) 59 dX /= norm 60 dY /= norm 61 return np.array([dX, dY, np.sqrt(1 - dX**2 - dY**2)]) 62 63 def get_offset(self, Point): 64 return self.SurfInterp(Point) 65 66 def RMS(self): 67 return self.rms 68 69 def PV(self): 70 pass 71 72class Fourrier(Defect): 73 """ 74 75 """ 76 def __init__(self, Support, RMS, slope=-2, smallest=0.1, biggest=None): 77 # The sizes are the wavelength in mm 78 rect = Support._CircumRect() # TODO fix this so it just uses a rectangle with known size 79 if biggest is None: 80 biggest = np.max(rect) 81 82 k_max = 2 / smallest 83 k_min = 2 / biggest 84 ResX = int(round(k_max * rect[0] / 2))+1 85 ResY = int(round(k_max * rect[1])) 86 87 kXX, kYY = np.meshgrid( 88 np.linspace(0, k_max, num=ResX, dtype='float32', endpoint=False), 89 np.linspace(-k_max, k_max, num=ResY, dtype='float32', endpoint=False), 90 sparse=True) 91 92 maskedFFT = np.ma.masked_outside(np.sqrt(kXX**2 + kYY**2), k_min, k_max) 93 94 FFT = maskedFFT**slope * np.exp( 95 1j *np.random.uniform(0, 2 * np.pi, size=maskedFFT.shape).astype('float32') 96 ) 97 FFT = FFT.data*(1-FFT.mask) 98 99 deformation = np.fft.irfft2(np.fft.ifftshift(FFT, axes=0)) 100 RMS_factor = RMS/np.std(deformation) 101 deformation *= RMS_factor 102 103 DerivX = np.fft.irfft2(np.fft.ifftshift(FFT * 1j * kXX * RMS_factor, axes=0))*np.pi/2 104 kY = np.concatenate((kYY[kYY.shape[0]//2:],kYY[:kYY.shape[0]//2])) 105 DerivY = np.fft.irfft2(np.fft.ifftshift(FFT * 1j * RMS_factor, axes=0)*kY)*np.pi/2 106 #del FFT 107 108 X = np.linspace(-rect[0]/2, rect[0]/2, num=(ResX-1)*2) # Because iRfft 109 Y = np.linspace(-rect[1]/2, rect[1]/2, num=ResY) 110 111 DerivInterpX = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(DerivX), method="linear") 112 DerivInterpY = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(DerivY), method="linear") 113 SurfInterp = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(deformation), method="linear") 114 115 self.DerivX = DerivX 116 self.DerivY = DerivY 117 self.rms = np.std(deformation) 118 self.deformation = deformation 119 self.DerivInterp = lambda x: np.array([DerivInterpX(x[:2]), DerivInterpY(x[:2])]) 120 self.SurfInterp = lambda x: SurfInterp(x[:2]) 121 122 def get_normal(self, Point): 123 """ 124 Calculates and returns the surface normal at the given Point. 125 It uses the derivative interpolators to compute the partial derivatives of the surface 126 deformation and returns a normalized vector representing the surface normal. 127 """ 128 dX, dY = self.DerivInterp(Point) 129 norm = np.linalg.norm([dX, dY, 1]) 130 dX /= norm 131 dY /= norm 132 return np.array([dX, dY, np.sqrt(1 - dX**2 - dY**2)]) 133 134 def get_offset(self, Point): 135 """ 136 Calculates and returns the surface offset at the given Point. 137 It uses the surface deformation interpolator to determine the displacement 138 at the specified point on the surface. 139 """ 140 return self.SurfInterp(Point) 141 142 def RMS(self): 143 """ 144 Returns the root mean square (RMS) value of the surface deformation (self.rms). 145 """ 146 return self.rms 147 148 def PV(self): 149 pass 150 151 152class Zernike(Defect): 153 def __init__(self, Support, coefficients): 154 self.coefficients = coefficients 155 self.max_order = np.max([k[0] for k in coefficients]) 156 self.support = Support 157 self.R = Support._CircumCirc() 158 159 def get_normal(self, Point): 160 x, y, z = Point / self.R 161 val, derivX, derivY = zernike_gradient([x], [y], self.max_order) 162 dX = 0 163 dY = 0 164 for k in self.coefficients: 165 dX += self.coefficients[k] * derivX[k][0][1][0] 166 dY += self.coefficients[k] * derivY[k][0][1][0] 167 dX /= self.R 168 dY /= self.R 169 return np.array([-dX, -dY, 1]) 170 171 def get_offset(self, Point): 172 x, y, z = Point / self.R 173 val, derivX, derivY = zernike_gradient([x], [y], self.max_order) 174 Z = 0 175 for k in self.coefficients: 176 Z += self.coefficients[k] * val[k][0][1][0] 177 return Z 178 179 def RMS(self): 180 return np.sqrt(np.sum([i**2 for i in self.coefficients.values()])) 181 182 def PV(self): 183 pass
25class Defect(ABC): 26 """ 27 Abstract class representing a defect on the optical surface. 28 """ 29 30 @abstractmethod 31 def RMS(self): 32 pass 33 34 @abstractmethod 35 def PV(self): 36 pass
Abstract class representing a defect on the optical surface.
38class MeasuredMap(Defect): 39 """ 40 Class describing a defect map measured experimentally (for instance using a Hartmann wavefront sensor). Note that the provided image should cover the entire support. 41 """ 42 def __init__(self, Support, Map): 43 self.deformation = Map 44 self. Support = Support 45 rect = Support._CircumRect() # TODO fix this so it just uses a rectangle with known size 46 X = np.linspace(-rect[0], rect[0], num=self.deformation.shape[0]) 47 Y = np.linspace(-rect[1], rect[1], num=self.deformation.shape[1]) 48 self.DerivX, self.DerivY = np.gradient(self.deformation, rect/self.deformation.shape) 49 DerivInterpX = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(self.DerivX), method="linear") 50 DerivInterpY = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(self.DerivY), method="linear") 51 SurfInterp = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(self.deformation), method="linear") 52 53 self.rms = np.std(self.deformation) 54 self.DerivInterp = lambda x: np.array([DerivInterpX(x[:2]), DerivInterpY(x[:2])]) 55 self.SurfInterp = lambda x: SurfInterp(x[:2]) 56 def get_normal(self, Point): 57 Grad = self.DerivInterp(Point) 58 dX, dY = Grad.flatten() 59 norm = np.linalg.norm([dX, dY, 1]) 60 dX /= norm 61 dY /= norm 62 return np.array([dX, dY, np.sqrt(1 - dX**2 - dY**2)]) 63 64 def get_offset(self, Point): 65 return self.SurfInterp(Point) 66 67 def RMS(self): 68 return self.rms 69 70 def PV(self): 71 pass
Class describing a defect map measured experimentally (for instance using a Hartmann wavefront sensor). Note that the provided image should cover the entire support.
42 def __init__(self, Support, Map): 43 self.deformation = Map 44 self. Support = Support 45 rect = Support._CircumRect() # TODO fix this so it just uses a rectangle with known size 46 X = np.linspace(-rect[0], rect[0], num=self.deformation.shape[0]) 47 Y = np.linspace(-rect[1], rect[1], num=self.deformation.shape[1]) 48 self.DerivX, self.DerivY = np.gradient(self.deformation, rect/self.deformation.shape) 49 DerivInterpX = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(self.DerivX), method="linear") 50 DerivInterpY = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(self.DerivY), method="linear") 51 SurfInterp = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(self.deformation), method="linear") 52 53 self.rms = np.std(self.deformation) 54 self.DerivInterp = lambda x: np.array([DerivInterpX(x[:2]), DerivInterpY(x[:2])]) 55 self.SurfInterp = lambda x: SurfInterp(x[:2])
73class Fourrier(Defect): 74 """ 75 76 """ 77 def __init__(self, Support, RMS, slope=-2, smallest=0.1, biggest=None): 78 # The sizes are the wavelength in mm 79 rect = Support._CircumRect() # TODO fix this so it just uses a rectangle with known size 80 if biggest is None: 81 biggest = np.max(rect) 82 83 k_max = 2 / smallest 84 k_min = 2 / biggest 85 ResX = int(round(k_max * rect[0] / 2))+1 86 ResY = int(round(k_max * rect[1])) 87 88 kXX, kYY = np.meshgrid( 89 np.linspace(0, k_max, num=ResX, dtype='float32', endpoint=False), 90 np.linspace(-k_max, k_max, num=ResY, dtype='float32', endpoint=False), 91 sparse=True) 92 93 maskedFFT = np.ma.masked_outside(np.sqrt(kXX**2 + kYY**2), k_min, k_max) 94 95 FFT = maskedFFT**slope * np.exp( 96 1j *np.random.uniform(0, 2 * np.pi, size=maskedFFT.shape).astype('float32') 97 ) 98 FFT = FFT.data*(1-FFT.mask) 99 100 deformation = np.fft.irfft2(np.fft.ifftshift(FFT, axes=0)) 101 RMS_factor = RMS/np.std(deformation) 102 deformation *= RMS_factor 103 104 DerivX = np.fft.irfft2(np.fft.ifftshift(FFT * 1j * kXX * RMS_factor, axes=0))*np.pi/2 105 kY = np.concatenate((kYY[kYY.shape[0]//2:],kYY[:kYY.shape[0]//2])) 106 DerivY = np.fft.irfft2(np.fft.ifftshift(FFT * 1j * RMS_factor, axes=0)*kY)*np.pi/2 107 #del FFT 108 109 X = np.linspace(-rect[0]/2, rect[0]/2, num=(ResX-1)*2) # Because iRfft 110 Y = np.linspace(-rect[1]/2, rect[1]/2, num=ResY) 111 112 DerivInterpX = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(DerivX), method="linear") 113 DerivInterpY = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(DerivY), method="linear") 114 SurfInterp = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(deformation), method="linear") 115 116 self.DerivX = DerivX 117 self.DerivY = DerivY 118 self.rms = np.std(deformation) 119 self.deformation = deformation 120 self.DerivInterp = lambda x: np.array([DerivInterpX(x[:2]), DerivInterpY(x[:2])]) 121 self.SurfInterp = lambda x: SurfInterp(x[:2]) 122 123 def get_normal(self, Point): 124 """ 125 Calculates and returns the surface normal at the given Point. 126 It uses the derivative interpolators to compute the partial derivatives of the surface 127 deformation and returns a normalized vector representing the surface normal. 128 """ 129 dX, dY = self.DerivInterp(Point) 130 norm = np.linalg.norm([dX, dY, 1]) 131 dX /= norm 132 dY /= norm 133 return np.array([dX, dY, np.sqrt(1 - dX**2 - dY**2)]) 134 135 def get_offset(self, Point): 136 """ 137 Calculates and returns the surface offset at the given Point. 138 It uses the surface deformation interpolator to determine the displacement 139 at the specified point on the surface. 140 """ 141 return self.SurfInterp(Point) 142 143 def RMS(self): 144 """ 145 Returns the root mean square (RMS) value of the surface deformation (self.rms). 146 """ 147 return self.rms 148 149 def PV(self): 150 pass
77 def __init__(self, Support, RMS, slope=-2, smallest=0.1, biggest=None): 78 # The sizes are the wavelength in mm 79 rect = Support._CircumRect() # TODO fix this so it just uses a rectangle with known size 80 if biggest is None: 81 biggest = np.max(rect) 82 83 k_max = 2 / smallest 84 k_min = 2 / biggest 85 ResX = int(round(k_max * rect[0] / 2))+1 86 ResY = int(round(k_max * rect[1])) 87 88 kXX, kYY = np.meshgrid( 89 np.linspace(0, k_max, num=ResX, dtype='float32', endpoint=False), 90 np.linspace(-k_max, k_max, num=ResY, dtype='float32', endpoint=False), 91 sparse=True) 92 93 maskedFFT = np.ma.masked_outside(np.sqrt(kXX**2 + kYY**2), k_min, k_max) 94 95 FFT = maskedFFT**slope * np.exp( 96 1j *np.random.uniform(0, 2 * np.pi, size=maskedFFT.shape).astype('float32') 97 ) 98 FFT = FFT.data*(1-FFT.mask) 99 100 deformation = np.fft.irfft2(np.fft.ifftshift(FFT, axes=0)) 101 RMS_factor = RMS/np.std(deformation) 102 deformation *= RMS_factor 103 104 DerivX = np.fft.irfft2(np.fft.ifftshift(FFT * 1j * kXX * RMS_factor, axes=0))*np.pi/2 105 kY = np.concatenate((kYY[kYY.shape[0]//2:],kYY[:kYY.shape[0]//2])) 106 DerivY = np.fft.irfft2(np.fft.ifftshift(FFT * 1j * RMS_factor, axes=0)*kY)*np.pi/2 107 #del FFT 108 109 X = np.linspace(-rect[0]/2, rect[0]/2, num=(ResX-1)*2) # Because iRfft 110 Y = np.linspace(-rect[1]/2, rect[1]/2, num=ResY) 111 112 DerivInterpX = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(DerivX), method="linear") 113 DerivInterpY = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(DerivY), method="linear") 114 SurfInterp = scipy.interpolate.RegularGridInterpolator((X, Y), np.transpose(deformation), method="linear") 115 116 self.DerivX = DerivX 117 self.DerivY = DerivY 118 self.rms = np.std(deformation) 119 self.deformation = deformation 120 self.DerivInterp = lambda x: np.array([DerivInterpX(x[:2]), DerivInterpY(x[:2])]) 121 self.SurfInterp = lambda x: SurfInterp(x[:2])
123 def get_normal(self, Point): 124 """ 125 Calculates and returns the surface normal at the given Point. 126 It uses the derivative interpolators to compute the partial derivatives of the surface 127 deformation and returns a normalized vector representing the surface normal. 128 """ 129 dX, dY = self.DerivInterp(Point) 130 norm = np.linalg.norm([dX, dY, 1]) 131 dX /= norm 132 dY /= norm 133 return np.array([dX, dY, np.sqrt(1 - dX**2 - dY**2)])
Calculates and returns the surface normal at the given Point. It uses the derivative interpolators to compute the partial derivatives of the surface deformation and returns a normalized vector representing the surface normal.
135 def get_offset(self, Point): 136 """ 137 Calculates and returns the surface offset at the given Point. 138 It uses the surface deformation interpolator to determine the displacement 139 at the specified point on the surface. 140 """ 141 return self.SurfInterp(Point)
Calculates and returns the surface offset at the given Point. It uses the surface deformation interpolator to determine the displacement at the specified point on the surface.
153class Zernike(Defect): 154 def __init__(self, Support, coefficients): 155 self.coefficients = coefficients 156 self.max_order = np.max([k[0] for k in coefficients]) 157 self.support = Support 158 self.R = Support._CircumCirc() 159 160 def get_normal(self, Point): 161 x, y, z = Point / self.R 162 val, derivX, derivY = zernike_gradient([x], [y], self.max_order) 163 dX = 0 164 dY = 0 165 for k in self.coefficients: 166 dX += self.coefficients[k] * derivX[k][0][1][0] 167 dY += self.coefficients[k] * derivY[k][0][1][0] 168 dX /= self.R 169 dY /= self.R 170 return np.array([-dX, -dY, 1]) 171 172 def get_offset(self, Point): 173 x, y, z = Point / self.R 174 val, derivX, derivY = zernike_gradient([x], [y], self.max_order) 175 Z = 0 176 for k in self.coefficients: 177 Z += self.coefficients[k] * val[k][0][1][0] 178 return Z 179 180 def RMS(self): 181 return np.sqrt(np.sum([i**2 for i in self.coefficients.values()])) 182 183 def PV(self): 184 pass
Abstract class representing a defect on the optical surface.
160 def get_normal(self, Point): 161 x, y, z = Point / self.R 162 val, derivX, derivY = zernike_gradient([x], [y], self.max_order) 163 dX = 0 164 dY = 0 165 for k in self.coefficients: 166 dX += self.coefficients[k] * derivX[k][0][1][0] 167 dY += self.coefficients[k] * derivY[k][0][1][0] 168 dX /= self.R 169 dY /= self.R 170 return np.array([-dX, -dY, 1])