diff --git a/AO_modules/mis_registration_identification_algorithm/__init__.py b/AO_modules/mis_registration_identification_algorithm/__init__.py deleted file mode 100644 index c03131d..0000000 --- a/AO_modules/mis_registration_identification_algorithm/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Apr 3 10:56:59 2020 - -@author: cheritie -""" - diff --git a/AO_modules/tools/__init__.py b/AO_modules/tools/__init__.py deleted file mode 100755 index c03131d..0000000 --- a/AO_modules/tools/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Apr 3 10:56:59 2020 - -@author: cheritie -""" - diff --git a/AO_modules/Atmosphere.py b/OOPAO/Atmosphere.py old mode 100755 new mode 100644 similarity index 96% rename from AO_modules/Atmosphere.py rename to OOPAO/Atmosphere.py index 3b3bd78..26f6283 --- a/AO_modules/Atmosphere.py +++ b/OOPAO/Atmosphere.py @@ -1,711 +1,713 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Aug 14 10:59:02 2020 - -@author: cheritie -""" -import inspect -import numpy as np -from AO_modules.phaseStats import makeCovarianceMatrix,ft_phase_screen -from AO_modules.tools.tools import emptyClass,translationImageMatrix,globalTransformation, createFolder, pol2cart, cart2pol -from AO_modules.tools.interpolateGeometricalTransformation import interpolate_cube -from AO_modules.tools.displayTools import getColorOrder - -import time -import json -import jsonpickle -import matplotlib.pyplot as plt -from numpy.random import RandomState -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CLASS INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -class Atmosphere: - def __init__(self,telescope,r0,L0,windSpeed,fractionalR0,windDirection,altitude,mode=2, param = None, asterism = None): - """ - An Atmosphere object is made of one or several layer of turbulence that follow the Van Karmann statistics. Each layer is considered to be independant to the other ones and has its own properties (direction, speed, etc.) - - The Atmosphere object can be defined for a single Source object (default) or multi Source Object. The Source coordinates allow to span different areas in the field (defined as well by the tel.fov). - - If the source type is an LGS the cone effect is considered using an interpolation. NGS and LGS can be combined together in the asterism object. - - The convention chosen is that all the wavelength-dependant atmosphere parameters are expressed at 500 nm. - - - ************************** REQUIRED PARAMETERS ************************** - It requires the following parameters: - - _ telescope : the telescope object to which the Atmosphere is associated. This object carries the phase, flux, pupil information and sampling time as well as the type of source (NGS/LGS, source/asterism) - _ r0 : the Fried parameter in m - _ L0 : Outer scale parameter - _ windSpeed : List of wind-speed for each layer in [m/s] - _ fractionalR0 : Cn2 profile of the turbulence. This should be a list of values for each layer - _ windDirection : List of wind-direction for each layer in [deg] - _ altitude : List of altitude for each layer in [m] - - ************************** OPTIONAL PARAMETERS ************************** - - _ mode : Different ways to generate the spectrum of the atmosphere - _ param : parameter file of the system. Once computed, the covariance matrices are saved in the calibration data folder and loaded instead of re-computed evry time. - _ asterism : if the system contains multiple source, an astrism should be input to the atmosphere object - - ************************** COUPLING A TELESCOPE AND AN ATMOSPHERE OBJECT ************************** - A Telescope object "tel" can be coupled to an Atmosphere object "atm" using: - _ tel + atm - This means that a bridge is created between atm and tel: everytime that atm.OPD is updated, the tel.OPD property is automatically set to atm.OPD to reproduce the effect of the turbulence. - - A Telescope object "tel" can be separated of an Atmosphere object "atm" using: - _ tel - atm - This corresponds to a diffraction limited case (no turbulence) - - - ************************** PROPERTIES ************************** - - The main properties of the Atmosphere object are listed here: - - _ atm.OPD : Optical Path Difference in [m] truncated by the telescope pupil. If the atmosphere has multiple sources, the OPD is a list of OPD for each source - _ atm.OPD_no_pupil : Optical Path Difference in [m]. If the atmosphere has multiple sources, the OPD is a list of OPD for each source - _ atm.r0 - _ atm.L0 - _ atm.nLayer : number of turbulence layers - _ atm.seeingArcsec : seeing in arcsec at 500 nm - _ atm.layer_X : access the child object corresponding to the layer X where X starts at 0 - - the following properties can be updated on the fly: - _ atm.r0 - _ atm.windSpeed - _ atm.windDirection - ************************** FUNCTIONS ************************** - - _ atm.update() : update the OPD of the atmosphere for each layer according to the time step defined by tel.samplingTime - _ atm.generateNewPhaseScreen(seed) : generate a new phase screen for the atmosphere OPD - _ atm.print_atm_at_wavelength(wavelength) : prompt seeing and r0 at specified wavelength - _ atm.print_atm() : prompt the main properties of the atm object - _ display_atm_layers(layer_index) : imshow the OPD of each layer with the intersection beam for each source - - """ - self.hasNotBeenInitialized = True - self.r0_def = 0.15 # Fried Parameter in m - self.r0 = r0 # Fried Parameter in m - self.fractionalR0 = fractionalR0 # Cn2 square profile - self.L0 = L0 # Outer Scale in m - self.altitude = altitude # altitude of the layers - self.nLayer = len(fractionalR0) # number of layer - self.windSpeed = windSpeed # wind speed of the layers in m/s - self.windDirection = windDirection # wind direction in degrees - self.tag = 'atmosphere' # Tag of the object - self.nExtra = 2 # number of extra pixel to generate the phase screens - self.wavelength = 500*1e-9 # Wavelengt used to define the properties of the atmosphere - self.tel = telescope # associated telescope object - self.mode = mode # DEBUG -> first phase screen generation mode - self.seeingArcsec = 206265*(self.wavelength/self.r0) - self.asterism = asterism # case when multiple sources are considered (LGS and NGS) - self.param = param - if self.asterism is not None: - self.oversampling_factor = np.max((np.asarray(self.asterism.coordinates)[:,0]/(self.tel.resolution/2))) - else: - self.oversampling_factor = self.tel.src.coordinates[0]/(self.tel.resolution/2) - - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ATM INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - def initializeAtmosphere(self,telescope): - phase_support = self.initialize_phase_support() - if self.hasNotBeenInitialized: - self.initial_r0 = self.r0 - for i_layer in range(self.nLayer): - # create the layer - print('Creation of layer' + str(i_layer+1) + '/' + str(self.nLayer) + ' ...' ) - tmpLayer=self.buildLayer(telescope,self.r0_def,self.L0,i_layer = i_layer) - setattr(self,'layer_'+str(i_layer+1),tmpLayer) - - phase_support = self.fill_phase_support(tmpLayer,phase_support,i_layer) - tmpLayer.phase_support = phase_support - tmpLayer.phase *= self.wavelength/2/np.pi - - else: - print('Re-setting the atmosphere to its initial state...' ) - self.r0 = self.initial_r0 - for i_layer in range(self.nLayer): - print('Updating layer' + str(i_layer+1) + '/' + str(self.nLayer) + ' ...' ) - tmpLayer = getattr(self,'layer_'+str(i_layer+1)) - tmpLayer.phase = tmpLayer.initialPhase/self.wavelength*2*np.pi - tmpLayer.randomState = RandomState(42+i_layer*1000) - - Z = tmpLayer.phase[tmpLayer.innerMask[1:-1,1:-1]!=0] - X = np.matmul(tmpLayer.A,Z) + np.matmul(tmpLayer.B,tmpLayer.randomState.normal( size=tmpLayer.B.shape[1])) - - tmpLayer.mapShift[tmpLayer.outerMask!=0] = X - tmpLayer.mapShift[tmpLayer.outerMask==0] = np.reshape(tmpLayer.phase,tmpLayer.resolution*tmpLayer.resolution) - - tmpLayer.notDoneOnce = True - - setattr(self,'layer_'+str(i_layer+1),tmpLayer) - - phase_support = self.fill_phase_support(tmpLayer,phase_support,i_layer) - - # wavelenfth scaling - tmpLayer.phase *= self.wavelength/2/np.pi - self.generateNewPhaseScreen(seed=0) - - self.hasNotBeenInitialized = False - # save the resulting phase screen in OPD - self.set_OPD(phase_support) - self.print_atm() - - def buildLayer(self,telescope,r0,L0,i_layer,compute_turbulence = True): - """ - Generation of phase screens using the method introduced in Assemat et al (2006) - """ - - - # initialize layer object - layer = emptyClass() - # create a random state to allow reproductible sequences of phase screens - layer.randomState = RandomState(42+i_layer*1000) - - # gather properties of the atmosphere - layer.altitude = self.altitude[i_layer] - layer.windSpeed = self.windSpeed[i_layer] - layer.direction = self.windDirection[i_layer] - - # compute the X and Y wind speed - layer.vY = layer.windSpeed*np.cos(np.deg2rad(layer.direction)) - layer.vX = layer.windSpeed*np.sin(np.deg2rad(layer.direction)) - - # Diameter and resolution of the layer including the Field Of View and the number of extra pixels - layer.D = self.tel.D+2*np.tan(self.tel.fov/2)*layer.altitude*self.oversampling_factor - layer.resolution = int(np.ceil((self.tel.resolution/self.tel.D)*layer.D)) - - layer.D_fov = self.tel.D+2*np.tan(self.tel.fov/2)*layer.altitude - layer.resolution_fov = int(np.ceil((self.tel.resolution/self.tel.D)*layer.D)) - - layer.center = layer.resolution//2 - - if self.asterism is None: - [x_z,y_z] = pol2cart(self.tel.src.coordinates[0]*(layer.D_fov-self.tel.D)/self.tel.D,np.deg2rad(self.tel.src.coordinates[1])) - - center_x = int(y_z)+layer.resolution//2 - center_y = int(x_z)+layer.resolution//2 - - layer.pupil_footprint = np.zeros([layer.resolution,layer.resolution]) - layer.pupil_footprint[center_x-self.tel.resolution//2:center_x+self.tel.resolution//2,center_y-self.tel.resolution//2:center_y+self.tel.resolution//2 ] = 1 - else: - layer.pupil_footprint= [] - for i in range(self.asterism.n_source): - [x_z,y_z] = pol2cart(self.asterism.coordinates[i][0]*(layer.D_fov-self.tel.D)/self.tel.D,np.deg2rad(self.asterism.coordinates[i][1])) - - center_x = int(y_z)+layer.resolution//2 - center_y = int(x_z)+layer.resolution//2 - - pupil_footprint = np.zeros([layer.resolution,layer.resolution]) - pupil_footprint[center_x-self.tel.resolution//2:center_x+self.tel.resolution//2,center_y-self.tel.resolution//2:center_y+self.tel.resolution//2 ] = 1 - layer.pupil_footprint.append(pupil_footprint) - # layer pixel size - layer.d0 = layer.D/layer.resolution - - # number of pixel for the phase screens computation - layer.nExtra = self.nExtra - layer.nPixel = int(1+np.round(layer.D/layer.d0)) - if compute_turbulence: - print('-> Computing the initial phase screen...') - a=time.time() - if self.mode == 2: - from AO_modules.phaseStats import ft_sh_phase_screen - layer.phase = ft_sh_phase_screen(self,layer.resolution,layer.D/layer.resolution,seed=i_layer) - else: - layer.phase = ft_phase_screen(self,layer.resolution,layer.D/layer.resolution,seed=i_layer) - layer.initialPhase = layer.phase.copy() - layer.seed = i_layer - b=time.time() - print('initial phase screen : ' +str(b-a) +' s') - - # Outer ring of pixel for the phase screens update - layer.outerMask = np.ones([layer.resolution+layer.nExtra,layer.resolution+layer.nExtra]) - layer.outerMask[1:-1,1:-1] = 0 - - # inner pixels that contains the phase screens - layer.innerMask = np.ones([layer.resolution+layer.nExtra,layer.resolution+layer.nExtra]) - layer.innerMask -= layer.outerMask - layer.innerMask[1+layer.nExtra:-1-layer.nExtra,1+layer.nExtra:-1-layer.nExtra] = 0 - - l = np.linspace(0,layer.resolution+1,layer.resolution+2) * layer.D/(layer.resolution-1) - u,v = np.meshgrid(l,l) - - layer.innerZ = u[layer.innerMask!=0] + 1j*v[layer.innerMask!=0] - layer.outerZ = u[layer.outerMask!=0] + 1j*v[layer.outerMask!=0] - - layer.ZZt, layer.ZXt, layer.XXt, layer.ZZt_inv = self.get_covariance_matrices(layer) - - layer.ZZt_r0 = self.ZZt_r0.copy() - layer.ZXt_r0 = self.ZXt_r0.copy() - layer.XXt_r0 = self.XXt_r0.copy() - layer.ZZt_inv_r0 = self.ZZt_inv_r0.copy() - - layer.A = np.matmul(layer.ZXt_r0.T,layer.ZZt_inv_r0) - layer.BBt = layer.XXt_r0 - np.matmul(layer.A,layer.ZXt_r0) - layer.B = np.linalg.cholesky(layer.BBt) - layer.mapShift = np.zeros([layer.nPixel+1,layer.nPixel+1]) - Z = layer.phase[layer.innerMask[1:-1,1:-1]!=0] - X = np.matmul(layer.A,Z) + np.matmul(layer.B,layer.randomState.normal(size=layer.B.shape[1])) - - layer.mapShift[layer.outerMask!=0] = X - layer.mapShift[layer.outerMask==0] = np.reshape(layer.phase,layer.resolution*layer.resolution) - layer.notDoneOnce = True - - print('Done!') - - - return layer - - - def add_row(self,layer,stepInPixel): - shiftMatrix = translationImageMatrix(layer.mapShift,[stepInPixel[0],stepInPixel[1]]) #units are in pixel of the M1 - tmp = globalTransformation(layer.mapShift,shiftMatrix) - onePixelShiftedPhaseScreen = tmp[1:-1,1:-1] - Z = onePixelShiftedPhaseScreen[layer.innerMask[1:-1,1:-1]!=0] - X = layer.A@Z + layer.B@layer.randomState.normal(size=layer.B.shape[1]) - layer.mapShift[layer.outerMask!=0] = X - layer.mapShift[layer.outerMask==0] = np.reshape(onePixelShiftedPhaseScreen,layer.resolution*layer.resolution) - return onePixelShiftedPhaseScreen - - def updateLayer(self,layer): - self.ps_loop = layer.D / (layer.resolution) - ps_turb_x = layer.vX*self.tel.samplingTime - ps_turb_y = layer.vY*self.tel.samplingTime - - if layer.vX==0 and layer.vY==0: - layer.phase = layer.phase - - else: - if layer.notDoneOnce: - layer.notDoneOnce = False - layer.ratio = np.zeros(2) - layer.ratio[0] = ps_turb_x/self.ps_loop - layer.ratio[1] = ps_turb_y/self.ps_loop - layer.buff = np.zeros(2) - tmpRatio = np.abs(layer.ratio) - tmpRatio[np.isinf(tmpRatio)]=0 - nScreens = (tmpRatio) - nScreens = nScreens.astype('int') - - stepInPixel =np.zeros(2) - stepInSubPixel =np.zeros(2) - - for i in range(nScreens.min()): - stepInPixel[0]=1 - stepInPixel[1]=1 - stepInPixel=stepInPixel*np.sign(layer.ratio) - layer.phase = self.add_row(layer,stepInPixel) - - for j in range(nScreens.max()-nScreens.min()): - stepInPixel[0]=1 - stepInPixel[1]=1 - stepInPixel=stepInPixel*np.sign(layer.ratio) - stepInPixel[np.where(nScreens==nScreens.min())]=0 - layer.phase = self.add_row(layer,stepInPixel) - - - stepInSubPixel[0] = (np.abs(layer.ratio[0])%1)*np.sign(layer.ratio[0]) - stepInSubPixel[1] = (np.abs(layer.ratio[1])%1)*np.sign(layer.ratio[1]) - - layer.buff += stepInSubPixel - if np.abs(layer.buff[0])>=1 or np.abs(layer.buff[1])>=1: - stepInPixel[0] = 1*np.sign(layer.buff[0]) - stepInPixel[1] = 1*np.sign(layer.buff[1]) - stepInPixel[np.where(np.abs(layer.buff)<1)]=0 - - layer.phase = self.add_row(layer,stepInPixel) - - layer.buff[0] = (np.abs(layer.buff[0])%1)*np.sign(layer.buff[0]) - layer.buff[1] = (np.abs(layer.buff[1])%1)*np.sign(layer.buff[1]) - - shiftMatrix = translationImageMatrix(layer.mapShift,[layer.buff[0],layer.buff[1]]) #units are in pixel of the M1 - layer.phase = globalTransformation(layer.mapShift,shiftMatrix)[1:-1,1:-1] - - def update(self): - phase_support = self.initialize_phase_support() - for i_layer in range(self.nLayer): - tmpLayer=getattr(self,'layer_'+str(i_layer+1)) - self.updateLayer(tmpLayer) - phase_support = self.fill_phase_support(tmpLayer,phase_support,i_layer) - self.set_OPD(phase_support) - if self.tel.isPaired: - self*self.tel - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ATM METHODS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - def initialize_phase_support(self): - if self.asterism is None: - phase_support = np.zeros([self.tel.resolution,self.tel.resolution]) - else: - phase_support = [] - for i in range(self.asterism.n_source): - phase_support.append(np.zeros([self.tel.resolution,self.tel.resolution])) - return phase_support - def fill_phase_support(self,tmpLayer,phase_support,i_layer): - if self.asterism is None: - phase_support+= np.reshape(tmpLayer.phase[np.where(tmpLayer.pupil_footprint==1)],[self.tel.resolution,self.tel.resolution])* np.sqrt(self.fractionalR0[i_layer]) - # wavelenfth scaling - tmpLayer.phase *= self.wavelength/2/np.pi - else: - for i in range(self.asterism.n_source): - if self.asterism.src[i].type == 'LGS': - sub_im = np.reshape(tmpLayer.phase[np.where(tmpLayer.pupil_footprint[i]==1)],[self.tel.resolution,self.tel.resolution]) - alpha_cone = np.arctan(self.tel.D/2/self.asterism.altitude[i]) - h = self.asterism.altitude[i]-tmpLayer.altitude - if np.isinf(h): - r =self.tel.D/2 - else: - r = h*np.tan(alpha_cone) - ratio = self.tel.D/r/2 - cube_in = np.atleast_3d(sub_im).T - - pixel_size_in = tmpLayer.D/tmpLayer.resolution - pixel_size_out = pixel_size_in/ratio - resolution_out = self.tel.resolution - - phase_support[i]+= np.squeeze(interpolate_cube(cube_in, pixel_size_in, pixel_size_out, resolution_out)).T* np.sqrt(self.fractionalR0[i_layer]) - - else: - phase_support[i]+= np.reshape(tmpLayer.phase[np.where(tmpLayer.pupil_footprint[i]==1)],[self.tel.resolution,self.tel.resolution])* np.sqrt(self.fractionalR0[i_layer]) - return phase_support - - def set_OPD(self,phase_support): - if self.asterism is None: - self.OPD_no_pupil = phase_support*self.wavelength/2/np.pi - self.OPD = self.OPD_no_pupil*self.tel.pupil - else: - self.OPD =[] - self.OPD_no_pupil = [] - for i in range(self.asterism.n_source): - self.OPD_no_pupil.append(phase_support[i]*self.wavelength/2/np.pi) - self.OPD.append(self.OPD_no_pupil[i]*self.tel.pupil) - return - def get_covariance_matrices(self,layer): - # Compute the covariance matrices - compute_covariance_matrices = True - - if self.tel.fov == 0: - try: - c=time.time() - self.ZZt_r0 = self.ZZt_r0 - d=time.time() - print('ZZt.. : ' +str(d-c) +' s') - self.ZXt_r0 = self.ZXt_r0 - e=time.time() - print('ZXt.. : ' +str(e-d) +' s') - self.XXt_r0 = self.XXt_r0 - f=time.time() - print('XXt.. : ' +str(f-e) +' s') - self.ZZt_inv_r0 = self.ZZt_inv_r0 - - print('SCAO system considered: covariance matrices were already computed!') - compute_covariance_matrices = False - except: - compute_covariance_matrices = True - if compute_covariance_matrices: - c=time.time() - self.ZZt = makeCovarianceMatrix(layer.innerZ,layer.innerZ,self) - - if self.param is None: - self.ZZt_inv = np.linalg.pinv(self.ZZt) - else: - try: - print('Loading pre-computed data...') - name_data = 'ZZt_inv_spider_L0_'+str(self.L0)+'_m_r0_'+str(self.r0_def)+'_shape_'+str(self.ZZt.shape[0])+'x'+str(self.ZZt.shape[1])+'.json' - location_data = self.param['pathInput'] + self.param['name'] + '/sk_v/' - try: - with open(location_data+name_data ) as f: - C = json.load(f) - data_loaded = jsonpickle.decode(C) - except: - createFolder(location_data) - with open(location_data+name_data ) as f: - C = json.load(f) - data_loaded = jsonpickle.decode(C) - self.ZZt_inv = data_loaded['ZZt_inv'] - - except: - print('Something went wrong.. re-computing ZZt_inv ...') - name_data = 'ZZt_inv_spider_L0_'+str(self.L0)+'_m_r0_'+str(self.r0_def)+'_shape_'+str(self.ZZt.shape[0])+'x'+str(self.ZZt.shape[1])+'.json' - location_data = self.param['pathInput'] + self.param['name'] + '/sk_v/' - createFolder(location_data) - - self.ZZt_inv = np.linalg.pinv(self.ZZt) - - print('saving for future...') - data = dict() - data['pupil'] = self.tel.pupil - data['ZZt_inv'] = self.ZZt_inv - - data_encoded = jsonpickle.encode(data) - with open(location_data+name_data, 'w') as f: - json.dump(data_encoded, f) - d=time.time() - print('ZZt.. : ' +str(d-c) +' s') - self.ZXt = makeCovarianceMatrix(layer.innerZ,layer.outerZ,self) - e=time.time() - print('ZXt.. : ' +str(e-d) +' s') - self.XXt = makeCovarianceMatrix(layer.outerZ,layer.outerZ,self) - f=time.time() - print('XXt.. : ' +str(f-e) +' s') - - self.ZZt_r0 = self.ZZt*(self.r0_def/self.r0)**(5/3) - self.ZXt_r0 = self.ZXt*(self.r0_def/self.r0)**(5/3) - self.XXt_r0 = self.XXt*(self.r0_def/self.r0)**(5/3) - self.ZZt_inv_r0 = self.ZZt_inv/((self.r0_def/self.r0)**(5/3)) - return self.ZZt, self.ZXt, self.XXt, self.ZZt_inv - - def generateNewPhaseScreen(self,seed = None): - if seed is None: - t = time.localtime() - seed = t.tm_hour*3600 + t.tm_min*60 + t.tm_sec - phase_support = self.initialize_phase_support() - for i_layer in range(self.nLayer): - tmpLayer=getattr(self,'layer_'+str(i_layer+1)) - - if self.mode ==1: - import aotools as ao - phaseScreen = ao.turbulence.infinitephasescreen.PhaseScreenVonKarman(tmpLayer.resolution,tmpLayer.D/(tmpLayer.resolution),self.r0,self.L0,random_seed=seed+i_layer) - phase = phaseScreen.scrn - else: - if self.mode == 2: - from AO_modules.phaseStats import ft_sh_phase_screen - phase = ft_sh_phase_screen(self,tmpLayer.resolution,tmpLayer.D/tmpLayer.resolution,seed=seed+i_layer) - else: - phase = ft_phase_screen(self,tmpLayer.resolution,tmpLayer.D/tmpLayer.resolution,seed=seed+i_layer) - - tmpLayer.phase = phase - tmpLayer.randomState = RandomState(42+i_layer*1000) - - Z = tmpLayer.phase[tmpLayer.innerMask[1:-1,1:-1]!=0] - X = np.matmul(tmpLayer.A,Z) + np.matmul(tmpLayer.B,tmpLayer.randomState.normal( size=tmpLayer.B.shape[1])) - - tmpLayer.mapShift[tmpLayer.outerMask!=0] = X - tmpLayer.mapShift[tmpLayer.outerMask==0] = np.reshape(tmpLayer.phase,tmpLayer.resolution*tmpLayer.resolution) - tmpLayer.notDoneOnce = True - - setattr(self,'layer_'+str(i_layer+1),tmpLayer ) - phase_support = self.fill_phase_support(tmpLayer,phase_support,i_layer) - self.set_OPD(phase_support) - if self.tel.isPaired: - self*self.tel - - - def print_atm_at_wavelength(self,wavelength): - - r0_wvl = self.r0*((wavelength/self.wavelength)**(5/3)) - seeingArcsec_wvl = 206265*(wavelength/r0_wvl) - - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ATMOSPHERE AT '+str(wavelength)+' nm %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('r0 \t\t'+str(r0_wvl) + ' \t [m]') - print('Seeing \t' + str(np.round(seeingArcsec_wvl,2)) + str('\t ["]')) - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - return - - def print_atm(self): - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ATMOSPHERE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('{: ^18s}'.format('Layer') + '{: ^18s}'.format('Direction [deg]')+ '{: ^18s}'.format('Speed [m/s]')+ '{: ^18s}'.format('Altitude [m]')+ '{: ^18s}'.format('Cn2 [m-2/3]') ) - print('------------------------------------------------------------------------------------------') - - for i_layer in range(self.nLayer): - print('{: ^18s}'.format(str(i_layer+1)) + '{: ^18s}'.format(str(self.windDirection[i_layer]))+ '{: ^18s}'.format(str(self.windSpeed[i_layer]))+ '{: ^18s}'.format(str(self.altitude[i_layer]))+ '{: ^18s}'.format(str(self.fractionalR0[i_layer]) )) - print('------------------------------------------------------------------------------------------') - print('******************************************************************************************') - - print('{: ^18s}'.format('r0') + '{: ^18s}'.format(str(self.r0)+' [m]' )) - print('{: ^18s}'.format('L0') + '{: ^18s}'.format(str(self.L0)+' [m]' )) - print('{: ^18s}'.format('Seeing (V)') + '{: ^18s}'.format(str(np.round(self.seeingArcsec,2))+' ["]')) - print('{: ^18s}'.format('Frequency') + '{: ^18s}'.format(str(np.round(1/self.tel.samplingTime,2))+' [Hz]' )) - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - - def __mul__(self,obj): - obj.OPD=self.OPD - obj.OPD_no_pupil=self.OPD_no_pupil - obj.isPaired=True - return obj - - - def display_atm_layers(self,layer_index= None,fig_index = None): - if layer_index is None: - layer_index = list(np.arange(self.nLayer)) - if type(layer_index) is not list: - raise TypeError(' layer_index should be a list') - col = getColorOrder() - if fig_index is None: - - fig_index = time.time_ns() - plt.figure(fig_index) - axis_list = [] - for i in range(len(layer_index)): - axis_list.append(plt.subplot(1,len(layer_index),i+1)) - - for i_l,ax in enumerate(axis_list): - - tmpLayer = getattr(self, 'layer_'+str(layer_index[i_l]+1)) - ax.imshow(tmpLayer.phase,extent = [-tmpLayer.D/2,tmpLayer.D/2,-tmpLayer.D/2,tmpLayer.D/2]) - center = tmpLayer.D/2 - [x_tel,y_tel] = pol2cart(tmpLayer.D_fov/2, np.linspace(0,2*np.pi,100,endpoint=True)) - if self.tel.src.tag =='asterism': - for i_source in range(len(self.tel.src.src)): - - [x_c,y_c] = pol2cart(self.tel.D/2, np.linspace(0,2*np.pi,100,endpoint=True)) - alpha_cone = np.arctan(self.tel.D/2/self.tel.src.src[i_source].altitude) - - h = self.tel.src.src[i_source].altitude-tmpLayer.altitude - if np.isinf(h): - r =self.tel.D/2 - else: - r = h*np.tan(alpha_cone) - [x_cone,y_cone] = pol2cart(r, np.linspace(0,2*np.pi,100,endpoint=True)) - - [x_z,y_z] = pol2cart(self.tel.src.src[i_source].coordinates[0]*(tmpLayer.D_fov-self.tel.D)/self.tel.resolution,np.deg2rad(self.tel.src.src[i_source].coordinates[1])) - center = 0 - [x_c,y_c] = pol2cart(tmpLayer.D_fov/2, np.linspace(0,2*np.pi,100,endpoint=True)) - - ax.plot(x_cone+x_z+center,y_cone+y_z+center,'-', color = col [i_source]) - ax.fill(x_cone+x_z+center,y_cone+y_z+center,y_z+center, alpha = 0.25, color = col[i_source]) - else: - [x_c,y_c] = pol2cart(self.tel.D/2, np.linspace(0,2*np.pi,100,endpoint=True)) - alpha_cone = np.arctan(self.tel.D/2/self.tel.src.altitude) - - h = self.tel.src.altitude-tmpLayer.altitude - if np.isinf(h): - r =self.tel.D/2 - else: - r = h*np.tan(alpha_cone) - [x_cone,y_cone] = pol2cart(r, np.linspace(0,2*np.pi,100,endpoint=True)) - - [x_z,y_z] = pol2cart(self.tel.src.coordinates[0]*(tmpLayer.D_fov-self.tel.D)/self.tel.resolution,np.deg2rad(self.tel.src.coordinates[1])) - - center = 0 - [x_c,y_c] = pol2cart(tmpLayer.D_fov/2, np.linspace(0,2*np.pi,100,endpoint=True)) - - ax.plot(x_cone+x_z+center,y_cone+y_z+center,'-') - ax.fill(x_cone+x_z+center,y_cone+y_z+center,y_z+center, alpha = 0.6) - - ax.set_xlabel('[m]') - ax.set_ylabel('[m]') - ax.set_title('Altitude '+str(tmpLayer.altitude)+' m') - ax.plot(x_tel+center,y_tel+center,'--',color = 'k') - - # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ATM PROPERTIES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - - @property - def r0(self): - return self._r0 - - @r0.setter - def r0(self,val): - self._r0 = val - - if self.hasNotBeenInitialized is False: - print('Updating the Atmosphere covariance matrices...') - self.seeingArcsec = 206265*(self.wavelength/val) - for i_layer in range(self.nLayer): - tmpLayer = getattr(self,'layer_'+str(i_layer+1)) - - tmpLayer.ZZt_r0 = tmpLayer.ZZt*(self.r0_def/self.r0)**(5/3) - tmpLayer.ZXt_r0 = tmpLayer.ZXt*(self.r0_def/self.r0)**(5/3) - tmpLayer.XXt_r0 = tmpLayer.XXt*(self.r0_def/self.r0)**(5/3) - tmpLayer.ZZt_inv_r0 = tmpLayer.ZZt_inv/((self.r0_def/self.r0)**(5/3)) - BBt = tmpLayer.XXt_r0 - np.matmul(tmpLayer.A,tmpLayer.ZXt_r0) - tmpLayer.B = np.linalg.cholesky(BBt) - - @property - def L0(self): - return self._L0 - - @L0.setter - def L0(self,val): - self._L0 = val - if self.hasNotBeenInitialized is False: - print('Updating the Atmosphere covariance matrices...') - - self.hasNotBeenInitialized = True - del self.ZZt - del self.XXt - del self.ZXt - del self.ZZt_inv - self.initializeAtmosphere(self.tel) - @property - def windSpeed(self): - return self._windSpeed - - @windSpeed.setter - def windSpeed(self,val): - self._windSpeed = val - - if self.hasNotBeenInitialized is False: - if len(val)!= self.nLayer: - print('Error! Wrong value for the wind-speed! Make sure that you inpute a wind-speed for each layer') - else: - print('Updating the wing speed...') - for i_layer in range(self.nLayer): - tmpLayer = getattr(self,'layer_'+str(i_layer+1)) - tmpLayer.windSpeed = val[i_layer] - tmpLayer.vY = tmpLayer.windSpeed*np.cos(np.deg2rad(tmpLayer.direction)) - tmpLayer.vX = tmpLayer.windSpeed*np.sin(np.deg2rad(tmpLayer.direction)) - ps_turb_x = tmpLayer.vX*self.tel.samplingTime - ps_turb_y = tmpLayer.vY*self.tel.samplingTime - tmpLayer.ratio[0] = ps_turb_x/self.ps_loop - tmpLayer.ratio[1] = ps_turb_y/self.ps_loop - setattr(self,'layer_'+str(i_layer+1),tmpLayer ) - # self.print_atm() - - @property - def windDirection(self): - return self._windDirection - - @windDirection.setter - def windDirection(self,val): - self._windDirection = val - - if self.hasNotBeenInitialized is False: - if len(val)!= self.nLayer: - print('Error! Wrong value for the wind-speed! Make sure that you inpute a wind-speed for each layer') - else: - print('Updating the wind direction...') - for i_layer in range(self.nLayer): - tmpLayer = getattr(self,'layer_'+str(i_layer+1)) - # tmpLayer.notDoneOnce = True - tmpLayer.direction = val[i_layer] - tmpLayer.vY = tmpLayer.windSpeed*np.cos(np.deg2rad(tmpLayer.direction)) - tmpLayer.vX = tmpLayer.windSpeed*np.sin(np.deg2rad(tmpLayer.direction)) - ps_turb_x = tmpLayer.vX*self.tel.samplingTime - ps_turb_y = tmpLayer.vY*self.tel.samplingTime - tmpLayer.ratio[0] = ps_turb_x/self.ps_loop - tmpLayer.ratio[1] = ps_turb_y/self.ps_loop - setattr(self,'layer_'+str(i_layer+1),tmpLayer ) - # self.print_atm() - - - - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - def show(self): - attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) - print(self.tag+':') - for a in attributes: - if not(a[0].startswith('__') and a[0].endswith('__')): - if not(a[0].startswith('_')): - if not np.shape(a[1]): - tmp=a[1] - try: - print(' '+str(a[0])+': '+str(tmp.tag)+' object') - except: - print(' '+str(a[0])+': '+str(a[1])) - else: - if np.ndim(a[1])>1: - print(' '+str(a[0])+': '+str(np.shape(a[1]))) - -class Layer: - pass - - - - - - - - - +# -*- coding: utf-8 -*- +""" +Created on Fri Aug 14 10:59:02 2020 + +@author: cheritie +""" + +import inspect +import json +import time + +import jsonpickle +import matplotlib.pyplot as plt +import numpy as np +from numpy.random import RandomState + +from .phaseStats import ft_phase_screen, ft_sh_phase_screen, makeCovarianceMatrix +from .tools.displayTools import getColorOrder +from .tools.interpolateGeometricalTransformation import interpolate_cube +from .tools.tools import createFolder, emptyClass, globalTransformation, pol2cart, translationImageMatrix + + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CLASS INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +class Atmosphere: + def __init__(self,telescope,r0,L0,windSpeed,fractionalR0,windDirection,altitude,mode=2, param = None, asterism = None): + """ + An Atmosphere object is made of one or several layer of turbulence that follow the Van Karmann statistics. Each layer is considered to be independant to the other ones and has its own properties (direction, speed, etc.) + + The Atmosphere object can be defined for a single Source object (default) or multi Source Object. The Source coordinates allow to span different areas in the field (defined as well by the tel.fov). + + If the source type is an LGS the cone effect is considered using an interpolation. NGS and LGS can be combined together in the asterism object. + + The convention chosen is that all the wavelength-dependant atmosphere parameters are expressed at 500 nm. + + + ************************** REQUIRED PARAMETERS ************************** + It requires the following parameters: + + _ telescope : the telescope object to which the Atmosphere is associated. This object carries the phase, flux, pupil information and sampling time as well as the type of source (NGS/LGS, source/asterism) + _ r0 : the Fried parameter in m + _ L0 : Outer scale parameter + _ windSpeed : List of wind-speed for each layer in [m/s] + _ fractionalR0 : Cn2 profile of the turbulence. This should be a list of values for each layer + _ windDirection : List of wind-direction for each layer in [deg] + _ altitude : List of altitude for each layer in [m] + + ************************** OPTIONAL PARAMETERS ************************** + + _ mode : Different ways to generate the spectrum of the atmosphere + _ param : parameter file of the system. Once computed, the covariance matrices are saved in the calibration data folder and loaded instead of re-computed evry time. + _ asterism : if the system contains multiple source, an astrism should be input to the atmosphere object + + ************************** COUPLING A TELESCOPE AND AN ATMOSPHERE OBJECT ************************** + A Telescope object "tel" can be coupled to an Atmosphere object "atm" using: + _ tel + atm + This means that a bridge is created between atm and tel: everytime that atm.OPD is updated, the tel.OPD property is automatically set to atm.OPD to reproduce the effect of the turbulence. + + A Telescope object "tel" can be separated of an Atmosphere object "atm" using: + _ tel - atm + This corresponds to a diffraction limited case (no turbulence) + + + ************************** PROPERTIES ************************** + + The main properties of the Atmosphere object are listed here: + + _ atm.OPD : Optical Path Difference in [m] truncated by the telescope pupil. If the atmosphere has multiple sources, the OPD is a list of OPD for each source + _ atm.OPD_no_pupil : Optical Path Difference in [m]. If the atmosphere has multiple sources, the OPD is a list of OPD for each source + _ atm.r0 + _ atm.L0 + _ atm.nLayer : number of turbulence layers + _ atm.seeingArcsec : seeing in arcsec at 500 nm + _ atm.layer_X : access the child object corresponding to the layer X where X starts at 0 + + the following properties can be updated on the fly: + _ atm.r0 + _ atm.windSpeed + _ atm.windDirection + ************************** FUNCTIONS ************************** + + _ atm.update() : update the OPD of the atmosphere for each layer according to the time step defined by tel.samplingTime + _ atm.generateNewPhaseScreen(seed) : generate a new phase screen for the atmosphere OPD + _ atm.print_atm_at_wavelength(wavelength) : prompt seeing and r0 at specified wavelength + _ atm.print_atm() : prompt the main properties of the atm object + _ display_atm_layers(layer_index) : imshow the OPD of each layer with the intersection beam for each source + + """ + self.hasNotBeenInitialized = True + self.r0_def = 0.15 # Fried Parameter in m + self.r0 = r0 # Fried Parameter in m + self.fractionalR0 = fractionalR0 # Cn2 square profile + self.L0 = L0 # Outer Scale in m + self.altitude = altitude # altitude of the layers + self.nLayer = len(fractionalR0) # number of layer + self.windSpeed = windSpeed # wind speed of the layers in m/s + self.windDirection = windDirection # wind direction in degrees + self.tag = 'atmosphere' # Tag of the object + self.nExtra = 2 # number of extra pixel to generate the phase screens + self.wavelength = 500*1e-9 # Wavelengt used to define the properties of the atmosphere + self.tel = telescope # associated telescope object + self.mode = mode # DEBUG -> first phase screen generation mode + self.seeingArcsec = 206265*(self.wavelength/self.r0) + self.asterism = asterism # case when multiple sources are considered (LGS and NGS) + self.param = param + if self.asterism is not None: + self.oversampling_factor = np.max((np.asarray(self.asterism.coordinates)[:,0]/(self.tel.resolution/2))) + else: + self.oversampling_factor = self.tel.src.coordinates[0]/(self.tel.resolution/2) + + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ATM INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + def initializeAtmosphere(self,telescope): + phase_support = self.initialize_phase_support() + if self.hasNotBeenInitialized: + self.initial_r0 = self.r0 + for i_layer in range(self.nLayer): + # create the layer + print('Creation of layer' + str(i_layer+1) + '/' + str(self.nLayer) + ' ...' ) + tmpLayer=self.buildLayer(telescope,self.r0_def,self.L0,i_layer = i_layer) + setattr(self,'layer_'+str(i_layer+1),tmpLayer) + + phase_support = self.fill_phase_support(tmpLayer,phase_support,i_layer) + tmpLayer.phase_support = phase_support + tmpLayer.phase *= self.wavelength/2/np.pi + + else: + print('Re-setting the atmosphere to its initial state...' ) + self.r0 = self.initial_r0 + for i_layer in range(self.nLayer): + print('Updating layer' + str(i_layer+1) + '/' + str(self.nLayer) + ' ...' ) + tmpLayer = getattr(self,'layer_'+str(i_layer+1)) + tmpLayer.phase = tmpLayer.initialPhase/self.wavelength*2*np.pi + tmpLayer.randomState = RandomState(42+i_layer*1000) + + Z = tmpLayer.phase[tmpLayer.innerMask[1:-1,1:-1]!=0] + X = np.matmul(tmpLayer.A,Z) + np.matmul(tmpLayer.B,tmpLayer.randomState.normal( size=tmpLayer.B.shape[1])) + + tmpLayer.mapShift[tmpLayer.outerMask!=0] = X + tmpLayer.mapShift[tmpLayer.outerMask==0] = np.reshape(tmpLayer.phase,tmpLayer.resolution*tmpLayer.resolution) + + tmpLayer.notDoneOnce = True + + setattr(self,'layer_'+str(i_layer+1),tmpLayer) + + phase_support = self.fill_phase_support(tmpLayer,phase_support,i_layer) + + # wavelenfth scaling + tmpLayer.phase *= self.wavelength/2/np.pi + self.generateNewPhaseScreen(seed=0) + + self.hasNotBeenInitialized = False + # save the resulting phase screen in OPD + self.set_OPD(phase_support) + self.print_atm() + + def buildLayer(self,telescope,r0,L0,i_layer,compute_turbulence = True): + """ + Generation of phase screens using the method introduced in Assemat et al (2006) + """ + + + # initialize layer object + layer = emptyClass() + # create a random state to allow reproductible sequences of phase screens + layer.randomState = RandomState(42+i_layer*1000) + + # gather properties of the atmosphere + layer.altitude = self.altitude[i_layer] + layer.windSpeed = self.windSpeed[i_layer] + layer.direction = self.windDirection[i_layer] + + # compute the X and Y wind speed + layer.vY = layer.windSpeed*np.cos(np.deg2rad(layer.direction)) + layer.vX = layer.windSpeed*np.sin(np.deg2rad(layer.direction)) + + # Diameter and resolution of the layer including the Field Of View and the number of extra pixels + layer.D = self.tel.D+2*np.tan(self.tel.fov/2)*layer.altitude*self.oversampling_factor + layer.resolution = int(np.ceil((self.tel.resolution/self.tel.D)*layer.D)) + + layer.D_fov = self.tel.D+2*np.tan(self.tel.fov/2)*layer.altitude + layer.resolution_fov = int(np.ceil((self.tel.resolution/self.tel.D)*layer.D)) + + layer.center = layer.resolution//2 + + if self.asterism is None: + [x_z,y_z] = pol2cart(self.tel.src.coordinates[0]*(layer.D_fov-self.tel.D)/self.tel.D,np.deg2rad(self.tel.src.coordinates[1])) + + center_x = int(y_z)+layer.resolution//2 + center_y = int(x_z)+layer.resolution//2 + + layer.pupil_footprint = np.zeros([layer.resolution,layer.resolution]) + layer.pupil_footprint[center_x-self.tel.resolution//2:center_x+self.tel.resolution//2,center_y-self.tel.resolution//2:center_y+self.tel.resolution//2 ] = 1 + else: + layer.pupil_footprint= [] + for i in range(self.asterism.n_source): + [x_z,y_z] = pol2cart(self.asterism.coordinates[i][0]*(layer.D_fov-self.tel.D)/self.tel.D,np.deg2rad(self.asterism.coordinates[i][1])) + + center_x = int(y_z)+layer.resolution//2 + center_y = int(x_z)+layer.resolution//2 + + pupil_footprint = np.zeros([layer.resolution,layer.resolution]) + pupil_footprint[center_x-self.tel.resolution//2:center_x+self.tel.resolution//2,center_y-self.tel.resolution//2:center_y+self.tel.resolution//2 ] = 1 + layer.pupil_footprint.append(pupil_footprint) + # layer pixel size + layer.d0 = layer.D/layer.resolution + + # number of pixel for the phase screens computation + layer.nExtra = self.nExtra + layer.nPixel = int(1+np.round(layer.D/layer.d0)) + if compute_turbulence: + print('-> Computing the initial phase screen...') + a=time.time() + if self.mode == 2: + layer.phase = ft_sh_phase_screen(self,layer.resolution,layer.D/layer.resolution,seed=i_layer) + else: + layer.phase = ft_phase_screen(self,layer.resolution,layer.D/layer.resolution,seed=i_layer) + layer.initialPhase = layer.phase.copy() + layer.seed = i_layer + b=time.time() + print('initial phase screen : ' +str(b-a) +' s') + + # Outer ring of pixel for the phase screens update + layer.outerMask = np.ones([layer.resolution+layer.nExtra,layer.resolution+layer.nExtra]) + layer.outerMask[1:-1,1:-1] = 0 + + # inner pixels that contains the phase screens + layer.innerMask = np.ones([layer.resolution+layer.nExtra,layer.resolution+layer.nExtra]) + layer.innerMask -= layer.outerMask + layer.innerMask[1+layer.nExtra:-1-layer.nExtra,1+layer.nExtra:-1-layer.nExtra] = 0 + + l = np.linspace(0,layer.resolution+1,layer.resolution+2) * layer.D/(layer.resolution-1) + u,v = np.meshgrid(l,l) + + layer.innerZ = u[layer.innerMask!=0] + 1j*v[layer.innerMask!=0] + layer.outerZ = u[layer.outerMask!=0] + 1j*v[layer.outerMask!=0] + + layer.ZZt, layer.ZXt, layer.XXt, layer.ZZt_inv = self.get_covariance_matrices(layer) + + layer.ZZt_r0 = self.ZZt_r0.copy() + layer.ZXt_r0 = self.ZXt_r0.copy() + layer.XXt_r0 = self.XXt_r0.copy() + layer.ZZt_inv_r0 = self.ZZt_inv_r0.copy() + + layer.A = np.matmul(layer.ZXt_r0.T,layer.ZZt_inv_r0) + layer.BBt = layer.XXt_r0 - np.matmul(layer.A,layer.ZXt_r0) + layer.B = np.linalg.cholesky(layer.BBt) + layer.mapShift = np.zeros([layer.nPixel+1,layer.nPixel+1]) + Z = layer.phase[layer.innerMask[1:-1,1:-1]!=0] + X = np.matmul(layer.A,Z) + np.matmul(layer.B,layer.randomState.normal(size=layer.B.shape[1])) + + layer.mapShift[layer.outerMask!=0] = X + layer.mapShift[layer.outerMask==0] = np.reshape(layer.phase,layer.resolution*layer.resolution) + layer.notDoneOnce = True + + print('Done!') + + + return layer + + + def add_row(self,layer,stepInPixel): + shiftMatrix = translationImageMatrix(layer.mapShift,[stepInPixel[0],stepInPixel[1]]) #units are in pixel of the M1 + tmp = globalTransformation(layer.mapShift,shiftMatrix) + onePixelShiftedPhaseScreen = tmp[1:-1,1:-1] + Z = onePixelShiftedPhaseScreen[layer.innerMask[1:-1,1:-1]!=0] + X = layer.A@Z + layer.B@layer.randomState.normal(size=layer.B.shape[1]) + layer.mapShift[layer.outerMask!=0] = X + layer.mapShift[layer.outerMask==0] = np.reshape(onePixelShiftedPhaseScreen,layer.resolution*layer.resolution) + return onePixelShiftedPhaseScreen + + def updateLayer(self,layer): + self.ps_loop = layer.D / (layer.resolution) + ps_turb_x = layer.vX*self.tel.samplingTime + ps_turb_y = layer.vY*self.tel.samplingTime + + if layer.vX==0 and layer.vY==0: + layer.phase = layer.phase + + else: + if layer.notDoneOnce: + layer.notDoneOnce = False + layer.ratio = np.zeros(2) + layer.ratio[0] = ps_turb_x/self.ps_loop + layer.ratio[1] = ps_turb_y/self.ps_loop + layer.buff = np.zeros(2) + tmpRatio = np.abs(layer.ratio) + tmpRatio[np.isinf(tmpRatio)]=0 + nScreens = (tmpRatio) + nScreens = nScreens.astype('int') + + stepInPixel =np.zeros(2) + stepInSubPixel =np.zeros(2) + + for i in range(nScreens.min()): + stepInPixel[0]=1 + stepInPixel[1]=1 + stepInPixel=stepInPixel*np.sign(layer.ratio) + layer.phase = self.add_row(layer,stepInPixel) + + for j in range(nScreens.max()-nScreens.min()): + stepInPixel[0]=1 + stepInPixel[1]=1 + stepInPixel=stepInPixel*np.sign(layer.ratio) + stepInPixel[np.where(nScreens==nScreens.min())]=0 + layer.phase = self.add_row(layer,stepInPixel) + + + stepInSubPixel[0] = (np.abs(layer.ratio[0])%1)*np.sign(layer.ratio[0]) + stepInSubPixel[1] = (np.abs(layer.ratio[1])%1)*np.sign(layer.ratio[1]) + + layer.buff += stepInSubPixel + if np.abs(layer.buff[0])>=1 or np.abs(layer.buff[1])>=1: + stepInPixel[0] = 1*np.sign(layer.buff[0]) + stepInPixel[1] = 1*np.sign(layer.buff[1]) + stepInPixel[np.where(np.abs(layer.buff)<1)]=0 + + layer.phase = self.add_row(layer,stepInPixel) + + layer.buff[0] = (np.abs(layer.buff[0])%1)*np.sign(layer.buff[0]) + layer.buff[1] = (np.abs(layer.buff[1])%1)*np.sign(layer.buff[1]) + + shiftMatrix = translationImageMatrix(layer.mapShift,[layer.buff[0],layer.buff[1]]) #units are in pixel of the M1 + layer.phase = globalTransformation(layer.mapShift,shiftMatrix)[1:-1,1:-1] + + def update(self): + phase_support = self.initialize_phase_support() + for i_layer in range(self.nLayer): + tmpLayer=getattr(self,'layer_'+str(i_layer+1)) + self.updateLayer(tmpLayer) + phase_support = self.fill_phase_support(tmpLayer,phase_support,i_layer) + self.set_OPD(phase_support) + if self.tel.isPaired: + self*self.tel + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ATM METHODS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + def initialize_phase_support(self): + if self.asterism is None: + phase_support = np.zeros([self.tel.resolution,self.tel.resolution]) + else: + phase_support = [] + for i in range(self.asterism.n_source): + phase_support.append(np.zeros([self.tel.resolution,self.tel.resolution])) + return phase_support + def fill_phase_support(self,tmpLayer,phase_support,i_layer): + if self.asterism is None: + phase_support+= np.reshape(tmpLayer.phase[np.where(tmpLayer.pupil_footprint==1)],[self.tel.resolution,self.tel.resolution])* np.sqrt(self.fractionalR0[i_layer]) + # wavelenfth scaling + tmpLayer.phase *= self.wavelength/2/np.pi + else: + for i in range(self.asterism.n_source): + if self.asterism.src[i].type == 'LGS': + sub_im = np.reshape(tmpLayer.phase[np.where(tmpLayer.pupil_footprint[i]==1)],[self.tel.resolution,self.tel.resolution]) + alpha_cone = np.arctan(self.tel.D/2/self.asterism.altitude[i]) + h = self.asterism.altitude[i]-tmpLayer.altitude + if np.isinf(h): + r =self.tel.D/2 + else: + r = h*np.tan(alpha_cone) + ratio = self.tel.D/r/2 + cube_in = np.atleast_3d(sub_im).T + + pixel_size_in = tmpLayer.D/tmpLayer.resolution + pixel_size_out = pixel_size_in/ratio + resolution_out = self.tel.resolution + + phase_support[i]+= np.squeeze(interpolate_cube(cube_in, pixel_size_in, pixel_size_out, resolution_out)).T* np.sqrt(self.fractionalR0[i_layer]) + + else: + phase_support[i]+= np.reshape(tmpLayer.phase[np.where(tmpLayer.pupil_footprint[i]==1)],[self.tel.resolution,self.tel.resolution])* np.sqrt(self.fractionalR0[i_layer]) + return phase_support + + def set_OPD(self,phase_support): + if self.asterism is None: + self.OPD_no_pupil = phase_support*self.wavelength/2/np.pi + self.OPD = self.OPD_no_pupil*self.tel.pupil + else: + self.OPD =[] + self.OPD_no_pupil = [] + for i in range(self.asterism.n_source): + self.OPD_no_pupil.append(phase_support[i]*self.wavelength/2/np.pi) + self.OPD.append(self.OPD_no_pupil[i]*self.tel.pupil) + return + def get_covariance_matrices(self,layer): + # Compute the covariance matrices + compute_covariance_matrices = True + + if self.tel.fov == 0: + try: + c=time.time() + self.ZZt_r0 = self.ZZt_r0 + d=time.time() + print('ZZt.. : ' +str(d-c) +' s') + self.ZXt_r0 = self.ZXt_r0 + e=time.time() + print('ZXt.. : ' +str(e-d) +' s') + self.XXt_r0 = self.XXt_r0 + f=time.time() + print('XXt.. : ' +str(f-e) +' s') + self.ZZt_inv_r0 = self.ZZt_inv_r0 + + print('SCAO system considered: covariance matrices were already computed!') + compute_covariance_matrices = False + except: + compute_covariance_matrices = True + if compute_covariance_matrices: + c=time.time() + self.ZZt = makeCovarianceMatrix(layer.innerZ,layer.innerZ,self) + + if self.param is None: + self.ZZt_inv = np.linalg.pinv(self.ZZt) + else: + try: + print('Loading pre-computed data...') + name_data = 'ZZt_inv_spider_L0_'+str(self.L0)+'_m_r0_'+str(self.r0_def)+'_shape_'+str(self.ZZt.shape[0])+'x'+str(self.ZZt.shape[1])+'.json' + location_data = self.param['pathInput'] + self.param['name'] + '/sk_v/' + try: + with open(location_data+name_data ) as f: + C = json.load(f) + data_loaded = jsonpickle.decode(C) + except: + createFolder(location_data) + with open(location_data+name_data ) as f: + C = json.load(f) + data_loaded = jsonpickle.decode(C) + self.ZZt_inv = data_loaded['ZZt_inv'] + + except: + print('Something went wrong.. re-computing ZZt_inv ...') + name_data = 'ZZt_inv_spider_L0_'+str(self.L0)+'_m_r0_'+str(self.r0_def)+'_shape_'+str(self.ZZt.shape[0])+'x'+str(self.ZZt.shape[1])+'.json' + location_data = self.param['pathInput'] + self.param['name'] + '/sk_v/' + createFolder(location_data) + + self.ZZt_inv = np.linalg.pinv(self.ZZt) + + print('saving for future...') + data = dict() + data['pupil'] = self.tel.pupil + data['ZZt_inv'] = self.ZZt_inv + + data_encoded = jsonpickle.encode(data) + with open(location_data+name_data, 'w') as f: + json.dump(data_encoded, f) + d=time.time() + print('ZZt.. : ' +str(d-c) +' s') + self.ZXt = makeCovarianceMatrix(layer.innerZ,layer.outerZ,self) + e=time.time() + print('ZXt.. : ' +str(e-d) +' s') + self.XXt = makeCovarianceMatrix(layer.outerZ,layer.outerZ,self) + f=time.time() + print('XXt.. : ' +str(f-e) +' s') + + self.ZZt_r0 = self.ZZt*(self.r0_def/self.r0)**(5/3) + self.ZXt_r0 = self.ZXt*(self.r0_def/self.r0)**(5/3) + self.XXt_r0 = self.XXt*(self.r0_def/self.r0)**(5/3) + self.ZZt_inv_r0 = self.ZZt_inv/((self.r0_def/self.r0)**(5/3)) + return self.ZZt, self.ZXt, self.XXt, self.ZZt_inv + + def generateNewPhaseScreen(self,seed = None): + if seed is None: + t = time.localtime() + seed = t.tm_hour*3600 + t.tm_min*60 + t.tm_sec + phase_support = self.initialize_phase_support() + for i_layer in range(self.nLayer): + tmpLayer=getattr(self,'layer_'+str(i_layer+1)) + + if self.mode ==1: + import aotools as ao + phaseScreen = ao.turbulence.infinitephasescreen.PhaseScreenVonKarman(tmpLayer.resolution,tmpLayer.D/(tmpLayer.resolution),self.r0,self.L0,random_seed=seed+i_layer) + phase = phaseScreen.scrn + else: + if self.mode == 2: + phase = ft_sh_phase_screen(self,tmpLayer.resolution,tmpLayer.D/tmpLayer.resolution,seed=seed+i_layer) + else: + phase = ft_phase_screen(self,tmpLayer.resolution,tmpLayer.D/tmpLayer.resolution,seed=seed+i_layer) + + tmpLayer.phase = phase + tmpLayer.randomState = RandomState(42+i_layer*1000) + + Z = tmpLayer.phase[tmpLayer.innerMask[1:-1,1:-1]!=0] + X = np.matmul(tmpLayer.A,Z) + np.matmul(tmpLayer.B,tmpLayer.randomState.normal( size=tmpLayer.B.shape[1])) + + tmpLayer.mapShift[tmpLayer.outerMask!=0] = X + tmpLayer.mapShift[tmpLayer.outerMask==0] = np.reshape(tmpLayer.phase,tmpLayer.resolution*tmpLayer.resolution) + tmpLayer.notDoneOnce = True + + setattr(self,'layer_'+str(i_layer+1),tmpLayer ) + phase_support = self.fill_phase_support(tmpLayer,phase_support,i_layer) + self.set_OPD(phase_support) + if self.tel.isPaired: + self*self.tel + + + def print_atm_at_wavelength(self,wavelength): + + r0_wvl = self.r0*((wavelength/self.wavelength)**(5/3)) + seeingArcsec_wvl = 206265*(wavelength/r0_wvl) + + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ATMOSPHERE AT '+str(wavelength)+' nm %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('r0 \t\t'+str(r0_wvl) + ' \t [m]') + print('Seeing \t' + str(np.round(seeingArcsec_wvl,2)) + str('\t ["]')) + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + return + + def print_atm(self): + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ATMOSPHERE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('{: ^18s}'.format('Layer') + '{: ^18s}'.format('Direction [deg]')+ '{: ^18s}'.format('Speed [m/s]')+ '{: ^18s}'.format('Altitude [m]')+ '{: ^18s}'.format('Cn2 [m-2/3]') ) + print('------------------------------------------------------------------------------------------') + + for i_layer in range(self.nLayer): + print('{: ^18s}'.format(str(i_layer+1)) + '{: ^18s}'.format(str(self.windDirection[i_layer]))+ '{: ^18s}'.format(str(self.windSpeed[i_layer]))+ '{: ^18s}'.format(str(self.altitude[i_layer]))+ '{: ^18s}'.format(str(self.fractionalR0[i_layer]) )) + print('------------------------------------------------------------------------------------------') + print('******************************************************************************************') + + print('{: ^18s}'.format('r0') + '{: ^18s}'.format(str(self.r0)+' [m]' )) + print('{: ^18s}'.format('L0') + '{: ^18s}'.format(str(self.L0)+' [m]' )) + print('{: ^18s}'.format('Seeing (V)') + '{: ^18s}'.format(str(np.round(self.seeingArcsec,2))+' ["]')) + print('{: ^18s}'.format('Frequency') + '{: ^18s}'.format(str(np.round(1/self.tel.samplingTime,2))+' [Hz]' )) + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + + def __mul__(self,obj): + obj.OPD=self.OPD + obj.OPD_no_pupil=self.OPD_no_pupil + obj.isPaired=True + return obj + + + def display_atm_layers(self,layer_index= None,fig_index = None): + if layer_index is None: + layer_index = list(np.arange(self.nLayer)) + if type(layer_index) is not list: + raise TypeError(' layer_index should be a list') + col = getColorOrder() + if fig_index is None: + + fig_index = time.time_ns() + plt.figure(fig_index) + axis_list = [] + for i in range(len(layer_index)): + axis_list.append(plt.subplot(1,len(layer_index),i+1)) + + for i_l,ax in enumerate(axis_list): + + tmpLayer = getattr(self, 'layer_'+str(layer_index[i_l]+1)) + ax.imshow(tmpLayer.phase,extent = [-tmpLayer.D/2,tmpLayer.D/2,-tmpLayer.D/2,tmpLayer.D/2]) + center = tmpLayer.D/2 + [x_tel,y_tel] = pol2cart(tmpLayer.D_fov/2, np.linspace(0,2*np.pi,100,endpoint=True)) + if self.tel.src.tag =='asterism': + for i_source in range(len(self.tel.src.src)): + + [x_c,y_c] = pol2cart(self.tel.D/2, np.linspace(0,2*np.pi,100,endpoint=True)) + alpha_cone = np.arctan(self.tel.D/2/self.tel.src.src[i_source].altitude) + + h = self.tel.src.src[i_source].altitude-tmpLayer.altitude + if np.isinf(h): + r =self.tel.D/2 + else: + r = h*np.tan(alpha_cone) + [x_cone,y_cone] = pol2cart(r, np.linspace(0,2*np.pi,100,endpoint=True)) + + [x_z,y_z] = pol2cart(self.tel.src.src[i_source].coordinates[0]*(tmpLayer.D_fov-self.tel.D)/self.tel.resolution,np.deg2rad(self.tel.src.src[i_source].coordinates[1])) + center = 0 + [x_c,y_c] = pol2cart(tmpLayer.D_fov/2, np.linspace(0,2*np.pi,100,endpoint=True)) + + ax.plot(x_cone+x_z+center,y_cone+y_z+center,'-', color = col [i_source]) + ax.fill(x_cone+x_z+center,y_cone+y_z+center,y_z+center, alpha = 0.25, color = col[i_source]) + else: + [x_c,y_c] = pol2cart(self.tel.D/2, np.linspace(0,2*np.pi,100,endpoint=True)) + alpha_cone = np.arctan(self.tel.D/2/self.tel.src.altitude) + + h = self.tel.src.altitude-tmpLayer.altitude + if np.isinf(h): + r =self.tel.D/2 + else: + r = h*np.tan(alpha_cone) + [x_cone,y_cone] = pol2cart(r, np.linspace(0,2*np.pi,100,endpoint=True)) + + [x_z,y_z] = pol2cart(self.tel.src.coordinates[0]*(tmpLayer.D_fov-self.tel.D)/self.tel.resolution,np.deg2rad(self.tel.src.coordinates[1])) + + center = 0 + [x_c,y_c] = pol2cart(tmpLayer.D_fov/2, np.linspace(0,2*np.pi,100,endpoint=True)) + + ax.plot(x_cone+x_z+center,y_cone+y_z+center,'-') + ax.fill(x_cone+x_z+center,y_cone+y_z+center,y_z+center, alpha = 0.6) + + ax.set_xlabel('[m]') + ax.set_ylabel('[m]') + ax.set_title('Altitude '+str(tmpLayer.altitude)+' m') + ax.plot(x_tel+center,y_tel+center,'--',color = 'k') + + # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% ATM PROPERTIES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + + @property + def r0(self): + return self._r0 + + @r0.setter + def r0(self,val): + self._r0 = val + + if self.hasNotBeenInitialized is False: + print('Updating the Atmosphere covariance matrices...') + self.seeingArcsec = 206265*(self.wavelength/val) + for i_layer in range(self.nLayer): + tmpLayer = getattr(self,'layer_'+str(i_layer+1)) + + tmpLayer.ZZt_r0 = tmpLayer.ZZt*(self.r0_def/self.r0)**(5/3) + tmpLayer.ZXt_r0 = tmpLayer.ZXt*(self.r0_def/self.r0)**(5/3) + tmpLayer.XXt_r0 = tmpLayer.XXt*(self.r0_def/self.r0)**(5/3) + tmpLayer.ZZt_inv_r0 = tmpLayer.ZZt_inv/((self.r0_def/self.r0)**(5/3)) + BBt = tmpLayer.XXt_r0 - np.matmul(tmpLayer.A,tmpLayer.ZXt_r0) + tmpLayer.B = np.linalg.cholesky(BBt) + + @property + def L0(self): + return self._L0 + + @L0.setter + def L0(self,val): + self._L0 = val + if self.hasNotBeenInitialized is False: + print('Updating the Atmosphere covariance matrices...') + + self.hasNotBeenInitialized = True + del self.ZZt + del self.XXt + del self.ZXt + del self.ZZt_inv + self.initializeAtmosphere(self.tel) + @property + def windSpeed(self): + return self._windSpeed + + @windSpeed.setter + def windSpeed(self,val): + self._windSpeed = val + + if self.hasNotBeenInitialized is False: + if len(val)!= self.nLayer: + print('Error! Wrong value for the wind-speed! Make sure that you inpute a wind-speed for each layer') + else: + print('Updating the wing speed...') + for i_layer in range(self.nLayer): + tmpLayer = getattr(self,'layer_'+str(i_layer+1)) + tmpLayer.windSpeed = val[i_layer] + tmpLayer.vY = tmpLayer.windSpeed*np.cos(np.deg2rad(tmpLayer.direction)) + tmpLayer.vX = tmpLayer.windSpeed*np.sin(np.deg2rad(tmpLayer.direction)) + ps_turb_x = tmpLayer.vX*self.tel.samplingTime + ps_turb_y = tmpLayer.vY*self.tel.samplingTime + tmpLayer.ratio[0] = ps_turb_x/self.ps_loop + tmpLayer.ratio[1] = ps_turb_y/self.ps_loop + setattr(self,'layer_'+str(i_layer+1),tmpLayer ) + # self.print_atm() + + @property + def windDirection(self): + return self._windDirection + + @windDirection.setter + def windDirection(self,val): + self._windDirection = val + + if self.hasNotBeenInitialized is False: + if len(val)!= self.nLayer: + print('Error! Wrong value for the wind-speed! Make sure that you inpute a wind-speed for each layer') + else: + print('Updating the wind direction...') + for i_layer in range(self.nLayer): + tmpLayer = getattr(self,'layer_'+str(i_layer+1)) + # tmpLayer.notDoneOnce = True + tmpLayer.direction = val[i_layer] + tmpLayer.vY = tmpLayer.windSpeed*np.cos(np.deg2rad(tmpLayer.direction)) + tmpLayer.vX = tmpLayer.windSpeed*np.sin(np.deg2rad(tmpLayer.direction)) + ps_turb_x = tmpLayer.vX*self.tel.samplingTime + ps_turb_y = tmpLayer.vY*self.tel.samplingTime + tmpLayer.ratio[0] = ps_turb_x/self.ps_loop + tmpLayer.ratio[1] = ps_turb_y/self.ps_loop + setattr(self,'layer_'+str(i_layer+1),tmpLayer ) + # self.print_atm() + + + + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + def show(self): + attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) + print(self.tag+':') + for a in attributes: + if not(a[0].startswith('__') and a[0].endswith('__')): + if not(a[0].startswith('_')): + if not np.shape(a[1]): + tmp=a[1] + try: + print(' '+str(a[0])+': '+str(tmp.tag)+' object') + except: + print(' '+str(a[0])+': '+str(a[1])) + else: + if np.ndim(a[1])>1: + print(' '+str(a[0])+': '+str(np.shape(a[1]))) + +class Layer: + pass + + + + + + + + + \ No newline at end of file diff --git a/AO_modules/DeformableMirror.py b/OOPAO/DeformableMirror.py old mode 100755 new mode 100644 similarity index 96% rename from AO_modules/DeformableMirror.py rename to OOPAO/DeformableMirror.py index 01596a5..a447e69 --- a/AO_modules/DeformableMirror.py +++ b/OOPAO/DeformableMirror.py @@ -1,429 +1,430 @@ -# -*- coding: utf-8 -*- -""" -Created on Thu Feb 20 11:32:10 2020 - -@author: cheritie -""" -import numpy as np -import inspect -import sys -from AO_modules.MisRegistration import MisRegistration -from joblib import Parallel, delayed - -import ctypes -import time - -from AO_modules.M4_model.make_M4_influenceFunctions import makeM4influenceFunctions -from AO_modules.tools.tools import print_, pol2cart, emptyClass -from AO_modules.tools.interpolateGeometricalTransformation import interpolate_cube - -# try : -# mkl_rt = ctypes.CDLL('libmkl_rt.so') -# mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads -# mkl_set_num_threads(8) -# except: -# try: -# mkl_rt = ctypes.CDLL('./mkl_rt.dll') -# mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads -# mkl_set_num_threads(8) -# except: -# try: -# import mkl -# mkl_set_num_threads = mkl.set_num_threads -# except: -# mkl_set_num_threads = None - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CLASS INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -class DeformableMirror: - def __init__(self,telescope,nSubap,mechCoupling = 0.35, coordinates=0, pitch=0, modes=0, misReg=0, M4_param = [], nJobs = 30, nThreads = 20,print_dm_properties = True,floating_precision = 64, altitude = None ): - self.print_dm_properties = print_dm_properties - self.floating_precision = floating_precision - self.M4_param = M4_param - if M4_param: - if M4_param['isM4']: - print_('Building the set of influence functions of M4...',print_dm_properties) - # generate the M4 influence functions - - pup = telescope.pupil - filename = M4_param['m4_filename'] - nAct = M4_param['nActuator'] - - a = time.time() - # compute M4 influence functions - try: - coordinates_M4 = makeM4influenceFunctions(pup = pup,\ - filename = filename,\ - misReg = misReg,\ - dm = self,\ - nAct = nAct,\ - nJobs = nJobs,\ - nThreads = nThreads,\ - order = M4_param['order_m4_interpolation'],\ - floating_precision = floating_precision) - except: - coordinates_M4 = makeM4influenceFunctions(pup = pup,\ - filename = filename,\ - misReg = misReg,\ - dm = self,\ - nAct = nAct,\ - nJobs = nJobs,\ - nThreads = nThreads,\ - floating_precision = floating_precision) - - # selection of the valid M4 actuators - if M4_param['validActCriteria']!=0: - IF_STD = np.std(np.squeeze(self.modes[telescope.pupilLogical,:]), axis=0) - ACTXPC=np.where(IF_STD >= np.mean(IF_STD)*M4_param['validActCriteria']) - self.modes = self.modes[:,ACTXPC[0]] - - coordinates = coordinates_M4[ACTXPC[0],:] - else: - coordinates = coordinates_M4 - # normalize coordinates - coordinates = (coordinates/telescope.resolution - 0.5)*40 - self.M4_param = M4_param - self.isM4 = True - print_ ('Done!',print_dm_properties) - b = time.time() - - print_('Done! M4 influence functions computed in ' + str(b-a) + ' s!',print_dm_properties) - else: - self.isM4 = False - else: - self.isM4 = False - self.telescope = telescope - self.altitude = altitude - if altitude is None: - self.resolution = telescope.resolution # Resolution of the DM influence Functions - self.mechCoupling = mechCoupling - self.tag = 'deformableMirror' - self.D = telescope.D - else: - if telescope.src.tag == 'asterism': - self.oversampling_factor = np.max((np.asarray(self.telescope.src.coordinates)[:,0]/(self.telescope.resolution/2))) - else: - self.oversampling_factor = self.telescope.src.coordinates[0]/(self.telescope.resolution/2) - self.altitude_layer = self.buildLayer(self.telescope,altitude) - self.resolution = self.altitude_layer.resolution # Resolution of the DM influence Functions - self.mechCoupling = mechCoupling - self.tag = 'deformableMirror' - self.D = self.altitude_layer.D - - - - # case with no pitch specified (Cartesian geometry) - if pitch==0: - self.pitch = self.D/(nSubap) # size of a subaperture - else: - self.pitch = pitch - - if misReg==0: - # create a MisReg object to store the different mis-registration - self.misReg = MisRegistration() - else: - self.misReg=misReg - - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% DM INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -# If no coordinates are given, the DM is in a Cartesian Geometry - - if np.ndim(coordinates)==0: - print_('No coordinates loaded.. taking the cartesian geometry as a default',print_dm_properties) - self.nAct = nSubap+1 # In that case corresponds to the number of actuator along the diameter - self.nActAlongDiameter = self.nAct-1 - - # set the coordinates of the DM object to produce a cartesian geometry - x = np.linspace(-(self.D)/2,(self.D)/2,self.nAct) - X,Y=np.meshgrid(x,x) - - # compute the initial set of coordinates - self.xIF0 = np.reshape(X,[self.nAct**2]) - self.yIF0 = np.reshape(Y,[self.nAct**2]) - - # select valid actuators (central and outer obstruction) - r = np.sqrt(self.xIF0**2 + self.yIF0**2) - validActInner = r>(telescope.centralObstruction*self.D/2-0.5*self.pitch) - validActOuter = r<=(self.D/2+0.7533*self.pitch) - - self.validAct = validActInner*validActOuter - self.nValidAct = sum(self.validAct) - - # If the coordinates are specified - - else: - print_('Coordinates loaded...',print_dm_properties) - - self.xIF0 = coordinates[:,0] - self.yIF0 = coordinates[:,1] - self.nAct = len(self.xIF0) # In that case corresponds to the total number of actuators - self.nActAlongDiameter = (self.D)/self.pitch - - validAct=(np.arange(0,self.nAct)) # In that case assumed that all the Influence Functions provided are controlled actuators - - self.validAct = validAct.astype(int) - self.nValidAct = self.nAct - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% INFLUENCE FUNCTIONS COMPUTATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - # initial coordinates - xIF0 = self.xIF0[self.validAct] - yIF0 = self.yIF0[self.validAct] - - # anamorphosis - xIF3,yIF3 = self.anamorphosis(xIF0,yIF0,self.misReg.anamorphosisAngle*np.pi/180,self.misReg.tangentialScaling,self.misReg.radialScaling) - - # rotation - xIF4,yIF4 = self.rotateDM(xIF3,yIF3,self.misReg.rotationAngle*np.pi/180) - - # shifts - xIF = xIF4-self.misReg.shiftX - yIF = yIF4-self.misReg.shiftY - - self.xIF = xIF - self.yIF = yIF - - - # corresponding coordinates on the pixel grid - u0x = self.resolution/2+xIF*self.resolution/self.D - u0y = self.resolution/2+yIF*self.resolution/self.D - self.nIF = len(xIF) - # store the coordinates - self.coordinates = np.zeros([self.nIF,2]) - self.coordinates[:,0] = xIF - self.coordinates[:,1] = yIF - - if self.isM4==False: - print_('Generating a Deformable Mirror: ',print_dm_properties) - if np.ndim(modes)==0: - print_('Computing the 2D zonal modes...',print_dm_properties) - # FWHM of the gaussian depends on the anamorphosis - def joblib_construction(): - Q=Parallel(n_jobs=8,prefer='threads')(delayed(self.modesComputation)(i,j) for i,j in zip(u0x,u0y)) - return Q - self.modes=np.squeeze(np.moveaxis(np.asarray(joblib_construction()),0,-1)) - - else: - print_('Loading the 2D zonal modes...',print_dm_properties) - self.modes = modes - print_('Done!',print_dm_properties) - - else: - print_('Using M4 Influence Functions',print_dm_properties) - if floating_precision==32: - self.coefs = np.zeros(self.nValidAct,dtype=np.float32) - else: - self.coefs = np.zeros(self.nValidAct,dtype=np.float64) - self.current_coefs = self.coefs.copy() - if self.print_dm_properties: - self.print_properties() - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% GEOMETRICAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - def buildLayer(self,telescope,altitude): - # initialize layer object - layer = emptyClass() - - # gather properties of the atmosphere - layer.altitude = altitude - - # Diameter and resolution of the layer including the Field Of View and the number of extra pixels - layer.D = telescope.D+2*np.tan(telescope.fov/2)*layer.altitude*self.oversampling_factor - layer.resolution = int(np.ceil((telescope.resolution/telescope.D)*layer.D)) - layer.D_fov = telescope.D+2*np.tan(telescope.fov/2)*layer.altitude - layer.resolution_fov = int(np.ceil((telescope.resolution/telescope.D)*layer.D)) - layer.center = layer.resolution//2 - - if telescope.src.tag =='source': - [x_z,y_z] = pol2cart(telescope.src.coordinates[0]*(layer.D_fov-telescope.D)/telescope.D,np.deg2rad(telescope.src.coordinates[1])) - - center_x = int(y_z)+layer.resolution//2 - center_y = int(x_z)+layer.resolution//2 - - layer.pupil_footprint = np.zeros([layer.resolution,layer.resolution]) - layer.pupil_footprint[center_x-telescope.resolution//2:center_x+telescope.resolution//2,center_y-telescope.resolution//2:center_y+telescope.resolution//2 ] = 1 - else: - layer.pupil_footprint= [] - layer.center_x= [] - layer.center_y= [] - - for i in range(telescope.src.n_source): - [x_z,y_z] = pol2cart(telescope.src.coordinates[i][0]*(layer.D_fov-telescope.D)/telescope.D,np.deg2rad(telescope.src.coordinates[i][1])) - - center_x = int(y_z)+layer.resolution//2 - center_y = int(x_z)+layer.resolution//2 - - pupil_footprint = np.zeros([layer.resolution,layer.resolution]) - pupil_footprint[center_x-telescope.resolution//2:center_x+telescope.resolution//2,center_y-telescope.resolution//2:center_y+telescope.resolution//2 ] = 1 - layer.pupil_footprint.append(pupil_footprint) - layer.center_x.append(center_x) - layer.center_y.append(center_y) - - return layer - def get_OPD_altitude(self,i_source): - - if np.ndim(self.OPD)==2: - OPD = np.reshape(self.OPD[np.where(self.altitude_layer.pupil_footprint[i_source]==1)],[self.telescope.resolution,self.telescope.resolution]) - else: - OPD = np.reshape(self.OPD[self.altitude_layer.center_x[i_source]-self.telescope.resolution//2:self.altitude_layer.center_x[i_source]+self.telescope.resolution//2,self.altitude_layer.center_y[i_source]-self.telescope.resolution//2:self.altitude_layer.center_y[i_source]+self.telescope.resolution//2,:],[self.telescope.resolution,self.telescope.resolution,self.OPD.shape[2]]) - - if self.telescope.src.src[i_source].type == 'LGS': - if np.ndim(self.OPD)==2: - sub_im = np.atleast_3d(OPD) - else: - sub_im = np.moveaxis(OPD,2,0) - - alpha_cone = np.arctan(self.telescope.D/2/self.telescope.src.altitude[i_source]) - h = self.telescope.src.altitude[i_source]-self.altitude_layer.altitude - if np.isinf(h): - r =self.telescope.D/2 - else: - r = h*np.tan(alpha_cone) - ratio = self.telescope.D/r/2 - cube_in = sub_im.T - pixel_size_in = self.altitude_layer.D/self.altitude_layer.resolution - pixel_size_out = pixel_size_in/ratio - resolution_out = self.telescope.resolution - - OPD = np.asarray(np.squeeze(interpolate_cube(cube_in, pixel_size_in, pixel_size_out, resolution_out)).T) - - return OPD - - - - - def dm_propagation(self,telescope,OPD_in = None, i_source = None): - if self.coefs.all() == self.current_coefs.all(): - self.coefs = self.coefs - if OPD_in is None: - OPD_in = telescope.OPD_no_pupil - - if i_source is not None: - dm_OPD = self.get_OPD_altitude(i_source) - else: - dm_OPD = self.OPD - - # case where the telescope is paired to an atmosphere - if telescope.isPaired: - if telescope.isPetalFree: - telescope.removePetalling() - # case with single OPD - if np.ndim(self.OPD)==2: - OPD_out_no_pupil = OPD_in + dm_OPD - # case with multiple OPD - else: - OPD_out_no_pupil = np.tile(OPD_in[...,None],(1,1,self.OPD.shape[2]))+dm_OPD - - # case where the telescope is separated from a telescope object - else: - OPD_out_no_pupil = dm_OPD - - return OPD_out_no_pupil - - def rotateDM(self,x,y,angle): - xOut = x*np.cos(angle)-y*np.sin(angle) - yOut = y*np.cos(angle)+x*np.sin(angle) - return xOut,yOut - - def anamorphosis(self,x,y,angle,mRad,mNorm): - - mRad += 1 - mNorm += 1 - xOut = x * (mRad*np.cos(angle)**2 + mNorm* np.sin(angle)**2) + y * (mNorm*np.sin(2*angle)/2 - mRad*np.sin(2*angle)/2) - yOut = y * (mRad*np.sin(angle)**2 + mNorm* np.cos(angle)**2) + x * (mNorm*np.sin(2*angle)/2 - mRad*np.sin(2*angle)/2) - - return xOut,yOut - - def modesComputation(self,i,j): - x0 = i - y0 = j - cx = (1+self.misReg.radialScaling)*(self.resolution/self.nActAlongDiameter)/np.sqrt(2*np.log(1./self.mechCoupling)) - cy = (1+self.misReg.tangentialScaling)*(self.resolution/self.nActAlongDiameter)/np.sqrt(2*np.log(1./self.mechCoupling)) - -# Radial direction of the anamorphosis - theta = self.misReg.anamorphosisAngle*np.pi/180 - x = np.linspace(0,1,self.resolution)*self.resolution - X,Y = np.meshgrid(x,x) - -# Compute the 2D Gaussian coefficients - a = np.cos(theta)**2/(2*cx**2) + np.sin(theta)**2/(2*cy**2) - b = -np.sin(2*theta)/(4*cx**2) + np.sin(2*theta)/(4*cy**2) - c = np.sin(theta)**2/(2*cx**2) + np.cos(theta)**2/(2*cy**2) - - G=np.exp(-(a*(X-x0)**2 +2*b*(X-x0)*(Y-y0) + c*(Y-y0)**2)) - output = np.reshape(G,[1,self.resolution**2]) - return output - - def print_properties(self): - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% DEFORMABLE MIRROR %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('{: ^21s}'.format('Controlled Actuators') + '{: ^18s}'.format(str(self.nValidAct))) - print('{: ^21s}'.format('M4') + '{: ^18s}'.format(str(self.isM4))) - print('{: ^21s}'.format('Pitch') + '{: ^18s}'.format(str(self.pitch)) +'{: ^18s}'.format('[m]')) - print('{: ^21s}'.format('Mechanical Coupling') + '{: ^18s}'.format(str(self.mechCoupling)) +'{: ^18s}'.format('[%]' )) - print('-------------------------------------------------------------------------------') - print('Mis-registration:') - self.misReg.print_() - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - -# -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% DM PROPERTIES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - @property - def coefs(self): - return self._coefs - - @coefs.setter - def coefs(self,val): - if np.isscalar(val): - if val==0: - if self.floating_precision==32: - self._coefs = np.zeros(self.nValidAct,dtype=np.float32) - else: - self._coefs = np.zeros(self.nValidAct,dtype=np.float64) - - try: - self.OPD = np.float64(np.reshape(np.matmul(self.modes,self._coefs),[self.resolution,self.resolution])) - except: - self.OPD= np.float64(np.reshape(self.modes@self._coefs,[self.resolution,self.resolution])) - - else: - print('Error: wrong value for the coefficients') - else: - self._coefs=val - if len(val)==self.nValidAct: -# case of a single mode at a time - if np.ndim(val)==1: - try: - self.OPD = np.float64(np.reshape(np.matmul(self.modes,self._coefs),[self.resolution,self.resolution])) - except: - self.OPD = np.float64(np.reshape(self.modes@self._coefs,[self.resolution,self.resolution])) - -# case of multiple modes at a time - else: - try: - self.OPD = np.float64(np.reshape(np.matmul(self.modes,self._coefs),[self.resolution,self.resolution,val.shape[1]])) - except: - self.OPD = np.float64(np.reshape(self.modes@self._coefs,[self.resolution,self.resolution,val.shape[1]])) - - else: - print('Error: wrong value for the coefficients') - sys.exit(0) - self.current_coefs = self.coefs.copy() - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - def show(self): - attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) - print(self.tag+':') - for a in attributes: - if not(a[0].startswith('__') and a[0].endswith('__')): - if not(a[0].startswith('_')): - if not np.shape(a[1]): - tmp=a[1] - try: - print(' '+str(a[0])+': '+str(tmp.tag)+' object') - except: - print(' '+str(a[0])+': '+str(a[1])) - else: - if np.ndim(a[1])>1: - print(' '+str(a[0])+': '+str(np.shape(a[1]))) +# -*- coding: utf-8 -*- +""" +Created on Thu Feb 20 11:32:10 2020 + +@author: cheritie +""" + +import inspect +import sys +import time + +import numpy as np +from joblib import Parallel, delayed + +from .M4_model.make_M4_influenceFunctions import makeM4influenceFunctions +from .MisRegistration import MisRegistration +from .tools.interpolateGeometricalTransformation import interpolate_cube +from .tools.tools import emptyClass, pol2cart, print_ + + +# try : +# mkl_rt = ctypes.CDLL('libmkl_rt.so') +# mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads +# mkl_set_num_threads(8) +# except: +# try: +# mkl_rt = ctypes.CDLL('./mkl_rt.dll') +# mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads +# mkl_set_num_threads(8) +# except: +# try: +# import mkl +# mkl_set_num_threads = mkl.set_num_threads +# except: +# mkl_set_num_threads = None + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CLASS INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +class DeformableMirror: + def __init__(self,telescope,nSubap,mechCoupling = 0.35, coordinates=0, pitch=0, modes=0, misReg=0, M4_param = [], nJobs = 30, nThreads = 20,print_dm_properties = True,floating_precision = 64, altitude = None ): + self.print_dm_properties = print_dm_properties + self.floating_precision = floating_precision + self.M4_param = M4_param + if M4_param: + if M4_param['isM4']: + print_('Building the set of influence functions of M4...',print_dm_properties) + # generate the M4 influence functions + + pup = telescope.pupil + filename = M4_param['m4_filename'] + nAct = M4_param['nActuator'] + + a = time.time() + # compute M4 influence functions + try: + coordinates_M4 = makeM4influenceFunctions(pup = pup,\ + filename = filename,\ + misReg = misReg,\ + dm = self,\ + nAct = nAct,\ + nJobs = nJobs,\ + nThreads = nThreads,\ + order = M4_param['order_m4_interpolation'],\ + floating_precision = floating_precision) + except: + coordinates_M4 = makeM4influenceFunctions(pup = pup,\ + filename = filename,\ + misReg = misReg,\ + dm = self,\ + nAct = nAct,\ + nJobs = nJobs,\ + nThreads = nThreads,\ + floating_precision = floating_precision) + + # selection of the valid M4 actuators + if M4_param['validActCriteria']!=0: + IF_STD = np.std(np.squeeze(self.modes[telescope.pupilLogical,:]), axis=0) + ACTXPC=np.where(IF_STD >= np.mean(IF_STD)*M4_param['validActCriteria']) + self.modes = self.modes[:,ACTXPC[0]] + + coordinates = coordinates_M4[ACTXPC[0],:] + else: + coordinates = coordinates_M4 + # normalize coordinates + coordinates = (coordinates/telescope.resolution - 0.5)*40 + self.M4_param = M4_param + self.isM4 = True + print_ ('Done!',print_dm_properties) + b = time.time() + + print_('Done! M4 influence functions computed in ' + str(b-a) + ' s!',print_dm_properties) + else: + self.isM4 = False + else: + self.isM4 = False + self.telescope = telescope + self.altitude = altitude + if altitude is None: + self.resolution = telescope.resolution # Resolution of the DM influence Functions + self.mechCoupling = mechCoupling + self.tag = 'deformableMirror' + self.D = telescope.D + else: + if telescope.src.tag == 'asterism': + self.oversampling_factor = np.max((np.asarray(self.telescope.src.coordinates)[:,0]/(self.telescope.resolution/2))) + else: + self.oversampling_factor = self.telescope.src.coordinates[0]/(self.telescope.resolution/2) + self.altitude_layer = self.buildLayer(self.telescope,altitude) + self.resolution = self.altitude_layer.resolution # Resolution of the DM influence Functions + self.mechCoupling = mechCoupling + self.tag = 'deformableMirror' + self.D = self.altitude_layer.D + + + + # case with no pitch specified (Cartesian geometry) + if pitch==0: + self.pitch = self.D/(nSubap) # size of a subaperture + else: + self.pitch = pitch + + if misReg==0: + # create a MisReg object to store the different mis-registration + self.misReg = MisRegistration() + else: + self.misReg=misReg + + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% DM INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +# If no coordinates are given, the DM is in a Cartesian Geometry + + if np.ndim(coordinates)==0: + print_('No coordinates loaded.. taking the cartesian geometry as a default',print_dm_properties) + self.nAct = nSubap+1 # In that case corresponds to the number of actuator along the diameter + self.nActAlongDiameter = self.nAct-1 + + # set the coordinates of the DM object to produce a cartesian geometry + x = np.linspace(-(self.D)/2,(self.D)/2,self.nAct) + X,Y=np.meshgrid(x,x) + + # compute the initial set of coordinates + self.xIF0 = np.reshape(X,[self.nAct**2]) + self.yIF0 = np.reshape(Y,[self.nAct**2]) + + # select valid actuators (central and outer obstruction) + r = np.sqrt(self.xIF0**2 + self.yIF0**2) + validActInner = r>(telescope.centralObstruction*self.D/2-0.5*self.pitch) + validActOuter = r<=(self.D/2+0.7533*self.pitch) + + self.validAct = validActInner*validActOuter + self.nValidAct = sum(self.validAct) + + # If the coordinates are specified + + else: + print_('Coordinates loaded...',print_dm_properties) + + self.xIF0 = coordinates[:,0] + self.yIF0 = coordinates[:,1] + self.nAct = len(self.xIF0) # In that case corresponds to the total number of actuators + self.nActAlongDiameter = (self.D)/self.pitch + + validAct=(np.arange(0,self.nAct)) # In that case assumed that all the Influence Functions provided are controlled actuators + + self.validAct = validAct.astype(int) + self.nValidAct = self.nAct + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% INFLUENCE FUNCTIONS COMPUTATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + # initial coordinates + xIF0 = self.xIF0[self.validAct] + yIF0 = self.yIF0[self.validAct] + + # anamorphosis + xIF3,yIF3 = self.anamorphosis(xIF0,yIF0,self.misReg.anamorphosisAngle*np.pi/180,self.misReg.tangentialScaling,self.misReg.radialScaling) + + # rotation + xIF4,yIF4 = self.rotateDM(xIF3,yIF3,self.misReg.rotationAngle*np.pi/180) + + # shifts + xIF = xIF4-self.misReg.shiftX + yIF = yIF4-self.misReg.shiftY + + self.xIF = xIF + self.yIF = yIF + + + # corresponding coordinates on the pixel grid + u0x = self.resolution/2+xIF*self.resolution/self.D + u0y = self.resolution/2+yIF*self.resolution/self.D + self.nIF = len(xIF) + # store the coordinates + self.coordinates = np.zeros([self.nIF,2]) + self.coordinates[:,0] = xIF + self.coordinates[:,1] = yIF + + if self.isM4==False: + print_('Generating a Deformable Mirror: ',print_dm_properties) + if np.ndim(modes)==0: + print_('Computing the 2D zonal modes...',print_dm_properties) + # FWHM of the gaussian depends on the anamorphosis + def joblib_construction(): + Q=Parallel(n_jobs=8,prefer='threads')(delayed(self.modesComputation)(i,j) for i,j in zip(u0x,u0y)) + return Q + self.modes=np.squeeze(np.moveaxis(np.asarray(joblib_construction()),0,-1)) + + else: + print_('Loading the 2D zonal modes...',print_dm_properties) + self.modes = modes + print_('Done!',print_dm_properties) + + else: + print_('Using M4 Influence Functions',print_dm_properties) + if floating_precision==32: + self.coefs = np.zeros(self.nValidAct,dtype=np.float32) + else: + self.coefs = np.zeros(self.nValidAct,dtype=np.float64) + self.current_coefs = self.coefs.copy() + if self.print_dm_properties: + self.print_properties() + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% GEOMETRICAL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + def buildLayer(self,telescope,altitude): + # initialize layer object + layer = emptyClass() + + # gather properties of the atmosphere + layer.altitude = altitude + + # Diameter and resolution of the layer including the Field Of View and the number of extra pixels + layer.D = telescope.D+2*np.tan(telescope.fov/2)*layer.altitude*self.oversampling_factor + layer.resolution = int(np.ceil((telescope.resolution/telescope.D)*layer.D)) + layer.D_fov = telescope.D+2*np.tan(telescope.fov/2)*layer.altitude + layer.resolution_fov = int(np.ceil((telescope.resolution/telescope.D)*layer.D)) + layer.center = layer.resolution//2 + + if telescope.src.tag =='source': + [x_z,y_z] = pol2cart(telescope.src.coordinates[0]*(layer.D_fov-telescope.D)/telescope.D,np.deg2rad(telescope.src.coordinates[1])) + + center_x = int(y_z)+layer.resolution//2 + center_y = int(x_z)+layer.resolution//2 + + layer.pupil_footprint = np.zeros([layer.resolution,layer.resolution]) + layer.pupil_footprint[center_x-telescope.resolution//2:center_x+telescope.resolution//2,center_y-telescope.resolution//2:center_y+telescope.resolution//2 ] = 1 + else: + layer.pupil_footprint= [] + layer.center_x= [] + layer.center_y= [] + + for i in range(telescope.src.n_source): + [x_z,y_z] = pol2cart(telescope.src.coordinates[i][0]*(layer.D_fov-telescope.D)/telescope.D,np.deg2rad(telescope.src.coordinates[i][1])) + + center_x = int(y_z)+layer.resolution//2 + center_y = int(x_z)+layer.resolution//2 + + pupil_footprint = np.zeros([layer.resolution,layer.resolution]) + pupil_footprint[center_x-telescope.resolution//2:center_x+telescope.resolution//2,center_y-telescope.resolution//2:center_y+telescope.resolution//2 ] = 1 + layer.pupil_footprint.append(pupil_footprint) + layer.center_x.append(center_x) + layer.center_y.append(center_y) + + return layer + def get_OPD_altitude(self,i_source): + + if np.ndim(self.OPD)==2: + OPD = np.reshape(self.OPD[np.where(self.altitude_layer.pupil_footprint[i_source]==1)],[self.telescope.resolution,self.telescope.resolution]) + else: + OPD = np.reshape(self.OPD[self.altitude_layer.center_x[i_source]-self.telescope.resolution//2:self.altitude_layer.center_x[i_source]+self.telescope.resolution//2,self.altitude_layer.center_y[i_source]-self.telescope.resolution//2:self.altitude_layer.center_y[i_source]+self.telescope.resolution//2,:],[self.telescope.resolution,self.telescope.resolution,self.OPD.shape[2]]) + + if self.telescope.src.src[i_source].type == 'LGS': + if np.ndim(self.OPD)==2: + sub_im = np.atleast_3d(OPD) + else: + sub_im = np.moveaxis(OPD,2,0) + + alpha_cone = np.arctan(self.telescope.D/2/self.telescope.src.altitude[i_source]) + h = self.telescope.src.altitude[i_source]-self.altitude_layer.altitude + if np.isinf(h): + r =self.telescope.D/2 + else: + r = h*np.tan(alpha_cone) + ratio = self.telescope.D/r/2 + cube_in = sub_im.T + pixel_size_in = self.altitude_layer.D/self.altitude_layer.resolution + pixel_size_out = pixel_size_in/ratio + resolution_out = self.telescope.resolution + + OPD = np.asarray(np.squeeze(interpolate_cube(cube_in, pixel_size_in, pixel_size_out, resolution_out)).T) + + return OPD + + + + + def dm_propagation(self,telescope,OPD_in = None, i_source = None): + if self.coefs.all() == self.current_coefs.all(): + self.coefs = self.coefs + if OPD_in is None: + OPD_in = telescope.OPD_no_pupil + + if i_source is not None: + dm_OPD = self.get_OPD_altitude(i_source) + else: + dm_OPD = self.OPD + + # case where the telescope is paired to an atmosphere + if telescope.isPaired: + if telescope.isPetalFree: + telescope.removePetalling() + # case with single OPD + if np.ndim(self.OPD)==2: + OPD_out_no_pupil = OPD_in + dm_OPD + # case with multiple OPD + else: + OPD_out_no_pupil = np.tile(OPD_in[...,None],(1,1,self.OPD.shape[2]))+dm_OPD + + # case where the telescope is separated from a telescope object + else: + OPD_out_no_pupil = dm_OPD + + return OPD_out_no_pupil + + def rotateDM(self,x,y,angle): + xOut = x*np.cos(angle)-y*np.sin(angle) + yOut = y*np.cos(angle)+x*np.sin(angle) + return xOut,yOut + + def anamorphosis(self,x,y,angle,mRad,mNorm): + + mRad += 1 + mNorm += 1 + xOut = x * (mRad*np.cos(angle)**2 + mNorm* np.sin(angle)**2) + y * (mNorm*np.sin(2*angle)/2 - mRad*np.sin(2*angle)/2) + yOut = y * (mRad*np.sin(angle)**2 + mNorm* np.cos(angle)**2) + x * (mNorm*np.sin(2*angle)/2 - mRad*np.sin(2*angle)/2) + + return xOut,yOut + + def modesComputation(self,i,j): + x0 = i + y0 = j + cx = (1+self.misReg.radialScaling)*(self.resolution/self.nActAlongDiameter)/np.sqrt(2*np.log(1./self.mechCoupling)) + cy = (1+self.misReg.tangentialScaling)*(self.resolution/self.nActAlongDiameter)/np.sqrt(2*np.log(1./self.mechCoupling)) + +# Radial direction of the anamorphosis + theta = self.misReg.anamorphosisAngle*np.pi/180 + x = np.linspace(0,1,self.resolution)*self.resolution + X,Y = np.meshgrid(x,x) + +# Compute the 2D Gaussian coefficients + a = np.cos(theta)**2/(2*cx**2) + np.sin(theta)**2/(2*cy**2) + b = -np.sin(2*theta)/(4*cx**2) + np.sin(2*theta)/(4*cy**2) + c = np.sin(theta)**2/(2*cx**2) + np.cos(theta)**2/(2*cy**2) + + G=np.exp(-(a*(X-x0)**2 +2*b*(X-x0)*(Y-y0) + c*(Y-y0)**2)) + output = np.reshape(G,[1,self.resolution**2]) + return output + + def print_properties(self): + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% DEFORMABLE MIRROR %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('{: ^21s}'.format('Controlled Actuators') + '{: ^18s}'.format(str(self.nValidAct))) + print('{: ^21s}'.format('M4') + '{: ^18s}'.format(str(self.isM4))) + print('{: ^21s}'.format('Pitch') + '{: ^18s}'.format(str(self.pitch)) +'{: ^18s}'.format('[m]')) + print('{: ^21s}'.format('Mechanical Coupling') + '{: ^18s}'.format(str(self.mechCoupling)) +'{: ^18s}'.format('[%]' )) + print('-------------------------------------------------------------------------------') + print('Mis-registration:') + self.misReg.print_() + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + +# +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% DM PROPERTIES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + @property + def coefs(self): + return self._coefs + + @coefs.setter + def coefs(self,val): + if np.isscalar(val): + if val==0: + if self.floating_precision==32: + self._coefs = np.zeros(self.nValidAct,dtype=np.float32) + else: + self._coefs = np.zeros(self.nValidAct,dtype=np.float64) + + try: + self.OPD = np.float64(np.reshape(np.matmul(self.modes,self._coefs),[self.resolution,self.resolution])) + except: + self.OPD= np.float64(np.reshape(self.modes@self._coefs,[self.resolution,self.resolution])) + + else: + print('Error: wrong value for the coefficients') + else: + self._coefs=val + if len(val)==self.nValidAct: +# case of a single mode at a time + if np.ndim(val)==1: + try: + self.OPD = np.float64(np.reshape(np.matmul(self.modes,self._coefs),[self.resolution,self.resolution])) + except: + self.OPD = np.float64(np.reshape(self.modes@self._coefs,[self.resolution,self.resolution])) + +# case of multiple modes at a time + else: + try: + self.OPD = np.float64(np.reshape(np.matmul(self.modes,self._coefs),[self.resolution,self.resolution,val.shape[1]])) + except: + self.OPD = np.float64(np.reshape(self.modes@self._coefs,[self.resolution,self.resolution,val.shape[1]])) + + else: + print('Error: wrong value for the coefficients') + sys.exit(0) + self.current_coefs = self.coefs.copy() + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + def show(self): + attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) + print(self.tag+':') + for a in attributes: + if not(a[0].startswith('__') and a[0].endswith('__')): + if not(a[0].startswith('_')): + if not np.shape(a[1]): + tmp=a[1] + try: + print(' '+str(a[0])+': '+str(tmp.tag)+' object') + except: + print(' '+str(a[0])+': '+str(a[1])) + else: + if np.ndim(a[1])>1: + print(' '+str(a[0])+': '+str(np.shape(a[1]))) # %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/AO_modules/Detector.py b/OOPAO/Detector.py old mode 100755 new mode 100644 similarity index 97% rename from AO_modules/Detector.py rename to OOPAO/Detector.py index 9a8d523..4d8e0e5 --- a/AO_modules/Detector.py +++ b/OOPAO/Detector.py @@ -1,41 +1,44 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Feb 24 14:37:29 2020 - -@author: cheritie -""" - -import numpy as np -import inspect -class Detector: - def __init__(self,nRes,readoutNoise=0,photonNoise=0,QE=1): - self.resolution=nRes - self.QE=1 - self.readoutNoise=readoutNoise - self.photonNoise=photonNoise - self.frame=np.zeros([nRes,nRes]) - self.tag='detector' - - def rebin(self,arr, new_shape): - shape = (new_shape[0], arr.shape[0] // new_shape[0], - new_shape[1], arr.shape[1] // new_shape[1]) - out = (arr.reshape(shape).mean(-1).mean(1)) * (arr.shape[0] // new_shape[0]) * (arr.shape[1] // new_shape[1]) - return out - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - def show(self): - attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) - print(self.tag+':') - for a in attributes: - if not(a[0].startswith('__') and a[0].endswith('__')): - if not(a[0].startswith('_')): - if not np.shape(a[1]): - tmp=a[1] - try: - print(' '+str(a[0])+': '+str(tmp.tag)+' object') - except: - print(' '+str(a[0])+': '+str(a[1])) - else: - if np.ndim(a[1])>1: +# -*- coding: utf-8 -*- +""" +Created on Mon Feb 24 14:37:29 2020 + +@author: cheritie +""" + +import inspect + +import numpy as np + + +class Detector: + def __init__(self,nRes,readoutNoise=0,photonNoise=0,QE=1): + self.resolution=nRes + self.QE=1 + self.readoutNoise=readoutNoise + self.photonNoise=photonNoise + self.frame=np.zeros([nRes,nRes]) + self.tag='detector' + + def rebin(self,arr, new_shape): + shape = (new_shape[0], arr.shape[0] // new_shape[0], + new_shape[1], arr.shape[1] // new_shape[1]) + out = (arr.reshape(shape).mean(-1).mean(1)) * (arr.shape[0] // new_shape[0]) * (arr.shape[1] // new_shape[1]) + return out + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + def show(self): + attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) + print(self.tag+':') + for a in attributes: + if not(a[0].startswith('__') and a[0].endswith('__')): + if not(a[0].startswith('_')): + if not np.shape(a[1]): + tmp=a[1] + try: + print(' '+str(a[0])+': '+str(tmp.tag)+' object') + except: + print(' '+str(a[0])+': '+str(a[1])) + else: + if np.ndim(a[1])>1: print(' '+str(a[0])+': '+str(np.shape(a[1]))) \ No newline at end of file diff --git a/AO_modules/closed_loop/__init__.py b/OOPAO/M4_model/__init__.py similarity index 91% rename from AO_modules/closed_loop/__init__.py rename to OOPAO/M4_model/__init__.py index c03131d..822b683 100644 --- a/AO_modules/closed_loop/__init__.py +++ b/OOPAO/M4_model/__init__.py @@ -1,7 +1,6 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Apr 3 10:56:59 2020 - -@author: cheritie -""" - +# -*- coding: utf-8 -*- +""" +Created on Fri Apr 3 10:56:59 2020 + +@author: cheritie +""" diff --git a/AO_modules/M4_model/make_M4_influenceFunctions.py b/OOPAO/M4_model/make_M4_influenceFunctions.py similarity index 93% rename from AO_modules/M4_model/make_M4_influenceFunctions.py rename to OOPAO/M4_model/make_M4_influenceFunctions.py index 4a5564d..5f1bb51 100644 --- a/AO_modules/M4_model/make_M4_influenceFunctions.py +++ b/OOPAO/M4_model/make_M4_influenceFunctions.py @@ -1,188 +1,196 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Dec 2 15:31:25 2020 - -@author: cheritie -""" -import numpy as np -import skimage.transform as sk -import ctypes -import time - -from astropy.io import fits as pfits -from joblib import Parallel, delayed - -from AO_modules.MisRegistration import MisRegistration -from AO_modules.tools.interpolateGeometricalTransformation import rotateImageMatrix,rotation,translationImageMatrix,translation,anamorphosis,anamorphosisImageMatrix - -try : - mkl_rt = ctypes.CDLL('libmkl_rt.so') - mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads - mkl_set_num_threads(8) -except: - try: - mkl_rt = ctypes.CDLL('./mkl_rt.dll') - mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads - mkl_set_num_threads(8) - except: - try: - import mkl - mkl_set_num_threads = mkl.set_num_threads - except: - mkl_set_num_threads = None -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% START OF THE FUNCTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -def makeM4influenceFunctions(pup, filename, misReg = 0, dm = None, nAct = 0, nJobs = 30, nThreads = 20, order =1,floating_precision=64, D = 40): - try: - mkl_set_num_threads(nThreads) - except: - print('Could not optimize the parallelisation of the code ') - # read the FITS file of the cropped influence functions. - hdul = pfits.open( filename ) - print("Read influence function from fits file : ", filename) - - xC = hdul[0].header['XCENTER'] - pixelSize_M4_original = hdul[0].header['pixsize'] - i1, j1 = hdul[1].data - influenceFunctions_M4 = hdul[2].data - xpos, ypos = hdul[3].data - - # size of influence functions and number of actuators - nx, ny, nActu = influenceFunctions_M4.shape - - # create a MisReg object to store the different mis-registration - if np.isscalar(misReg): - if misReg==0: - misReg=MisRegistration() - else: - print('ERROR: wrong value for the mis-registrations') - - # case with less actuators (debug purposes) - if nAct!=0: - nActu=nAct - - # coordinates of M4 before the interpolation - coordinates_M4_original = np.zeros([nAct,2]) - coordinates_M4_original[:,0] = xpos[:nAct] - coordinates_M4_original[:,1] = ypos[:nAct] - - # size of the influence functions maps - resolution_M4_original = int(np.ceil(2*xC)) - - # resolution of the M1 pupil - resolution_M1 = pup.shape[1] - - # compute the pixel scale of the M1 pupil - pixelSize_M1 = D/resolution_M1 - - # compute the ratio_M4_M1 between both pixel scale. - ratio_M4_M1 = pixelSize_M4_original/pixelSize_M1 - # after the interpolation the image will be shifted of a fraction of pixel extra if ratio_M4_M1 is not an integer - extra = (ratio_M4_M1)%1 - - nPix = (resolution_M4_original-resolution_M1) - - extra = extra/2 + (np.floor(ratio_M4_M1)-1)*0.5 - nCrop = (nPix/2) - - - # allocate memory to store the influence functions - influMap = np.zeros([resolution_M4_original,resolution_M4_original]) - -#-------------------- The Following Transformations are applied in the following order ----------------------------------- - - # 1) Down scaling to get the right pixel size according to the resolution of M1 - downScaling = anamorphosisImageMatrix(influMap,0,[ratio_M4_M1,ratio_M4_M1]) - - # 2) transformations for the mis-registration - anamMatrix = anamorphosisImageMatrix(influMap,misReg.anamorphosisAngle,[1+misReg.radialScaling,1+misReg.tangentialScaling]) - rotMatrix = rotateImageMatrix(influMap,misReg.rotationAngle) - shiftMatrix = translationImageMatrix(influMap,[misReg.shiftY/pixelSize_M1,misReg.shiftX/pixelSize_M1]) #units are in m - - # Shift of half a pixel to center the images on an even number of pixels - alignmentMatrix = translationImageMatrix(influMap,[-nCrop + extra,-nCrop + extra]) - - # 3) Global transformation matrix - transformationMatrix = downScaling + anamMatrix + rotMatrix + shiftMatrix + alignmentMatrix - - def globalTransformation(image): - output = sk.warp(image,(transformationMatrix).inverse,output_shape = [resolution_M1,resolution_M1],order=order) - return output - - # definition of the function that is run in parallel for each - def reconstruction_IF(i,j,k): - - i=int(i) - j=int(j) - - if floating_precision==32: - # support of the IFS - influMap = np.zeros([resolution_M4_original,resolution_M4_original],dtype=np.float32) - # fill up the support with the 2D IFS [120x120] - influMap[i:i+nx, j:j+ny] = np.float32(k) - else: - influMap = np.zeros([resolution_M4_original,resolution_M4_original],dtype=np.float64) - # fill up the support with the 2D IFS [120x120] - influMap[i:i+nx, j:j+ny] = k - - output = globalTransformation(influMap) - - del influMap - return output - - # permute the dimensions for joblib - influenceFunctions_M4 = np.moveaxis(influenceFunctions_M4,-1,0) - # only consider nActu influence functions - influenceFunctions_M4 = influenceFunctions_M4[:nActu,:,:] - i1 = i1[:nActu] - j1 = j1[:nActu] - - - print('Reconstructing the influence functions ... ') - def joblib_reconstruction(): - Q=Parallel(n_jobs=nJobs,prefer='threads')(delayed(reconstruction_IF)(i,j,k) for i,j,k in zip(i1,j1,influenceFunctions_M4)) - return Q - # output of the reconstruction - influenceFunctions_M4 = np.moveaxis(np.asarray(joblib_reconstruction()),0,-1) - - # recenter the initial coordinates_M4_originalinates around 0 - coordinates_M4 = ((coordinates_M4_original-resolution_M4_original/2)*ratio_M4_M1) - - # apply the transformations and re-center them for the new resolution resolution_M1 - coordinates_M4 = translation(rotation(anamorphosis(coordinates_M4,misReg.anamorphosisAngle*np.pi/180,misReg.radialScaling,misReg.tangentialScaling),-misReg.rotationAngle*np.pi/180),[misReg.shiftX/pixelSize_M1,misReg.shiftY/pixelSize_M1])+resolution_M1/2 - - # store the influence function in sparse matrices - if dm is None: - influenceFunctions_M4 = np.reshape(influenceFunctions_M4,[resolution_M1*resolution_M1,nActu]) - return influenceFunctions_M4,coordinates_M4,nActu - else: - dm.modes = np.reshape(influenceFunctions_M4,[resolution_M1*resolution_M1,nActu]) - return coordinates_M4 - - - -def getPetalModes(tel,dm,i_petal): - try: - if np.isscalar(i_petal): - # initiliaze dm coef - petal = np.zeros(dm.nValidAct) - petal[(i_petal-1)*892:i_petal*892]=-np.ones(892) - dm.coefs = petal - tel*dm - tmp = np.abs(tel.OPD) > 1 - out = tmp - else: - out=np.zeros([tel.resolution,tel.resolution,len(i_petal)]) - for i in range(len(i_petal)): - petal = np.zeros(dm.nValidAct) - petal[(i_petal[i]-1)*892:i_petal[i]*892]=-np.ones(892) - dm.coefs = petal - tel*dm - tmp = np.abs(tel.OPD) > 1 - out[:,:,i] = tmp - out_float = out.astype(float) - except: - print('Error, the numbe or actuator does not match 892 actuator per petal.. returning an empty list.') - out = [] - out_float = [] +# -*- coding: utf-8 -*- +""" +Created on Wed Dec 2 15:31:25 2020 + +@author: cheritie +""" + +import ctypes + +import numpy as np +import skimage.transform as sk +from astropy.io import fits as pfits +from joblib import Parallel, delayed + +from ..MisRegistration import MisRegistration +from ..tools.interpolateGeometricalTransformation import (anamorphosis, + anamorphosisImageMatrix, + rotateImageMatrix, + rotation, + translation, + translationImageMatrix) + +try: + mkl_rt = ctypes.CDLL('libmkl_rt.so') + mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads + mkl_set_num_threads(8) +except: + try: + mkl_rt = ctypes.CDLL('./mkl_rt.dll') + mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads + mkl_set_num_threads(8) + except: + try: + import mkl + mkl_set_num_threads = mkl.set_num_threads + except: + mkl_set_num_threads = None + + +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% START OF THE FUNCTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +def makeM4influenceFunctions(pup, filename, misReg = 0, dm = None, nAct = 0, nJobs = 30, nThreads = 20, order =1,floating_precision=64, D = 40): + try: + mkl_set_num_threads(nThreads) + except: + print('Could not optimize the parallelisation of the code ') + # read the FITS file of the cropped influence functions. + hdul = pfits.open( filename ) + print("Read influence function from fits file : ", filename) + + xC = hdul[0].header['XCENTER'] + pixelSize_M4_original = hdul[0].header['pixsize'] + i1, j1 = hdul[1].data + influenceFunctions_M4 = hdul[2].data + xpos, ypos = hdul[3].data + + # size of influence functions and number of actuators + nx, ny, nActu = influenceFunctions_M4.shape + + # create a MisReg object to store the different mis-registration + if np.isscalar(misReg): + if misReg==0: + misReg=MisRegistration() + else: + print('ERROR: wrong value for the mis-registrations') + + # case with less actuators (debug purposes) + if nAct!=0: + nActu=nAct + + # coordinates of M4 before the interpolation + coordinates_M4_original = np.zeros([nAct,2]) + coordinates_M4_original[:,0] = xpos[:nAct] + coordinates_M4_original[:,1] = ypos[:nAct] + + # size of the influence functions maps + resolution_M4_original = int(np.ceil(2*xC)) + + # resolution of the M1 pupil + resolution_M1 = pup.shape[1] + + # compute the pixel scale of the M1 pupil + pixelSize_M1 = D/resolution_M1 + + # compute the ratio_M4_M1 between both pixel scale. + ratio_M4_M1 = pixelSize_M4_original/pixelSize_M1 + # after the interpolation the image will be shifted of a fraction of pixel extra if ratio_M4_M1 is not an integer + extra = (ratio_M4_M1)%1 + + nPix = (resolution_M4_original-resolution_M1) + + extra = extra/2 + (np.floor(ratio_M4_M1)-1)*0.5 + nCrop = (nPix/2) + + + # allocate memory to store the influence functions + influMap = np.zeros([resolution_M4_original,resolution_M4_original]) + +#-------------------- The Following Transformations are applied in the following order ----------------------------------- + + # 1) Down scaling to get the right pixel size according to the resolution of M1 + downScaling = anamorphosisImageMatrix(influMap,0,[ratio_M4_M1,ratio_M4_M1]) + + # 2) transformations for the mis-registration + anamMatrix = anamorphosisImageMatrix(influMap,misReg.anamorphosisAngle,[1+misReg.radialScaling,1+misReg.tangentialScaling]) + rotMatrix = rotateImageMatrix(influMap,misReg.rotationAngle) + shiftMatrix = translationImageMatrix(influMap,[misReg.shiftY/pixelSize_M1,misReg.shiftX/pixelSize_M1]) #units are in m + + # Shift of half a pixel to center the images on an even number of pixels + alignmentMatrix = translationImageMatrix(influMap,[-nCrop + extra,-nCrop + extra]) + + # 3) Global transformation matrix + transformationMatrix = downScaling + anamMatrix + rotMatrix + shiftMatrix + alignmentMatrix + + def globalTransformation(image): + output = sk.warp(image,(transformationMatrix).inverse,output_shape = [resolution_M1,resolution_M1],order=order) + return output + + # definition of the function that is run in parallel for each + def reconstruction_IF(i,j,k): + + i=int(i) + j=int(j) + + if floating_precision==32: + # support of the IFS + influMap = np.zeros([resolution_M4_original,resolution_M4_original],dtype=np.float32) + # fill up the support with the 2D IFS [120x120] + influMap[i:i+nx, j:j+ny] = np.float32(k) + else: + influMap = np.zeros([resolution_M4_original,resolution_M4_original],dtype=np.float64) + # fill up the support with the 2D IFS [120x120] + influMap[i:i+nx, j:j+ny] = k + + output = globalTransformation(influMap) + + del influMap + return output + + # permute the dimensions for joblib + influenceFunctions_M4 = np.moveaxis(influenceFunctions_M4,-1,0) + # only consider nActu influence functions + influenceFunctions_M4 = influenceFunctions_M4[:nActu,:,:] + i1 = i1[:nActu] + j1 = j1[:nActu] + + + print('Reconstructing the influence functions ... ') + def joblib_reconstruction(): + Q=Parallel(n_jobs=nJobs,prefer='threads')(delayed(reconstruction_IF)(i,j,k) for i,j,k in zip(i1,j1,influenceFunctions_M4)) + return Q + # output of the reconstruction + influenceFunctions_M4 = np.moveaxis(np.asarray(joblib_reconstruction()),0,-1) + + # recenter the initial coordinates_M4_originalinates around 0 + coordinates_M4 = ((coordinates_M4_original-resolution_M4_original/2)*ratio_M4_M1) + + # apply the transformations and re-center them for the new resolution resolution_M1 + coordinates_M4 = translation(rotation(anamorphosis(coordinates_M4,misReg.anamorphosisAngle*np.pi/180,misReg.radialScaling,misReg.tangentialScaling),-misReg.rotationAngle*np.pi/180),[misReg.shiftX/pixelSize_M1,misReg.shiftY/pixelSize_M1])+resolution_M1/2 + + # store the influence function in sparse matrices + if dm is None: + influenceFunctions_M4 = np.reshape(influenceFunctions_M4,[resolution_M1*resolution_M1,nActu]) + return influenceFunctions_M4,coordinates_M4,nActu + else: + dm.modes = np.reshape(influenceFunctions_M4,[resolution_M1*resolution_M1,nActu]) + return coordinates_M4 + + + +def getPetalModes(tel,dm,i_petal): + try: + if np.isscalar(i_petal): + # initiliaze dm coef + petal = np.zeros(dm.nValidAct) + petal[(i_petal-1)*892:i_petal*892]=-np.ones(892) + dm.coefs = petal + tel*dm + tmp = np.abs(tel.OPD) > 1 + out = tmp + else: + out=np.zeros([tel.resolution,tel.resolution,len(i_petal)]) + for i in range(len(i_petal)): + petal = np.zeros(dm.nValidAct) + petal[(i_petal[i]-1)*892:i_petal[i]*892]=-np.ones(892) + dm.coefs = petal + tel*dm + tmp = np.abs(tel.OPD) > 1 + out[:,:,i] = tmp + out_float = out.astype(float) + except: + print('Error, the numbe or actuator does not match 892 actuator per petal.. returning an empty list.') + out = [] + out_float = [] return out, out_float \ No newline at end of file diff --git a/AO_modules/MisRegistration.py b/OOPAO/MisRegistration.py old mode 100755 new mode 100644 similarity index 98% rename from AO_modules/MisRegistration.py rename to OOPAO/MisRegistration.py index 0b7e6d1..e6a39b5 --- a/AO_modules/MisRegistration.py +++ b/OOPAO/MisRegistration.py @@ -1,268 +1,271 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Jun 26 14:01:10 2020 - -@author: cheritie -""" -import numpy as np -import inspect - -class MisRegistration: - def __init__(self,param=None): - - self.tag = 'misRegistration' - self.isInitialized = False - - if param is None: - self.rotationAngle = 0 # rotation angle in degrees - self.shiftX = 0 # shift X in m - self.shiftY = 0 # shift Y in m - self.anamorphosisAngle = 0 # amamorphosis angle in degrees - self.tangentialScaling = 0 # normal scaling in % of diameter - self.radialScaling = 0 # radial scaling in % of diameter - else: - if isinstance(param,dict): - # print('MisRegistration object created from a ditionnary') - self.rotationAngle = param['rotationAngle'] # rotation angle in degrees - self.shiftX = param['shiftX'] # shift X in m - self.shiftY = param['shiftY'] # shift Y in m - self.anamorphosisAngle = param['anamorphosisAngle'] # amamorphosis angle in degrees - self.tangentialScaling = param['tangentialScaling'] # normal scaling in % of diameter - self.radialScaling = param['radialScaling'] # radial scaling in % of diameter - else: - if inspect.isclass(type(param)): - if param.tag == 'misRegistration': - # print('MisRegistration object created from an existing Misregistration object') - - self.rotationAngle = param.rotationAngle # rotation angle in degrees - self.shiftX = param.shiftX # shift X in m - self.shiftY = param.shiftY # shift Y in m - self.anamorphosisAngle = param.anamorphosisAngle # amamorphosis angle in degrees - self.tangentialScaling = param.tangentialScaling # normal scaling in % of diameter - self.radialScaling = param.radialScaling - else: - print('wrong type of object passed to a MisRegistration object') - else: - print('wrong type of object passed to a MisRegistration object') - - - - if self.radialScaling ==0 and self.tangentialScaling ==0: - self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ - 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ - 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ - 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ - 'radScal_' + str('%.2f' %(self.radialScaling+1.)) +'_'\ - 'tangScal_' + str('%.2f' %(self.tangentialScaling+1.)) - else: - self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ - 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ - 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ - 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ - 'radScal_' + str('%.4f' %(self.radialScaling+1.)) +'_'\ - 'tangScal_' + str('%.4f' %(self.tangentialScaling+1.)) - - self.isInitialized = True - -# mis-registrations can be added or sub-tracted using + and - - def __add__(self,misRegObject): - if misRegObject.tag == 'misRegistration': - tmp = MisRegistration() - tmp.rotationAngle = self.rotationAngle + misRegObject.rotationAngle - tmp.shiftX = self.shiftX + misRegObject.shiftX - tmp.shiftY = self.shiftY + misRegObject.shiftY - tmp.anamorphosisAngle = self.anamorphosisAngle + misRegObject.anamorphosisAngle - tmp.tangentialScaling = self.tangentialScaling + misRegObject.tangentialScaling - tmp.radialScaling = self.radialScaling + misRegObject.radialScaling - else: - print('Error you are trying to combine a MisRegistration Object with the wrong type of object') - return tmp - - def __sub__(self,misRegObject): - if misRegObject.tag == 'misRegistration': - tmp = MisRegistration() - - tmp.rotationAngle = self.rotationAngle - misRegObject.rotationAngle - tmp.shiftX = self.shiftX - misRegObject.shiftX - tmp.shiftY = self.shiftY - misRegObject.shiftY - tmp.anamorphosisAngle = self.anamorphosisAngle - misRegObject.anamorphosisAngle - tmp.tangentialScaling = self.tangentialScaling - misRegObject.tangentialScaling - tmp.radialScaling = self.radialScaling - misRegObject.radialScaling - else: - print('Error you are trying to combine a MisRegistration Object with the wrong type of object') - return tmp - - def __eq__(self, other): - if isinstance(other, self.__class__): - return self.__dict__ == other.__dict__ - else: - return False - - def __ne__(self, other): - return not self.__eq__(other) -# ----------------------------------------- Properties ----------------------------------------- - @property - def rotationAngle(self): - return self._rotationAngle - - @rotationAngle.setter - def rotationAngle(self,val): - self._rotationAngle = val - if self.isInitialized: - if self.radialScaling ==0 and self.tangentialScaling ==0: - self.misRegName = 'rot_' + str('%.2f' %val) +'_deg_'\ - 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ - 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ - 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ - 'radScal_' + str('%.2f' %(self.radialScaling+1.)) +'_'\ - 'tangScal_' + str('%.2f' %(self.tangentialScaling+1.)) - else: - self.misRegName = 'rot_' + str('%.2f' %val) +'_deg_'\ - 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ - 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ - 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ - 'radScal_' + str('%.4f' %(self.radialScaling+1.)) +'_'\ - 'tangScal_' + str('%.4f' %(self.tangentialScaling+1.)) - - @property - def shiftX(self): - return self._shiftX - - @shiftX.setter - def shiftX(self,val): - self._shiftX = val - if self.isInitialized: - if self.radialScaling ==0 and self.tangentialScaling ==0: - self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ - 'sX_' + str('%.2f' %(val)) +'_m_'\ - 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ - 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ - 'radScal_' + str('%.2f' %(self.radialScaling+1.)) +'_'\ - 'tangScal_' + str('%.2f' %(self.tangentialScaling+1.)) - else: - self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ - 'sX_' + str('%.2f' %(val)) +'_m_'\ - 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ - 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ - 'radScal_' + str('%.4f' %(self.radialScaling+1.)) +'_'\ - 'tangScal_' + str('%.4f' %(self.tangentialScaling+1.)) - - - - @property - def shiftY(self): - return self._shiftY - - @shiftY.setter - def shiftY(self,val): - self._shiftY = val - if self.isInitialized: - if self.radialScaling ==0 and self.tangentialScaling ==0: - self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ - 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ - 'sY_' + str('%.2f' %(val)) +'_m_'\ - 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ - 'radScal_' + str('%.2f' %(self.radialScaling+1.)) +'_'\ - 'tangScal_' + str('%.2f' %(self.tangentialScaling+1.)) - else: - self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ - 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ - 'sY_' + str('%.2f' %(val)) +'_m_'\ - 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ - 'radScal_' + str('%.4f' %(self.radialScaling+1.)) +'_'\ - 'tangScal_' + str('%.4f' %(self.tangentialScaling+1.)) - - @property - def anamorphosisAngle(self): - return self._anamorphosisAngle - - @anamorphosisAngle.setter - def anamorphosisAngle(self,val): - self._anamorphosisAngle = val - if self.isInitialized: - if self.radialScaling ==0 and self.tangentialScaling ==0: - self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ - 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ - 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ - 'anamAngle_' + str('%.2f' %val) +'_deg_'\ - 'radScal_' + str('%.2f' %(self.radialScaling+1.)) +'_'\ - 'tangScal_' + str('%.2f' %(self.tangentialScaling+1.)) - else: - self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ - 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ - 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ - 'anamAngle_' + str('%.2f' %val) +'_deg_'\ - 'radScal_' + str('%.4f' %(self.radialScaling+1.)) +'_'\ - 'tangScal_' + str('%.4f' %(self.tangentialScaling+1.)) - - - @property - def radialScaling(self): - return self._radialScaling - - @radialScaling.setter - def radialScaling(self,val): - self._radialScaling = val - if self.isInitialized: - if self.radialScaling ==0 and self.tangentialScaling ==0: - self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ - 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ - 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ - 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ - 'radScal_' + str('%.2f' %(val+1.)) +'_'\ - 'tangScal_' + str('%.2f' %(self.tangentialScaling+1.)) - else: - self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ - 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ - 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ - 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ - 'radScal_' + str('%.4f' %(val+1.)) +'_'\ - 'tangScal_' + str('%.4f' %(self.tangentialScaling+1.)) - - - @property - def tangentialScaling(self): - return self._tangentialScaling - - @tangentialScaling.setter - def tangentialScaling(self,val): - self._tangentialScaling = val - if self.isInitialized: - if self.radialScaling ==0 and self.tangentialScaling ==0: - - self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ - 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ - 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ - 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ - 'radScal_' + str('%.2f' %(self.radialScaling+1.)) +'_'\ - 'tangScal_' + str('%.2f' %(val+1.)) - else: - self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ - 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ - 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ - 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ - 'radScal_' + str('%.4f' %(self.radialScaling+1.)) +'_'\ - 'tangScal_' + str('%.4f' %(val+1.)) - - - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - def print_(self): - print('{: ^14s}'.format('Rotation [deg]') + '\t' + '{: ^11s}'.format('Shift X [m]')+ '\t' + '{: ^11s}'.format('Shift Y [m]')+ '\t' + '{: ^18s}'.format('Radial Scaling [%]')+ '\t' + '{: ^22s}'.format('Tangential Scaling [%]')) - print("{: ^14s}".format(str(self.rotationAngle) ) + '\t' +'{: ^11s}'.format(str(self.shiftX))+'\t' + '{: ^11s}'.format(str(self.shiftY)) +'\t' +'{: ^18s}'.format(str(self.radialScaling))+'\t' + '{: ^22s}'.format(str(self.tangentialScaling))) - - def show(self): - attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) - print(self.tag+':') - for a in attributes: - if not(a[0].startswith('__') and a[0].endswith('__')): - if not(a[0].startswith('_')): - if not np.shape(a[1]): - tmp=a[1] - try: - print(' '+str(a[0])+': '+str(tmp.tag)+' object') - except: - print(' '+str(a[0])+': '+str(a[1])) - else: - if np.ndim(a[1])>1: - print(' '+str(a[0])+': '+str(np.shape(a[1]))) +# -*- coding: utf-8 -*- +""" +Created on Fri Jun 26 14:01:10 2020 + +@author: cheritie +""" + +import inspect + +import numpy as np + + +class MisRegistration: + def __init__(self,param=None): + + self.tag = 'misRegistration' + self.isInitialized = False + + if param is None: + self.rotationAngle = 0 # rotation angle in degrees + self.shiftX = 0 # shift X in m + self.shiftY = 0 # shift Y in m + self.anamorphosisAngle = 0 # amamorphosis angle in degrees + self.tangentialScaling = 0 # normal scaling in % of diameter + self.radialScaling = 0 # radial scaling in % of diameter + else: + if isinstance(param,dict): + # print('MisRegistration object created from a ditionnary') + self.rotationAngle = param['rotationAngle'] # rotation angle in degrees + self.shiftX = param['shiftX'] # shift X in m + self.shiftY = param['shiftY'] # shift Y in m + self.anamorphosisAngle = param['anamorphosisAngle'] # amamorphosis angle in degrees + self.tangentialScaling = param['tangentialScaling'] # normal scaling in % of diameter + self.radialScaling = param['radialScaling'] # radial scaling in % of diameter + else: + if inspect.isclass(type(param)): + if param.tag == 'misRegistration': + # print('MisRegistration object created from an existing Misregistration object') + + self.rotationAngle = param.rotationAngle # rotation angle in degrees + self.shiftX = param.shiftX # shift X in m + self.shiftY = param.shiftY # shift Y in m + self.anamorphosisAngle = param.anamorphosisAngle # amamorphosis angle in degrees + self.tangentialScaling = param.tangentialScaling # normal scaling in % of diameter + self.radialScaling = param.radialScaling + else: + print('wrong type of object passed to a MisRegistration object') + else: + print('wrong type of object passed to a MisRegistration object') + + + + if self.radialScaling ==0 and self.tangentialScaling ==0: + self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ + 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ + 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ + 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ + 'radScal_' + str('%.2f' %(self.radialScaling+1.)) +'_'\ + 'tangScal_' + str('%.2f' %(self.tangentialScaling+1.)) + else: + self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ + 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ + 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ + 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ + 'radScal_' + str('%.4f' %(self.radialScaling+1.)) +'_'\ + 'tangScal_' + str('%.4f' %(self.tangentialScaling+1.)) + + self.isInitialized = True + +# mis-registrations can be added or sub-tracted using + and - + def __add__(self,misRegObject): + if misRegObject.tag == 'misRegistration': + tmp = MisRegistration() + tmp.rotationAngle = self.rotationAngle + misRegObject.rotationAngle + tmp.shiftX = self.shiftX + misRegObject.shiftX + tmp.shiftY = self.shiftY + misRegObject.shiftY + tmp.anamorphosisAngle = self.anamorphosisAngle + misRegObject.anamorphosisAngle + tmp.tangentialScaling = self.tangentialScaling + misRegObject.tangentialScaling + tmp.radialScaling = self.radialScaling + misRegObject.radialScaling + else: + print('Error you are trying to combine a MisRegistration Object with the wrong type of object') + return tmp + + def __sub__(self,misRegObject): + if misRegObject.tag == 'misRegistration': + tmp = MisRegistration() + + tmp.rotationAngle = self.rotationAngle - misRegObject.rotationAngle + tmp.shiftX = self.shiftX - misRegObject.shiftX + tmp.shiftY = self.shiftY - misRegObject.shiftY + tmp.anamorphosisAngle = self.anamorphosisAngle - misRegObject.anamorphosisAngle + tmp.tangentialScaling = self.tangentialScaling - misRegObject.tangentialScaling + tmp.radialScaling = self.radialScaling - misRegObject.radialScaling + else: + print('Error you are trying to combine a MisRegistration Object with the wrong type of object') + return tmp + + def __eq__(self, other): + if isinstance(other, self.__class__): + return self.__dict__ == other.__dict__ + else: + return False + + def __ne__(self, other): + return not self.__eq__(other) +# ----------------------------------------- Properties ----------------------------------------- + @property + def rotationAngle(self): + return self._rotationAngle + + @rotationAngle.setter + def rotationAngle(self,val): + self._rotationAngle = val + if self.isInitialized: + if self.radialScaling ==0 and self.tangentialScaling ==0: + self.misRegName = 'rot_' + str('%.2f' %val) +'_deg_'\ + 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ + 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ + 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ + 'radScal_' + str('%.2f' %(self.radialScaling+1.)) +'_'\ + 'tangScal_' + str('%.2f' %(self.tangentialScaling+1.)) + else: + self.misRegName = 'rot_' + str('%.2f' %val) +'_deg_'\ + 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ + 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ + 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ + 'radScal_' + str('%.4f' %(self.radialScaling+1.)) +'_'\ + 'tangScal_' + str('%.4f' %(self.tangentialScaling+1.)) + + @property + def shiftX(self): + return self._shiftX + + @shiftX.setter + def shiftX(self,val): + self._shiftX = val + if self.isInitialized: + if self.radialScaling ==0 and self.tangentialScaling ==0: + self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ + 'sX_' + str('%.2f' %(val)) +'_m_'\ + 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ + 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ + 'radScal_' + str('%.2f' %(self.radialScaling+1.)) +'_'\ + 'tangScal_' + str('%.2f' %(self.tangentialScaling+1.)) + else: + self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ + 'sX_' + str('%.2f' %(val)) +'_m_'\ + 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ + 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ + 'radScal_' + str('%.4f' %(self.radialScaling+1.)) +'_'\ + 'tangScal_' + str('%.4f' %(self.tangentialScaling+1.)) + + + + @property + def shiftY(self): + return self._shiftY + + @shiftY.setter + def shiftY(self,val): + self._shiftY = val + if self.isInitialized: + if self.radialScaling ==0 and self.tangentialScaling ==0: + self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ + 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ + 'sY_' + str('%.2f' %(val)) +'_m_'\ + 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ + 'radScal_' + str('%.2f' %(self.radialScaling+1.)) +'_'\ + 'tangScal_' + str('%.2f' %(self.tangentialScaling+1.)) + else: + self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ + 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ + 'sY_' + str('%.2f' %(val)) +'_m_'\ + 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ + 'radScal_' + str('%.4f' %(self.radialScaling+1.)) +'_'\ + 'tangScal_' + str('%.4f' %(self.tangentialScaling+1.)) + + @property + def anamorphosisAngle(self): + return self._anamorphosisAngle + + @anamorphosisAngle.setter + def anamorphosisAngle(self,val): + self._anamorphosisAngle = val + if self.isInitialized: + if self.radialScaling ==0 and self.tangentialScaling ==0: + self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ + 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ + 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ + 'anamAngle_' + str('%.2f' %val) +'_deg_'\ + 'radScal_' + str('%.2f' %(self.radialScaling+1.)) +'_'\ + 'tangScal_' + str('%.2f' %(self.tangentialScaling+1.)) + else: + self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ + 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ + 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ + 'anamAngle_' + str('%.2f' %val) +'_deg_'\ + 'radScal_' + str('%.4f' %(self.radialScaling+1.)) +'_'\ + 'tangScal_' + str('%.4f' %(self.tangentialScaling+1.)) + + + @property + def radialScaling(self): + return self._radialScaling + + @radialScaling.setter + def radialScaling(self,val): + self._radialScaling = val + if self.isInitialized: + if self.radialScaling ==0 and self.tangentialScaling ==0: + self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ + 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ + 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ + 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ + 'radScal_' + str('%.2f' %(val+1.)) +'_'\ + 'tangScal_' + str('%.2f' %(self.tangentialScaling+1.)) + else: + self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ + 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ + 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ + 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ + 'radScal_' + str('%.4f' %(val+1.)) +'_'\ + 'tangScal_' + str('%.4f' %(self.tangentialScaling+1.)) + + + @property + def tangentialScaling(self): + return self._tangentialScaling + + @tangentialScaling.setter + def tangentialScaling(self,val): + self._tangentialScaling = val + if self.isInitialized: + if self.radialScaling ==0 and self.tangentialScaling ==0: + + self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ + 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ + 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ + 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ + 'radScal_' + str('%.2f' %(self.radialScaling+1.)) +'_'\ + 'tangScal_' + str('%.2f' %(val+1.)) + else: + self.misRegName = 'rot_' + str('%.2f' %self.rotationAngle) +'_deg_'\ + 'sX_' + str('%.2f' %(self.shiftX)) +'_m_'\ + 'sY_' + str('%.2f' %(self.shiftY)) +'_m_'\ + 'anamAngle_' + str('%.2f' %self.anamorphosisAngle) +'_deg_'\ + 'radScal_' + str('%.4f' %(self.radialScaling+1.)) +'_'\ + 'tangScal_' + str('%.4f' %(val+1.)) + + + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + def print_(self): + print('{: ^14s}'.format('Rotation [deg]') + '\t' + '{: ^11s}'.format('Shift X [m]')+ '\t' + '{: ^11s}'.format('Shift Y [m]')+ '\t' + '{: ^18s}'.format('Radial Scaling [%]')+ '\t' + '{: ^22s}'.format('Tangential Scaling [%]')) + print("{: ^14s}".format(str(self.rotationAngle) ) + '\t' +'{: ^11s}'.format(str(self.shiftX))+'\t' + '{: ^11s}'.format(str(self.shiftY)) +'\t' +'{: ^18s}'.format(str(self.radialScaling))+'\t' + '{: ^22s}'.format(str(self.tangentialScaling))) + + def show(self): + attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) + print(self.tag+':') + for a in attributes: + if not(a[0].startswith('__') and a[0].endswith('__')): + if not(a[0].startswith('_')): + if not np.shape(a[1]): + tmp=a[1] + try: + print(' '+str(a[0])+': '+str(tmp.tag)+' object') + except: + print(' '+str(a[0])+': '+str(a[1])) + else: + if np.ndim(a[1])>1: + print(' '+str(a[0])+': '+str(np.shape(a[1]))) diff --git a/AO_modules/Pyramid.py b/OOPAO/Pyramid.py old mode 100755 new mode 100644 similarity index 98% rename from AO_modules/Pyramid.py rename to OOPAO/Pyramid.py index 86c0ab2..efccfbb --- a/AO_modules/Pyramid.py +++ b/OOPAO/Pyramid.py @@ -1,955 +1,960 @@ -# -*- coding: utf-8 -*- -""" -Created on Sun Feb 23 19:35:18 2020 - -@author: cheritie -""" -import numpy as np -import scipy.ndimage as sp -import sys -import inspect -import time -import matplotlib.pyplot as plt -import multiprocessing -from AO_modules.Detector import Detector -try: - # error - import cupy as np_cp -except: - import numpy as np_cp - # print('NO GPU available!') -try: - from joblib import Parallel, delayed -except: - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('WARNING: The joblib module is not installed. This would speed up considerably the operations.') - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - -# import ctypes -# try : -# mkl_rt = ctypes.CDLL('libmkl_rt.so') -# mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads -# mkl_set_num_threads(6) -# except: -# try: -# mkl_rt = ctypes.CDLL('./mkl_rt.dll') -# mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads -# mkl_set_num_threads(6) -# except: -# try: -# import mkl -# mkl_set_num_threads = mkl.set_num_threads -# except: -# mkl_set_num_threads = None -class Pyramid: -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CLASS INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - def __init__(self,nSubap,telescope,modulation,lightRatio, postProcessing='slopesMaps',psfCentering=True, n_pix_separation = 2, calibModulation=50, n_pix_edge=None,extraModulationFactor=0,zeroPadding=None,pupilSeparationRatio=None,edgePixel = None,binning =1,nTheta_user_defined=None,userValidSignal=None,old_mask=False,rooftop = None,delta_theta = 0, user_modulation_path = None): - """ - A Pyramid object consists in defining a 2D phase mask located at the focal plane of the telescope to perform the Fourier Filtering of the EM-Field. - By default the Pyramid detector is considered to be noise-free (for calibration purposes). These properties can be switched on and off on the fly (see properties) - ************************** REQUIRED PARAMETERS ************************** - It requires the following parameters: - _ nSubap : the number of subapertures (ie the diameter of the Pyramid Pupils in pixels) - _ telescope : the telescope object to which the Pyramid is associated. This object carries the phase, flux and pupil information - _ modulation : the Tip-Tilt modulation in [lambda/D] where lambda is the NGS wavelength and D the telescope diameter - _ lightRatio : criterion to select the valid subaperture based on flux considerations - _ n_pix_separation : number of pixels separating the Pyramid Pupils in number of pixels of the detector -- default value is 2 pixels - _ n_pix_edge : number of pixel at the edge of the Pyramid Pupils in number of pixels of the detector -- default value is n_pix_separation's value - _ postProcessing : processing of the signals ('fullFrame' or 'slopesMaps') -- default value is 'slopesMaps' - - DEPRECIATED PARAMETERS: - _ pupilSeparationRatio : Separation ratio of the PWFS pupils (Diameter/Distance Center to Center) -- DEPRECIATED -> use n_pix_separation instead) - _ edgePixel : number of pixel at the edge of the Pyramid Pupils - - ************************** OPTIONAL PARAMETERS ************************** - - _ calibModulation : Defines the modulation used to select the valid subapertures -- default value is 50 lambda/D - _ extraModulationFactor : Extra Factor to increase/reduce the number of modulation point (extraModulationFactor = 1 means 4 modulation points added, 1 for each quadrant) - _ psfCentering : If False, the Pyramid mask is centered on 1 pixel, if True, the Pyramid mask is centered on 4 pixels -- default value is True - _ binning : binning factor of the PWFS detector signals -- default Value is 1 - _ nTheta_user_defined : user-defined number of Tip/Tilt modulation points - _ delta_theta : delta angle for the modulation points, default value is 0 (on the edge between two sides of the Pyramid) - _ userValidSignal : user-defined valid pixel mask for the signals computation - _ rooftop : if different to None, allows to compute a two-sided Pyramid ("V" corresponds to a vertical split, "H" corresponds to an horizontal split) - _ zeroPadding : User-defined zero-padding value in pixels that will be added to each side of the arrays. Consider using the n_pix_edge parameter that allows to do the same thing. - _ pupilReflectivcty : Defines the reflectivity of the Telescope object. If not set to 1, it can be input as a 2D map of uneven reflectivy correspondong to the pupil mask. - _ user_modulation_path : user-defined modulation path ( a list of [x,y] coordinates in lambda/D units is expected) - - ************************** PROPAGATING THE LIGHT TO THE PYRAMID OBJECT ************************** - The light can be propagated from a telescope object tel through the Pyramid object wfs using the * operator: - _ tel*wfs - This operation will trigger: - _ propagation of the tel.src light through the PWFS detector (phase and flux) - _ binning of the Pyramid signals - _ addition of eventual photon noise and readout noise - _ computation of the Pyramid signals - - - ************************** PROPERTIES ************************** - - The main properties of a Telescope object are listed here: - _ wfs.nSignal : the length of the signal measured by the Pyramid - _ wfs.signal : signal measured by the Pyramid of length wfs.nSignal - _ wfs.signal_2D : 2D map of the signal measured by the Pyramid - _ wfs.apply_shift_wfs : apply a tip tilt to each quadrant to move the Pyramid pupils - _ wfs.random_state_photon_noise : a random state cycle can be defined to reproduces random sequences of noise -- default is based on the current clock time - _ wfs.random_state_readout_noise : a random state cycle can be defined to reproduces random sequences of noise -- default is based on the current clock time - _ wfs.random_state_background : a random state cycle can be defined to reproduces random sequences of noise -- default is based on the current clock time - _ wfs.fov : Field of View of the Pyramid in arcsec - - the following properties can be updated on the fly: - _ wfs.modulation : update the modulation radius and update the reference signal - _ wfs.cam.photonNoise : Photon noise can be set to True or False - _ wfs.cam.readoutNoise : Readout noise can be set to True or False - _ wfs.backgroundNoise : Background noise can be set to True or False - _ wfs.lightRatio : reset the valid subaperture selection considering the new value - - """ - try: - # try to find GPU - # error - import cupy as np_cp - self.gpu_available = True - self.convert_for_gpu = np_cp.asarray - self.convert_for_numpy = np_cp.asnumpy - self.nJobs = 1 - self.mempool = np_cp.get_default_memory_pool() - from AO_modules.tools.tools import get_gpu_memory - self.mem_gpu = get_gpu_memory() - - print('GPU available!') - for i in range(len(self.mem_gpu)): - print('GPU device '+str(i)+' : '+str(self.mem_gpu[i]/1024)+ 'GB memory') - - except: - import numpy as np_cp - def no_function(input_matrix): - return input_matrix - self.gpu_available = False - self.convert_for_gpu = no_function - self.convert_for_numpy = no_function - - # initialize the Pyramid Object - self.telescope = telescope # telescope attached to the wfs - if self.telescope.resolution/nSubap <4 or (self.telescope.resolution/nSubap)%2 !=0: - raise ValueError('The resolution should be an even number and be a multiple of 2**i where i>=2') - self.delta_theta = delta_theta # delta theta in degree to change the position of the modulation point (default is 0 <=> modulation point on the edge of two sides of the pyramid) - self.nTheta_user_defined = nTheta_user_defined # user defined number of modulation point - self.extraModulationFactor = extraModulationFactor # Extra Factor to increase/reduce the number of modulation point (extraModulationFactor = 1 means 4 modulation points added, 1 for each quadrant) - self.nSubap = nSubap # Number of subaperture - self.edgePixel = n_pix_edge # Number of pixel on the edges of the PWFS pupils - self.centerPixel = 0 # Value used for the centering for the slopes-maps computation - self.postProcessing = postProcessing # type of processing of the signals (see self.postProcessing) - self.userValidSignal = userValidSignal # user defined mask for the valid pixel selection - self.psfCentering = psfCentering # tag for the PSF centering on or 4 pixels - self.backgroundNoise = False # background noise in photon - self.binning = binning # binning factor for the detector - self.old_mask = old_mask - self.random_state_photon_noise = np.random.RandomState(seed=int(time.time())) # random states to reproduce sequences of noise - self.random_state_readout_noise = np.random.RandomState(seed=int(time.time())) # random states to reproduce sequences of noise - self.random_state_background = np.random.RandomState(seed=int(time.time())) # random states to reproduce sequences of noise - self.user_modulation_path = user_modulation_path # user defined modulation path - self.pupilSeparationRatio = pupilSeparationRatio # Separation ratio of the PWFS pupils (Diameter/Distance Center to Center) -- DEPRECIATED -> use n_pix_separation instead) - self.weight_vector = None - if edgePixel is not None: - print('WARNING: The use of the edgePixel property has been depreciated. Consider using the n_pix_edge instead') - if pupilSeparationRatio is not None: - print('WARNING: The use of the pupilSeparationRatio property has been depreciated. Consider using the n_pix_separation instead') - # backward compatibility - if np.isscalar(self.pupilSeparationRatio): - self.n_pix_separation = (self.pupilSeparationRatio-1)*self.nSubap - self.sx = [0,0,0,0] - self.sy = [0,0,0,0] - else: - self.n_pix_separation = 0 - self.sx = [] - self.sy = [] - for i in range(4): - self.sx.append((self.pupilSeparationRatio[i][0]-1)*self.nSubap) - self.sy.append((self.pupilSeparationRatio[i][1]-1)*self.nSubap) - else: - self.n_pix_separation = n_pix_separation - self.pupilSeparationRatio = 1+self.n_pix_separation/self.nSubap - self.sx = [0,0,0,0] - self.sy = [0,0,0,0] - if n_pix_edge is None: - self.n_pix_edge = self.n_pix_separation//2 - else: - self.n_pix_edge = n_pix_edge - if n_pix_edge != self.n_pix_separation//2: - print('WARNING: The recommanded value for n_pix_edge is '+str(self.n_pix_separation//2) +' instead of '+ str(n_pix_edge)) - if self.gpu_available: - self.joblib_setting = 'processes' - else: - self.joblib_setting = 'threads' - self.rooftop = rooftop - - if zeroPadding is None: - # Case where the zero-padding is not specificed => taking the smallest value ensuring to get edgePixel space from the edge. - self.nRes = int((self.nSubap*2+self.n_pix_separation+self.n_pix_edge*2)*self.telescope.resolution/self.nSubap) - self.zeroPaddingFactor = self.nRes/self.telescope.resolution # zero-Padding Factor - self.zeroPadding = (self.nRes - self.telescope.resolution)//2 # zero-Padding Factor - else: - # Case where the zero-padding is specificed => making sure that the value is large enough to display the PWFS pupils - self.zeroPadding = zeroPadding - if np.max(self.pupilSeparationRatio)<=2*self.zeroPadding/self.telescope.resolution: - self.nRes = int(2*(self.zeroPadding)+self.telescope.resolution) # Resolution of the zero-padded images - self.zeroPaddingFactor = self.nRes/self.telescope.resolution # zero-Padding Factor - else: - raise ValueError('Error: The Separation of the pupils is too large for this value of zeroPadding!') - - self.tag = 'pyramid' # Tag of the object - self.cam = Detector(round(nSubap*self.zeroPaddingFactor)) # WFS detector object - self.lightRatio = lightRatio + 0.001 # Light ratio for the valid pixels selection 23/09/2022 cth: 0.001 added for backward compatibility - if calibModulation>= self.telescope.resolution/2: - self.calibModulation = self.telescope.resolution/2 -1 - else: - self.calibModulation = calibModulation # Modulation used for the valid pixel selection - self.isInitialized = False # Flag for the initialization of the WFS - self.isCalibrated = False # Flag for the initialization of the WFS - self.delta_Tip = 0 # delta Tip for the modulation - self.delta_Tilt = 0 # delta Tilt for the modulation - self.center = self.nRes//2 # Center of the zero-Padded array - self.supportPadded = self.convert_for_gpu(np.pad(self.telescope.pupil.astype(complex),((self.zeroPadding,self.zeroPadding),(self.zeroPadding,self.zeroPadding)),'constant')) - self.spatialFilter = None # case where a spatial filter is considered - self.fov = 206265*self.telescope.resolution*(self.telescope.src.wavelength/self.telescope.D) # fov in arcsec - n_cpu = multiprocessing.cpu_count() - # joblib settings for parallization - if self.gpu_available is False: - if n_cpu > 16: - self.nJobs = 32 # number of jobs for the joblib package - else: - self.nJobs = 8 - self.n_max = 20*500 - else: - # quantify GPU max memory usage - A = np.ones([self.nRes,self.nRes]) + 1j*np.ones([self.nRes,self.nRes]) - self.n_max = int(0.75*(np.min(self.mem_gpu)/1024)/(A.nbytes/1024/1024/1024)) - del A - - # Prepare the Tip Tilt for the modulation -- normalized to apply the modulation in terms of lambda/D - [self.Tip,self.Tilt] = np.meshgrid(np.linspace(-np.pi,np.pi,self.telescope.resolution),np.linspace(-np.pi,np.pi,self.telescope.resolution)) - self.Tilt *= self.telescope.pupil - self.Tip *= self.telescope.pupil - - # compute the phasor to center the PSF on 4 pixels - [xx,yy] = np.meshgrid(np.linspace(0,self.nRes-1,self.nRes),np.linspace(0,self.nRes-1,self.nRes)) - self.phasor = self.convert_for_gpu(np.exp(-(1j*np.pi*(self.nRes+1)/self.nRes)*(xx+yy))) - - # Creating the PWFS mask - self.mask_computation() - - # initialize the reference slopes and units - self.slopesUnits = 1 - self.referenceSignal = 0 - self.referenceSignal_2D = 0 - self.referencePyramidFrame = 0 - self.modulation = modulation # Modulation radius (in lambda/D) - - # Select the valid pixels - print('Selection of the valid pixels...') - self.initialization(self.telescope) - print('Acquisition of the reference slopes and units calibration...') - # set the modulation radius and propagate light - self.modulation = modulation - self.wfs_calibration(self.telescope) - self.telescope.resetOPD() - self.wfs_measure(phase_in=self.telescope.src.phase) - - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PYRAMID WFS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('{: ^18s}'.format('Pupils Diameter') + '{: ^18s}'.format(str(self.nSubap)) +'{: ^18s}'.format('[pixels]' )) - print('{: ^18s}'.format('Pupils Separation') + '{: ^18s}'.format(str(self.n_pix_separation)) +'{: ^18s}'.format('[pixels]' )) - print('{: ^18s}'.format('FoV') + '{: ^18s}'.format(str(np.round(self.fov,2))) +'{: ^18s}'.format('[arcsec]' )) - print('{: ^18s}'.format('TT Modulation') + '{: ^18s}'.format(str(self.modulation)) +'{: ^18s}'.format('[lamda/D]' )) - print('{: ^18s}'.format('PSF Core Sampling') + '{: ^18s}'.format(str(1+self.psfCentering*3)) +'{: ^18s}'.format('[pixel(s)]' )) - print('{: ^18s}'.format('Valid Pixels') + '{: ^18s}'.format(str(self.nSignal)) +'{: ^18s}'.format('[pixel(s)]' )) - print('{: ^18s}'.format('Signal Computation') + '{: ^18s}'.format(str(self.postProcessing) )) - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - -## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% WFS INITIALIZATION PROPERTIES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - def mask_computation(self): - print('Pyramid Mask initialization...') - if self.old_mask is False: - self.m = self.get_phase_mask(resolution = self.nRes,n_subap = self.nSubap, n_pix_separation = self.n_pix_separation, n_pix_edge = self.n_pix_edge, psf_centering = self.psfCentering, sx = self.sx, sy = self.sy) - self.initial_m = self.m.copy() - self.mask = self.convert_for_gpu(np.complex64(np.exp(1j*self.m))) # compute the PWFS mask) - self.initial_mask = np.copy(self.mask) # Save a copy of the initial mask - else: - raise DeprecationWarning('The use of the old_mask parameter has been depreciated') - - def apply_shift_wfs(self,sx =None,sy=None,mis_reg = None,units = 'pixels'): - if sx is None: - sx = 0 - if sy is None: - sy = 0 - if mis_reg is not None: - sx = [mis_reg.dX_1,mis_reg.dX_2,mis_reg.dX_3,mis_reg.dX_4] - sy = [mis_reg.dY_1,mis_reg.dY_2,mis_reg.dY_3,mis_reg.dY_4] - # apply a TIP/TILT of the PWFS mask to shift the pupils - if units == 'pixels': - factor = 1 - if units == 'm': - factor = 1/(self.telescope.pixelSize*(self.telescope.resolution/self.nSubap)) - # sx and sy are the units of displacements in pixels - if np.isscalar(sx) and np.isscalar(sy): - shift_x = [factor*sx,factor*sx,factor*sx,factor*sx] - shift_y = [factor*sy,factor*sy,factor*sy,factor*sy] - else: - if len(sx)==4 and len(sy)==4: - shift_x = [] - shift_y = [] - [shift_x.append(i_x*factor) for i_x in sx] - [shift_y.append(i_y*factor) for i_y in sy] - else: - raise ValueError('Wrong size for sx and/or sy, a list of 4 values is expected.') - if np.max(np.abs(shift_x))>self.n_pix_edge or np.max(np.abs(shift_y))>self.n_pix_edge: - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('WARNING!!!! The Pyramid pupils have been shifted outside of the detector!! Wrapping of the signal is currently occuring!!') - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - - self.sx = shift_x - self.sy = shift_y - self.m = self.get_phase_mask(resolution = self.nRes,n_subap = self.nSubap, n_pix_separation = self.n_pix_separation, n_pix_edge = self.n_pix_edge, psf_centering = self.psfCentering, sx = shift_x, sy = shift_y) - self.mask = self.convert_for_gpu(np.complex64(np.exp(1j*self.m))) - # print('Updating the reference slopes and Wavelength Calibration for the new modulation...') - self.slopesUnits = 1 - self.referenceSignal = 0 - self.referenceSignal_2D = 0 - self.wfs_calibration(self.telescope) - - def get_phase_mask(self,resolution, n_subap, n_pix_separation, n_pix_edge, psf_centering = False, sx = [0,0,0,0], sy=[0,0,0,0]): - # size of the mask in pixel - n_tot = int((n_subap*2+n_pix_separation+n_pix_edge*2)*self.telescope.resolution/self.nSubap) - - # normalization factor for the Tip/Tilt - norma = (n_subap + n_pix_separation)*(self.telescope.resolution/self.nSubap) - # support for the mask - m = np.zeros([n_tot,n_tot]) - if psf_centering: - # mask centered on 4 pixel - lim = np.pi/4 - d_pix = np.pi/4 /(n_tot//2) # size of a pixel in angle - lim = lim - d_pix - - # create a Tip/Tilt combination for each quadrant - [Tip,Tilt] = np.meshgrid(np.linspace(-lim,lim,n_tot//2),np.linspace(-lim,lim,n_tot//2)) - - m[:n_tot//2 ,:n_tot//2 ] = Tip * (1- sx[0]/(n_subap+n_pix_separation/2))*norma + Tilt * (1- sy[0]/(n_subap+n_pix_separation/2))*norma - m[:n_tot//2 ,-n_tot//2: ] = -Tip * (1+ sx[1]/(n_subap+n_pix_separation/2))*norma + Tilt * (1- sy[1]/(n_subap+n_pix_separation/2))*norma - m[-n_tot//2 :,-n_tot//2:] = -Tip * (1+ sx[2]/(n_subap+n_pix_separation/2))*norma + -Tilt * (1+ sy[2]/(n_subap+n_pix_separation/2))*norma - m[-n_tot//2 :,:n_tot//2 ] = Tip * (1- sx[3]/(n_subap+n_pix_separation/2))*norma + -Tilt * (1+ sy[3]/(n_subap+n_pix_separation/2))*norma - - else: - # mask centered on 1 pixel - d_pix = (np.pi/4) /(n_tot/2) # size of a pixel in angle - lim_p = np.pi/4 - lim_m = np.pi/4 -2*d_pix - - # create a Tip/Tilt combination for each quadrant - [Tip_1,Tilt_1] = np.meshgrid(np.linspace(-lim_p,lim_p,n_tot//2 +1),np.linspace(-lim_p,lim_p,n_tot//2 +1)) - [Tip_2,Tilt_2] = np.meshgrid(np.linspace(-lim_p,lim_p,n_tot//2 +1),np.linspace(-lim_m,lim_m,n_tot//2 -1)) - [Tip_3,Tilt_3] = np.meshgrid(np.linspace(-lim_m,lim_m,n_tot//2 -1),np.linspace(-lim_m,lim_m,n_tot//2 -1)) - [Tip_4,Tilt_4] = np.meshgrid(np.linspace(-lim_m,lim_m,n_tot//2 -1),np.linspace(-lim_p,lim_p,n_tot//2 +1)) - - m[:n_tot//2 +1,:n_tot//2+1] = Tip_1 * (1- sx[0]/(n_subap+n_pix_separation/2))*norma + Tilt_1 * (1- sy[0]/(n_subap+n_pix_separation/2))*norma - m[:n_tot//2 +1,-n_tot//2+1:] = -Tip_4 * (1+ sx[1]/(n_subap+n_pix_separation/2))*norma + Tilt_4 * (1- sy[1]/(n_subap+n_pix_separation/2))*norma - m[-n_tot//2 +1:,-n_tot//2 +1:] = -Tip_3 * (1+ sx[2]/(n_subap+n_pix_separation/2))*norma + -Tilt_3 * (1+ sy[2]/(n_subap+n_pix_separation/2))*norma - m[-n_tot//2 +1:,:n_tot//2 +1] = Tip_2 * (1- sx[3]/(n_subap+n_pix_separation/2))*norma + -Tilt_2 * (1+ sy[3]/(n_subap+n_pix_separation/2))*norma - - return -m # sign convention for backward compatibility -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% WFS INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - def initialization(self,telescope): - telescope.resetOPD() - if self.userValidSignal is None: - print('The valid pixel are selected on flux considerations') - self.modulation = self.calibModulation # set the modulation to a large value - self.wfs_measure(phase_in=self.telescope.src.phase) - # save initialization frame - self.initFrame = self.cam.frame - - # save the number of signals depending on the case - if self.postProcessing == 'slopesMaps' or self.postProcessing == 'slopesMaps_incidence_flux': - # select the valid pixels of the detector according to the flux (case slopes-maps) - I1 = self.grabQuadrant(1) - I2 = self.grabQuadrant(2) - I3 = self.grabQuadrant(3) - I4 = self.grabQuadrant(4) - - # sum of the 4 quadrants - self.I4Q = I1+I2+I3+I4 - # valid pixels to consider for the slopes-maps computation - self.validI4Q = (self.I4Q>=self.lightRatio*self.I4Q.max()) - self.validSignal = np.concatenate((self.validI4Q,self.validI4Q)) - self.nSignal = int(np.sum(self.validSignal)) - - if self.postProcessing == 'fullFrame': - # select the valid pixels of the detector according to the flux (case full-frame) - self.validSignal = (self.initFrame>=self.lightRatio*self.initFrame.max()) - self.nSignal = int(np.sum(self.validSignal)) - else: - print('You are using a user-defined mask for the selection of the valid pixel') - if self.postProcessing == 'slopesMaps': - - # select the valid pixels of the detector according to the flux (case full-frame) - self.validI4Q = self.userValidSignal - self.validSignal = np.concatenate((self.validI4Q,self.validI4Q)) - self.nSignal = int(np.sum(self.validSignal)) - - if self.postProcessing == 'fullFrame': - self.validSignal = self.userValidSignal - self.nSignal = int(np.sum(self.validSignal)) - - # Tag to indicate that the wfs is initialized - self.isInitialized = True - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% WFS CALIBRATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - def wfs_calibration(self,telescope): - # reference slopes acquisition - telescope.OPD = telescope.pupil.astype(float) - # compute the refrence slopes - self.wfs_measure(phase_in=self.telescope.src.phase) - self.referenceSignal_2D,self.referenceSignal = self.signalProcessing() - - # 2D reference Frame before binning with detector - self.referencePyramidFrame = np.copy(self.pyramidFrame) - if self.isCalibrated is False: - print('WFS calibrated!') - self.isCalibrated= True - telescope.OPD = telescope.pupil.astype(float) -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PYRAMID TRANSFORM %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - def pyramid_transform(self,phase_in): - # copy of the support for the zero-padding - support = self.supportPadded.copy() - # em field corresponding to phase_in - if np.ndim(self.telescope.OPD)==2: - if self.modulation==0: - em_field = self.maskAmplitude*np.exp(1j*(phase_in)) - else: - em_field = self.maskAmplitude*np.exp(1j*(self.convert_for_gpu(self.telescope.src.phase)+phase_in)) - else: - em_field = self.maskAmplitude*np.exp(1j*phase_in) - # zero-padding for the FFT computation - support[self.center-self.telescope.resolution//2:self.center+self.telescope.resolution//2,self.center-self.telescope.resolution//2:self.center+self.telescope.resolution//2] = em_field - del em_field - # case with mask centered on 4 pixels - if self.psfCentering: - em_field_ft = np_cp.fft.fft2(support*self.phasor) - em_field_pwfs = np_cp.fft.ifft2(em_field_ft*self.mask) - I = np_cp.abs(em_field_pwfs)**2 - # case with mask centered on 1 pixel - else: - if self.spatialFilter is not None: - em_field_ft = np_cp.fft.fftshift(np_cp.fft.fft2(support))*self.spatialFilter - else: - em_field_ft = np_cp.fft.fftshift(np_cp.fft.fft2(support)) - - em_field_pwfs = np_cp.fft.ifft2(em_field_ft*self.mask) - I = np_cp.abs(em_field_pwfs)**2 - del support - del em_field_pwfs - self.modulation_camera_em.append(self.convert_for_numpy(em_field_ft)) - - del em_field_ft - del phase_in - return I - - def setPhaseBuffer(self,phaseIn): - B = self.phaseBuffModulationLowres_CPU+phaseIn - return B - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PYRAMID PROPAGATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - def pyramid_propagation(self,telescope): - # backward compatibility with previous version - self.wfs_measure(phase_in=telescope.src.phase) - return - - def wfs_measure(self,phase_in=None): - if phase_in is not None: - self.telescope.src.phase = phase_in - # mask amplitude for the light propagation - self.maskAmplitude = self.convert_for_gpu(self.telescope.pupilReflectivity) - - if self.spatialFilter is not None: - if np.ndim(phase_in)==2: - support_spatial_filter = np.copy(self.supportPadded) - em_field = self.maskAmplitude*np.exp(1j*(self.telescope.src.phase)) - support_spatial_filter[self.center-self.telescope.resolution//2:self.center+self.telescope.resolution//2,self.center-self.telescope.resolution//2:self.center+self.telescope.resolution//2] = em_field - self.em_field_spatial_filter = (np.fft.fft2(support_spatial_filter*self.phasor)) - self.pupil_plane_spatial_filter = (np.fft.ifft2(self.em_field_spatial_filter*self.spatialFilter)) - - # modulation camera - self.modulation_camera_em=[] - - if self.modulation==0: - if np.ndim(phase_in)==2: - self.pyramidFrame = self.convert_for_numpy(self.pyramid_transform(self.convert_for_gpu(self.telescope.src.phase))) - self*self.cam - if self.isInitialized and self.isCalibrated: - self.pyramidSignal_2D,self.pyramidSignal=self.signalProcessing() - else: - nModes = phase_in.shape[2] - # move axis to get the number of modes first - self.phase_buffer = self.convert_for_gpu(np.moveaxis(self.telescope.src.phase,-1,0)) - - #define the parallel jobs - def job_loop_multiple_modes_non_modulated(): - Q = Parallel(n_jobs=self.nJobs,prefer=self.joblib_setting)(delayed(self.pyramid_transform)(i) for i in self.phase_buffer) - return Q - # apply the pyramid transform in parallel - maps = job_loop_multiple_modes_non_modulated() - - self.pyramidSignal_2D = np.zeros([self.validSignal.shape[0],self.validSignal.shape[1],nModes]) - self.pyramidSignal = np.zeros([self.nSignal,nModes]) - - for i in range(nModes): - self.pyramidFrame = self.convert_for_numpy(maps[i]) - self*self.cam - if self.isInitialized: - self.pyramidSignal_2D[:,:,i],self.pyramidSignal[:,i] = self.signalProcessing() - del maps - - else: - if np.ndim(phase_in)==2: - n_max_ = self.n_max - if self.nTheta>n_max_: - # break problem in pieces: - nCycle = int(np.ceil(self.nTheta/n_max_)) - # print(self.nTheta) - maps = self.convert_for_numpy(np_cp.zeros([self.nRes,self.nRes])) - for i in range(nCycle): - if self.gpu_available: - try: - self.mempool = np_cp.get_default_memory_pool() - self.mempool.free_all_blocks() - except: - print('could not free the memory') - if i<nCycle-1: - def job_loop_single_mode_modulated(): - Q = Parallel(n_jobs=self.nJobs,prefer=self.joblib_setting)(delayed(self.pyramid_transform)(i) for i in self.convert_for_gpu(self.phaseBuffModulationLowres[i*n_max_:(i+1)*n_max_,:,:])) - return Q - maps+=self.convert_for_numpy(np_cp.sum(np_cp.asarray(job_loop_single_mode_modulated()),axis=0)) - else: - def job_loop_single_mode_modulated(): - Q = Parallel(n_jobs=self.nJobs,prefer=self.joblib_setting)(delayed(self.pyramid_transform)(i) for i in self.convert_for_gpu(self.phaseBuffModulationLowres[i*n_max_:,:,:])) - return Q - maps+=self.convert_for_numpy(np_cp.sum(np_cp.asarray(job_loop_single_mode_modulated()),axis=0)) - self.pyramidFrame=maps/self.nTheta - del maps - else: - #define the parallel jobs - def job_loop_single_mode_modulated(): - Q = Parallel(n_jobs=self.nJobs,prefer=self.joblib_setting)(delayed(self.pyramid_transform)(i) for i in self.phaseBuffModulationLowres) - return Q - # apply the pyramid transform in parallel - self.maps=np_cp.asarray(job_loop_single_mode_modulated()) - # compute the sum of the pyramid frames for each modulation points - if self.weight_vector is None: - self.pyramidFrame=self.convert_for_numpy(np_cp.sum((self.maps),axis=0))/self.nTheta - else: - weighted_map = np.reshape(self.maps,[self.nTheta,self.nRes**2]) - self.weighted_map = np.diag(self.weight_vector)@weighted_map - self.pyramidFrame= np.reshape(self.convert_for_numpy(np_cp.sum((self.weighted_map),axis=0))/self.nTheta,[self.nRes,self.nRes]) - - - #propagate to the detector - self*self.cam - - if self.isInitialized and self.isCalibrated: - self.pyramidSignal_2D,self.pyramidSignal=self.signalProcessing() - else: - if np.ndim(phase_in)==3: - nModes = phase_in.shape[2] - # move axis to get the number of modes first - self.phase_buffer = np.moveaxis(self.telescope.src.phase,-1,0) - - def jobLoop_setPhaseBuffer(): - Q = Parallel(n_jobs=self.nJobs,prefer=self.joblib_setting)(delayed(self.setPhaseBuffer)(i) for i in self.phase_buffer) - return Q - - self.phaseBuffer = (np.reshape(np.asarray(jobLoop_setPhaseBuffer()),[nModes*self.nTheta,self.telescope.resolution,self.telescope.resolution])) - n_measurements = nModes*self.nTheta - n_max = self.n_max - n_measurement_max = int(np.floor(n_max/self.nTheta)) - maps = np_cp.zeros([n_measurements,self.nRes,self.nRes]) - - if n_measurements >n_max: - nCycle = int(np.ceil(nModes/n_measurement_max)) - for i in range(nCycle): - if self.gpu_available: - try: - self.mempool = np_cp.get_default_memory_pool() - self.mempool.free_all_blocks() - except: - print('could not free the memory') - if i<nCycle-1: - def job_loop_multiple_mode_modulated(): - Q = Parallel(n_jobs=self.nJobs,prefer=self.joblib_setting)(delayed(self.pyramid_transform)(i) for i in self.convert_for_gpu(self.phaseBuffer[i*n_measurement_max*self.nTheta:(i+1)*n_measurement_max*self.nTheta,:,:])) - return Q - maps[i*n_measurement_max*self.nTheta:(i+1)*n_measurement_max*self.nTheta,:,:] = np_cp.asarray(job_loop_multiple_mode_modulated()) - else: - def job_loop_multiple_mode_modulated(): - Q = Parallel(n_jobs=self.nJobs,prefer=self.joblib_setting)(delayed(self.pyramid_transform)(i) for i in self.convert_for_gpu(self.phaseBuffer[i*n_measurement_max*self.nTheta:,:,:])) - return Q - maps[i*n_measurement_max*self.nTheta:,:,:] = np_cp.asarray(job_loop_multiple_mode_modulated()) - self.bufferPyramidFrames = self.convert_for_numpy(maps) - del self.phaseBuffer - del maps - if self.gpu_available: - try: - self.mempool = np_cp.get_default_memory_pool() - self.mempool.free_all_blocks() - except: - print('could not free the memory') - else: - def job_loop_multiple_mode_modulated(): - Q = Parallel(n_jobs=self.nJobs,prefer=self.joblib_setting)(delayed(self.pyramid_transform)(i) for i in self.convert_for_gpu(self.phaseBuffer)) - return Q - - self.bufferPyramidFrames = self.convert_for_numpy(np_cp.asarray(job_loop_multiple_mode_modulated())) - - self.pyramidSignal_2D = np.zeros([self.validSignal.shape[0],self.validSignal.shape[1],nModes]) - self.pyramidSignal = np.zeros([self.nSignal,nModes]) - - for i in range(nModes): - self.pyramidFrame = np_cp.sum(self.bufferPyramidFrames[i*(self.nTheta):(self.nTheta)+i*(self.nTheta)],axis=0)/self.nTheta - self*self.cam - if self.isInitialized: - self.pyramidSignal_2D[:,:,i],self.pyramidSignal[:,i] = self.signalProcessing() - del self.bufferPyramidFrames - else: - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('Error - Wrong dimension for the input phase. Aborting....') - print('Aborting...') - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - sys.exit(0) - if self.gpu_available: - try: - self.mempool = np_cp.get_default_memory_pool() - self.mempool.free_all_blocks() - except: - print('could not free the memory') - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% WFS SIGNAL PROCESSING %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - def signalProcessing(self,cameraFrame=None): - if cameraFrame is None: - cameraFrame=self.cam.frame - if self.postProcessing == 'slopesMaps': - # slopes-maps computation - I1 = self.grabQuadrant(1,cameraFrame=0)*self.validI4Q - I2 = self.grabQuadrant(2,cameraFrame=0)*self.validI4Q - I3 = self.grabQuadrant(3,cameraFrame=0)*self.validI4Q - I4 = self.grabQuadrant(4,cameraFrame=0)*self.validI4Q - # global normalisation - I4Q = I1+I2+I3+I4 - norma = np.mean(I4Q[self.validI4Q]) - # slopesMaps computation cropped to the valid pixels - Sx = (I1-I2+I4-I3) - Sy = (I1-I4+I2-I3) - # 2D slopes maps - slopesMaps = (np.concatenate((Sx,Sy)/norma) - self.referenceSignal_2D) *self.slopesUnits - # slopes vector - slopes = slopesMaps[np.where(self.validSignal==1)] - return slopesMaps,slopes - - if self.postProcessing == 'slopesMaps_incidence_flux': - # slopes-maps computation - I1 = self.grabQuadrant(1,cameraFrame=0)*self.validI4Q - I2 = self.grabQuadrant(2,cameraFrame=0)*self.validI4Q - I3 = self.grabQuadrant(3,cameraFrame=0)*self.validI4Q - I4 = self.grabQuadrant(4,cameraFrame=0)*self.validI4Q - - # global normalisation - I4Q = I1+I2+I3+I4 - subArea = (self.telescope.D / self.nSubap)**2 - norma = np.float64(self.telescope.src.nPhoton*self.telescope.samplingTime*subArea) - - # slopesMaps computation cropped to the valid pixels - Sx = (I1-I2+I4-I3) - Sy = (I1-I4+I2-I3) - - # 2D slopes maps - slopesMaps = (np.concatenate((Sx,Sy)/norma) - self.referenceSignal_2D) *self.slopesUnits - - # slopes vector - slopes = slopesMaps[np.where(self.validSignal==1)] - return slopesMaps,slopes - - if self.postProcessing == 'fullFrame': - # global normalization - norma = np.sum(cameraFrame[self.validSignal]) - # 2D full-frame - fullFrameMaps = (cameraFrame / norma ) - self.referenceSignal_2D - # full-frame vector - fullFrame = fullFrameMaps[np.where(self.validSignal==1)] - - return fullFrameMaps,fullFrame - - def get_modulation_frame(self, radius = 6, norma = True): - self.modulation_camera_frame = np.sum(np.abs(self.modulation_camera_em)**2,axis=0) - - N_trunc = int(self.nRes/2 - radius*self.modulation*self.zeroPaddingFactor ) - - modulation_camera_frame_zoom = self.modulation_camera_frame[N_trunc:-N_trunc,N_trunc:-N_trunc] - - if norma: - modulation_camera_frame_zoom/= modulation_camera_frame_zoom.max() - - return modulation_camera_frame_zoom -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% GRAB QUADRANTS FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - def grabQuadrant(self,n,cameraFrame=0): - - nExtraPix = int(np.round((np.max(self.pupilSeparationRatio)-1)*self.telescope.resolution/(self.telescope.resolution/self.nSubap)/2/self.binning)) - centerPixel = int(np.round((self.cam.resolution/self.binning)/2)) - n_pixels = int(np.ceil(self.nSubap/self.binning)) - if cameraFrame ==0: - cameraFrame=self.cam.frame.copy() - - if self.rooftop is None: - if n==3: - I=cameraFrame[nExtraPix+centerPixel:(nExtraPix+centerPixel+n_pixels),nExtraPix+centerPixel:(nExtraPix+centerPixel+n_pixels)] - if n==4: - I=cameraFrame[nExtraPix+centerPixel:(nExtraPix+centerPixel+n_pixels),-nExtraPix+centerPixel-n_pixels:(-nExtraPix+centerPixel)] - if n==1: - I=cameraFrame[-nExtraPix+centerPixel-n_pixels:(-nExtraPix+centerPixel),-nExtraPix+centerPixel-n_pixels:(-nExtraPix+centerPixel)] - if n==2: - I=cameraFrame[-nExtraPix+centerPixel-n_pixels:(-nExtraPix+centerPixel),nExtraPix+centerPixel:(nExtraPix+centerPixel+n_pixels)] - else: - if self.rooftop == 'V': - if n==1: - I=cameraFrame[centerPixel-n_pixels//2:(centerPixel)+n_pixels//2,(self.edgePixel//2):(self.edgePixel//2 +n_pixels)] - if n==2: - I=cameraFrame[centerPixel-n_pixels//2:(centerPixel)+n_pixels//2,(self.edgePixel//2 +n_pixels+nExtraPix*2):(self.edgePixel//2+nExtraPix*2+2*n_pixels)] - if n==4: - I=cameraFrame[centerPixel-n_pixels//2:(centerPixel)+n_pixels//2,(self.edgePixel//2):(self.edgePixel//2 +n_pixels)] - if n==3: - I=cameraFrame[centerPixel-n_pixels//2:(centerPixel)+n_pixels//2,(self.edgePixel//2 +n_pixels+nExtraPix*2):(self.edgePixel//2+nExtraPix*2+2*n_pixels)] - else: - if n==1: - I=cameraFrame[(self.edgePixel//2):(self.edgePixel//2 +n_pixels),centerPixel-n_pixels//2:(centerPixel)+n_pixels//2] - if n==2: - I=cameraFrame[(self.edgePixel//2 +n_pixels+nExtraPix*2):(self.edgePixel//2+nExtraPix*2+2*n_pixels),centerPixel-n_pixels//2:(centerPixel)+n_pixels//2] - if n==4: - I=cameraFrame[(self.edgePixel//2):(self.edgePixel//2 +n_pixels),centerPixel-n_pixels//2:(centerPixel)+n_pixels//2] - if n==3: - I=cameraFrame[(self.edgePixel//2 +n_pixels+nExtraPix*2):(self.edgePixel//2+nExtraPix*2+2*n_pixels),centerPixel-n_pixels//2:(centerPixel)+n_pixels//2] - return I - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% WFS PROPERTIES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - #properties required for backward compatibility (20/10/2020) - @property - def pyramidSignal(self): - return self._pyramidSignal - - @pyramidSignal.setter - def pyramidSignal(self,val): - self._pyramidSignal = val - self.signal = val - - @property - def pyramidSignal_2D(self): - return self._pyramidSignal_2D - - @pyramidSignal_2D.setter - def pyramidSignal_2D(self,val): - self._pyramidSignal_2D = val - self.signal_2D = val - - @property - def lightRatio(self): - return self._lightRatio - - @lightRatio.setter - def lightRatio(self,val): - self._lightRatio = val - if hasattr(self,'isInitialized'): - if self.isInitialized: - print('Updating the map if valid pixels ...') - self.validI4Q = (self.I4Q>=self._lightRatio*self.I4Q.max()) - self.validSignal = np.concatenate((self.validI4Q,self.validI4Q)) - self.validPix = (self.initFrame>=self.lightRatio*self.initFrame.max()) - - # save the number of signals depending on the case - if self.postProcessing == 'slopesMaps': - self.nSignal = np.sum(self.validSignal) - # display - xPix,yPix = np.where(self.validI4Q==1) - plt.figure() - plt.imshow(self.I4Q.T) - plt.plot(xPix,yPix,'+') - if self.postProcessing == 'fullFrame': - self.nSignal = np.sum(self.validPix) - print('Done!') - - @property - def spatialFilter(self): - return self._spatialFilter - - @spatialFilter.setter - def spatialFilter(self,val): - self._spatialFilter = val - if self.isInitialized: - if val is None: - print('No spatial filter considered') - self.mask = self.initial_mask - if self.isCalibrated: - print('Updating the reference slopes and Wavelength Calibration for the new modulation...') - self.slopesUnits = 1 - self.referenceSignal = 0 - self.referenceSignal_2D = 0 - self.wfs_calibration(self.telescope) - print('Done!') - else: - tmp = np.ones([self.nRes,self.nRes]) - tmp[:,0] = 0 - Tip = (sp.morphology.distance_transform_edt(tmp)) - Tilt = (sp.morphology.distance_transform_edt(np.transpose(tmp))) - - # normalize the TT to apply the modulation in terms of lambda/D - self.Tip_spatial_filter = (((Tip/Tip.max())-0.5)*2*np.pi) - self.Tilt_spatial_filter = (((Tilt/Tilt.max())-0.5)*2*np.pi) - if val.shape == self.mask.shape: - print('A spatial filter is now considered') - self.mask = self.initial_mask * val - plt.figure() - plt.imshow(np.real(self.mask)) - plt.title('Spatial Filter considered') - if self.isCalibrated: - print('Updating the reference slopes and Wavelength Calibration for the new modulation...') - self.slopesUnits = 1 - self.referenceSignal = 0 - self.referenceSignal_2D = 0 - self.wfs_calibration(self.telescope) - print('Done!') - else: - print('ERROR: wrong shape for the spatial filter. No spatial filter attached to the mask') - self.mask = self.initial_mask - - @property - def delta_Tip(self): - return self._delta_Tip - - @delta_Tip.setter - def delta_Tip(self,val): - self._delta_Tip = val - if self.isCalibrated: - self.modulation = self.modulation - @property - def delta_Tilt(self): - return self._delta_Tilt - - @delta_Tilt.setter - def delta_Tilt(self,val): - self._delta_Tilt = val - if self.isCalibrated: - self.modulation = self.modulation - @property - def modulation(self): - return self._modulation - - @modulation.setter - def modulation(self,val): - self._modulation = val - if self._modulation>=(self.telescope.resolution//2): - raise ValueError('Error the modulation radius is too large for this resolution! Consider using a larger telescope resolution!') - if val !=0: - self.modulation_path = [] - if self.user_modulation_path is not None: - self.modulation_path = self.user_modulation_path - self.nTheta = len(self.user_modulation_path) - else: - # define the modulation points - perimeter = np.pi*2*self._modulation - if self.nTheta_user_defined is None: - self.nTheta = 4*int((self.extraModulationFactor+np.ceil(perimeter/4))) - else: - self.nTheta = self.nTheta_user_defined - - self.thetaModulation = np.linspace(0+self.delta_theta,2*np.pi+self.delta_theta,self.nTheta,endpoint=False) - for i in range(self.nTheta): - dTheta = self.thetaModulation[i] - self.modulation_path.append([self.modulation*np.cos(dTheta)+self.delta_Tip, self.modulation*np.sin(dTheta)+self.delta_Tilt]) - - self.phaseBuffModulation = np.zeros([self.nTheta,self.nRes,self.nRes]).astype(np_cp.float32) - self.phaseBuffModulationLowres = np.zeros([self.nTheta,self.telescope.resolution,self.telescope.resolution]).astype(np_cp.float32) - - for i in range(self.nTheta): - self.TT = (self.modulation_path[i][0]*self.Tip+self.modulation_path[i][1]*self.Tilt)*self.telescope.pupil - self.phaseBuffModulation[i,self.center-self.telescope.resolution//2:self.center+self.telescope.resolution//2,self.center-self.telescope.resolution//2:self.center+self.telescope.resolution//2] = self.TT - self.phaseBuffModulationLowres[i,:,:] = self.TT - self.phaseBuffModulationLowres_CPU = self.phaseBuffModulationLowres.copy() - if self.gpu_available: - if self.nTheta<=self.n_max: - self.phaseBuffModulationLowres = self.convert_for_gpu(self.phaseBuffModulationLowres) - else: - self.nTheta = 1 - - if hasattr(self,'isCalibrated'): - if self.isCalibrated: - print('Updating the reference slopes and Wavelength Calibration for the new modulation...') - self.slopesUnits = 1 - self.referenceSignal = 0 - self.referenceSignal_2D = 0 - self.wfs_calibration(self.telescope) - print('Done!') - - @property - def backgroundNoise(self): - return self._backgroundNoise - - @backgroundNoise.setter - def backgroundNoise(self,val): - self._backgroundNoise = val - if val == True: - self.backgroundNoiseMap = [] - - def __mul__(self,obj): - if obj.tag=='detector': - I = self.pyramidFrame - obj.frame = (obj.rebin(I,(obj.resolution,obj.resolution))) - if self.binning != 1: - try: - obj.frame = (obj.rebin(obj.frame,(obj.resolution//self.binning,obj.resolution//self.binning))) - except: - print('ERROR: the shape of the detector ('+str(obj.frame.shape)+') is not valid with the binning value requested:'+str(self.binning)+'!') - obj.frame = obj.frame *(self.telescope.src.fluxMap.sum())/obj.frame.sum() - - if obj.photonNoise!=0: - obj.frame = self.random_state_photon_noise.poisson(obj.frame) - - if obj.readoutNoise!=0: - obj.frame += np.int64(np.round(self.random_state_readout_noise.randn(obj.resolution,obj.resolution)*obj.readoutNoise)) -# obj.frame = np.round(obj.frame) - - if self.backgroundNoise is True: - self.backgroundNoiseAdded = self.random_state_background.poisson(self.backgroundNoiseMap) - obj.frame +=self.backgroundNoiseAdded - else: - print('Error light propagated to the wrong type of object') - return -1 - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - def show(self): - attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) - print(self.tag+':') - for a in attributes: - if not(a[0].startswith('__') and a[0].endswith('__')): - if not(a[0].startswith('_')): - if not np.shape(a[1]): - tmp=a[1] - try: - print(' '+str(a[0])+': '+str(tmp.tag)+' object') - except: - print(' '+str(a[0])+': '+str(a[1])) - else: - if np.ndim(a[1])>1: - print(' '+str(a[0])+': '+str(np.shape(a[1]))) - - - - - +# -*- coding: utf-8 -*- +""" +Created on Sun Feb 23 19:35:18 2020 + +@author: cheritie +""" + +import inspect +import multiprocessing +import sys +import time + +import matplotlib.pyplot as plt +import numpy as np +import scipy.ndimage as sp + +from .Detector import Detector + +try: + # error + import cupy as np_cp +except: + import numpy as np_cp + # print('NO GPU available!') +try: + from joblib import Parallel, delayed +except: + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('WARNING: The joblib module is not installed. This would speed up considerably the operations.') + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + + +# import ctypes +# try : +# mkl_rt = ctypes.CDLL('libmkl_rt.so') +# mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads +# mkl_set_num_threads(6) +# except: +# try: +# mkl_rt = ctypes.CDLL('./mkl_rt.dll') +# mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads +# mkl_set_num_threads(6) +# except: +# try: +# import mkl +# mkl_set_num_threads = mkl.set_num_threads +# except: +# mkl_set_num_threads = None +class Pyramid: +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CLASS INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + def __init__(self,nSubap,telescope,modulation,lightRatio, postProcessing='slopesMaps',psfCentering=True, n_pix_separation = 2, calibModulation=50, n_pix_edge=None,extraModulationFactor=0,zeroPadding=None,pupilSeparationRatio=None,edgePixel = None,binning =1,nTheta_user_defined=None,userValidSignal=None,old_mask=False,rooftop = None,delta_theta = 0, user_modulation_path = None): + """ + A Pyramid object consists in defining a 2D phase mask located at the focal plane of the telescope to perform the Fourier Filtering of the EM-Field. + By default the Pyramid detector is considered to be noise-free (for calibration purposes). These properties can be switched on and off on the fly (see properties) + ************************** REQUIRED PARAMETERS ************************** + It requires the following parameters: + _ nSubap : the number of subapertures (ie the diameter of the Pyramid Pupils in pixels) + _ telescope : the telescope object to which the Pyramid is associated. This object carries the phase, flux and pupil information + _ modulation : the Tip-Tilt modulation in [lambda/D] where lambda is the NGS wavelength and D the telescope diameter + _ lightRatio : criterion to select the valid subaperture based on flux considerations + _ n_pix_separation : number of pixels separating the Pyramid Pupils in number of pixels of the detector -- default value is 2 pixels + _ n_pix_edge : number of pixel at the edge of the Pyramid Pupils in number of pixels of the detector -- default value is n_pix_separation's value + _ postProcessing : processing of the signals ('fullFrame' or 'slopesMaps') -- default value is 'slopesMaps' + + DEPRECIATED PARAMETERS: + _ pupilSeparationRatio : Separation ratio of the PWFS pupils (Diameter/Distance Center to Center) -- DEPRECIATED -> use n_pix_separation instead) + _ edgePixel : number of pixel at the edge of the Pyramid Pupils + + ************************** OPTIONAL PARAMETERS ************************** + + _ calibModulation : Defines the modulation used to select the valid subapertures -- default value is 50 lambda/D + _ extraModulationFactor : Extra Factor to increase/reduce the number of modulation point (extraModulationFactor = 1 means 4 modulation points added, 1 for each quadrant) + _ psfCentering : If False, the Pyramid mask is centered on 1 pixel, if True, the Pyramid mask is centered on 4 pixels -- default value is True + _ binning : binning factor of the PWFS detector signals -- default Value is 1 + _ nTheta_user_defined : user-defined number of Tip/Tilt modulation points + _ delta_theta : delta angle for the modulation points, default value is 0 (on the edge between two sides of the Pyramid) + _ userValidSignal : user-defined valid pixel mask for the signals computation + _ rooftop : if different to None, allows to compute a two-sided Pyramid ("V" corresponds to a vertical split, "H" corresponds to an horizontal split) + _ zeroPadding : User-defined zero-padding value in pixels that will be added to each side of the arrays. Consider using the n_pix_edge parameter that allows to do the same thing. + _ pupilReflectivcty : Defines the reflectivity of the Telescope object. If not set to 1, it can be input as a 2D map of uneven reflectivy correspondong to the pupil mask. + _ user_modulation_path : user-defined modulation path ( a list of [x,y] coordinates in lambda/D units is expected) + + ************************** PROPAGATING THE LIGHT TO THE PYRAMID OBJECT ************************** + The light can be propagated from a telescope object tel through the Pyramid object wfs using the * operator: + _ tel*wfs + This operation will trigger: + _ propagation of the tel.src light through the PWFS detector (phase and flux) + _ binning of the Pyramid signals + _ addition of eventual photon noise and readout noise + _ computation of the Pyramid signals + + + ************************** PROPERTIES ************************** + + The main properties of a Telescope object are listed here: + _ wfs.nSignal : the length of the signal measured by the Pyramid + _ wfs.signal : signal measured by the Pyramid of length wfs.nSignal + _ wfs.signal_2D : 2D map of the signal measured by the Pyramid + _ wfs.apply_shift_wfs : apply a tip tilt to each quadrant to move the Pyramid pupils + _ wfs.random_state_photon_noise : a random state cycle can be defined to reproduces random sequences of noise -- default is based on the current clock time + _ wfs.random_state_readout_noise : a random state cycle can be defined to reproduces random sequences of noise -- default is based on the current clock time + _ wfs.random_state_background : a random state cycle can be defined to reproduces random sequences of noise -- default is based on the current clock time + _ wfs.fov : Field of View of the Pyramid in arcsec + + the following properties can be updated on the fly: + _ wfs.modulation : update the modulation radius and update the reference signal + _ wfs.cam.photonNoise : Photon noise can be set to True or False + _ wfs.cam.readoutNoise : Readout noise can be set to True or False + _ wfs.backgroundNoise : Background noise can be set to True or False + _ wfs.lightRatio : reset the valid subaperture selection considering the new value + + """ + try: + # try to find GPU + # error + import cupy as np_cp + self.gpu_available = True + self.convert_for_gpu = np_cp.asarray + self.convert_for_numpy = np_cp.asnumpy + self.nJobs = 1 + self.mempool = np_cp.get_default_memory_pool() + from AO_modules.tools.tools import get_gpu_memory + self.mem_gpu = get_gpu_memory() + + print('GPU available!') + for i in range(len(self.mem_gpu)): + print('GPU device '+str(i)+' : '+str(self.mem_gpu[i]/1024)+ 'GB memory') + + except: + import numpy as np_cp + def no_function(input_matrix): + return input_matrix + self.gpu_available = False + self.convert_for_gpu = no_function + self.convert_for_numpy = no_function + + # initialize the Pyramid Object + self.telescope = telescope # telescope attached to the wfs + if self.telescope.resolution/nSubap <4 or (self.telescope.resolution/nSubap)%2 !=0: + raise ValueError('The resolution should be an even number and be a multiple of 2**i where i>=2') + self.delta_theta = delta_theta # delta theta in degree to change the position of the modulation point (default is 0 <=> modulation point on the edge of two sides of the pyramid) + self.nTheta_user_defined = nTheta_user_defined # user defined number of modulation point + self.extraModulationFactor = extraModulationFactor # Extra Factor to increase/reduce the number of modulation point (extraModulationFactor = 1 means 4 modulation points added, 1 for each quadrant) + self.nSubap = nSubap # Number of subaperture + self.edgePixel = n_pix_edge # Number of pixel on the edges of the PWFS pupils + self.centerPixel = 0 # Value used for the centering for the slopes-maps computation + self.postProcessing = postProcessing # type of processing of the signals (see self.postProcessing) + self.userValidSignal = userValidSignal # user defined mask for the valid pixel selection + self.psfCentering = psfCentering # tag for the PSF centering on or 4 pixels + self.backgroundNoise = False # background noise in photon + self.binning = binning # binning factor for the detector + self.old_mask = old_mask + self.random_state_photon_noise = np.random.RandomState(seed=int(time.time())) # random states to reproduce sequences of noise + self.random_state_readout_noise = np.random.RandomState(seed=int(time.time())) # random states to reproduce sequences of noise + self.random_state_background = np.random.RandomState(seed=int(time.time())) # random states to reproduce sequences of noise + self.user_modulation_path = user_modulation_path # user defined modulation path + self.pupilSeparationRatio = pupilSeparationRatio # Separation ratio of the PWFS pupils (Diameter/Distance Center to Center) -- DEPRECIATED -> use n_pix_separation instead) + self.weight_vector = None + if edgePixel is not None: + print('WARNING: The use of the edgePixel property has been depreciated. Consider using the n_pix_edge instead') + if pupilSeparationRatio is not None: + print('WARNING: The use of the pupilSeparationRatio property has been depreciated. Consider using the n_pix_separation instead') + # backward compatibility + if np.isscalar(self.pupilSeparationRatio): + self.n_pix_separation = (self.pupilSeparationRatio-1)*self.nSubap + self.sx = [0,0,0,0] + self.sy = [0,0,0,0] + else: + self.n_pix_separation = 0 + self.sx = [] + self.sy = [] + for i in range(4): + self.sx.append((self.pupilSeparationRatio[i][0]-1)*self.nSubap) + self.sy.append((self.pupilSeparationRatio[i][1]-1)*self.nSubap) + else: + self.n_pix_separation = n_pix_separation + self.pupilSeparationRatio = 1+self.n_pix_separation/self.nSubap + self.sx = [0,0,0,0] + self.sy = [0,0,0,0] + if n_pix_edge is None: + self.n_pix_edge = self.n_pix_separation//2 + else: + self.n_pix_edge = n_pix_edge + if n_pix_edge != self.n_pix_separation//2: + print('WARNING: The recommanded value for n_pix_edge is '+str(self.n_pix_separation//2) +' instead of '+ str(n_pix_edge)) + if self.gpu_available: + self.joblib_setting = 'processes' + else: + self.joblib_setting = 'threads' + self.rooftop = rooftop + + if zeroPadding is None: + # Case where the zero-padding is not specificed => taking the smallest value ensuring to get edgePixel space from the edge. + self.nRes = int((self.nSubap*2+self.n_pix_separation+self.n_pix_edge*2)*self.telescope.resolution/self.nSubap) + self.zeroPaddingFactor = self.nRes/self.telescope.resolution # zero-Padding Factor + self.zeroPadding = (self.nRes - self.telescope.resolution)//2 # zero-Padding Factor + else: + # Case where the zero-padding is specificed => making sure that the value is large enough to display the PWFS pupils + self.zeroPadding = zeroPadding + if np.max(self.pupilSeparationRatio)<=2*self.zeroPadding/self.telescope.resolution: + self.nRes = int(2*(self.zeroPadding)+self.telescope.resolution) # Resolution of the zero-padded images + self.zeroPaddingFactor = self.nRes/self.telescope.resolution # zero-Padding Factor + else: + raise ValueError('Error: The Separation of the pupils is too large for this value of zeroPadding!') + + self.tag = 'pyramid' # Tag of the object + self.cam = Detector(round(nSubap*self.zeroPaddingFactor)) # WFS detector object + self.lightRatio = lightRatio + 0.001 # Light ratio for the valid pixels selection 23/09/2022 cth: 0.001 added for backward compatibility + if calibModulation>= self.telescope.resolution/2: + self.calibModulation = self.telescope.resolution/2 -1 + else: + self.calibModulation = calibModulation # Modulation used for the valid pixel selection + self.isInitialized = False # Flag for the initialization of the WFS + self.isCalibrated = False # Flag for the initialization of the WFS + self.delta_Tip = 0 # delta Tip for the modulation + self.delta_Tilt = 0 # delta Tilt for the modulation + self.center = self.nRes//2 # Center of the zero-Padded array + self.supportPadded = self.convert_for_gpu(np.pad(self.telescope.pupil.astype(complex),((self.zeroPadding,self.zeroPadding),(self.zeroPadding,self.zeroPadding)),'constant')) + self.spatialFilter = None # case where a spatial filter is considered + self.fov = 206265*self.telescope.resolution*(self.telescope.src.wavelength/self.telescope.D) # fov in arcsec + n_cpu = multiprocessing.cpu_count() + # joblib settings for parallization + if self.gpu_available is False: + if n_cpu > 16: + self.nJobs = 32 # number of jobs for the joblib package + else: + self.nJobs = 8 + self.n_max = 20*500 + else: + # quantify GPU max memory usage + A = np.ones([self.nRes,self.nRes]) + 1j*np.ones([self.nRes,self.nRes]) + self.n_max = int(0.75*(np.min(self.mem_gpu)/1024)/(A.nbytes/1024/1024/1024)) + del A + + # Prepare the Tip Tilt for the modulation -- normalized to apply the modulation in terms of lambda/D + [self.Tip,self.Tilt] = np.meshgrid(np.linspace(-np.pi,np.pi,self.telescope.resolution),np.linspace(-np.pi,np.pi,self.telescope.resolution)) + self.Tilt *= self.telescope.pupil + self.Tip *= self.telescope.pupil + + # compute the phasor to center the PSF on 4 pixels + [xx,yy] = np.meshgrid(np.linspace(0,self.nRes-1,self.nRes),np.linspace(0,self.nRes-1,self.nRes)) + self.phasor = self.convert_for_gpu(np.exp(-(1j*np.pi*(self.nRes+1)/self.nRes)*(xx+yy))) + + # Creating the PWFS mask + self.mask_computation() + + # initialize the reference slopes and units + self.slopesUnits = 1 + self.referenceSignal = 0 + self.referenceSignal_2D = 0 + self.referencePyramidFrame = 0 + self.modulation = modulation # Modulation radius (in lambda/D) + + # Select the valid pixels + print('Selection of the valid pixels...') + self.initialization(self.telescope) + print('Acquisition of the reference slopes and units calibration...') + # set the modulation radius and propagate light + self.modulation = modulation + self.wfs_calibration(self.telescope) + self.telescope.resetOPD() + self.wfs_measure(phase_in=self.telescope.src.phase) + + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PYRAMID WFS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('{: ^18s}'.format('Pupils Diameter') + '{: ^18s}'.format(str(self.nSubap)) +'{: ^18s}'.format('[pixels]' )) + print('{: ^18s}'.format('Pupils Separation') + '{: ^18s}'.format(str(self.n_pix_separation)) +'{: ^18s}'.format('[pixels]' )) + print('{: ^18s}'.format('FoV') + '{: ^18s}'.format(str(np.round(self.fov,2))) +'{: ^18s}'.format('[arcsec]' )) + print('{: ^18s}'.format('TT Modulation') + '{: ^18s}'.format(str(self.modulation)) +'{: ^18s}'.format('[lamda/D]' )) + print('{: ^18s}'.format('PSF Core Sampling') + '{: ^18s}'.format(str(1+self.psfCentering*3)) +'{: ^18s}'.format('[pixel(s)]' )) + print('{: ^18s}'.format('Valid Pixels') + '{: ^18s}'.format(str(self.nSignal)) +'{: ^18s}'.format('[pixel(s)]' )) + print('{: ^18s}'.format('Signal Computation') + '{: ^18s}'.format(str(self.postProcessing) )) + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + +## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% WFS INITIALIZATION PROPERTIES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + def mask_computation(self): + print('Pyramid Mask initialization...') + if self.old_mask is False: + self.m = self.get_phase_mask(resolution = self.nRes,n_subap = self.nSubap, n_pix_separation = self.n_pix_separation, n_pix_edge = self.n_pix_edge, psf_centering = self.psfCentering, sx = self.sx, sy = self.sy) + self.initial_m = self.m.copy() + self.mask = self.convert_for_gpu(np.complex64(np.exp(1j*self.m))) # compute the PWFS mask) + self.initial_mask = np.copy(self.mask) # Save a copy of the initial mask + else: + raise DeprecationWarning('The use of the old_mask parameter has been depreciated') + + def apply_shift_wfs(self,sx =None,sy=None,mis_reg = None,units = 'pixels'): + if sx is None: + sx = 0 + if sy is None: + sy = 0 + if mis_reg is not None: + sx = [mis_reg.dX_1,mis_reg.dX_2,mis_reg.dX_3,mis_reg.dX_4] + sy = [mis_reg.dY_1,mis_reg.dY_2,mis_reg.dY_3,mis_reg.dY_4] + # apply a TIP/TILT of the PWFS mask to shift the pupils + if units == 'pixels': + factor = 1 + if units == 'm': + factor = 1/(self.telescope.pixelSize*(self.telescope.resolution/self.nSubap)) + # sx and sy are the units of displacements in pixels + if np.isscalar(sx) and np.isscalar(sy): + shift_x = [factor*sx,factor*sx,factor*sx,factor*sx] + shift_y = [factor*sy,factor*sy,factor*sy,factor*sy] + else: + if len(sx)==4 and len(sy)==4: + shift_x = [] + shift_y = [] + [shift_x.append(i_x*factor) for i_x in sx] + [shift_y.append(i_y*factor) for i_y in sy] + else: + raise ValueError('Wrong size for sx and/or sy, a list of 4 values is expected.') + if np.max(np.abs(shift_x))>self.n_pix_edge or np.max(np.abs(shift_y))>self.n_pix_edge: + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('WARNING!!!! The Pyramid pupils have been shifted outside of the detector!! Wrapping of the signal is currently occuring!!') + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + + self.sx = shift_x + self.sy = shift_y + self.m = self.get_phase_mask(resolution = self.nRes,n_subap = self.nSubap, n_pix_separation = self.n_pix_separation, n_pix_edge = self.n_pix_edge, psf_centering = self.psfCentering, sx = shift_x, sy = shift_y) + self.mask = self.convert_for_gpu(np.complex64(np.exp(1j*self.m))) + # print('Updating the reference slopes and Wavelength Calibration for the new modulation...') + self.slopesUnits = 1 + self.referenceSignal = 0 + self.referenceSignal_2D = 0 + self.wfs_calibration(self.telescope) + + def get_phase_mask(self,resolution, n_subap, n_pix_separation, n_pix_edge, psf_centering = False, sx = [0,0,0,0], sy=[0,0,0,0]): + # size of the mask in pixel + n_tot = int((n_subap*2+n_pix_separation+n_pix_edge*2)*self.telescope.resolution/self.nSubap) + + # normalization factor for the Tip/Tilt + norma = (n_subap + n_pix_separation)*(self.telescope.resolution/self.nSubap) + # support for the mask + m = np.zeros([n_tot,n_tot]) + if psf_centering: + # mask centered on 4 pixel + lim = np.pi/4 + d_pix = np.pi/4 /(n_tot//2) # size of a pixel in angle + lim = lim - d_pix + + # create a Tip/Tilt combination for each quadrant + [Tip,Tilt] = np.meshgrid(np.linspace(-lim,lim,n_tot//2),np.linspace(-lim,lim,n_tot//2)) + + m[:n_tot//2 ,:n_tot//2 ] = Tip * (1- sx[0]/(n_subap+n_pix_separation/2))*norma + Tilt * (1- sy[0]/(n_subap+n_pix_separation/2))*norma + m[:n_tot//2 ,-n_tot//2: ] = -Tip * (1+ sx[1]/(n_subap+n_pix_separation/2))*norma + Tilt * (1- sy[1]/(n_subap+n_pix_separation/2))*norma + m[-n_tot//2 :,-n_tot//2:] = -Tip * (1+ sx[2]/(n_subap+n_pix_separation/2))*norma + -Tilt * (1+ sy[2]/(n_subap+n_pix_separation/2))*norma + m[-n_tot//2 :,:n_tot//2 ] = Tip * (1- sx[3]/(n_subap+n_pix_separation/2))*norma + -Tilt * (1+ sy[3]/(n_subap+n_pix_separation/2))*norma + + else: + # mask centered on 1 pixel + d_pix = (np.pi/4) /(n_tot/2) # size of a pixel in angle + lim_p = np.pi/4 + lim_m = np.pi/4 -2*d_pix + + # create a Tip/Tilt combination for each quadrant + [Tip_1,Tilt_1] = np.meshgrid(np.linspace(-lim_p,lim_p,n_tot//2 +1),np.linspace(-lim_p,lim_p,n_tot//2 +1)) + [Tip_2,Tilt_2] = np.meshgrid(np.linspace(-lim_p,lim_p,n_tot//2 +1),np.linspace(-lim_m,lim_m,n_tot//2 -1)) + [Tip_3,Tilt_3] = np.meshgrid(np.linspace(-lim_m,lim_m,n_tot//2 -1),np.linspace(-lim_m,lim_m,n_tot//2 -1)) + [Tip_4,Tilt_4] = np.meshgrid(np.linspace(-lim_m,lim_m,n_tot//2 -1),np.linspace(-lim_p,lim_p,n_tot//2 +1)) + + m[:n_tot//2 +1,:n_tot//2+1] = Tip_1 * (1- sx[0]/(n_subap+n_pix_separation/2))*norma + Tilt_1 * (1- sy[0]/(n_subap+n_pix_separation/2))*norma + m[:n_tot//2 +1,-n_tot//2+1:] = -Tip_4 * (1+ sx[1]/(n_subap+n_pix_separation/2))*norma + Tilt_4 * (1- sy[1]/(n_subap+n_pix_separation/2))*norma + m[-n_tot//2 +1:,-n_tot//2 +1:] = -Tip_3 * (1+ sx[2]/(n_subap+n_pix_separation/2))*norma + -Tilt_3 * (1+ sy[2]/(n_subap+n_pix_separation/2))*norma + m[-n_tot//2 +1:,:n_tot//2 +1] = Tip_2 * (1- sx[3]/(n_subap+n_pix_separation/2))*norma + -Tilt_2 * (1+ sy[3]/(n_subap+n_pix_separation/2))*norma + + return -m # sign convention for backward compatibility +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% WFS INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + def initialization(self,telescope): + telescope.resetOPD() + if self.userValidSignal is None: + print('The valid pixel are selected on flux considerations') + self.modulation = self.calibModulation # set the modulation to a large value + self.wfs_measure(phase_in=self.telescope.src.phase) + # save initialization frame + self.initFrame = self.cam.frame + + # save the number of signals depending on the case + if self.postProcessing == 'slopesMaps' or self.postProcessing == 'slopesMaps_incidence_flux': + # select the valid pixels of the detector according to the flux (case slopes-maps) + I1 = self.grabQuadrant(1) + I2 = self.grabQuadrant(2) + I3 = self.grabQuadrant(3) + I4 = self.grabQuadrant(4) + + # sum of the 4 quadrants + self.I4Q = I1+I2+I3+I4 + # valid pixels to consider for the slopes-maps computation + self.validI4Q = (self.I4Q>=self.lightRatio*self.I4Q.max()) + self.validSignal = np.concatenate((self.validI4Q,self.validI4Q)) + self.nSignal = int(np.sum(self.validSignal)) + + if self.postProcessing == 'fullFrame': + # select the valid pixels of the detector according to the flux (case full-frame) + self.validSignal = (self.initFrame>=self.lightRatio*self.initFrame.max()) + self.nSignal = int(np.sum(self.validSignal)) + else: + print('You are using a user-defined mask for the selection of the valid pixel') + if self.postProcessing == 'slopesMaps': + + # select the valid pixels of the detector according to the flux (case full-frame) + self.validI4Q = self.userValidSignal + self.validSignal = np.concatenate((self.validI4Q,self.validI4Q)) + self.nSignal = int(np.sum(self.validSignal)) + + if self.postProcessing == 'fullFrame': + self.validSignal = self.userValidSignal + self.nSignal = int(np.sum(self.validSignal)) + + # Tag to indicate that the wfs is initialized + self.isInitialized = True + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% WFS CALIBRATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + def wfs_calibration(self,telescope): + # reference slopes acquisition + telescope.OPD = telescope.pupil.astype(float) + # compute the refrence slopes + self.wfs_measure(phase_in=self.telescope.src.phase) + self.referenceSignal_2D,self.referenceSignal = self.signalProcessing() + + # 2D reference Frame before binning with detector + self.referencePyramidFrame = np.copy(self.pyramidFrame) + if self.isCalibrated is False: + print('WFS calibrated!') + self.isCalibrated= True + telescope.OPD = telescope.pupil.astype(float) +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PYRAMID TRANSFORM %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + def pyramid_transform(self,phase_in): + # copy of the support for the zero-padding + support = self.supportPadded.copy() + # em field corresponding to phase_in + if np.ndim(self.telescope.OPD)==2: + if self.modulation==0: + em_field = self.maskAmplitude*np.exp(1j*(phase_in)) + else: + em_field = self.maskAmplitude*np.exp(1j*(self.convert_for_gpu(self.telescope.src.phase)+phase_in)) + else: + em_field = self.maskAmplitude*np.exp(1j*phase_in) + # zero-padding for the FFT computation + support[self.center-self.telescope.resolution//2:self.center+self.telescope.resolution//2,self.center-self.telescope.resolution//2:self.center+self.telescope.resolution//2] = em_field + del em_field + # case with mask centered on 4 pixels + if self.psfCentering: + em_field_ft = np_cp.fft.fft2(support*self.phasor) + em_field_pwfs = np_cp.fft.ifft2(em_field_ft*self.mask) + I = np_cp.abs(em_field_pwfs)**2 + # case with mask centered on 1 pixel + else: + if self.spatialFilter is not None: + em_field_ft = np_cp.fft.fftshift(np_cp.fft.fft2(support))*self.spatialFilter + else: + em_field_ft = np_cp.fft.fftshift(np_cp.fft.fft2(support)) + + em_field_pwfs = np_cp.fft.ifft2(em_field_ft*self.mask) + I = np_cp.abs(em_field_pwfs)**2 + del support + del em_field_pwfs + self.modulation_camera_em.append(self.convert_for_numpy(em_field_ft)) + + del em_field_ft + del phase_in + return I + + def setPhaseBuffer(self,phaseIn): + B = self.phaseBuffModulationLowres_CPU+phaseIn + return B + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PYRAMID PROPAGATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + def pyramid_propagation(self,telescope): + # backward compatibility with previous version + self.wfs_measure(phase_in=telescope.src.phase) + return + + def wfs_measure(self,phase_in=None): + if phase_in is not None: + self.telescope.src.phase = phase_in + # mask amplitude for the light propagation + self.maskAmplitude = self.convert_for_gpu(self.telescope.pupilReflectivity) + + if self.spatialFilter is not None: + if np.ndim(phase_in)==2: + support_spatial_filter = np.copy(self.supportPadded) + em_field = self.maskAmplitude*np.exp(1j*(self.telescope.src.phase)) + support_spatial_filter[self.center-self.telescope.resolution//2:self.center+self.telescope.resolution//2,self.center-self.telescope.resolution//2:self.center+self.telescope.resolution//2] = em_field + self.em_field_spatial_filter = (np.fft.fft2(support_spatial_filter*self.phasor)) + self.pupil_plane_spatial_filter = (np.fft.ifft2(self.em_field_spatial_filter*self.spatialFilter)) + + # modulation camera + self.modulation_camera_em=[] + + if self.modulation==0: + if np.ndim(phase_in)==2: + self.pyramidFrame = self.convert_for_numpy(self.pyramid_transform(self.convert_for_gpu(self.telescope.src.phase))) + self*self.cam + if self.isInitialized and self.isCalibrated: + self.pyramidSignal_2D,self.pyramidSignal=self.signalProcessing() + else: + nModes = phase_in.shape[2] + # move axis to get the number of modes first + self.phase_buffer = self.convert_for_gpu(np.moveaxis(self.telescope.src.phase,-1,0)) + + #define the parallel jobs + def job_loop_multiple_modes_non_modulated(): + Q = Parallel(n_jobs=self.nJobs,prefer=self.joblib_setting)(delayed(self.pyramid_transform)(i) for i in self.phase_buffer) + return Q + # apply the pyramid transform in parallel + maps = job_loop_multiple_modes_non_modulated() + + self.pyramidSignal_2D = np.zeros([self.validSignal.shape[0],self.validSignal.shape[1],nModes]) + self.pyramidSignal = np.zeros([self.nSignal,nModes]) + + for i in range(nModes): + self.pyramidFrame = self.convert_for_numpy(maps[i]) + self*self.cam + if self.isInitialized: + self.pyramidSignal_2D[:,:,i],self.pyramidSignal[:,i] = self.signalProcessing() + del maps + + else: + if np.ndim(phase_in)==2: + n_max_ = self.n_max + if self.nTheta>n_max_: + # break problem in pieces: + nCycle = int(np.ceil(self.nTheta/n_max_)) + # print(self.nTheta) + maps = self.convert_for_numpy(np_cp.zeros([self.nRes,self.nRes])) + for i in range(nCycle): + if self.gpu_available: + try: + self.mempool = np_cp.get_default_memory_pool() + self.mempool.free_all_blocks() + except: + print('could not free the memory') + if i<nCycle-1: + def job_loop_single_mode_modulated(): + Q = Parallel(n_jobs=self.nJobs,prefer=self.joblib_setting)(delayed(self.pyramid_transform)(i) for i in self.convert_for_gpu(self.phaseBuffModulationLowres[i*n_max_:(i+1)*n_max_,:,:])) + return Q + maps+=self.convert_for_numpy(np_cp.sum(np_cp.asarray(job_loop_single_mode_modulated()),axis=0)) + else: + def job_loop_single_mode_modulated(): + Q = Parallel(n_jobs=self.nJobs,prefer=self.joblib_setting)(delayed(self.pyramid_transform)(i) for i in self.convert_for_gpu(self.phaseBuffModulationLowres[i*n_max_:,:,:])) + return Q + maps+=self.convert_for_numpy(np_cp.sum(np_cp.asarray(job_loop_single_mode_modulated()),axis=0)) + self.pyramidFrame=maps/self.nTheta + del maps + else: + #define the parallel jobs + def job_loop_single_mode_modulated(): + Q = Parallel(n_jobs=self.nJobs,prefer=self.joblib_setting)(delayed(self.pyramid_transform)(i) for i in self.phaseBuffModulationLowres) + return Q + # apply the pyramid transform in parallel + self.maps=np_cp.asarray(job_loop_single_mode_modulated()) + # compute the sum of the pyramid frames for each modulation points + if self.weight_vector is None: + self.pyramidFrame=self.convert_for_numpy(np_cp.sum((self.maps),axis=0))/self.nTheta + else: + weighted_map = np.reshape(self.maps,[self.nTheta,self.nRes**2]) + self.weighted_map = np.diag(self.weight_vector)@weighted_map + self.pyramidFrame= np.reshape(self.convert_for_numpy(np_cp.sum((self.weighted_map),axis=0))/self.nTheta,[self.nRes,self.nRes]) + + + #propagate to the detector + self*self.cam + + if self.isInitialized and self.isCalibrated: + self.pyramidSignal_2D,self.pyramidSignal=self.signalProcessing() + else: + if np.ndim(phase_in)==3: + nModes = phase_in.shape[2] + # move axis to get the number of modes first + self.phase_buffer = np.moveaxis(self.telescope.src.phase,-1,0) + + def jobLoop_setPhaseBuffer(): + Q = Parallel(n_jobs=self.nJobs,prefer=self.joblib_setting)(delayed(self.setPhaseBuffer)(i) for i in self.phase_buffer) + return Q + + self.phaseBuffer = (np.reshape(np.asarray(jobLoop_setPhaseBuffer()),[nModes*self.nTheta,self.telescope.resolution,self.telescope.resolution])) + n_measurements = nModes*self.nTheta + n_max = self.n_max + n_measurement_max = int(np.floor(n_max/self.nTheta)) + maps = np_cp.zeros([n_measurements,self.nRes,self.nRes]) + + if n_measurements >n_max: + nCycle = int(np.ceil(nModes/n_measurement_max)) + for i in range(nCycle): + if self.gpu_available: + try: + self.mempool = np_cp.get_default_memory_pool() + self.mempool.free_all_blocks() + except: + print('could not free the memory') + if i<nCycle-1: + def job_loop_multiple_mode_modulated(): + Q = Parallel(n_jobs=self.nJobs,prefer=self.joblib_setting)(delayed(self.pyramid_transform)(i) for i in self.convert_for_gpu(self.phaseBuffer[i*n_measurement_max*self.nTheta:(i+1)*n_measurement_max*self.nTheta,:,:])) + return Q + maps[i*n_measurement_max*self.nTheta:(i+1)*n_measurement_max*self.nTheta,:,:] = np_cp.asarray(job_loop_multiple_mode_modulated()) + else: + def job_loop_multiple_mode_modulated(): + Q = Parallel(n_jobs=self.nJobs,prefer=self.joblib_setting)(delayed(self.pyramid_transform)(i) for i in self.convert_for_gpu(self.phaseBuffer[i*n_measurement_max*self.nTheta:,:,:])) + return Q + maps[i*n_measurement_max*self.nTheta:,:,:] = np_cp.asarray(job_loop_multiple_mode_modulated()) + self.bufferPyramidFrames = self.convert_for_numpy(maps) + del self.phaseBuffer + del maps + if self.gpu_available: + try: + self.mempool = np_cp.get_default_memory_pool() + self.mempool.free_all_blocks() + except: + print('could not free the memory') + else: + def job_loop_multiple_mode_modulated(): + Q = Parallel(n_jobs=self.nJobs,prefer=self.joblib_setting)(delayed(self.pyramid_transform)(i) for i in self.convert_for_gpu(self.phaseBuffer)) + return Q + + self.bufferPyramidFrames = self.convert_for_numpy(np_cp.asarray(job_loop_multiple_mode_modulated())) + + self.pyramidSignal_2D = np.zeros([self.validSignal.shape[0],self.validSignal.shape[1],nModes]) + self.pyramidSignal = np.zeros([self.nSignal,nModes]) + + for i in range(nModes): + self.pyramidFrame = np_cp.sum(self.bufferPyramidFrames[i*(self.nTheta):(self.nTheta)+i*(self.nTheta)],axis=0)/self.nTheta + self*self.cam + if self.isInitialized: + self.pyramidSignal_2D[:,:,i],self.pyramidSignal[:,i] = self.signalProcessing() + del self.bufferPyramidFrames + else: + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('Error - Wrong dimension for the input phase. Aborting....') + print('Aborting...') + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + sys.exit(0) + if self.gpu_available: + try: + self.mempool = np_cp.get_default_memory_pool() + self.mempool.free_all_blocks() + except: + print('could not free the memory') + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% WFS SIGNAL PROCESSING %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + def signalProcessing(self,cameraFrame=None): + if cameraFrame is None: + cameraFrame=self.cam.frame + if self.postProcessing == 'slopesMaps': + # slopes-maps computation + I1 = self.grabQuadrant(1,cameraFrame=0)*self.validI4Q + I2 = self.grabQuadrant(2,cameraFrame=0)*self.validI4Q + I3 = self.grabQuadrant(3,cameraFrame=0)*self.validI4Q + I4 = self.grabQuadrant(4,cameraFrame=0)*self.validI4Q + # global normalisation + I4Q = I1+I2+I3+I4 + norma = np.mean(I4Q[self.validI4Q]) + # slopesMaps computation cropped to the valid pixels + Sx = (I1-I2+I4-I3) + Sy = (I1-I4+I2-I3) + # 2D slopes maps + slopesMaps = (np.concatenate((Sx,Sy)/norma) - self.referenceSignal_2D) *self.slopesUnits + # slopes vector + slopes = slopesMaps[np.where(self.validSignal==1)] + return slopesMaps,slopes + + if self.postProcessing == 'slopesMaps_incidence_flux': + # slopes-maps computation + I1 = self.grabQuadrant(1,cameraFrame=0)*self.validI4Q + I2 = self.grabQuadrant(2,cameraFrame=0)*self.validI4Q + I3 = self.grabQuadrant(3,cameraFrame=0)*self.validI4Q + I4 = self.grabQuadrant(4,cameraFrame=0)*self.validI4Q + + # global normalisation + I4Q = I1+I2+I3+I4 + subArea = (self.telescope.D / self.nSubap)**2 + norma = np.float64(self.telescope.src.nPhoton*self.telescope.samplingTime*subArea) + + # slopesMaps computation cropped to the valid pixels + Sx = (I1-I2+I4-I3) + Sy = (I1-I4+I2-I3) + + # 2D slopes maps + slopesMaps = (np.concatenate((Sx,Sy)/norma) - self.referenceSignal_2D) *self.slopesUnits + + # slopes vector + slopes = slopesMaps[np.where(self.validSignal==1)] + return slopesMaps,slopes + + if self.postProcessing == 'fullFrame': + # global normalization + norma = np.sum(cameraFrame[self.validSignal]) + # 2D full-frame + fullFrameMaps = (cameraFrame / norma ) - self.referenceSignal_2D + # full-frame vector + fullFrame = fullFrameMaps[np.where(self.validSignal==1)] + + return fullFrameMaps,fullFrame + + def get_modulation_frame(self, radius = 6, norma = True): + self.modulation_camera_frame = np.sum(np.abs(self.modulation_camera_em)**2,axis=0) + + N_trunc = int(self.nRes/2 - radius*self.modulation*self.zeroPaddingFactor ) + + modulation_camera_frame_zoom = self.modulation_camera_frame[N_trunc:-N_trunc,N_trunc:-N_trunc] + + if norma: + modulation_camera_frame_zoom/= modulation_camera_frame_zoom.max() + + return modulation_camera_frame_zoom +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% GRAB QUADRANTS FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + def grabQuadrant(self,n,cameraFrame=0): + + nExtraPix = int(np.round((np.max(self.pupilSeparationRatio)-1)*self.telescope.resolution/(self.telescope.resolution/self.nSubap)/2/self.binning)) + centerPixel = int(np.round((self.cam.resolution/self.binning)/2)) + n_pixels = int(np.ceil(self.nSubap/self.binning)) + if cameraFrame ==0: + cameraFrame=self.cam.frame.copy() + + if self.rooftop is None: + if n==3: + I=cameraFrame[nExtraPix+centerPixel:(nExtraPix+centerPixel+n_pixels),nExtraPix+centerPixel:(nExtraPix+centerPixel+n_pixels)] + if n==4: + I=cameraFrame[nExtraPix+centerPixel:(nExtraPix+centerPixel+n_pixels),-nExtraPix+centerPixel-n_pixels:(-nExtraPix+centerPixel)] + if n==1: + I=cameraFrame[-nExtraPix+centerPixel-n_pixels:(-nExtraPix+centerPixel),-nExtraPix+centerPixel-n_pixels:(-nExtraPix+centerPixel)] + if n==2: + I=cameraFrame[-nExtraPix+centerPixel-n_pixels:(-nExtraPix+centerPixel),nExtraPix+centerPixel:(nExtraPix+centerPixel+n_pixels)] + else: + if self.rooftop == 'V': + if n==1: + I=cameraFrame[centerPixel-n_pixels//2:(centerPixel)+n_pixels//2,(self.edgePixel//2):(self.edgePixel//2 +n_pixels)] + if n==2: + I=cameraFrame[centerPixel-n_pixels//2:(centerPixel)+n_pixels//2,(self.edgePixel//2 +n_pixels+nExtraPix*2):(self.edgePixel//2+nExtraPix*2+2*n_pixels)] + if n==4: + I=cameraFrame[centerPixel-n_pixels//2:(centerPixel)+n_pixels//2,(self.edgePixel//2):(self.edgePixel//2 +n_pixels)] + if n==3: + I=cameraFrame[centerPixel-n_pixels//2:(centerPixel)+n_pixels//2,(self.edgePixel//2 +n_pixels+nExtraPix*2):(self.edgePixel//2+nExtraPix*2+2*n_pixels)] + else: + if n==1: + I=cameraFrame[(self.edgePixel//2):(self.edgePixel//2 +n_pixels),centerPixel-n_pixels//2:(centerPixel)+n_pixels//2] + if n==2: + I=cameraFrame[(self.edgePixel//2 +n_pixels+nExtraPix*2):(self.edgePixel//2+nExtraPix*2+2*n_pixels),centerPixel-n_pixels//2:(centerPixel)+n_pixels//2] + if n==4: + I=cameraFrame[(self.edgePixel//2):(self.edgePixel//2 +n_pixels),centerPixel-n_pixels//2:(centerPixel)+n_pixels//2] + if n==3: + I=cameraFrame[(self.edgePixel//2 +n_pixels+nExtraPix*2):(self.edgePixel//2+nExtraPix*2+2*n_pixels),centerPixel-n_pixels//2:(centerPixel)+n_pixels//2] + return I + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% WFS PROPERTIES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + #properties required for backward compatibility (20/10/2020) + @property + def pyramidSignal(self): + return self._pyramidSignal + + @pyramidSignal.setter + def pyramidSignal(self,val): + self._pyramidSignal = val + self.signal = val + + @property + def pyramidSignal_2D(self): + return self._pyramidSignal_2D + + @pyramidSignal_2D.setter + def pyramidSignal_2D(self,val): + self._pyramidSignal_2D = val + self.signal_2D = val + + @property + def lightRatio(self): + return self._lightRatio + + @lightRatio.setter + def lightRatio(self,val): + self._lightRatio = val + if hasattr(self,'isInitialized'): + if self.isInitialized: + print('Updating the map if valid pixels ...') + self.validI4Q = (self.I4Q>=self._lightRatio*self.I4Q.max()) + self.validSignal = np.concatenate((self.validI4Q,self.validI4Q)) + self.validPix = (self.initFrame>=self.lightRatio*self.initFrame.max()) + + # save the number of signals depending on the case + if self.postProcessing == 'slopesMaps': + self.nSignal = np.sum(self.validSignal) + # display + xPix,yPix = np.where(self.validI4Q==1) + plt.figure() + plt.imshow(self.I4Q.T) + plt.plot(xPix,yPix,'+') + if self.postProcessing == 'fullFrame': + self.nSignal = np.sum(self.validPix) + print('Done!') + + @property + def spatialFilter(self): + return self._spatialFilter + + @spatialFilter.setter + def spatialFilter(self,val): + self._spatialFilter = val + if self.isInitialized: + if val is None: + print('No spatial filter considered') + self.mask = self.initial_mask + if self.isCalibrated: + print('Updating the reference slopes and Wavelength Calibration for the new modulation...') + self.slopesUnits = 1 + self.referenceSignal = 0 + self.referenceSignal_2D = 0 + self.wfs_calibration(self.telescope) + print('Done!') + else: + tmp = np.ones([self.nRes,self.nRes]) + tmp[:,0] = 0 + Tip = (sp.morphology.distance_transform_edt(tmp)) + Tilt = (sp.morphology.distance_transform_edt(np.transpose(tmp))) + + # normalize the TT to apply the modulation in terms of lambda/D + self.Tip_spatial_filter = (((Tip/Tip.max())-0.5)*2*np.pi) + self.Tilt_spatial_filter = (((Tilt/Tilt.max())-0.5)*2*np.pi) + if val.shape == self.mask.shape: + print('A spatial filter is now considered') + self.mask = self.initial_mask * val + plt.figure() + plt.imshow(np.real(self.mask)) + plt.title('Spatial Filter considered') + if self.isCalibrated: + print('Updating the reference slopes and Wavelength Calibration for the new modulation...') + self.slopesUnits = 1 + self.referenceSignal = 0 + self.referenceSignal_2D = 0 + self.wfs_calibration(self.telescope) + print('Done!') + else: + print('ERROR: wrong shape for the spatial filter. No spatial filter attached to the mask') + self.mask = self.initial_mask + + @property + def delta_Tip(self): + return self._delta_Tip + + @delta_Tip.setter + def delta_Tip(self,val): + self._delta_Tip = val + if self.isCalibrated: + self.modulation = self.modulation + @property + def delta_Tilt(self): + return self._delta_Tilt + + @delta_Tilt.setter + def delta_Tilt(self,val): + self._delta_Tilt = val + if self.isCalibrated: + self.modulation = self.modulation + @property + def modulation(self): + return self._modulation + + @modulation.setter + def modulation(self,val): + self._modulation = val + if self._modulation>=(self.telescope.resolution//2): + raise ValueError('Error the modulation radius is too large for this resolution! Consider using a larger telescope resolution!') + if val !=0: + self.modulation_path = [] + if self.user_modulation_path is not None: + self.modulation_path = self.user_modulation_path + self.nTheta = len(self.user_modulation_path) + else: + # define the modulation points + perimeter = np.pi*2*self._modulation + if self.nTheta_user_defined is None: + self.nTheta = 4*int((self.extraModulationFactor+np.ceil(perimeter/4))) + else: + self.nTheta = self.nTheta_user_defined + + self.thetaModulation = np.linspace(0+self.delta_theta,2*np.pi+self.delta_theta,self.nTheta,endpoint=False) + for i in range(self.nTheta): + dTheta = self.thetaModulation[i] + self.modulation_path.append([self.modulation*np.cos(dTheta)+self.delta_Tip, self.modulation*np.sin(dTheta)+self.delta_Tilt]) + + self.phaseBuffModulation = np.zeros([self.nTheta,self.nRes,self.nRes]).astype(np_cp.float32) + self.phaseBuffModulationLowres = np.zeros([self.nTheta,self.telescope.resolution,self.telescope.resolution]).astype(np_cp.float32) + + for i in range(self.nTheta): + self.TT = (self.modulation_path[i][0]*self.Tip+self.modulation_path[i][1]*self.Tilt)*self.telescope.pupil + self.phaseBuffModulation[i,self.center-self.telescope.resolution//2:self.center+self.telescope.resolution//2,self.center-self.telescope.resolution//2:self.center+self.telescope.resolution//2] = self.TT + self.phaseBuffModulationLowres[i,:,:] = self.TT + self.phaseBuffModulationLowres_CPU = self.phaseBuffModulationLowres.copy() + if self.gpu_available: + if self.nTheta<=self.n_max: + self.phaseBuffModulationLowres = self.convert_for_gpu(self.phaseBuffModulationLowres) + else: + self.nTheta = 1 + + if hasattr(self,'isCalibrated'): + if self.isCalibrated: + print('Updating the reference slopes and Wavelength Calibration for the new modulation...') + self.slopesUnits = 1 + self.referenceSignal = 0 + self.referenceSignal_2D = 0 + self.wfs_calibration(self.telescope) + print('Done!') + + @property + def backgroundNoise(self): + return self._backgroundNoise + + @backgroundNoise.setter + def backgroundNoise(self,val): + self._backgroundNoise = val + if val == True: + self.backgroundNoiseMap = [] + + def __mul__(self,obj): + if obj.tag=='detector': + I = self.pyramidFrame + obj.frame = (obj.rebin(I,(obj.resolution,obj.resolution))) + if self.binning != 1: + try: + obj.frame = (obj.rebin(obj.frame,(obj.resolution//self.binning,obj.resolution//self.binning))) + except: + print('ERROR: the shape of the detector ('+str(obj.frame.shape)+') is not valid with the binning value requested:'+str(self.binning)+'!') + obj.frame = obj.frame *(self.telescope.src.fluxMap.sum())/obj.frame.sum() + + if obj.photonNoise!=0: + obj.frame = self.random_state_photon_noise.poisson(obj.frame) + + if obj.readoutNoise!=0: + obj.frame += np.int64(np.round(self.random_state_readout_noise.randn(obj.resolution,obj.resolution)*obj.readoutNoise)) +# obj.frame = np.round(obj.frame) + + if self.backgroundNoise is True: + self.backgroundNoiseAdded = self.random_state_background.poisson(self.backgroundNoiseMap) + obj.frame +=self.backgroundNoiseAdded + else: + print('Error light propagated to the wrong type of object') + return -1 + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + def show(self): + attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) + print(self.tag+':') + for a in attributes: + if not(a[0].startswith('__') and a[0].endswith('__')): + if not(a[0].startswith('_')): + if not np.shape(a[1]): + tmp=a[1] + try: + print(' '+str(a[0])+': '+str(tmp.tag)+' object') + except: + print(' '+str(a[0])+': '+str(a[1])) + else: + if np.ndim(a[1])>1: + print(' '+str(a[0])+': '+str(np.shape(a[1]))) + + + + + diff --git a/AO_modules/SPRINT.py b/OOPAO/SPRINT.py similarity index 95% rename from AO_modules/SPRINT.py rename to OOPAO/SPRINT.py index 4f84a20..adbc033 100644 --- a/AO_modules/SPRINT.py +++ b/OOPAO/SPRINT.py @@ -1,156 +1,158 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Jun 2 13:30:51 2021 - -@author: cheritie -""" - -from AO_modules.mis_registration_identification_algorithm.estimateMisRegistration import estimateMisRegistration -from AO_modules.mis_registration_identification_algorithm.computeMetaSensitivyMatrix import computeMetaSensitivityMatrix -from AO_modules.MisRegistration import MisRegistration -from AO_modules.calibration.CalibrationVault import CalibrationVault -import numpy as np - -class SPRINT: - def __init__(self, obj, basis, nameFolder = None, nameSystem = None, mis_registration_zero_point = None, wfs_mis_registered= None, fast_algorithm = False, n_mis_reg = 3, recompute_sensitivity = False): - print('Setting up SPRINT..') - # modal basis considered - self.basis = basis - self.n_mis_reg = n_mis_reg - self.recompute_sensitivity = recompute_sensitivity - - # Case where the shifts are applied in the WFS space - self.wfs_mis_registered = wfs_mis_registered - # fast version of the algorithm (WARNING: not stable) - self.fast_algorithm = fast_algorithm - - # zero point for the sensitivity matrices - if mis_registration_zero_point is None: - self.mis_registration_zero_point = MisRegistration() - else: - self.mis_registration_zero_point = mis_registration_zero_point - - # epsilon mis-registration for the computation of the directional gradients - self.epsilonMisRegistration = MisRegistration() - self.epsilonMisRegistration.shiftX = np.round(obj.dm.pitch /100,4) - self.epsilonMisRegistration.shiftY = np.round(obj.dm.pitch /100,4) - self.epsilonMisRegistration.rotationAngle = np.round(np.rad2deg(np.arctan(self.epsilonMisRegistration.shiftX)/(obj.tel.D/2)),4) - self.epsilonMisRegistration.radialScaling = 0.01 - self.epsilonMisRegistration.tangentialScaling = 0.01 - - # folder name to save the sensitivity matrices - if nameFolder is None: - self.nameFolder_sensitivity_matrice = obj.param['pathInput'] +'/'+ obj.param['name']+'/s_mat/' - else: - self.nameFolder_sensitivity_matrice = nameFolder - - # name of the system considered - if nameSystem is None: - self.name_system = '' - else: - self.name_system = nameSystem - - # pre-compute the sensitivity matrices - [self.metaMatrix,self.calib_0] = computeMetaSensitivityMatrix(nameFolder = self.nameFolder_sensitivity_matrice,\ - nameSystem = self.name_system,\ - tel = obj.tel,\ - atm = obj.atm,\ - ngs = obj.ngs,\ - dm_0 = obj.dm,\ - pitch = obj.dm.pitch,\ - wfs = obj.wfs,\ - basis = basis,\ - misRegistrationZeroPoint = self.mis_registration_zero_point,\ - epsilonMisRegistration = self.epsilonMisRegistration,\ - param = obj.param,\ - wfs_mis_registrated = wfs_mis_registered,\ - n_mis_reg = self.n_mis_reg,\ - fast = self.fast_algorithm,\ - recompute_sensitivity = self.recompute_sensitivity ) - - - self.metaMatrix_init = CalibrationVault(self.metaMatrix.D) - self.mis_registration_zero_point_init = self.mis_registration_zero_point - - print('Done!') - - def estimate(self,obj,on_sky_slopes, n_iteration = 3, n_update_zero_point = 0 ,precision = 3, gain_estimation = 1): - """ - Method of SPRINT to estimate the mis-registrations parameters - - obj : a class containing the different objects, tel, dm, atm, ngs and wfs - _ on_sky_slopes : the wfs signal used to identify the mis-registration parameters - _ n_iteration : the number of iterations to consider - - exemple: - Sprint.estimate(obj, my_wfs_signal, n_iteration = 3) - The estimation are available using: - Sprint.mis_registration_out.shift_x ---- shift in m - Sprint.mis_registration_out.shift_y ---- shift in m - Sprint.mis_registration_out.rotation ---- rotation in degree - """ - - calib_misReg_in = CalibrationVault(on_sky_slopes,invert = False) - # reinitialize the meta matrix - self.metaMatrix = CalibrationVault(self.metaMatrix_init.D) - self.mis_registration_zero_point = self.mis_registration_zero_point_init - - for i_update in range(n_update_zero_point+1): - if i_update>0: - print('----------------------------------') - - print('Mis-Registrations Intermediate Value:') - self.mis_registration_out.print_() - print('----------------------------------') - - print('Updating the set of sensitivity matrices...',end=' ') - # update zero point: - self.mis_registration_zero_point = self.mis_registration_out - # pre-compute the sensitivity matrices - [self.metaMatrix,self.calib_0] = computeMetaSensitivityMatrix(nameFolder = self.nameFolder_sensitivity_matrice,\ - nameSystem = self.name_system,\ - tel = obj.tel,\ - atm = obj.atm,\ - ngs = obj.ngs,\ - dm_0 = obj.dm,\ - pitch = obj.dm.pitch,\ - wfs = obj.wfs,\ - basis = self.basis,\ - misRegistrationZeroPoint = self.mis_registration_zero_point,\ - epsilonMisRegistration = self.epsilonMisRegistration,\ - param = obj.param,\ - wfs_mis_registrated = self.wfs_mis_registered,\ - save_sensitivity_matrices = False,\ - n_mis_reg = self.n_mis_reg,\ - fast = self.fast_algorithm,\ - recompute_sensitivity = self.recompute_sensitivity ) - - print('Done!') - - # estimate mis-registrations - [self.mis_registration_out ,self.scaling_factor ,self.mis_registration_buffer,self.validity_flag] = estimateMisRegistration( nameFolder = self.nameFolder_sensitivity_matrice,\ - nameSystem = self.name_system,\ - tel = obj.tel,\ - atm = obj.atm,\ - ngs = obj.ngs,\ - dm_0 = obj.dm,\ - calib_in = calib_misReg_in,\ - wfs = obj.wfs,\ - basis = self.basis,\ - misRegistrationZeroPoint = self.mis_registration_zero_point,\ - epsilonMisRegistration = self.epsilonMisRegistration,\ - param = obj.param,\ - return_all = True,\ - nIteration = n_iteration,\ - fast = self.fast_algorithm,\ - wfs_mis_registrated = self.wfs_mis_registered,\ - sensitivity_matrices = self.metaMatrix,\ - precision = precision,\ - gainEstimation = gain_estimation) - - - print('----------------------------------') - - print('Final Mis-Registrations identified:') - self.mis_registration_out.print_() - print('Mis-registration Validity Flag: '+ str(self.validity_flag)) - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') +# -*- coding: utf-8 -*- +""" +Created on Wed Jun 2 13:30:51 2021 + +@author: cheritie +""" + +import numpy as np + +from .MisRegistration import MisRegistration +from .calibration.CalibrationVault import CalibrationVault +from .mis_registration_identification_algorithm.computeMetaSensitivyMatrix import computeMetaSensitivityMatrix +from .mis_registration_identification_algorithm.estimateMisRegistration import estimateMisRegistration + + +class SPRINT: + def __init__(self, obj, basis, nameFolder = None, nameSystem = None, mis_registration_zero_point = None, wfs_mis_registered= None, fast_algorithm = False, n_mis_reg = 3, recompute_sensitivity = False): + print('Setting up SPRINT..') + # modal basis considered + self.basis = basis + self.n_mis_reg = n_mis_reg + self.recompute_sensitivity = recompute_sensitivity + + # Case where the shifts are applied in the WFS space + self.wfs_mis_registered = wfs_mis_registered + # fast version of the algorithm (WARNING: not stable) + self.fast_algorithm = fast_algorithm + + # zero point for the sensitivity matrices + if mis_registration_zero_point is None: + self.mis_registration_zero_point = MisRegistration() + else: + self.mis_registration_zero_point = mis_registration_zero_point + + # epsilon mis-registration for the computation of the directional gradients + self.epsilonMisRegistration = MisRegistration() + self.epsilonMisRegistration.shiftX = np.round(obj.dm.pitch /100,4) + self.epsilonMisRegistration.shiftY = np.round(obj.dm.pitch /100,4) + self.epsilonMisRegistration.rotationAngle = np.round(np.rad2deg(np.arctan(self.epsilonMisRegistration.shiftX)/(obj.tel.D/2)),4) + self.epsilonMisRegistration.radialScaling = 0.01 + self.epsilonMisRegistration.tangentialScaling = 0.01 + + # folder name to save the sensitivity matrices + if nameFolder is None: + self.nameFolder_sensitivity_matrice = obj.param['pathInput'] +'/'+ obj.param['name']+'/s_mat/' + else: + self.nameFolder_sensitivity_matrice = nameFolder + + # name of the system considered + if nameSystem is None: + self.name_system = '' + else: + self.name_system = nameSystem + + # pre-compute the sensitivity matrices + [self.metaMatrix,self.calib_0] = computeMetaSensitivityMatrix(nameFolder = self.nameFolder_sensitivity_matrice,\ + nameSystem = self.name_system,\ + tel = obj.tel,\ + atm = obj.atm,\ + ngs = obj.ngs,\ + dm_0 = obj.dm,\ + pitch = obj.dm.pitch,\ + wfs = obj.wfs,\ + basis = basis,\ + misRegistrationZeroPoint = self.mis_registration_zero_point,\ + epsilonMisRegistration = self.epsilonMisRegistration,\ + param = obj.param,\ + wfs_mis_registrated = wfs_mis_registered,\ + n_mis_reg = self.n_mis_reg,\ + fast = self.fast_algorithm,\ + recompute_sensitivity = self.recompute_sensitivity ) + + + self.metaMatrix_init = CalibrationVault(self.metaMatrix.D) + self.mis_registration_zero_point_init = self.mis_registration_zero_point + + print('Done!') + + def estimate(self,obj,on_sky_slopes, n_iteration = 3, n_update_zero_point = 0 ,precision = 3, gain_estimation = 1): + """ + Method of SPRINT to estimate the mis-registrations parameters + - obj : a class containing the different objects, tel, dm, atm, ngs and wfs + _ on_sky_slopes : the wfs signal used to identify the mis-registration parameters + _ n_iteration : the number of iterations to consider + + exemple: + Sprint.estimate(obj, my_wfs_signal, n_iteration = 3) + The estimation are available using: + Sprint.mis_registration_out.shift_x ---- shift in m + Sprint.mis_registration_out.shift_y ---- shift in m + Sprint.mis_registration_out.rotation ---- rotation in degree + """ + + calib_misReg_in = CalibrationVault(on_sky_slopes,invert = False) + # reinitialize the meta matrix + self.metaMatrix = CalibrationVault(self.metaMatrix_init.D) + self.mis_registration_zero_point = self.mis_registration_zero_point_init + + for i_update in range(n_update_zero_point+1): + if i_update>0: + print('----------------------------------') + + print('Mis-Registrations Intermediate Value:') + self.mis_registration_out.print_() + print('----------------------------------') + + print('Updating the set of sensitivity matrices...',end=' ') + # update zero point: + self.mis_registration_zero_point = self.mis_registration_out + # pre-compute the sensitivity matrices + [self.metaMatrix,self.calib_0] = computeMetaSensitivityMatrix(nameFolder = self.nameFolder_sensitivity_matrice,\ + nameSystem = self.name_system,\ + tel = obj.tel,\ + atm = obj.atm,\ + ngs = obj.ngs,\ + dm_0 = obj.dm,\ + pitch = obj.dm.pitch,\ + wfs = obj.wfs,\ + basis = self.basis,\ + misRegistrationZeroPoint = self.mis_registration_zero_point,\ + epsilonMisRegistration = self.epsilonMisRegistration,\ + param = obj.param,\ + wfs_mis_registrated = self.wfs_mis_registered,\ + save_sensitivity_matrices = False,\ + n_mis_reg = self.n_mis_reg,\ + fast = self.fast_algorithm,\ + recompute_sensitivity = self.recompute_sensitivity ) + + print('Done!') + + # estimate mis-registrations + [self.mis_registration_out ,self.scaling_factor ,self.mis_registration_buffer,self.validity_flag] = estimateMisRegistration( nameFolder = self.nameFolder_sensitivity_matrice,\ + nameSystem = self.name_system,\ + tel = obj.tel,\ + atm = obj.atm,\ + ngs = obj.ngs,\ + dm_0 = obj.dm,\ + calib_in = calib_misReg_in,\ + wfs = obj.wfs,\ + basis = self.basis,\ + misRegistrationZeroPoint = self.mis_registration_zero_point,\ + epsilonMisRegistration = self.epsilonMisRegistration,\ + param = obj.param,\ + return_all = True,\ + nIteration = n_iteration,\ + fast = self.fast_algorithm,\ + wfs_mis_registrated = self.wfs_mis_registered,\ + sensitivity_matrices = self.metaMatrix,\ + precision = precision,\ + gainEstimation = gain_estimation) + + + print('----------------------------------') + + print('Final Mis-Registrations identified:') + self.mis_registration_out.print_() + print('Mis-registration Validity Flag: '+ str(self.validity_flag)) + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') diff --git a/AO_modules/ShackHartmann.py b/OOPAO/ShackHartmann.py similarity index 98% rename from AO_modules/ShackHartmann.py rename to OOPAO/ShackHartmann.py index 2743da9..c35622c 100644 --- a/AO_modules/ShackHartmann.py +++ b/OOPAO/ShackHartmann.py @@ -1,720 +1,722 @@ -# -*- coding: utf-8 -*- -""" -Created on Thu May 20 17:52:09 2021 - -@author: cheritie -""" -import numpy as np -import inspect -import time -from AO_modules.Detector import Detector -from AO_modules.tools.tools import bin_ndarray - -try: - from joblib import Parallel, delayed -except: - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('WARNING: The joblib module is not installed. This would speed up considerably the operations.') - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - -# import ctypes -# try : -# mkl_rt = ctypes.CDLL('libmkl_rt.so') -# mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads -# mkl_set_num_threads(6) -# except: -# try: -# mkl_rt = ctypes.CDLL('./mkl_rt.dll') -# mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads -# mkl_set_num_threads(6) -# except: -# print('Could not optimize the parallelisation of the code ') - - - -class ShackHartmann: - def __init__(self,nSubap,telescope,lightRatio,threshold_cog = 0.01,is_geometric = False, binning_factor = 1,padding_extension_factor = 1, threshold_convolution = 0.05): - """ - ************************** REQUIRED PARAMETERS ************************** - - A Shack Hartmann object consists in defining a 2D grd of lenslet arrays located at the pupil plane of the telescope to estimate the local tip/tilt seen by each lenslet. - By default the Shack Hartmann detector is considered to be noise-free (for calibration purposes). These properties can be switched on and off on the fly (see properties) - It requires the following parameters: - _ nSubap : the number of subapertures (ie the diameter of the Pyramid Pupils in pixels) - _ telescope : the telescope object to which the Shack Hartmann is associated. This object carries the phase, flux and pupil information - _ lightRatio : criterion to select the valid subaperture based on flux considerations - - - ************************** OPTIONAL PARAMETERS ************************** - - _ threshold_cog : threshold (with respect to the maximum value of the image) to apply to compute the center of gravity of the spots - _ is_geometric : if True, enables the geometric Shack Hartmann (direct measurement of gradient) if False, the diffractive computation is considered (default) - _ binning_factor : binning factor of the detector -- default Value is 1 - - # LGS related properties - _ padding_extension_factor : zero-padding factor oon the spots intensity images. This is a fast way to provide a larger field of view before the convolution with LGS spots is achieved and allow to prevent wrapping effects - _threshold_convolution : threshold considered to force the gaussian spots (elungated spots) to go to zero on the edges - - ************************** PROPAGATING THE LIGHT TO THE SH OBJECT ************************** - The light can be propagated from a telescope object tel through the Shack Hartmann object wfs using the * operator: - _ tel*wfs - This operation will trigger: - _ propagation of the tel.src light through the Shack Hartmann detector (phase and flux) - _ binning of the SH signals - _ addition of eventual photon noise and readout noise - _ computation of the Shack Hartmann signals - - - ************************** PROPERTIES ************************** - - The main properties of a Telescope object are listed here: - _ wfs.signal : signal measured by the Shack Hartmann - _ wfs.signal_2D : 2D map of the signal measured by the Shack Hartmann - _ wfs.random_state_photon_noise : a random state cycle can be defined to reproduces random sequences of noise -- default is based on the current clock time - _ wfs.random_state_readout_noise : a random state cycle can be defined to reproduces random sequences of noise -- default is based on the current clock time - _ wfs.random_state_background : a random state cycle can be defined to reproduces random sequences of noise -- default is based on the current clock time - _ wfs.fov_lenslet_arcsec : Field of View of the subapertures in arcsec - _ wfs.fov_pixel_binned_arcsec : Field of View of the pixel in arcsec - - the following properties can be updated on the fly: - _ wfs.modulation : update the modulation radius and update the reference signal - _ wfs.cam.photonNoise : Photon noise can be set to True or False - _ wfs.cam.readoutNoise : Readout noise can be set to True or False - _ wfs.backgroundNoise : Background noise can be set to True or False - _ wfs.lightRatio : reset the valid subaperture selection considering the new value - - """ - self.tag = 'shackHartmann' - self.telescope = telescope - self.is_geometric = is_geometric - self.nSubap = nSubap - self.lightRatio = lightRatio - self.binning_factor = binning_factor - self.zero_padding = 2 - self.padding_extension_factor = padding_extension_factor - self.threshold_convolution = threshold_convolution - self.threshold_cog = threshold_cog - - - # case where the spots are zeropadded to provide larger fOV - if padding_extension_factor>2: - self.n_pix_subap = int(padding_extension_factor*self.telescope.resolution// self.nSubap) - self.is_extended = True - self.binning_factor = padding_extension_factor - self.zero_padding = 1 - else: - self.n_pix_subap = self.telescope.resolution// self.nSubap - self.is_extended = False - - - # different resolutions needed - self.n_pix_subap_init = self.telescope.resolution// self.nSubap - self.extra_pixel = (self.n_pix_subap-self.n_pix_subap_init)//2 - self.n_pix_lenslet_init = self.n_pix_subap_init*self.zero_padding - self.n_pix_lenslet = self.n_pix_subap*self.zero_padding - self.center = self.n_pix_lenslet//2 - self.center_init = self.n_pix_lenslet_init//2 - self.lenslet_frame = np.zeros([self.n_pix_subap*self.zero_padding,self.n_pix_subap*self.zero_padding], dtype =complex) - self.outerMask = np.ones([self.n_pix_subap_init*self.zero_padding, self.n_pix_subap_init*self.zero_padding ]) - self.outerMask[1:-1,1:-1] = 0 - - # Compute camera frame in case of multiple measurements - self.get_camera_frame_multi = False - # detector camera - self.cam = Detector(round(nSubap*self.n_pix_subap)) # WFS detector object - self.cam.photonNoise = 0 - self.cam.readoutNoise = 0 # single lenslet - # noies random states - self.random_state_photon_noise = np.random.RandomState(seed=int(time.time())) # random states to reproduce sequences of noise - self.random_state_readout_noise = np.random.RandomState(seed=int(time.time())) # random states to reproduce sequences of noise - self.random_state_background = np.random.RandomState(seed=int(time.time())) # random states to reproduce sequences of noise - - # field of views - self.fov_lenslet_arcsec = self.n_pix_subap*206265*self.binning_factor/self.padding_extension_factor*self.telescope.src.wavelength/(self.telescope.D/self.nSubap) - self.fov_pixel_arcsec = self.fov_lenslet_arcsec/ self.n_pix_subap - self.fov_pixel_binned_arcsec = self.fov_lenslet_arcsec/ self.n_pix_subap_init - - X_map, Y_map= np.meshgrid(np.arange(self.n_pix_subap//self.binning_factor),np.arange(self.n_pix_subap//self.binning_factor)) - self.X_coord_map = np.atleast_3d(X_map).T - self.Y_coord_map = np.atleast_3d(Y_map).T - - if telescope.src.type == 'LGS': - self.is_LGS = True - else: - self.is_LGS = False - - # joblib parameter - self.nJobs = 1 - self.joblib_prefer = 'processes' - - # camera frame - self.camera_frame = np.zeros([self.n_pix_subap*(self.nSubap)//self.binning_factor,self.n_pix_subap*(self.nSubap)//self.binning_factor], dtype =float) - - # cube of lenslet zero padded - self.cube = np.zeros([self.nSubap**2,self.n_pix_lenslet_init,self.n_pix_lenslet_init]) - self.cube_flux = np.zeros([self.nSubap**2,self.n_pix_subap_init,self.n_pix_subap_init],dtype=(complex)) - self.index_x = [] - self.index_y = [] - - # phasor to center spots in the center of the lenslets - [xx,yy] = np.meshgrid(np.linspace(0,self.n_pix_lenslet_init-1,self.n_pix_lenslet_init),np.linspace(0,self.n_pix_lenslet_init-1,self.n_pix_lenslet_init)) - self.phasor = np.exp(-(1j*np.pi*(self.n_pix_lenslet_init+1)/self.n_pix_lenslet_init)*(xx+yy)) - self.phasor_tiled = np.moveaxis(np.tile(self.phasor[:,:,None],self.nSubap**2),2,0) - - # Get subapertures index and flux per subaperture - [xx,yy] = np.meshgrid(np.linspace(0,self.n_pix_lenslet-1,self.n_pix_lenslet),np.linspace(0,self.n_pix_lenslet-1,self.n_pix_lenslet)) - self.phasor_expanded = np.exp(-(1j*np.pi*(self.n_pix_lenslet+1)/self.n_pix_lenslet)*(xx+yy)) - self.phasor_expanded_tiled = np.moveaxis(np.tile(self.phasor_expanded[:,:,None],self.nSubap**2),2,0) - - self.initialize_flux() - for i in range(self.nSubap): - for j in range(self.nSubap): - self.index_x.append(i) - self.index_y.append(j) - - self.current_nPhoton = self.telescope.src.nPhoton - self.index_x = np.asarray(self.index_x) - self.index_y = np.asarray(self.index_y) - - print('Selecting valid subapertures based on flux considerations..') - - self.photon_per_subaperture_2D = np.reshape(self.photon_per_subaperture,[self.nSubap,self.nSubap]) - - self.valid_subapertures = np.reshape(self.photon_per_subaperture >= self.lightRatio*np.max(self.photon_per_subaperture), [self.nSubap,self.nSubap]) - - self.valid_subapertures_1D = np.reshape(self.valid_subapertures,[self.nSubap**2]) - - [self.validLenslets_x , self.validLenslets_y] = np.where(self.photon_per_subaperture_2D >= self.lightRatio*np.max(self.photon_per_subaperture)) - - # index of valid slopes X and Y - self.valid_slopes_maps = np.concatenate((self.valid_subapertures,self.valid_subapertures)) - - # number of valid lenslet - self.nValidSubaperture = int(np.sum(self.valid_subapertures)) - - self.nSignal = 2*self.nValidSubaperture - - - if self.is_LGS: - self.get_convolution_spot() - - # WFS initialization - self.initialize_wfs() - - - - - def initialize_wfs(self): - self.isInitialized = False - - readoutNoise = np.copy(self.cam.readoutNoise) - photonNoise = np.copy(self.cam.photonNoise) - - self.cam.photonNoise = 0 - self.cam.readoutNoise = 0 - - # reference signal - self.sx0 = np.zeros([self.nSubap,self.nSubap]) - self.sy0 = np.zeros([self.nSubap,self.nSubap]) - # signal vector - self.sx = np.zeros([self.nSubap,self.nSubap]) - self.sy = np.zeros([self.nSubap,self.nSubap]) - # signal map - self.SX = np.zeros([self.nSubap,self.nSubap]) - self.SY = np.zeros([self.nSubap,self.nSubap]) - # flux per subaperture - self.reference_slopes_maps = np.zeros([self.nSubap*2,self.nSubap]) - self.slopes_units = 1 - print('Acquiring reference slopes..') - self.telescope.resetOPD() - self.wfs_measure() - self.reference_slopes_maps = np.copy(self.signal_2D) - self.isInitialized = True - print('Done!') - - print('Setting slopes units..') - [Tip,Tilt] = np.meshgrid(np.linspace(0,self.telescope.resolution-1,self.telescope.resolution),np.linspace(0,self.telescope.resolution-1,self.telescope.resolution)) - # normalize to 2 pi p2v - Tip = (((Tip/Tip.max())-0.5)*2*np.pi) - mean_slope = np.zeros(5) - amp = 1e-9 - for i in range(5): - self.telescope.OPD = self.telescope.pupil*Tip*(i-2)*amp - self.telescope.OPD_no_pupil = Tip*(i-2)*amp - - self.wfs_measure() - mean_slope[i] = np.mean(self.signal[:self.nValidSubaperture]) - self.p = np.polyfit(np.linspace(-2,2,5)*amp,mean_slope,deg = 1) - self.slopes_units = np.abs(self.p[0]) - print('Done!') - self.cam.photonNoise = readoutNoise - self.cam.readoutNoise = photonNoise - self.telescope.resetOPD() - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% SHACK HARTMANN WFS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('{: ^20s}'.format('Subapertures') + '{: ^18s}'.format(str(self.nSubap)) ) - print('{: ^20s}'.format('Subaperture Size') + '{: ^18s}'.format(str(np.round(self.telescope.D/self.nSubap,2))) +'{: ^18s}'.format('[m]' )) - print('{: ^20s}'.format('Pixel FoV') + '{: ^18s}'.format(str(np.round(self.fov_pixel_binned_arcsec,2))) +'{: ^18s}'.format('[arcsec]' )) - print('{: ^20s}'.format('Subapertue FoV') + '{: ^18s}'.format(str(np.round(self.fov_lenslet_arcsec,2))) +'{: ^18s}'.format('[arcsec]' )) - print('{: ^20s}'.format('Valid Subaperture') + '{: ^18s}'.format(str(str(self.nValidSubaperture)))) - if self.is_LGS: - print('{: ^20s}'.format('Spot Elungation') + '{: ^18s}'.format(str(100*np.round(self.elungation_factor,3))) +'{: ^18s}'.format('% of a subap' )) - print('{: ^20s}'.format('Geometric WFS') + '{: ^18s}'.format(str(self.is_geometric))) - - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - if self.is_geometric: - print('WARNING: THE PHOTON AND READOUT NOISE ARE NOT CONSIDERED FOR GEOMETRIC SH-WFS') - - def centroid(self,image,threshold =0.01): - im = np.atleast_3d(image.copy()) - im[im<(threshold*im.max())] = 0 - centroid_out = np.zeros([im.shape[0],2]) - X_map, Y_map= np.meshgrid(np.arange(im.shape[1]),np.arange(im.shape[2])) - X_coord_map = np.atleast_3d(X_map).T - Y_coord_map = np.atleast_3d(Y_map).T - norma = np.sum(np.sum(im,axis=1),axis=1) - centroid_out[:,0] = np.sum(np.sum(im*X_coord_map,axis=1),axis=1)/norma - centroid_out[:,1] = np.sum(np.sum(im*Y_coord_map,axis=1),axis=1)/norma - return centroid_out -#%% DIFFRACTIVE - - def initialize_flux(self,input_flux_map = None): - if self.telescope.tag!='asterism': - if input_flux_map is None: - input_flux_map = self.telescope.src.fluxMap.T - tmp_flux_h_split = np.hsplit(input_flux_map,self.nSubap) - self.cube_flux = np.zeros([self.nSubap**2,self.n_pix_lenslet_init,self.n_pix_lenslet_init],dtype=float) - for i in range(self.nSubap): - tmp_flux_v_split = np.vsplit(tmp_flux_h_split[i],self.nSubap) - self.cube_flux[i*self.nSubap:(i+1)*self.nSubap,self.center_init - self.n_pix_subap_init//2:self.center_init+self.n_pix_subap_init//2,self.center_init - self.n_pix_subap_init//2:self.center_init+self.n_pix_subap_init//2] = np.asarray(tmp_flux_v_split) - self.photon_per_subaperture = np.apply_over_axes(np.sum, self.cube_flux, [1,2]) - self.current_nPhoton = self.telescope.src.nPhoton - return - - def get_lenslet_em_field(self,phase): - tmp_phase_h_split = np.hsplit(phase.T,self.nSubap) - self.cube_em = np.zeros([self.nSubap**2,self.n_pix_lenslet_init,self.n_pix_lenslet_init],dtype=complex) - for i in range(self.nSubap): - tmp_phase_v_split = np.vsplit(tmp_phase_h_split[i],self.nSubap) - self.cube_em[i*self.nSubap:(i+1)*self.nSubap,self.center_init - self.n_pix_subap_init//2:self.center_init+self.n_pix_subap_init//2,self.center_init - self.n_pix_subap_init//2:self.center_init+self.n_pix_subap_init//2] = np.exp(1j*np.asarray(tmp_phase_v_split)) - self.cube_em*=np.sqrt(self.cube_flux)*self.phasor_tiled - return self.cube_em - - def fill_camera_frame(self,ind_x,ind_y,I,index_frame=None): - if index_frame is None: - self.camera_frame[ind_x*self.n_pix_subap//self.binning_factor:(ind_x+1)*self.n_pix_subap//self.binning_factor,ind_y*self.n_pix_subap//self.binning_factor:(ind_y+1)*self.n_pix_subap//self.binning_factor] = I - else: - self.camera_frame[index_frame,ind_x*self.n_pix_subap//self.binning_factor:(ind_x+1)*self.n_pix_subap//self.binning_factor,ind_y*self.n_pix_subap//self.binning_factor:(ind_y+1)*self.n_pix_subap//self.binning_factor] = I - - def compute_camera_frame_multi(self,maps_intensity): - self.ind_frame =np.zeros(maps_intensity.shape[0],dtype=(int)) - self.maps_intensity = maps_intensity - index_x = np.tile(self.index_x[self.valid_subapertures_1D],self.phase_buffer.shape[0]) - index_y = np.tile(self.index_y[self.valid_subapertures_1D],self.phase_buffer.shape[0]) - - for i in range(self.phase_buffer.shape[0]): - self.ind_frame[i*self.nValidSubaperture:(i+1)*self.nValidSubaperture]=i - - def joblib_fill_camera_frame(): - Q=Parallel(n_jobs=1,prefer='processes')(delayed(self.fill_camera_frame)(i,j,k,l) for i,j,k,l in zip(index_x,index_y,self.maps_intensity,self.ind_frame)) - return Q - - joblib_fill_camera_frame() - return - - #%% GEOMETRIC - - def gradient_2D(self,arr): - res_x = (np.gradient(arr,axis=0)/self.telescope.pixelSize)*self.telescope.pupil - res_y = (np.gradient(arr,axis=1)/self.telescope.pixelSize)*self.telescope.pupil - return res_x,res_y - - def lenslet_propagation_geometric(self,arr): - - [SLx,SLy] = self.gradient_2D(arr) - - sy = (bin_ndarray(SLx, [self.nSubap,self.nSubap], operation='sum')) - sx = (bin_ndarray(SLy, [self.nSubap,self.nSubap], operation='sum')) - - return np.concatenate((sx,sy)) - - #%% LGS - def get_convolution_spot(self): - # compute the projection of the LGS on the subaperture to simulate - # the spots elongation using a convulotion with gaussian spot - [X0,Y0] = [self.telescope.src.laser_coordinates[1],-self.telescope.src.laser_coordinates[0]] # coordinates of the LLT in [m] from the center (sign convention adjusted to match display position on camera) - - # 3D coordinates - coordinates_3D = np.zeros([3,len(self.telescope.src.Na_profile[0,:])]) - coordinates_3D_ref = np.zeros([3,len(self.telescope.src.Na_profile[0,:])]) - delta_dx = np.zeros([2,len(self.telescope.src.Na_profile[0,:])]) - delta_dy = np.zeros([2,len(self.telescope.src.Na_profile[0,:])]) - - # coordinates of the subapertures - - x_subap = np.linspace(-self.telescope.D//2,self.telescope.D//2,self.nSubap) - y_subap = np.linspace(-self.telescope.D//2,self.telescope.D//2,self.nSubap) - # pre-allocate memory for shift x and y to apply to the gaussian spot - - # number of pixel - n_pix = self.n_pix_lenslet - # size of a pixel in m - d_pix = (self.telescope.D/self.nSubap)/self.n_pix_lenslet_init - v = np.linspace(-n_pix*d_pix/2,n_pix*d_pix/2,n_pix) - [alpha_x,alpha_y] = np.meshgrid(v,v) - - # FWHM of gaussian converted into pixel in arcsec - sigma_spot = self.telescope.src.FWHM_spot_up/(2*np.sqrt(np.log(2))) - for i in range(len(self.telescope.src.Na_profile[0,:])): - coordinates_3D[:2,i] = (self.telescope.D/4)*([X0,Y0]/self.telescope.src.Na_profile[0,i]) - coordinates_3D[2,i] = self.telescope.D**2./(8.*self.telescope.src.Na_profile[0,i])/(2.*np.sqrt(3.)) - coordinates_3D_ref[:,i] = coordinates_3D[:,i]-coordinates_3D[:,len(self.telescope.src.Na_profile[0,:])//2] - C_elung = [] - C_gauss = [] - shift_x_buffer = [] - shift_y_buffer = [] - - C_gauss = [] - criterion_elungation = self.n_pix_lenslet*(self.telescope.D/self.nSubap)/self.n_pix_lenslet_init - - valid_subap_1D = np.copy(self.valid_subapertures_1D[:]) - count = -1 - # gaussian spot (for calibration) - I_gauss = (self.telescope.src.Na_profile[1,:][0]/(self.telescope.src.Na_profile[0,:][0]**2)) * np.exp(- ((alpha_x)**2 + (alpha_y)**2)/(2*sigma_spot**2)) - I_gauss /= I_gauss.sum() - - for i_subap in range(len(x_subap)): - for j_subap in range(len(y_subap)): - count += 1 - if valid_subap_1D[count]: - I = np.zeros([n_pix,n_pix],dtype=(complex)) - # I_gauss = np.zeros([n_pix,n_pix],dtype=(complex)) - shift_X = np.zeros(len(self.telescope.src.Na_profile[0,:])) - shift_Y = np.zeros(len(self.telescope.src.Na_profile[0,:])) - for i in range(len(self.telescope.src.Na_profile[0,:])): - coordinates_3D[:2,i] = (self.telescope.D/4)*([X0,Y0]/self.telescope.src.Na_profile[0,i]) - coordinates_3D[2,i] = self.telescope.D**2./(8.*self.telescope.src.Na_profile[0,i])/(2.*np.sqrt(3.)) - - coordinates_3D_ref[:,i] = coordinates_3D[:,i]-coordinates_3D[:,len(self.telescope.src.Na_profile[0,:])//2] - - delta_dx[0,i] = coordinates_3D_ref[0,i]*(4/self.telescope.D) - delta_dy[0,i] = coordinates_3D_ref[1,i]*(4/self.telescope.D) - - delta_dx[1,i] = coordinates_3D_ref[2,i]*(np.sqrt(3)*(4/self.telescope.D)**2)*x_subap[i_subap] - delta_dy[1,i] = coordinates_3D_ref[2,i]*(np.sqrt(3)*(4/self.telescope.D)**2)*y_subap[j_subap] - - # resulting shift + conversion from radians to pixels in m - shift_X[i] = 206265*self.fov_pixel_arcsec*(delta_dx[0,i] + delta_dx[1,i]) - shift_Y[i] = 206265*self.fov_pixel_arcsec*(delta_dy[0,i] + delta_dy[1,i]) - - - I_tmp = (self.telescope.src.Na_profile[1,:][i]/(self.telescope.src.Na_profile[0,:][i]**2))*np.exp(- ((alpha_x-shift_X[i])**2 + (alpha_y-shift_Y[i])**2)/(2*sigma_spot**2)) - - I += I_tmp - - # truncation of the wings of the gaussian - I[I<self.threshold_convolution*I.max()] = 0 - # normalization to conserve energy - I /= I.sum() - # save - shift_x_buffer.append(shift_X) - shift_y_buffer.append(shift_Y) - - C_elung.append((np.fft.fft2(I.T))) - C_gauss.append((np.fft.fft2(I_gauss.T))) - - self.shift_x_buffer = np.asarray(shift_x_buffer) - self.shift_y_buffer = np.asarray(shift_y_buffer) - - self.shift_x_max_arcsec = np.max(shift_x_buffer,axis=1) - self.shift_y_max_arcsec = np.max(shift_y_buffer,axis=1) - - self.shift_x_min_arcsec = np.min(shift_x_buffer,axis=1) - self.shift_y_min_arcsec = np.min(shift_y_buffer,axis=1) - - self.max_elung_x = np.max(self.shift_x_max_arcsec-self.shift_x_min_arcsec) - self.max_elung_y = np.max(self.shift_y_max_arcsec-self.shift_y_min_arcsec) - self.elungation_factor = np.max([self.max_elung_x,self.max_elung_y])/criterion_elungation - - if self.max_elung_x>criterion_elungation or self.max_elung_y>criterion_elungation: - print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') - print('Warning: The largest spot elongation is '+str(np.round(self.elungation_factor,3))+' times larger than a subaperture! Consider using a higher resolution or increasing the padding_extension_factor parameter') - print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') - - self.C = np.asarray(C_elung.copy()) - self.C_gauss = np.asarray(C_gauss) - self.C_elung = np.asarray(C_elung) - - return - -#%% SH Measurement - def sh_measure(self,phase_in): - # backward compatibility with previous version - self.wfs_measure(phase_in=phase_in) - return - - def wfs_measure(self,phase_in = None): - if phase_in is not None: - self.telescope.src.phase = phase_in - - if self.current_nPhoton != self.telescope.src.nPhoton: - print('updating the flux of the SHWFS object') - self.initialize_flux() - - - if self.is_geometric is False: - ##%%%%%%%%%%%% DIFFRACTIVE SH WFS %%%%%%%%%%%% - if np.ndim(self.telescope.OPD)==2: - #-- case with a single wave-front to sense-- - - # reset camera frame to be filled up - self.camera_frame = np.zeros([self.n_pix_subap*(self.nSubap)//self.binning_factor,self.n_pix_subap*(self.nSubap)//self.binning_factor], dtype =float) - - # normalization for FFT - norma = self.cube.shape[1] - - # compute spot intensity - if self.telescope.spatialFilter is None: - phase = self.telescope.src.phase - self.initialize_flux() - - else: - phase = self.telescope.phase_filtered - self.initialize_flux(self.telescope.amplitude_filtered.T*self.telescope.src.fluxMap.T) - - - I = (np.abs(np.fft.fft2(np.asarray(self.get_lenslet_em_field(phase)),axes=[1,2])/norma)**2) - - # reduce to valid subaperture - I = I[self.valid_subapertures_1D,:,:] - - self.sum_I = np.sum(I,axis=0) - self.edge_subaperture_criterion = np.sum(I*self.outerMask)/np.sum(I) - if self.edge_subaperture_criterion>0.05: - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('WARNING !!!! THE LIGHT IN THE SUBAPERTURE IS MAYBE WRAPPING !!!'+str(np.round(100*self.edge_subaperture_criterion,1))+' % of the total flux detected on the edges of the subapertures. You may want to increase the seeing value or the resolution') - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - - # if FoV is extended, zero pad the spot intensity - if self.is_extended: - I = np.pad(I,[[0,0],[self.extra_pixel,self.extra_pixel],[self.extra_pixel,self.extra_pixel]]) - - # in case of LGS sensor, convolve with LGS spots to create spot elungation - if self.is_LGS: - I = np.fft.fftshift(np.abs((np.fft.ifft2(np.fft.fft2(I)*self.C))),axes = [1,2]) - - # bin the 2D spots intensity to get the desired number of pixel per subaperture - self.maps_intensity = bin_ndarray(I,[I.shape[0], self.n_pix_subap//self.binning_factor,self.n_pix_subap//self.binning_factor], operation='sum') - - # add photon/readout noise to 2D spots - if self.cam.photonNoise!=0: - self.maps_intensity = self.random_state_photon_noise.poisson(self.maps_intensity) - - if self.cam.readoutNoise!=0: - self.maps_intensity += np.int64(np.round(self.random_state_readout_noise.randn(self.maps_intensity.shape[0],self.maps_intensity.shape[1],self.maps_intensity.shape[2])*self.cam.readoutNoise)) - - # fill camera frame with computed intensity (only valid subapertures) - def joblib_fill_camera_frame(): - Q=Parallel(n_jobs=1,prefer='processes')(delayed(self.fill_camera_frame)(i,j,k) for i,j,k in zip(self.index_x[self.valid_subapertures_1D],self.index_y[self.valid_subapertures_1D],self.maps_intensity)) - return Q - joblib_fill_camera_frame() - - # compute the centroid on valid subaperture - self.centroid_lenslets = self.centroid(self.maps_intensity,self.threshold_cog) - - # discard nan and inf values - val_inf = np.where(np.isinf(self.centroid_lenslets)) - val_nan = np.where(np.isnan(self.centroid_lenslets)) - - if np.shape(val_inf)[1] !=0: - print('Warning! some subapertures are giving inf values!') - self.centroid_lenslets[np.where(np.isinf(self.centroid_lenslets))] = 0 - - if np.shape(val_nan)[1] !=0: - print('Warning! some subapertures are giving nan values!') - self.centroid_lenslets[np.where(np.isnan(self.centroid_lenslets))] = 0 - - # compute slopes-maps - self.SX[self.validLenslets_x,self.validLenslets_y] = self.centroid_lenslets[:,0] - self.SY[self.validLenslets_x,self.validLenslets_y] = self.centroid_lenslets[:,1] - - signal_2D = np.concatenate((self.SX,self.SY)) - self.reference_slopes_maps - signal_2D[~self.valid_slopes_maps] = 0 - self.signal_2D = signal_2D/self.slopes_units - self.signal = self.signal_2D[self.valid_slopes_maps] - - # assign camera_fram to sh.cam.frame - self*self.cam - else: - #-- case with multiple wave-fronts to sense-- - - # set phase buffer - self.phase_buffer = np.moveaxis(self.telescope.src.phase_no_pupil,-1,0) - - # tile the valid subaperture vector to match the number of input phase - valid_subap_1D_tiled = np.tile(self.valid_subapertures_1D,self.phase_buffer.shape[0]) - - # tile the valid LGS convolution spot to match the number of input phase - if self.is_LGS: - self.lgs_exp = np.tile(self.C,[self.phase_buffer.shape[0],1,1]) - - # reset camera frame - self.camera_frame = np.zeros([self.phase_buffer.shape[0],self.n_pix_subap*(self.nSubap)//self.binning_factor,self.n_pix_subap*(self.nSubap)//self.binning_factor], dtype =float) - - # compute 2D intensity for multiple input wavefronts - def compute_diffractive_signals_multi(): - Q=Parallel(n_jobs=1,prefer='processes')(delayed(self.get_lenslet_em_field)(i) for i in self.phase_buffer) - return Q - emf_buffer = np.reshape(np.asarray(compute_diffractive_signals_multi()),[self.phase_buffer.shape[0]*self.nSubap**2,self.n_pix_lenslet_init,self.n_pix_lenslet_init]) - - - # reduce to valid subaperture - emf_buffer = emf_buffer[valid_subap_1D_tiled,:,:] - - # normalization for FFT - norma = emf_buffer.shape[1] - # get the spots intensity - I = np.abs(np.fft.fft2(emf_buffer)/norma)**2 - - # if FoV is extended, zero pad the spot intensity - if self.is_extended: - I = np.pad(I,[[0,0],[self.extra_pixel,self.extra_pixel],[self.extra_pixel,self.extra_pixel]]) - - # if lgs convolve with gaussian - if self.is_LGS: - I = np.real(np.fft.ifft2(np.fft.fft2(I)*self.lgs_exp)) - # bin the 2D spots arrays - self.maps_intensity = (bin_ndarray(I, [I.shape[0],self.n_pix_subap,self.n_pix_subap], operation='sum')) - # add photon/readout noise to 2D spots - if self.cam.photonNoise!=0: - self.maps_intensity = self.random_state_photon_noise.poisson(self.maps_intensity) - - if self.cam.readoutNoise!=0: - self.maps_intensity += np.int64(np.round(self.random_state_readout_noise.randn(self.maps_intensity.shape[0],self.maps_intensity.shape[1],self.maps_intensity.shape[2])*self.cam.readoutNoise)) - - # fill up camera frame if requested (default is False) - if self.get_camera_frame_multi is True: - self.compute_camera_frame_multi(self.maps_intensity) - - # normalization for centroid computation - norma = np.sum(np.sum(self.maps_intensity,axis=1),axis=1) - - # centroid computation - self.centroid_lenslets = self.centroid(self.maps_intensity,self.threshold_cog) - - # re-organization of signals according to number of wavefronts considered - self.signal_2D = np.zeros([self.phase_buffer.shape[0],self.nSubap*2,self.nSubap]) - - for i in range(self.phase_buffer.shape[0]): - self.SX[self.validLenslets_x,self.validLenslets_y] = self.centroid_lenslets[i*self.nValidSubaperture:(i+1)*self.nValidSubaperture,0] - self.SY[self.validLenslets_x,self.validLenslets_y] = self.centroid_lenslets[i*self.nValidSubaperture:(i+1)*self.nValidSubaperture,1] - signal_2D = np.concatenate((self.SX,self.SY)) - self.reference_slopes_maps - signal_2D[~self.valid_slopes_maps] = 0 - self.signal_2D[i,:,:] = signal_2D/self.slopes_units - - self.signal = self.signal_2D[:,self.valid_slopes_maps].T - # assign camera_fram to sh.cam.frame - self*self.cam - - else: - ##%%%%%%%%%%%% GEOMETRIC SH WFS %%%%%%%%%%%% - if np.ndim(self.telescope.src.phase)==2: - self.camera_frame = np.zeros([self.n_pix_subap*(self.nSubap)//self.binning_factor,self.n_pix_subap*(self.nSubap)//self.binning_factor], dtype =float) - - self.signal_2D = self.lenslet_propagation_geometric(self.telescope.src.phase_no_pupil)*self.valid_slopes_maps/self.slopes_units - - self.signal = self.signal_2D[self.valid_slopes_maps] - - self*self.cam - - else: - self.phase_buffer = np.moveaxis(self.telescope.src.phase_no_pupil,-1,0) - - def compute_geometric_signals(): - Q=Parallel(n_jobs=1,prefer='processes')(delayed(self.lenslet_propagation_geometric)(i) for i in self.phase_buffer) - return Q - maps = compute_geometric_signals() - self.signal_2D = np.asarray(maps)/self.slopes_units - self.signal = self.signal_2D[:,self.valid_slopes_maps].T - - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% WFS PROPERTIES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - @property - def is_geometric(self): - return self._is_geometric - - @is_geometric.setter - def is_geometric(self,val): - self._is_geometric = val - if hasattr(self,'isInitialized'): - if self.isInitialized: - print('Re-initializing WFS...') - self.initialize_wfs() - - @property - def C(self): - return self._C - - @C.setter - def C(self,val): - self._C = val - if hasattr(self,'isInitialized'): - if self.isInitialized: - print('Re-initializing WFS...') - self.initialize_wfs() - @property - def lightRatio(self): - return self._lightRatio - - @lightRatio.setter - def lightRatio(self,val): - self._lightRatio = val - if hasattr(self,'isInitialized'): - if self.isInitialized: - print('Selecting valid subapertures based on flux considerations..') - - self.valid_subapertures = np.reshape(self.photon_per_subaperture >= self.lightRatio*np.max(self.photon_per_subaperture), [self.nSubap,self.nSubap]) - - self.valid_subapertures_1D = np.reshape(self.valid_subapertures,[self.nSubap**2]) - - [self.validLenslets_x , self.validLenslets_y] = np.where(self.photon_per_subaperture_2D >= self.lightRatio*np.max(self.photon_per_subaperture)) - - # index of valid slopes X and Y - self.valid_slopes_maps = np.concatenate((self.valid_subapertures,self.valid_subapertures)) - - # number of valid lenslet - self.nValidSubaperture = int(np.sum(self.valid_subapertures)) - - self.nSignal = 2*self.nValidSubaperture - - print('Re-initializing WFS...') - self.initialize_wfs() - print('Done!') -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% WFS INTERACTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - def __mul__(self,obj): - if obj.tag=='detector': - obj.frame = self.camera_frame - else: - print('Error light propagated to the wrong type of object') - return -1 - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - def show(self): - attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) - print(self.tag+':') - for a in attributes: - if not(a[0].startswith('__') and a[0].endswith('__')): - if not(a[0].startswith('_')): - if not np.shape(a[1]): - tmp=a[1] - try: - print(' '+str(a[0])+': '+str(tmp.tag)+' object') - except: - print(' '+str(a[0])+': '+str(a[1])) - else: - if np.ndim(a[1])>1: - print(' '+str(a[0])+': '+str(np.shape(a[1]))) - - +# -*- coding: utf-8 -*- +""" +Created on Thu May 20 17:52:09 2021 + +@author: cheritie +""" + +import inspect +import time + +import numpy as np + +from .Detector import Detector +from .tools.tools import bin_ndarray + +try: + from joblib import Parallel, delayed +except: + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('WARNING: The joblib module is not installed. This would speed up considerably the operations.') + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + +# import ctypes +# try : +# mkl_rt = ctypes.CDLL('libmkl_rt.so') +# mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads +# mkl_set_num_threads(6) +# except: +# try: +# mkl_rt = ctypes.CDLL('./mkl_rt.dll') +# mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads +# mkl_set_num_threads(6) +# except: +# print('Could not optimize the parallelisation of the code ') + + +class ShackHartmann: + def __init__(self,nSubap,telescope,lightRatio,threshold_cog = 0.01,is_geometric = False, binning_factor = 1,padding_extension_factor = 1, threshold_convolution = 0.05): + """ + ************************** REQUIRED PARAMETERS ************************** + + A Shack Hartmann object consists in defining a 2D grd of lenslet arrays located at the pupil plane of the telescope to estimate the local tip/tilt seen by each lenslet. + By default the Shack Hartmann detector is considered to be noise-free (for calibration purposes). These properties can be switched on and off on the fly (see properties) + It requires the following parameters: + _ nSubap : the number of subapertures (ie the diameter of the Pyramid Pupils in pixels) + _ telescope : the telescope object to which the Shack Hartmann is associated. This object carries the phase, flux and pupil information + _ lightRatio : criterion to select the valid subaperture based on flux considerations + + + ************************** OPTIONAL PARAMETERS ************************** + + _ threshold_cog : threshold (with respect to the maximum value of the image) to apply to compute the center of gravity of the spots + _ is_geometric : if True, enables the geometric Shack Hartmann (direct measurement of gradient) if False, the diffractive computation is considered (default) + _ binning_factor : binning factor of the detector -- default Value is 1 + + # LGS related properties + _ padding_extension_factor : zero-padding factor oon the spots intensity images. This is a fast way to provide a larger field of view before the convolution with LGS spots is achieved and allow to prevent wrapping effects + _threshold_convolution : threshold considered to force the gaussian spots (elungated spots) to go to zero on the edges + + ************************** PROPAGATING THE LIGHT TO THE SH OBJECT ************************** + The light can be propagated from a telescope object tel through the Shack Hartmann object wfs using the * operator: + _ tel*wfs + This operation will trigger: + _ propagation of the tel.src light through the Shack Hartmann detector (phase and flux) + _ binning of the SH signals + _ addition of eventual photon noise and readout noise + _ computation of the Shack Hartmann signals + + + ************************** PROPERTIES ************************** + + The main properties of a Telescope object are listed here: + _ wfs.signal : signal measured by the Shack Hartmann + _ wfs.signal_2D : 2D map of the signal measured by the Shack Hartmann + _ wfs.random_state_photon_noise : a random state cycle can be defined to reproduces random sequences of noise -- default is based on the current clock time + _ wfs.random_state_readout_noise : a random state cycle can be defined to reproduces random sequences of noise -- default is based on the current clock time + _ wfs.random_state_background : a random state cycle can be defined to reproduces random sequences of noise -- default is based on the current clock time + _ wfs.fov_lenslet_arcsec : Field of View of the subapertures in arcsec + _ wfs.fov_pixel_binned_arcsec : Field of View of the pixel in arcsec + + the following properties can be updated on the fly: + _ wfs.modulation : update the modulation radius and update the reference signal + _ wfs.cam.photonNoise : Photon noise can be set to True or False + _ wfs.cam.readoutNoise : Readout noise can be set to True or False + _ wfs.backgroundNoise : Background noise can be set to True or False + _ wfs.lightRatio : reset the valid subaperture selection considering the new value + + """ + self.tag = 'shackHartmann' + self.telescope = telescope + self.is_geometric = is_geometric + self.nSubap = nSubap + self.lightRatio = lightRatio + self.binning_factor = binning_factor + self.zero_padding = 2 + self.padding_extension_factor = padding_extension_factor + self.threshold_convolution = threshold_convolution + self.threshold_cog = threshold_cog + + + # case where the spots are zeropadded to provide larger fOV + if padding_extension_factor>2: + self.n_pix_subap = int(padding_extension_factor*self.telescope.resolution// self.nSubap) + self.is_extended = True + self.binning_factor = padding_extension_factor + self.zero_padding = 1 + else: + self.n_pix_subap = self.telescope.resolution// self.nSubap + self.is_extended = False + + + # different resolutions needed + self.n_pix_subap_init = self.telescope.resolution// self.nSubap + self.extra_pixel = (self.n_pix_subap-self.n_pix_subap_init)//2 + self.n_pix_lenslet_init = self.n_pix_subap_init*self.zero_padding + self.n_pix_lenslet = self.n_pix_subap*self.zero_padding + self.center = self.n_pix_lenslet//2 + self.center_init = self.n_pix_lenslet_init//2 + self.lenslet_frame = np.zeros([self.n_pix_subap*self.zero_padding,self.n_pix_subap*self.zero_padding], dtype =complex) + self.outerMask = np.ones([self.n_pix_subap_init*self.zero_padding, self.n_pix_subap_init*self.zero_padding ]) + self.outerMask[1:-1,1:-1] = 0 + + # Compute camera frame in case of multiple measurements + self.get_camera_frame_multi = False + # detector camera + self.cam = Detector(round(nSubap*self.n_pix_subap)) # WFS detector object + self.cam.photonNoise = 0 + self.cam.readoutNoise = 0 # single lenslet + # noies random states + self.random_state_photon_noise = np.random.RandomState(seed=int(time.time())) # random states to reproduce sequences of noise + self.random_state_readout_noise = np.random.RandomState(seed=int(time.time())) # random states to reproduce sequences of noise + self.random_state_background = np.random.RandomState(seed=int(time.time())) # random states to reproduce sequences of noise + + # field of views + self.fov_lenslet_arcsec = self.n_pix_subap*206265*self.binning_factor/self.padding_extension_factor*self.telescope.src.wavelength/(self.telescope.D/self.nSubap) + self.fov_pixel_arcsec = self.fov_lenslet_arcsec/ self.n_pix_subap + self.fov_pixel_binned_arcsec = self.fov_lenslet_arcsec/ self.n_pix_subap_init + + X_map, Y_map= np.meshgrid(np.arange(self.n_pix_subap//self.binning_factor),np.arange(self.n_pix_subap//self.binning_factor)) + self.X_coord_map = np.atleast_3d(X_map).T + self.Y_coord_map = np.atleast_3d(Y_map).T + + if telescope.src.type == 'LGS': + self.is_LGS = True + else: + self.is_LGS = False + + # joblib parameter + self.nJobs = 1 + self.joblib_prefer = 'processes' + + # camera frame + self.camera_frame = np.zeros([self.n_pix_subap*(self.nSubap)//self.binning_factor,self.n_pix_subap*(self.nSubap)//self.binning_factor], dtype =float) + + # cube of lenslet zero padded + self.cube = np.zeros([self.nSubap**2,self.n_pix_lenslet_init,self.n_pix_lenslet_init]) + self.cube_flux = np.zeros([self.nSubap**2,self.n_pix_subap_init,self.n_pix_subap_init],dtype=(complex)) + self.index_x = [] + self.index_y = [] + + # phasor to center spots in the center of the lenslets + [xx,yy] = np.meshgrid(np.linspace(0,self.n_pix_lenslet_init-1,self.n_pix_lenslet_init),np.linspace(0,self.n_pix_lenslet_init-1,self.n_pix_lenslet_init)) + self.phasor = np.exp(-(1j*np.pi*(self.n_pix_lenslet_init+1)/self.n_pix_lenslet_init)*(xx+yy)) + self.phasor_tiled = np.moveaxis(np.tile(self.phasor[:,:,None],self.nSubap**2),2,0) + + # Get subapertures index and flux per subaperture + [xx,yy] = np.meshgrid(np.linspace(0,self.n_pix_lenslet-1,self.n_pix_lenslet),np.linspace(0,self.n_pix_lenslet-1,self.n_pix_lenslet)) + self.phasor_expanded = np.exp(-(1j*np.pi*(self.n_pix_lenslet+1)/self.n_pix_lenslet)*(xx+yy)) + self.phasor_expanded_tiled = np.moveaxis(np.tile(self.phasor_expanded[:,:,None],self.nSubap**2),2,0) + + self.initialize_flux() + for i in range(self.nSubap): + for j in range(self.nSubap): + self.index_x.append(i) + self.index_y.append(j) + + self.current_nPhoton = self.telescope.src.nPhoton + self.index_x = np.asarray(self.index_x) + self.index_y = np.asarray(self.index_y) + + print('Selecting valid subapertures based on flux considerations..') + + self.photon_per_subaperture_2D = np.reshape(self.photon_per_subaperture,[self.nSubap,self.nSubap]) + + self.valid_subapertures = np.reshape(self.photon_per_subaperture >= self.lightRatio*np.max(self.photon_per_subaperture), [self.nSubap,self.nSubap]) + + self.valid_subapertures_1D = np.reshape(self.valid_subapertures,[self.nSubap**2]) + + [self.validLenslets_x , self.validLenslets_y] = np.where(self.photon_per_subaperture_2D >= self.lightRatio*np.max(self.photon_per_subaperture)) + + # index of valid slopes X and Y + self.valid_slopes_maps = np.concatenate((self.valid_subapertures,self.valid_subapertures)) + + # number of valid lenslet + self.nValidSubaperture = int(np.sum(self.valid_subapertures)) + + self.nSignal = 2*self.nValidSubaperture + + + if self.is_LGS: + self.get_convolution_spot() + + # WFS initialization + self.initialize_wfs() + + + + + def initialize_wfs(self): + self.isInitialized = False + + readoutNoise = np.copy(self.cam.readoutNoise) + photonNoise = np.copy(self.cam.photonNoise) + + self.cam.photonNoise = 0 + self.cam.readoutNoise = 0 + + # reference signal + self.sx0 = np.zeros([self.nSubap,self.nSubap]) + self.sy0 = np.zeros([self.nSubap,self.nSubap]) + # signal vector + self.sx = np.zeros([self.nSubap,self.nSubap]) + self.sy = np.zeros([self.nSubap,self.nSubap]) + # signal map + self.SX = np.zeros([self.nSubap,self.nSubap]) + self.SY = np.zeros([self.nSubap,self.nSubap]) + # flux per subaperture + self.reference_slopes_maps = np.zeros([self.nSubap*2,self.nSubap]) + self.slopes_units = 1 + print('Acquiring reference slopes..') + self.telescope.resetOPD() + self.wfs_measure() + self.reference_slopes_maps = np.copy(self.signal_2D) + self.isInitialized = True + print('Done!') + + print('Setting slopes units..') + [Tip,Tilt] = np.meshgrid(np.linspace(0,self.telescope.resolution-1,self.telescope.resolution),np.linspace(0,self.telescope.resolution-1,self.telescope.resolution)) + # normalize to 2 pi p2v + Tip = (((Tip/Tip.max())-0.5)*2*np.pi) + mean_slope = np.zeros(5) + amp = 1e-9 + for i in range(5): + self.telescope.OPD = self.telescope.pupil*Tip*(i-2)*amp + self.telescope.OPD_no_pupil = Tip*(i-2)*amp + + self.wfs_measure() + mean_slope[i] = np.mean(self.signal[:self.nValidSubaperture]) + self.p = np.polyfit(np.linspace(-2,2,5)*amp,mean_slope,deg = 1) + self.slopes_units = np.abs(self.p[0]) + print('Done!') + self.cam.photonNoise = readoutNoise + self.cam.readoutNoise = photonNoise + self.telescope.resetOPD() + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% SHACK HARTMANN WFS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('{: ^20s}'.format('Subapertures') + '{: ^18s}'.format(str(self.nSubap)) ) + print('{: ^20s}'.format('Subaperture Size') + '{: ^18s}'.format(str(np.round(self.telescope.D/self.nSubap,2))) +'{: ^18s}'.format('[m]' )) + print('{: ^20s}'.format('Pixel FoV') + '{: ^18s}'.format(str(np.round(self.fov_pixel_binned_arcsec,2))) +'{: ^18s}'.format('[arcsec]' )) + print('{: ^20s}'.format('Subapertue FoV') + '{: ^18s}'.format(str(np.round(self.fov_lenslet_arcsec,2))) +'{: ^18s}'.format('[arcsec]' )) + print('{: ^20s}'.format('Valid Subaperture') + '{: ^18s}'.format(str(str(self.nValidSubaperture)))) + if self.is_LGS: + print('{: ^20s}'.format('Spot Elungation') + '{: ^18s}'.format(str(100*np.round(self.elungation_factor,3))) +'{: ^18s}'.format('% of a subap' )) + print('{: ^20s}'.format('Geometric WFS') + '{: ^18s}'.format(str(self.is_geometric))) + + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + if self.is_geometric: + print('WARNING: THE PHOTON AND READOUT NOISE ARE NOT CONSIDERED FOR GEOMETRIC SH-WFS') + + def centroid(self,image,threshold =0.01): + im = np.atleast_3d(image.copy()) + im[im<(threshold*im.max())] = 0 + centroid_out = np.zeros([im.shape[0],2]) + X_map, Y_map= np.meshgrid(np.arange(im.shape[1]),np.arange(im.shape[2])) + X_coord_map = np.atleast_3d(X_map).T + Y_coord_map = np.atleast_3d(Y_map).T + norma = np.sum(np.sum(im,axis=1),axis=1) + centroid_out[:,0] = np.sum(np.sum(im*X_coord_map,axis=1),axis=1)/norma + centroid_out[:,1] = np.sum(np.sum(im*Y_coord_map,axis=1),axis=1)/norma + return centroid_out +#%% DIFFRACTIVE + + def initialize_flux(self,input_flux_map = None): + if self.telescope.tag!='asterism': + if input_flux_map is None: + input_flux_map = self.telescope.src.fluxMap.T + tmp_flux_h_split = np.hsplit(input_flux_map,self.nSubap) + self.cube_flux = np.zeros([self.nSubap**2,self.n_pix_lenslet_init,self.n_pix_lenslet_init],dtype=float) + for i in range(self.nSubap): + tmp_flux_v_split = np.vsplit(tmp_flux_h_split[i],self.nSubap) + self.cube_flux[i*self.nSubap:(i+1)*self.nSubap,self.center_init - self.n_pix_subap_init//2:self.center_init+self.n_pix_subap_init//2,self.center_init - self.n_pix_subap_init//2:self.center_init+self.n_pix_subap_init//2] = np.asarray(tmp_flux_v_split) + self.photon_per_subaperture = np.apply_over_axes(np.sum, self.cube_flux, [1,2]) + self.current_nPhoton = self.telescope.src.nPhoton + return + + def get_lenslet_em_field(self,phase): + tmp_phase_h_split = np.hsplit(phase.T,self.nSubap) + self.cube_em = np.zeros([self.nSubap**2,self.n_pix_lenslet_init,self.n_pix_lenslet_init],dtype=complex) + for i in range(self.nSubap): + tmp_phase_v_split = np.vsplit(tmp_phase_h_split[i],self.nSubap) + self.cube_em[i*self.nSubap:(i+1)*self.nSubap,self.center_init - self.n_pix_subap_init//2:self.center_init+self.n_pix_subap_init//2,self.center_init - self.n_pix_subap_init//2:self.center_init+self.n_pix_subap_init//2] = np.exp(1j*np.asarray(tmp_phase_v_split)) + self.cube_em*=np.sqrt(self.cube_flux)*self.phasor_tiled + return self.cube_em + + def fill_camera_frame(self,ind_x,ind_y,I,index_frame=None): + if index_frame is None: + self.camera_frame[ind_x*self.n_pix_subap//self.binning_factor:(ind_x+1)*self.n_pix_subap//self.binning_factor,ind_y*self.n_pix_subap//self.binning_factor:(ind_y+1)*self.n_pix_subap//self.binning_factor] = I + else: + self.camera_frame[index_frame,ind_x*self.n_pix_subap//self.binning_factor:(ind_x+1)*self.n_pix_subap//self.binning_factor,ind_y*self.n_pix_subap//self.binning_factor:(ind_y+1)*self.n_pix_subap//self.binning_factor] = I + + def compute_camera_frame_multi(self,maps_intensity): + self.ind_frame =np.zeros(maps_intensity.shape[0],dtype=(int)) + self.maps_intensity = maps_intensity + index_x = np.tile(self.index_x[self.valid_subapertures_1D],self.phase_buffer.shape[0]) + index_y = np.tile(self.index_y[self.valid_subapertures_1D],self.phase_buffer.shape[0]) + + for i in range(self.phase_buffer.shape[0]): + self.ind_frame[i*self.nValidSubaperture:(i+1)*self.nValidSubaperture]=i + + def joblib_fill_camera_frame(): + Q=Parallel(n_jobs=1,prefer='processes')(delayed(self.fill_camera_frame)(i,j,k,l) for i,j,k,l in zip(index_x,index_y,self.maps_intensity,self.ind_frame)) + return Q + + joblib_fill_camera_frame() + return + + #%% GEOMETRIC + + def gradient_2D(self,arr): + res_x = (np.gradient(arr,axis=0)/self.telescope.pixelSize)*self.telescope.pupil + res_y = (np.gradient(arr,axis=1)/self.telescope.pixelSize)*self.telescope.pupil + return res_x,res_y + + def lenslet_propagation_geometric(self,arr): + + [SLx,SLy] = self.gradient_2D(arr) + + sy = (bin_ndarray(SLx, [self.nSubap,self.nSubap], operation='sum')) + sx = (bin_ndarray(SLy, [self.nSubap,self.nSubap], operation='sum')) + + return np.concatenate((sx,sy)) + + #%% LGS + def get_convolution_spot(self): + # compute the projection of the LGS on the subaperture to simulate + # the spots elongation using a convulotion with gaussian spot + [X0,Y0] = [self.telescope.src.laser_coordinates[1],-self.telescope.src.laser_coordinates[0]] # coordinates of the LLT in [m] from the center (sign convention adjusted to match display position on camera) + + # 3D coordinates + coordinates_3D = np.zeros([3,len(self.telescope.src.Na_profile[0,:])]) + coordinates_3D_ref = np.zeros([3,len(self.telescope.src.Na_profile[0,:])]) + delta_dx = np.zeros([2,len(self.telescope.src.Na_profile[0,:])]) + delta_dy = np.zeros([2,len(self.telescope.src.Na_profile[0,:])]) + + # coordinates of the subapertures + + x_subap = np.linspace(-self.telescope.D//2,self.telescope.D//2,self.nSubap) + y_subap = np.linspace(-self.telescope.D//2,self.telescope.D//2,self.nSubap) + # pre-allocate memory for shift x and y to apply to the gaussian spot + + # number of pixel + n_pix = self.n_pix_lenslet + # size of a pixel in m + d_pix = (self.telescope.D/self.nSubap)/self.n_pix_lenslet_init + v = np.linspace(-n_pix*d_pix/2,n_pix*d_pix/2,n_pix) + [alpha_x,alpha_y] = np.meshgrid(v,v) + + # FWHM of gaussian converted into pixel in arcsec + sigma_spot = self.telescope.src.FWHM_spot_up/(2*np.sqrt(np.log(2))) + for i in range(len(self.telescope.src.Na_profile[0,:])): + coordinates_3D[:2,i] = (self.telescope.D/4)*([X0,Y0]/self.telescope.src.Na_profile[0,i]) + coordinates_3D[2,i] = self.telescope.D**2./(8.*self.telescope.src.Na_profile[0,i])/(2.*np.sqrt(3.)) + coordinates_3D_ref[:,i] = coordinates_3D[:,i]-coordinates_3D[:,len(self.telescope.src.Na_profile[0,:])//2] + C_elung = [] + C_gauss = [] + shift_x_buffer = [] + shift_y_buffer = [] + + C_gauss = [] + criterion_elungation = self.n_pix_lenslet*(self.telescope.D/self.nSubap)/self.n_pix_lenslet_init + + valid_subap_1D = np.copy(self.valid_subapertures_1D[:]) + count = -1 + # gaussian spot (for calibration) + I_gauss = (self.telescope.src.Na_profile[1,:][0]/(self.telescope.src.Na_profile[0,:][0]**2)) * np.exp(- ((alpha_x)**2 + (alpha_y)**2)/(2*sigma_spot**2)) + I_gauss /= I_gauss.sum() + + for i_subap in range(len(x_subap)): + for j_subap in range(len(y_subap)): + count += 1 + if valid_subap_1D[count]: + I = np.zeros([n_pix,n_pix],dtype=(complex)) + # I_gauss = np.zeros([n_pix,n_pix],dtype=(complex)) + shift_X = np.zeros(len(self.telescope.src.Na_profile[0,:])) + shift_Y = np.zeros(len(self.telescope.src.Na_profile[0,:])) + for i in range(len(self.telescope.src.Na_profile[0,:])): + coordinates_3D[:2,i] = (self.telescope.D/4)*([X0,Y0]/self.telescope.src.Na_profile[0,i]) + coordinates_3D[2,i] = self.telescope.D**2./(8.*self.telescope.src.Na_profile[0,i])/(2.*np.sqrt(3.)) + + coordinates_3D_ref[:,i] = coordinates_3D[:,i]-coordinates_3D[:,len(self.telescope.src.Na_profile[0,:])//2] + + delta_dx[0,i] = coordinates_3D_ref[0,i]*(4/self.telescope.D) + delta_dy[0,i] = coordinates_3D_ref[1,i]*(4/self.telescope.D) + + delta_dx[1,i] = coordinates_3D_ref[2,i]*(np.sqrt(3)*(4/self.telescope.D)**2)*x_subap[i_subap] + delta_dy[1,i] = coordinates_3D_ref[2,i]*(np.sqrt(3)*(4/self.telescope.D)**2)*y_subap[j_subap] + + # resulting shift + conversion from radians to pixels in m + shift_X[i] = 206265*self.fov_pixel_arcsec*(delta_dx[0,i] + delta_dx[1,i]) + shift_Y[i] = 206265*self.fov_pixel_arcsec*(delta_dy[0,i] + delta_dy[1,i]) + + + I_tmp = (self.telescope.src.Na_profile[1,:][i]/(self.telescope.src.Na_profile[0,:][i]**2))*np.exp(- ((alpha_x-shift_X[i])**2 + (alpha_y-shift_Y[i])**2)/(2*sigma_spot**2)) + + I += I_tmp + + # truncation of the wings of the gaussian + I[I<self.threshold_convolution*I.max()] = 0 + # normalization to conserve energy + I /= I.sum() + # save + shift_x_buffer.append(shift_X) + shift_y_buffer.append(shift_Y) + + C_elung.append((np.fft.fft2(I.T))) + C_gauss.append((np.fft.fft2(I_gauss.T))) + + self.shift_x_buffer = np.asarray(shift_x_buffer) + self.shift_y_buffer = np.asarray(shift_y_buffer) + + self.shift_x_max_arcsec = np.max(shift_x_buffer,axis=1) + self.shift_y_max_arcsec = np.max(shift_y_buffer,axis=1) + + self.shift_x_min_arcsec = np.min(shift_x_buffer,axis=1) + self.shift_y_min_arcsec = np.min(shift_y_buffer,axis=1) + + self.max_elung_x = np.max(self.shift_x_max_arcsec-self.shift_x_min_arcsec) + self.max_elung_y = np.max(self.shift_y_max_arcsec-self.shift_y_min_arcsec) + self.elungation_factor = np.max([self.max_elung_x,self.max_elung_y])/criterion_elungation + + if self.max_elung_x>criterion_elungation or self.max_elung_y>criterion_elungation: + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! WARNING !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') + print('Warning: The largest spot elongation is '+str(np.round(self.elungation_factor,3))+' times larger than a subaperture! Consider using a higher resolution or increasing the padding_extension_factor parameter') + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') + + self.C = np.asarray(C_elung.copy()) + self.C_gauss = np.asarray(C_gauss) + self.C_elung = np.asarray(C_elung) + + return + +#%% SH Measurement + def sh_measure(self,phase_in): + # backward compatibility with previous version + self.wfs_measure(phase_in=phase_in) + return + + def wfs_measure(self,phase_in = None): + if phase_in is not None: + self.telescope.src.phase = phase_in + + if self.current_nPhoton != self.telescope.src.nPhoton: + print('updating the flux of the SHWFS object') + self.initialize_flux() + + + if self.is_geometric is False: + ##%%%%%%%%%%%% DIFFRACTIVE SH WFS %%%%%%%%%%%% + if np.ndim(self.telescope.OPD)==2: + #-- case with a single wave-front to sense-- + + # reset camera frame to be filled up + self.camera_frame = np.zeros([self.n_pix_subap*(self.nSubap)//self.binning_factor,self.n_pix_subap*(self.nSubap)//self.binning_factor], dtype =float) + + # normalization for FFT + norma = self.cube.shape[1] + + # compute spot intensity + if self.telescope.spatialFilter is None: + phase = self.telescope.src.phase + self.initialize_flux() + + else: + phase = self.telescope.phase_filtered + self.initialize_flux(self.telescope.amplitude_filtered.T*self.telescope.src.fluxMap.T) + + + I = (np.abs(np.fft.fft2(np.asarray(self.get_lenslet_em_field(phase)),axes=[1,2])/norma)**2) + + # reduce to valid subaperture + I = I[self.valid_subapertures_1D,:,:] + + self.sum_I = np.sum(I,axis=0) + self.edge_subaperture_criterion = np.sum(I*self.outerMask)/np.sum(I) + if self.edge_subaperture_criterion>0.05: + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('WARNING !!!! THE LIGHT IN THE SUBAPERTURE IS MAYBE WRAPPING !!!'+str(np.round(100*self.edge_subaperture_criterion,1))+' % of the total flux detected on the edges of the subapertures. You may want to increase the seeing value or the resolution') + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + + # if FoV is extended, zero pad the spot intensity + if self.is_extended: + I = np.pad(I,[[0,0],[self.extra_pixel,self.extra_pixel],[self.extra_pixel,self.extra_pixel]]) + + # in case of LGS sensor, convolve with LGS spots to create spot elungation + if self.is_LGS: + I = np.fft.fftshift(np.abs((np.fft.ifft2(np.fft.fft2(I)*self.C))),axes = [1,2]) + + # bin the 2D spots intensity to get the desired number of pixel per subaperture + self.maps_intensity = bin_ndarray(I,[I.shape[0], self.n_pix_subap//self.binning_factor,self.n_pix_subap//self.binning_factor], operation='sum') + + # add photon/readout noise to 2D spots + if self.cam.photonNoise!=0: + self.maps_intensity = self.random_state_photon_noise.poisson(self.maps_intensity) + + if self.cam.readoutNoise!=0: + self.maps_intensity += np.int64(np.round(self.random_state_readout_noise.randn(self.maps_intensity.shape[0],self.maps_intensity.shape[1],self.maps_intensity.shape[2])*self.cam.readoutNoise)) + + # fill camera frame with computed intensity (only valid subapertures) + def joblib_fill_camera_frame(): + Q=Parallel(n_jobs=1,prefer='processes')(delayed(self.fill_camera_frame)(i,j,k) for i,j,k in zip(self.index_x[self.valid_subapertures_1D],self.index_y[self.valid_subapertures_1D],self.maps_intensity)) + return Q + joblib_fill_camera_frame() + + # compute the centroid on valid subaperture + self.centroid_lenslets = self.centroid(self.maps_intensity,self.threshold_cog) + + # discard nan and inf values + val_inf = np.where(np.isinf(self.centroid_lenslets)) + val_nan = np.where(np.isnan(self.centroid_lenslets)) + + if np.shape(val_inf)[1] !=0: + print('Warning! some subapertures are giving inf values!') + self.centroid_lenslets[np.where(np.isinf(self.centroid_lenslets))] = 0 + + if np.shape(val_nan)[1] !=0: + print('Warning! some subapertures are giving nan values!') + self.centroid_lenslets[np.where(np.isnan(self.centroid_lenslets))] = 0 + + # compute slopes-maps + self.SX[self.validLenslets_x,self.validLenslets_y] = self.centroid_lenslets[:,0] + self.SY[self.validLenslets_x,self.validLenslets_y] = self.centroid_lenslets[:,1] + + signal_2D = np.concatenate((self.SX,self.SY)) - self.reference_slopes_maps + signal_2D[~self.valid_slopes_maps] = 0 + self.signal_2D = signal_2D/self.slopes_units + self.signal = self.signal_2D[self.valid_slopes_maps] + + # assign camera_fram to sh.cam.frame + self*self.cam + else: + #-- case with multiple wave-fronts to sense-- + + # set phase buffer + self.phase_buffer = np.moveaxis(self.telescope.src.phase_no_pupil,-1,0) + + # tile the valid subaperture vector to match the number of input phase + valid_subap_1D_tiled = np.tile(self.valid_subapertures_1D,self.phase_buffer.shape[0]) + + # tile the valid LGS convolution spot to match the number of input phase + if self.is_LGS: + self.lgs_exp = np.tile(self.C,[self.phase_buffer.shape[0],1,1]) + + # reset camera frame + self.camera_frame = np.zeros([self.phase_buffer.shape[0],self.n_pix_subap*(self.nSubap)//self.binning_factor,self.n_pix_subap*(self.nSubap)//self.binning_factor], dtype =float) + + # compute 2D intensity for multiple input wavefronts + def compute_diffractive_signals_multi(): + Q=Parallel(n_jobs=1,prefer='processes')(delayed(self.get_lenslet_em_field)(i) for i in self.phase_buffer) + return Q + emf_buffer = np.reshape(np.asarray(compute_diffractive_signals_multi()),[self.phase_buffer.shape[0]*self.nSubap**2,self.n_pix_lenslet_init,self.n_pix_lenslet_init]) + + + # reduce to valid subaperture + emf_buffer = emf_buffer[valid_subap_1D_tiled,:,:] + + # normalization for FFT + norma = emf_buffer.shape[1] + # get the spots intensity + I = np.abs(np.fft.fft2(emf_buffer)/norma)**2 + + # if FoV is extended, zero pad the spot intensity + if self.is_extended: + I = np.pad(I,[[0,0],[self.extra_pixel,self.extra_pixel],[self.extra_pixel,self.extra_pixel]]) + + # if lgs convolve with gaussian + if self.is_LGS: + I = np.real(np.fft.ifft2(np.fft.fft2(I)*self.lgs_exp)) + # bin the 2D spots arrays + self.maps_intensity = (bin_ndarray(I, [I.shape[0],self.n_pix_subap,self.n_pix_subap], operation='sum')) + # add photon/readout noise to 2D spots + if self.cam.photonNoise!=0: + self.maps_intensity = self.random_state_photon_noise.poisson(self.maps_intensity) + + if self.cam.readoutNoise!=0: + self.maps_intensity += np.int64(np.round(self.random_state_readout_noise.randn(self.maps_intensity.shape[0],self.maps_intensity.shape[1],self.maps_intensity.shape[2])*self.cam.readoutNoise)) + + # fill up camera frame if requested (default is False) + if self.get_camera_frame_multi is True: + self.compute_camera_frame_multi(self.maps_intensity) + + # normalization for centroid computation + norma = np.sum(np.sum(self.maps_intensity,axis=1),axis=1) + + # centroid computation + self.centroid_lenslets = self.centroid(self.maps_intensity,self.threshold_cog) + + # re-organization of signals according to number of wavefronts considered + self.signal_2D = np.zeros([self.phase_buffer.shape[0],self.nSubap*2,self.nSubap]) + + for i in range(self.phase_buffer.shape[0]): + self.SX[self.validLenslets_x,self.validLenslets_y] = self.centroid_lenslets[i*self.nValidSubaperture:(i+1)*self.nValidSubaperture,0] + self.SY[self.validLenslets_x,self.validLenslets_y] = self.centroid_lenslets[i*self.nValidSubaperture:(i+1)*self.nValidSubaperture,1] + signal_2D = np.concatenate((self.SX,self.SY)) - self.reference_slopes_maps + signal_2D[~self.valid_slopes_maps] = 0 + self.signal_2D[i,:,:] = signal_2D/self.slopes_units + + self.signal = self.signal_2D[:,self.valid_slopes_maps].T + # assign camera_fram to sh.cam.frame + self*self.cam + + else: + ##%%%%%%%%%%%% GEOMETRIC SH WFS %%%%%%%%%%%% + if np.ndim(self.telescope.src.phase)==2: + self.camera_frame = np.zeros([self.n_pix_subap*(self.nSubap)//self.binning_factor,self.n_pix_subap*(self.nSubap)//self.binning_factor], dtype =float) + + self.signal_2D = self.lenslet_propagation_geometric(self.telescope.src.phase_no_pupil)*self.valid_slopes_maps/self.slopes_units + + self.signal = self.signal_2D[self.valid_slopes_maps] + + self*self.cam + + else: + self.phase_buffer = np.moveaxis(self.telescope.src.phase_no_pupil,-1,0) + + def compute_geometric_signals(): + Q=Parallel(n_jobs=1,prefer='processes')(delayed(self.lenslet_propagation_geometric)(i) for i in self.phase_buffer) + return Q + maps = compute_geometric_signals() + self.signal_2D = np.asarray(maps)/self.slopes_units + self.signal = self.signal_2D[:,self.valid_slopes_maps].T + + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% WFS PROPERTIES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + @property + def is_geometric(self): + return self._is_geometric + + @is_geometric.setter + def is_geometric(self,val): + self._is_geometric = val + if hasattr(self,'isInitialized'): + if self.isInitialized: + print('Re-initializing WFS...') + self.initialize_wfs() + + @property + def C(self): + return self._C + + @C.setter + def C(self,val): + self._C = val + if hasattr(self,'isInitialized'): + if self.isInitialized: + print('Re-initializing WFS...') + self.initialize_wfs() + @property + def lightRatio(self): + return self._lightRatio + + @lightRatio.setter + def lightRatio(self,val): + self._lightRatio = val + if hasattr(self,'isInitialized'): + if self.isInitialized: + print('Selecting valid subapertures based on flux considerations..') + + self.valid_subapertures = np.reshape(self.photon_per_subaperture >= self.lightRatio*np.max(self.photon_per_subaperture), [self.nSubap,self.nSubap]) + + self.valid_subapertures_1D = np.reshape(self.valid_subapertures,[self.nSubap**2]) + + [self.validLenslets_x , self.validLenslets_y] = np.where(self.photon_per_subaperture_2D >= self.lightRatio*np.max(self.photon_per_subaperture)) + + # index of valid slopes X and Y + self.valid_slopes_maps = np.concatenate((self.valid_subapertures,self.valid_subapertures)) + + # number of valid lenslet + self.nValidSubaperture = int(np.sum(self.valid_subapertures)) + + self.nSignal = 2*self.nValidSubaperture + + print('Re-initializing WFS...') + self.initialize_wfs() + print('Done!') +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% WFS INTERACTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + def __mul__(self,obj): + if obj.tag=='detector': + obj.frame = self.camera_frame + else: + print('Error light propagated to the wrong type of object') + return -1 + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + def show(self): + attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) + print(self.tag+':') + for a in attributes: + if not(a[0].startswith('__') and a[0].endswith('__')): + if not(a[0].startswith('_')): + if not np.shape(a[1]): + tmp=a[1] + try: + print(' '+str(a[0])+': '+str(tmp.tag)+' object') + except: + print(' '+str(a[0])+': '+str(a[1])) + else: + if np.ndim(a[1])>1: + print(' '+str(a[0])+': '+str(np.shape(a[1]))) + + diff --git a/AO_modules/Source.py b/OOPAO/Source.py old mode 100755 new mode 100644 similarity index 98% rename from AO_modules/Source.py rename to OOPAO/Source.py index 161d879..e3f49c0 --- a/AO_modules/Source.py +++ b/OOPAO/Source.py @@ -1,199 +1,202 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Feb 19 10:32:15 2020 - -@author: cheritie -""" -import numpy as np -import inspect -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CLASS INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -class Source: - def __init__(self,optBand,magnitude,coordinates = [0,0],altitude = np.inf, laser_coordinates = [0,0] ,Na_profile = None,FWHM_spot_up = None,display_properties=True): - """ - ************************** REQUIRED PARAMETERS ************************** - - A Source object is characterised by two parameter: - _ optBand : the optical band of the source (see the method photometry) - _ magnitude : The magnitude of the star - - ************************** COUPLING A SOURCE OBJECT ************************** - - Once generated, a Source object "src" can be coupled to a Telescope "tel" that contains the OPD. - _ This is achieved using the * operator : src*tel - _ It can be accessed using : tel.src - - - ************************** MAIN PROPERTIES ************************** - - The main properties of a Source object are listed here: - _ src.phase : 2D map of the phase scaled to the src wavelength corresponding to tel.OPD - _ src.type : Ngs or LGS - - _ src.nPhoton : number of photons per m2 per s. if this property is changed after the initialization, the magnitude is automatically updated to the right value. - _ src.fluxMap : 2D map of the number of photons per pixel per frame (depends on the loop frequency defined by tel.samplingTime) - _ src.display_properties : display the properties of the src object - ************************** OPTIONAL PROPERTIES ************************** - _ altitude : altitude of the source. Default is inf (NGS) - _ laser_coordinates : The coordinates in [m] of the laser launch telescope - _ Na_profile : An array of 2 dimensions and n sampling points for the Sodium profile. The first dimension corresponds to the altitude and the second dimention to the sodium profile value. - _ FWHM_spot_up : FWHM of the LGS spot in [arcsec] - ************************** EXEMPLE ************************** - - Create a source object in H band with a magnitude 8 and combine it to the telescope - src = Source(opticalBand = 'H', magnitude = 8) - src*tel - - - """ -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - self.is_initialized = False - self.display_properties = display_properties - tmp = self.photometry(optBand) # get the photometry properties - self.optBand = optBand # optical band - self.wavelength = tmp[0] # wavelength in m - self.bandwidth = tmp[1] # optical bandwidth - self.zeroPoint = tmp[2]/368 # zero point - self.magnitude = magnitude # magnitude - self.phase = [] # phase of the source - self.phase_no_pupil = [] # phase of the source (no pupil) - self.fluxMap = [] # 2D flux map of the source - self.nPhoton = self.zeroPoint*10**(-0.4*magnitude) # number of photon per m2 per s - self.tag = 'source' # tag of the object - self.altitude = altitude # altitude of the source object in m - self.coordinates = coordinates # polar coordinates [r,theta] - self.laser_coordinates = laser_coordinates # Laser Launch Telescope coordinates in [m] - - if Na_profile is not None and FWHM_spot_up is not None: - self.Na_profile = Na_profile - self.FWHM_spot_up = FWHM_spot_up - - # consider the altitude weigthed by Na profile - self.altitude = np.sum(Na_profile[0,:]*Na_profile[1,:]) - self.type = 'LGS' - else: - - self.type = 'NGS' - if self.display_properties: - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% SOURCE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('{: ^18s}'.format('Source') +'{: ^18s}'.format('Wavelength')+ '{: ^18s}'.format('Zenith [arcsec]')+ '{: ^18s}'.format('Azimuth [deg]')+ '{: ^18s}'.format('Altitude [m]')+ '{: ^18s}'.format('Magnitude') + '{: ^18s}'.format('Flux [phot/m2/s]') ) - print('------------------------------------------------------------------------------------------------------------------------------') - - print('{: ^18s}'.format(self.type) +'{: ^18s}'.format(str(self.wavelength))+ '{: ^18s}'.format(str(self.coordinates[0]))+ '{: ^18s}'.format(str(self.coordinates[1]))+'{: ^18s}'.format(str(np.round(self.altitude,2)))+ '{: ^18s}'.format(str(self.magnitude))+'{: ^18s}'.format(str(np.round(self.nPhoton,1))) ) - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - self.is_initialized = True - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% SOURCE INTERACTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - def __mul__(self,telescope): - telescope.src = self - telescope.resetOPD() - # update the phase of the source - self.phase = telescope.OPD*2*np.pi/self.wavelength - self.phase_no_pupil = telescope.OPD_no_pupil*2*np.pi/self.wavelength - - # compute the variance in the pupil - self.var = np.var(self.phase[np.where(telescope.pupil==1)]) - # assign the source object to the telescope object - - self.fluxMap = telescope.pupilReflectivity*self.nPhoton*telescope.samplingTime*(telescope.D/telescope.resolution)**2 - - return telescope - - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% SOURCE PHOTOMETRY %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - def photometry(self,arg): - # photometry object [wavelength, bandwidth, zeroPoint] - class phot: - pass - - phot.U = [ 0.360e-6 , 0.070e-6 , 2.0e12 ] - phot.B = [ 0.440e-6 , 0.100e-6 , 5.4e12 ] - phot.V0 = [ 0.500e-6 , 0.090e-6 , 3.3e12 ] - phot.V = [ 0.550e-6 , 0.090e-6 , 3.3e12 ] - phot.R = [ 0.640e-6 , 0.150e-6 , 4.0e12 ] - phot.I = [ 0.790e-6 , 0.150e-6 , 2.7e12 ] - phot.I1 = [ 0.700e-6 , 0.033e-6 , 2.7e12 ] - phot.I2 = [ 0.750e-6 , 0.033e-6 , 2.7e12 ] - phot.I3 = [ 0.800e-6 , 0.033e-6 , 2.7e12 ] - phot.I4 = [ 0.700e-6 , 0.100e-6 , 2.7e12 ] - phot.I5 = [ 0.850e-6 , 0.100e-6 , 2.7e12 ] - phot.I6 = [ 1.000e-6 , 0.100e-6 , 2.7e12 ] - phot.I7 = [ 0.850e-6 , 0.300e-6 , 2.7e12 ] - phot.R2 = [ 0.650e-6 , 0.300e-6 , 7.92e12 ] - phot.R3 = [ 0.600e-6 , 0.300e-6 , 7.92e12 ] - phot.R4 = [ 0.670e-6 , 0.300e-6 , 7.92e12 ] - phot.I8 = [ 0.750e-6 , 0.100e-6 , 2.7e12 ] - phot.I9 = [ 0.850e-6 , 0.300e-6 , 7.36e12 ] - phot.I10 = [ 0.900e-6 , 0.300e-6 , 2.7e12 ] - phot.J = [ 1.215e-6 , 0.260e-6 , 1.9e12 ] - phot.H = [ 1.654e-6 , 0.290e-6 , 1.1e12 ] - phot.Kp = [ 2.1245e-6 , 0.351e-6 , 6e11 ] - phot.Ks = [ 2.157e-6 , 0.320e-6 , 5.5e11 ] - phot.K = [ 2.179e-6 , 0.410e-6 , 7.0e11 ] - phot.K0 = [ 2.000e-6 , 0.410e-6 , 7.0e11 ] - phot.K1 = [ 2.400e-6 , 0.410e-6 , 7.0e11 ] - - - phot.L = [ 3.547e-6 , 0.570e-6 , 2.5e11 ] - phot.M = [ 4.769e-6 , 0.450e-6 , 8.4e10 ] - phot.Na = [ 0.589e-6 , 0 , 3.3e12 ] - phot.EOS = [ 1.064e-6 , 0 , 3.3e12 ] - - if isinstance(arg,str): - if hasattr(phot,arg): - return getattr(phot,arg) - else: - print('Error: Wrong name for the photometry object') - return -1 - else: - print('Error: The photometry object takes a scalar as an input') - return -1 -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% SOURCE PROPERTIES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - @property - def nPhoton(self): - return self._nPhoton - - @nPhoton.setter - def nPhoton(self,val): - self._nPhoton = val - self.magnitude = -2.5*np.log10(val/self.zeroPoint) - if self.is_initialized: - - print('NGS flux updated!') - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% SOURCE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('Wavelength \t'+str(round(self.wavelength*1e6,3)) + ' \t [microns]') - print('Optical Band \t'+self.optBand) - print('Magnitude \t' + str(self.magnitude)) - print('Flux \t\t'+ str(np.round(self.nPhoton)) + str('\t [photons/m2/s]')) - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - -# @property -# def magnitude(self): -# return self._magnitude -# -# @magnitude.setter -# def magnitude(self,val): -# self._magnitude = val -# self.nPhoton = self.zeroPoint*10**(-0.4*val) -# -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - def show(self): - attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) - print(self.tag+':') - for a in attributes: - if not(a[0].startswith('__') and a[0].endswith('__')): - if not(a[0].startswith('_')): - if not np.shape(a[1]): - tmp=a[1] - try: - print(' '+str(a[0])+': '+str(tmp.tag)+' object') - except: - print(' '+str(a[0])+': '+str(a[1])) - else: - if np.ndim(a[1])>1: - print(' '+str(a[0])+': '+str(np.shape(a[1]))) - +# -*- coding: utf-8 -*- +""" +Created on Wed Feb 19 10:32:15 2020 + +@author: cheritie +""" +import inspect + +import numpy as np + + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CLASS INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +class Source: + def __init__(self,optBand,magnitude,coordinates = [0,0],altitude = np.inf, laser_coordinates = [0,0] ,Na_profile = None,FWHM_spot_up = None,display_properties=True): + """ + ************************** REQUIRED PARAMETERS ************************** + + A Source object is characterised by two parameter: + _ optBand : the optical band of the source (see the method photometry) + _ magnitude : The magnitude of the star + + ************************** COUPLING A SOURCE OBJECT ************************** + + Once generated, a Source object "src" can be coupled to a Telescope "tel" that contains the OPD. + _ This is achieved using the * operator : src*tel + _ It can be accessed using : tel.src + + + ************************** MAIN PROPERTIES ************************** + + The main properties of a Source object are listed here: + _ src.phase : 2D map of the phase scaled to the src wavelength corresponding to tel.OPD + _ src.type : Ngs or LGS + + _ src.nPhoton : number of photons per m2 per s. if this property is changed after the initialization, the magnitude is automatically updated to the right value. + _ src.fluxMap : 2D map of the number of photons per pixel per frame (depends on the loop frequency defined by tel.samplingTime) + _ src.display_properties : display the properties of the src object + ************************** OPTIONAL PROPERTIES ************************** + _ altitude : altitude of the source. Default is inf (NGS) + _ laser_coordinates : The coordinates in [m] of the laser launch telescope + _ Na_profile : An array of 2 dimensions and n sampling points for the Sodium profile. The first dimension corresponds to the altitude and the second dimention to the sodium profile value. + _ FWHM_spot_up : FWHM of the LGS spot in [arcsec] + ************************** EXEMPLE ************************** + + Create a source object in H band with a magnitude 8 and combine it to the telescope + src = Source(opticalBand = 'H', magnitude = 8) + src*tel + + + """ +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + self.is_initialized = False + self.display_properties = display_properties + tmp = self.photometry(optBand) # get the photometry properties + self.optBand = optBand # optical band + self.wavelength = tmp[0] # wavelength in m + self.bandwidth = tmp[1] # optical bandwidth + self.zeroPoint = tmp[2]/368 # zero point + self.magnitude = magnitude # magnitude + self.phase = [] # phase of the source + self.phase_no_pupil = [] # phase of the source (no pupil) + self.fluxMap = [] # 2D flux map of the source + self.nPhoton = self.zeroPoint*10**(-0.4*magnitude) # number of photon per m2 per s + self.tag = 'source' # tag of the object + self.altitude = altitude # altitude of the source object in m + self.coordinates = coordinates # polar coordinates [r,theta] + self.laser_coordinates = laser_coordinates # Laser Launch Telescope coordinates in [m] + + if Na_profile is not None and FWHM_spot_up is not None: + self.Na_profile = Na_profile + self.FWHM_spot_up = FWHM_spot_up + + # consider the altitude weigthed by Na profile + self.altitude = np.sum(Na_profile[0,:]*Na_profile[1,:]) + self.type = 'LGS' + else: + + self.type = 'NGS' + if self.display_properties: + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% SOURCE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('{: ^18s}'.format('Source') +'{: ^18s}'.format('Wavelength')+ '{: ^18s}'.format('Zenith [arcsec]')+ '{: ^18s}'.format('Azimuth [deg]')+ '{: ^18s}'.format('Altitude [m]')+ '{: ^18s}'.format('Magnitude') + '{: ^18s}'.format('Flux [phot/m2/s]') ) + print('------------------------------------------------------------------------------------------------------------------------------') + + print('{: ^18s}'.format(self.type) +'{: ^18s}'.format(str(self.wavelength))+ '{: ^18s}'.format(str(self.coordinates[0]))+ '{: ^18s}'.format(str(self.coordinates[1]))+'{: ^18s}'.format(str(np.round(self.altitude,2)))+ '{: ^18s}'.format(str(self.magnitude))+'{: ^18s}'.format(str(np.round(self.nPhoton,1))) ) + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + self.is_initialized = True + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% SOURCE INTERACTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + def __mul__(self,telescope): + telescope.src = self + telescope.resetOPD() + # update the phase of the source + self.phase = telescope.OPD*2*np.pi/self.wavelength + self.phase_no_pupil = telescope.OPD_no_pupil*2*np.pi/self.wavelength + + # compute the variance in the pupil + self.var = np.var(self.phase[np.where(telescope.pupil==1)]) + # assign the source object to the telescope object + + self.fluxMap = telescope.pupilReflectivity*self.nPhoton*telescope.samplingTime*(telescope.D/telescope.resolution)**2 + + return telescope + + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% SOURCE PHOTOMETRY %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + def photometry(self,arg): + # photometry object [wavelength, bandwidth, zeroPoint] + class phot: + pass + + phot.U = [ 0.360e-6 , 0.070e-6 , 2.0e12 ] + phot.B = [ 0.440e-6 , 0.100e-6 , 5.4e12 ] + phot.V0 = [ 0.500e-6 , 0.090e-6 , 3.3e12 ] + phot.V = [ 0.550e-6 , 0.090e-6 , 3.3e12 ] + phot.R = [ 0.640e-6 , 0.150e-6 , 4.0e12 ] + phot.I = [ 0.790e-6 , 0.150e-6 , 2.7e12 ] + phot.I1 = [ 0.700e-6 , 0.033e-6 , 2.7e12 ] + phot.I2 = [ 0.750e-6 , 0.033e-6 , 2.7e12 ] + phot.I3 = [ 0.800e-6 , 0.033e-6 , 2.7e12 ] + phot.I4 = [ 0.700e-6 , 0.100e-6 , 2.7e12 ] + phot.I5 = [ 0.850e-6 , 0.100e-6 , 2.7e12 ] + phot.I6 = [ 1.000e-6 , 0.100e-6 , 2.7e12 ] + phot.I7 = [ 0.850e-6 , 0.300e-6 , 2.7e12 ] + phot.R2 = [ 0.650e-6 , 0.300e-6 , 7.92e12 ] + phot.R3 = [ 0.600e-6 , 0.300e-6 , 7.92e12 ] + phot.R4 = [ 0.670e-6 , 0.300e-6 , 7.92e12 ] + phot.I8 = [ 0.750e-6 , 0.100e-6 , 2.7e12 ] + phot.I9 = [ 0.850e-6 , 0.300e-6 , 7.36e12 ] + phot.I10 = [ 0.900e-6 , 0.300e-6 , 2.7e12 ] + phot.J = [ 1.215e-6 , 0.260e-6 , 1.9e12 ] + phot.H = [ 1.654e-6 , 0.290e-6 , 1.1e12 ] + phot.Kp = [ 2.1245e-6 , 0.351e-6 , 6e11 ] + phot.Ks = [ 2.157e-6 , 0.320e-6 , 5.5e11 ] + phot.K = [ 2.179e-6 , 0.410e-6 , 7.0e11 ] + phot.K0 = [ 2.000e-6 , 0.410e-6 , 7.0e11 ] + phot.K1 = [ 2.400e-6 , 0.410e-6 , 7.0e11 ] + + + phot.L = [ 3.547e-6 , 0.570e-6 , 2.5e11 ] + phot.M = [ 4.769e-6 , 0.450e-6 , 8.4e10 ] + phot.Na = [ 0.589e-6 , 0 , 3.3e12 ] + phot.EOS = [ 1.064e-6 , 0 , 3.3e12 ] + + if isinstance(arg,str): + if hasattr(phot,arg): + return getattr(phot,arg) + else: + print('Error: Wrong name for the photometry object') + return -1 + else: + print('Error: The photometry object takes a scalar as an input') + return -1 +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% SOURCE PROPERTIES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + @property + def nPhoton(self): + return self._nPhoton + + @nPhoton.setter + def nPhoton(self,val): + self._nPhoton = val + self.magnitude = -2.5*np.log10(val/self.zeroPoint) + if self.is_initialized: + + print('NGS flux updated!') + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% SOURCE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('Wavelength \t'+str(round(self.wavelength*1e6,3)) + ' \t [microns]') + print('Optical Band \t'+self.optBand) + print('Magnitude \t' + str(self.magnitude)) + print('Flux \t\t'+ str(np.round(self.nPhoton)) + str('\t [photons/m2/s]')) + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + +# @property +# def magnitude(self): +# return self._magnitude +# +# @magnitude.setter +# def magnitude(self,val): +# self._magnitude = val +# self.nPhoton = self.zeroPoint*10**(-0.4*val) +# +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + def show(self): + attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) + print(self.tag+':') + for a in attributes: + if not(a[0].startswith('__') and a[0].endswith('__')): + if not(a[0].startswith('_')): + if not np.shape(a[1]): + tmp=a[1] + try: + print(' '+str(a[0])+': '+str(tmp.tag)+' object') + except: + print(' '+str(a[0])+': '+str(a[1])) + else: + if np.ndim(a[1])>1: + print(' '+str(a[0])+': '+str(np.shape(a[1]))) + diff --git a/AO_modules/Telescope.py b/OOPAO/Telescope.py old mode 100755 new mode 100644 similarity index 97% rename from AO_modules/Telescope.py rename to OOPAO/Telescope.py index d5ec76a..a614c63 --- a/AO_modules/Telescope.py +++ b/OOPAO/Telescope.py @@ -1,498 +1,498 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Feb 19 10:23:18 2020 - -@author: cheritie -""" -import matplotlib.pyplot as plt -import numpy as np -import numexpr as ne -import inspect -from joblib import Parallel, delayed - -from AO_modules.Source import Source - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CLASS INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -class Telescope: - - def __init__(self,resolution, diameter,samplingTime=0.001,centralObstruction = 0,fov = 0,pupil=None,pupilReflectivity=1): - """ - ************************** REQUIRED PARAMETERS ************************** - - A Telescope object consists in defining the 2D mask of the entrance pupil. It is mainly characterised by two parameters - _ resolution : the resolution of the pupil mask - _ diameter : The physical diameter of the telescope in [m] - - If no pupil mask is input, the default pupil geometry is circular. - - ************************** OPTIONAL PARAMETERS ************************** - - _ samplingTime : Defines the frequency of the AO loop. It is used in the Atmosphere object to update the turbulence phase screens according to the wind speed. - _ centralObstruction : Adds a central obstruction in percentage of diameter. - _ fov : Defines the Field of View of the Telescope object. This will be useful for off-axis targets but it hasn't been properly implemented yet. - _ pupil : A user-defined pupil mask can be input to the Telescope object. It should consist of a binary array. - _ pupilReflectivcty : Defines the reflectivity of the Telescope object. If not set to 1, it can be input as a 2D map of uneven reflectivy correspondong to the pupil mask. - This property can be set after the initialization of the Telescope object. - - ************************** ADDING SPIDERS ******************************* - It is possible to add spiders to the telescope pupil using the following property: - - tel.apply_spiders(angle,thickness_spider,offset_X = None, offset_Y=None) - - where : - - angle is a list of angle in [degrees]. The length of angle defines the number of spider. - - thickness is the width of the spider in [m] - - offset_X is a list (same lenght as angle) of shift X to apply to the individual spider in [m] - - offset_Y is a list (same lenght as angle) of shift Y to apply to the individual spider in [m] - - ************************** COUPLING A SOURCE OBJECT ************************** - - Once generated, the telescope should be coupled with a Source object "src" that contains the wavelength and flux properties of a target. - _ This is achieved using the * operator : src*tel - _ It can be accessed using : tel.src - _ By default, a Source object in the visible with a magnitude 0 is coupled to the telescope object - - ************************** COUPLING WITH AN ATMOSPHERE OBJECT ************************** - - The telescope can be coupled to an Atmosphere object. In that case, the OPD of the atmosphere is automatically added to the telescope object. - _ Coupling an Atmosphere and telescope Object : tel+atm - _ Separating an Atmosphere and telescope Object : tel-atm - - ************************** COMPUTING THE PSF ************************** - - 1) PSF computation - tel.computePSF(zeroPaddingFactor) : computes the square module of the Fourier transform of the tel.src.phase using the zeropadding factor for the FFT - - - ************************** MAIN PROPERTIES ************************** - - The main properties of a Telescope object are listed here: - _ tel.OPD : the optical path difference - _ tel.src.phase : 2D map of the phase scaled to the src wavelength corresponding to tel.OPD - _ tel.PSF : Point Spread Function corresponding to to the tel.src.phase. This is not automatically set, it requires to run tel.computePSF(). - - - ************************** EXEMPLE ************************** - - 1) Create an 8-m diameter circular telescope with a central obstruction of 15% and the pupil sampled with 100 pixels along the diameter. - tel = Telescope(resolution = 100, diameter = 8, centralObstruction = 0.15) - - 2) Create a source object in H band with a magnitude 8 and combine it to the telescope - src = Source(optBand = 'H', magnitude = 8) - src*tel - - 3) Compute the PSF with a zero-padding factor of 2. - tel.computePSF(zeroPaddingFactor = 2) - - """ -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - self.isInitialized = False # Resolution of the telescope - - self.resolution = resolution # Resolution of the telescope - self.D = diameter # Diameter in m - self.pixelSize = self.D/self.resolution # size of the pixels in m - self.centralObstruction = centralObstruction # central obstruction - self.fov = fov # Field of View - self.samplingTime = samplingTime # AO loop speed - self.isPetalFree = False # Flag to remove the petalling effect with ane ELT system. - self.index_pixel_petals = None # indexes of the pixels corresponfong to the M1 petals. They need to be set externally -# Case where the pupil is not input: circular pupil with central obstruction - if pupil is None: - D = self.resolution+1 - x = np.linspace(-self.resolution/2,self.resolution/2,self.resolution) - xx,yy = np.meshgrid(x,x) - circle = xx**2+yy**2 - obs = circle>=(self.centralObstruction*D/2)**2 - self.pupil = circle<(D/2)**2 - self.pupil = self.pupil*obs - else: - print('User-defined pupil, the central obstruction will not be taken into account...') - self.pupil = pupil - - self.pupilReflectivity = self.pupil.astype(float)*pupilReflectivity # A non uniform reflectivity can be input by the user - self.pixelArea = np.sum(self.pupil) # Total number of pixels in the pupil area - self.pupilLogical = np.where(np.reshape(self.pupil,resolution*resolution)>0) # index of valid pixels in the pupil - self.src = Source(optBand = 'V', magnitude = 0, display_properties=False) # temporary source object associated to the telescope object - self.OPD = self.pupil.astype(float) # set the initial OPD - self.OPD_no_pupil = 1+self.pupil.astype(float)*0 # set the initial OPD - self.em_field = self.pupilReflectivity*np.exp(1j*self.src.phase) - self.tag = 'telescope' # tag of the object - self.isPaired = False # indicate if telescope object is paired with an atmosphere object - self.spatialFilter = None - - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% TELESCOPE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('{: ^18s}'.format('Diameter') + '{: ^18s}'.format(str(self.D)) +'{: ^18s}'.format('[m]' )) - print('{: ^18s}'.format('Resolution') + '{: ^18s}'.format(str(self.resolution)) +'{: ^18s}'.format('[pixels]' )) - print('{: ^18s}'.format('Pixel Size') + '{: ^18s}'.format(str(np.round(self.pixelSize,2))) +'{: ^18s}'.format('[m]' )) - print('{: ^18s}'.format('Surface') + '{: ^18s}'.format(str(np.round(self.pixelArea*self.pixelSize**2))) +'{: ^18s}'.format('[m2]' )) - print('{: ^18s}'.format('Central Obstruction') + '{: ^18s}'.format(str(100*self.centralObstruction)) +'{: ^18s}'.format('[% of diameter]' )) - print('{: ^18s}'.format('Pixels in the pupil') + '{: ^18s}'.format(str(self.pixelArea)) +'{: ^18s}'.format('[pixels]' )) - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - self.isInitialized= True -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PSF COMPUTATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - def computePSF(self,zeroPaddingFactor=2): - if hasattr(self,'src'): - # number of pixel considered - N = int(zeroPaddingFactor * self.resolution) - center = N//2 - norma = N - - if self.spatialFilter is not None: - self.spatialFilter.set_spatial_filter(zeroPaddingFactor = zeroPaddingFactor) - mask = self.spatialFilter.mask - amp_mask = self.amplitude_filtered - phase = self.phase_filtered - else: - mask = 1 - amp_mask = 1 - phase = self.src.phase - - # zeroPadded support for the FFT - supportPadded = np.zeros([N,N],dtype='complex') - supportPadded [center-self.resolution//2:center+self.resolution//2,center-self.resolution//2:center+self.resolution//2] = amp_mask*self.pupil*self.pupilReflectivity*np.sqrt(self.src.fluxMap)*np.exp(1j*phase) - [xx,yy] = np.meshgrid(np.linspace(0,N-1,N),np.linspace(0,N-1,N)) - self.phasor = np.exp(-(1j*np.pi*(N+1)/N)*(xx+yy)) - - - - # axis in arcsec - self.xPSF_arcsec = [-206265*(self.src.wavelength/self.D) * (self.resolution/2), 206265*(self.src.wavelength/self.D) * (self.resolution/2)] - self.yPSF_arcsec = [-206265*(self.src.wavelength/self.D) * (self.resolution/2), 206265*(self.src.wavelength/self.D) * (self.resolution/2)] - - # axis in radians - self.xPSF_rad = [-(self.src.wavelength/self.D) * (self.resolution/2),(self.src.wavelength/self.D) * (self.resolution/2)] - self.yPSF_rad = [-(self.src.wavelength/self.D) * (self.resolution/2),(self.src.wavelength/self.D) * (self.resolution/2)] - - # PSF computation - self.PSF = (np.abs(np.fft.fft2(supportPadded*self.phasor)*mask/norma)**2) - self.PSF_norma = self.PSF/self.PSF.max() - N_trunc = int(np.floor(2*N/6)) - self.PSF_norma_zoom = self.PSF_norma[N_trunc:-N_trunc,N_trunc:-N_trunc] - - else: - print('Error: no NGS associated to the Telescope. Combine a tel object with an ngs using ngs*tel') - return -1 - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PSF DISPLAY %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - def showPSF(self,zoom = 1): - # display the full PSF or zoom on the core of the PSF - if hasattr(self, 'PSF'): - print('Displaying the PSF...') - else: - self.computePSF(6) - print('Displaying the PSF...') - - if zoom: - plt.imshow(self.PSF_trunc,extent = [self.xPSF_trunc[0],self.xPSF_trunc[1],self.xPSF_trunc[0],self.xPSF_trunc[1]]) - else: - plt.imshow(self.PSF,extent = [self.xPSF[0],self.xPSF[1],self.xPSF[0],self.xPSF[1]]) - plt.xlabel('[arcsec]') - plt.ylabel('[arcsec]') - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% TELESCOPE PROPERTIES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - @property - def pupil(self): - return self._pupil - - @pupil.setter - def pupil(self,val): - self._pupil = val.astype(int) - self.pixelArea = np.sum(self._pupil) - tmp = np.reshape(self._pupil,self.resolution**2) - self.pupilLogical = np.where(tmp>0) - self.pupilReflectivity = self.pupil.astype(float) - if self.isInitialized: - print('Warning!: A new pupil is now considered, its reflectivity is considered to be uniform. Assign the proper reflectivity map to tel.pupilReflectivity if required.') - - @property - def OPD(self): - return self._OPD - - @OPD.setter - def OPD(self,val): - self._OPD = val - if type(val) is not list: - if self.src.tag == 'source': - self.src.phase = self._OPD*2*np.pi/self.src.wavelength - if np.ndim(self.OPD)==2: - self.mean_removed_OPD = (self.OPD - np.mean(self.OPD[np.where(self.pupil ==1)]))*self.pupil - else: - if self.src.tag == 'asterism': - for i in range(self.src.n_source): - self.src.src[i].phase = self._OPD*2*np.pi/self.src.src[i].wavelength - else: - raise TypeError('The wrong object was attached to the telescope') - else: - if self.src.tag == 'asterism': - if len(self._OPD)==self.src.n_source: - for i in range(self.src.n_source): - self.src.src[i].phase = self._OPD[i]*2*np.pi/self.src.src[i].wavelength - else: - raise TypeError('A list of OPD cannnot be propagated to a single source') - - - @property - def OPD_no_pupil(self): - return self._OPD_no_pupil - - @OPD_no_pupil.setter - def OPD_no_pupil(self,val): - self._OPD_no_pupil = val - if type(val) is not list: - if self.src.tag == 'source': - self.src.phase_no_pupil = self._OPD_no_pupil*2*np.pi/self.src.wavelength - else: - if self.src.tag == 'asterism': - for i in range(self.src.n_source): - self.src.src[i].phase_no_pupil = self._OPD_no_pupil*2*np.pi/self.src.src[i].wavelength - else: - raise TypeError('The wrong object was attached to the telescope') - else: - if self.src.tag == 'asterism': - if len(self._OPD_no_pupil)==self.src.n_source: - for i in range(self.src.n_source): - self.src.src[i].phase_no_pupil = self._OPD_no_pupil[i]*2*np.pi/self.src.src[i].wavelength - else: - raise TypeError('The lenght of the OPD list ('+str(len(self._OPD_no_pupil))+') does not match the number of sources ('+str(self.src.n_source)+')') - - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% TELESCOPE INTERACTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - def __mul__(self,obj): - # case where multiple objects are considered - if type(obj) is list: - if type(self.OPD) is list: - if len(self.OPD) == len(obj): - for i_obj in range(len(self.OPD)): - tel_tmp = getattr(obj[i_obj], 'telescope') - self.src.src[i_obj]*tel_tmp - tel_tmp.OPD = self.OPD[i_obj] - tel_tmp.OPD_no_pupil = self.OPD_no_pupil[i_obj] - setattr(obj[i_obj],'telescope',tel_tmp) - obj[i_obj].wfs_measure(phase_in = tel_tmp.src.phase) # propagation of the telescope-source phase screen to the pyramid-detector - else: - raise ValueError('Error! There is a mis-match between the number of Sources ('+str(len(self.OPD))+') and the number of WFS ('+str(len(obj))+')') - else: - for i_obj in range(len(obj)): - self*obj[i_obj] - else: - # interaction with WFS object: Propagation of the phase screen - if obj.tag=='pyramid' or obj.tag == 'double_wfs' or obj.tag=='shackHartmann': - if type(self.OPD) is list: - raise ValueError('Error! There is a mis-match between the number of Sources ('+str(len(self.OPD))+') and the number of WFS (1)') - else: - obj.telescope=self # assign the telescope object to the pyramid-telescope object - obj.wfs_measure(phase_in = self.src.phase) # propagation of the telescope-source phase screen to the pyramid-detector - # interaction with detector: computation of the PSF - if obj.tag=='detector': - self.computePSF() - obj.frame = obj.rebin(self.PSF,(obj.resolution,obj.resolution)) - - if obj.tag=='spatialFilter': - self.spatialFilter = obj - N = obj.resolution - EF_in = np.zeros([N,N],dtype='complex') - EF_in [obj.center-self.resolution//2:obj.center+self.resolution//2,obj.center-self.resolution//2:obj.center+self.resolution//2] = self.pupilReflectivity*np.exp(1j*(self.OPD_no_pupil*2*np.pi/self.src.wavelength)) - FP_in = np.fft.fft2(EF_in) - FP_filtered = FP_in*np.fft.fftshift(obj.mask) - em_field = np.fft.ifft2(FP_filtered) - self.em_field_filtered = em_field[obj.center-self.resolution//2:obj.center+self.resolution//2,obj.center-self.resolution//2:obj.center+self.resolution//2] - # self.phase_filtered = np.arctan2(np.imag(self.em_field_filtered),np.real(self.em_field_filtered))*self.pupil - self.phase_filtered = ((np.angle(self.em_field_filtered)))*self.pupil - - self.amplitude_filtered = np.abs(self.em_field_filtered) - return self - - if obj.tag=='deformableMirror': - pupil = np.atleast_3d(self.pupil) - - if self.src.tag == 'source': - self.OPD_no_pupil = obj.dm_propagation(self) - if np.ndim(self.OPD_no_pupil) == 2: - self.OPD = self.OPD_no_pupil*self.pupil - else: - self.OPD =self.OPD_no_pupil*pupil - else: - if self.src.tag == 'asterism': - if len(self.OPD) == self.src.n_source: - for i in range(self.src.n_source): - if obj.altitude is not None: - self.OPD_no_pupil[i] = obj.dm_propagation(self,OPD_in = self.OPD_no_pupil[i], i_source = i ) - else: - self.OPD_no_pupil[i] = obj.dm_propagation(self,OPD_in = self.OPD_no_pupil[i] ) - if np.ndim(self.OPD_no_pupil[i]) == 2: - self.OPD[i] = self.OPD_no_pupil[i]*self.pupil - else: - self.OPD[i] =self.OPD_no_pupil[i]*pupil - self.OPD = self.OPD - self.OPD_no_pupil = self.OPD_no_pupil - - else: - raise TypeError('The lenght of the OPD list ('+str(len(self._OPD_no_pupil))+') does not match the number of sources ('+str(self.src.n_source)+')') - else: - raise TypeError('The wrong object was attached to the telescope') - return self -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% TELESCOPE METHODS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - def resetOPD(self): - # re-initialize the telescope OPD to a flat wavefront - if self.src.tag == 'asterism': - self.OPD = [self.pupil.astype(float) for i in range(self.src.n_source)] - self.OPD_no_pupil = [self.pupil.astype(float)*0 +1 for i in range(self.src.n_source)] - else: - self.OPD = self.pupil.astype(float) - self.OPD_no_pupil = 1+0*self.pupil.astype(float) - - def apply_spiders(self,angle,thickness_spider,offset_X = None, offset_Y=None): - pup = np.copy(self.pupil) - max_offset = self.centralObstruction*self.D/2 - thickness_spider/2 - if offset_X is None: - offset_X = np.zeros(len(angle)) - - if offset_Y is None: - offset_Y = np.zeros(len(angle)) - - if np.max(np.abs(offset_X))>=max_offset or np.max(np.abs(offset_Y))>max_offset: - print('WARNING ! The spider offsets are too large! Weird things could happen!') - for i in range(len(angle)): - angle_val = (angle[i]+90)%360 - x = np.linspace(-self.D/2,self.D/2,self.resolution) - [X,Y] = np.meshgrid(x,x) - X+=offset_X[i] - map_dist = np.abs(X*np.cos(np.deg2rad(angle_val)) + Y*np.sin(np.deg2rad(-angle_val))) - - if 0<=angle_val<90: - map_dist[:self.resolution//2,:] = thickness_spider - if 90<=angle_val<180: - map_dist[:,:self.resolution//2] = thickness_spider - if 180<=angle_val<270: - map_dist[self.resolution//2:,:] = thickness_spider - if 270<=angle_val<360: - map_dist[:,self.resolution//2:] = thickness_spider - pup*= map_dist>thickness_spider/2 - - self.pupil = pup - return - - def removePetalling(self,image = None): - try: - # remove the petalling from image if the index_pixel_petals have been computed externally - if image is None: - petalFreeOPD = np.copy(self.OPD) - if self.index_pixel_petals is None: - print('ERROR : the indexes of the petals have not been set yet!') - print('Returning the current OPD..') - return self.OPD - else: - try: - N = self.index_pixel_petals.shape[2] - # Case with N petals - for i in range(N): - meanValue = np.mean(petalFreeOPD[np.where(np.squeeze(self.index_pixel_petals[:,:,i])==1)]) - # residualValue = meanValue % (self.src.wavelength ) - residualValue = 0 - petalFreeOPD[np.where(np.squeeze(self.index_pixel_petals[:,:,i])==1)] += residualValue- meanValue - except: - # Case with 1 petal - petalFreeOPD[np.where(np.squeeze(self.index_pixel_petals)==1)] -= np.mean(petalFreeOPD[np.where(np.squeeze(self.index_pixel_petals)==1)]) - # update the OPD with the petal-free OPD - self.OPD = petalFreeOPD - else: - petalFreeOPD = image - try: - N = self.index_pixel_petals.shape[2] - for i in range(N): - petalFreeOPD[np.where(np.squeeze(self.index_pixel_petals[:,:,i])==1)]-=np.mean(petalFreeOPD[np.where(np.squeeze(self.index_pixel_petals[:,:,i])==1)]) - except: - petalFreeOPD[np.where(np.squeeze(self.index_pixel_petals)==1)]-=np.mean(petalFreeOPD[np.where(np.squeeze(self.index_pixel_petals)==1)]) - return petalFreeOPD - except: - print('ERROR : the indexes of the petals have not been properly set yet!') - return self.OPD - - def pad(self,resolution_padded): - if np.ndim(self.OPD) == 2: - em_field_padded = np.zeros([resolution_padded,resolution_padded],dtype = complex) - OPD_padded = np.zeros([resolution_padded,resolution_padded],dtype = float) - center = resolution_padded//2 - em_field_padded[center-self.resolution//2:center+self.resolution//2,center-self.resolution//2:center+self.resolution//2] = self.em_field - OPD_padded[center-self.resolution//2:center+self.resolution//2,center-self.resolution//2:center+self.resolution//2] = self.OPD - else: - em_field_padded = np.zeros([resolution_padded,resolution_padded, self.OPD.shape[2]],dtype = complex) - OPD_padded = np.zeros([resolution_padded,resolution_padded,self.OPD.shape[2]],dtype = float) - center = resolution_padded//2 - em_field_padded[center-self.resolution//2:center+self.resolution//2,center-self.resolution//2:center+self.resolution//2,:] = self.em_field - OPD_padded[center-self.resolution//2:center+self.resolution//2,center-self.resolution//2:center+self.resolution//2,:] = self.OPD - return OPD_padded, em_field_padded - - def getPetalOPD(self,petalIndex,image = None): - if image is None: - petal_OPD = np.copy(self.OPD) - else: - petal_OPD = image.copy() - if self.index_pixel_petals is None: - print('ERROR : the indexes of the petals have not been set yet!') - print('Returning the current OPD..') - return self.OPD - else: - self.petalMask = np.zeros(self.pupil.shape) - try: - self.petalMask [np.where(np.squeeze(self.index_pixel_petals[:,:,petalIndex])==1)] = 1 - except: - self.petalMask [np.where(np.squeeze(self.index_pixel_petals)==1)] = 1 - - petal_OPD = petal_OPD * self.petalMask - return petal_OPD - - # Combining with an atmosphere object - def __add__(self,obj): - if obj.tag == 'atmosphere': - self.isPaired = True - self.OPD = obj.OPD.copy() - self.OPD_no_pupil = obj.OPD_no_pupil.copy() - - if self.isPetalFree: - self.removePetalling() - print('Telescope and Atmosphere combined!') - if obj.tag == 'spatialFilter': - self.spatialFilter = obj - self*obj - print('Telescope and Spatial Filter combined!') - - - # Separating from an atmosphere object - def __sub__(self,obj): - if obj.tag == 'atmosphere': - self.isPaired = False - self.resetOPD() - print('Telescope and Atmosphere separated!') - - if obj.tag == 'spatialFilter': - self.spatialFilter = None - print('Telescope and Spatial Filter separated!') - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - def show(self): - attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) - print(self.tag+':') - for a in attributes: - if not(a[0].startswith('__') and a[0].endswith('__')): - if not(a[0].startswith('_')): - if not np.shape(a[1]): - tmp=a[1] - try: - print(' '+str(a[0])+': '+str(tmp.tag)+' object') - except: - print(' '+str(a[0])+': '+str(a[1])) - else: - if np.ndim(a[1])>1: - print(' '+str(a[0])+': '+str(np.shape(a[1]))) - - - - - - +# -*- coding: utf-8 -*- +""" +Created on Wed Feb 19 10:23:18 2020 + +@author: cheritie +""" + +import inspect + +import matplotlib.pyplot as plt +import numpy as np + +from .Source import Source + + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% CLASS INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +class Telescope: + + def __init__(self,resolution, diameter,samplingTime=0.001,centralObstruction = 0,fov = 0,pupil=None,pupilReflectivity=1): + """ + ************************** REQUIRED PARAMETERS ************************** + + A Telescope object consists in defining the 2D mask of the entrance pupil. It is mainly characterised by two parameters + _ resolution : the resolution of the pupil mask + _ diameter : The physical diameter of the telescope in [m] + + If no pupil mask is input, the default pupil geometry is circular. + + ************************** OPTIONAL PARAMETERS ************************** + + _ samplingTime : Defines the frequency of the AO loop. It is used in the Atmosphere object to update the turbulence phase screens according to the wind speed. + _ centralObstruction : Adds a central obstruction in percentage of diameter. + _ fov : Defines the Field of View of the Telescope object. This will be useful for off-axis targets but it hasn't been properly implemented yet. + _ pupil : A user-defined pupil mask can be input to the Telescope object. It should consist of a binary array. + _ pupilReflectivcty : Defines the reflectivity of the Telescope object. If not set to 1, it can be input as a 2D map of uneven reflectivy correspondong to the pupil mask. + This property can be set after the initialization of the Telescope object. + + ************************** ADDING SPIDERS ******************************* + It is possible to add spiders to the telescope pupil using the following property: + + tel.apply_spiders(angle,thickness_spider,offset_X = None, offset_Y=None) + + where : + - angle is a list of angle in [degrees]. The length of angle defines the number of spider. + - thickness is the width of the spider in [m] + - offset_X is a list (same lenght as angle) of shift X to apply to the individual spider in [m] + - offset_Y is a list (same lenght as angle) of shift Y to apply to the individual spider in [m] + + ************************** COUPLING A SOURCE OBJECT ************************** + + Once generated, the telescope should be coupled with a Source object "src" that contains the wavelength and flux properties of a target. + _ This is achieved using the * operator : src*tel + _ It can be accessed using : tel.src + _ By default, a Source object in the visible with a magnitude 0 is coupled to the telescope object + + ************************** COUPLING WITH AN ATMOSPHERE OBJECT ************************** + + The telescope can be coupled to an Atmosphere object. In that case, the OPD of the atmosphere is automatically added to the telescope object. + _ Coupling an Atmosphere and telescope Object : tel+atm + _ Separating an Atmosphere and telescope Object : tel-atm + + ************************** COMPUTING THE PSF ************************** + + 1) PSF computation + tel.computePSF(zeroPaddingFactor) : computes the square module of the Fourier transform of the tel.src.phase using the zeropadding factor for the FFT + + + ************************** MAIN PROPERTIES ************************** + + The main properties of a Telescope object are listed here: + _ tel.OPD : the optical path difference + _ tel.src.phase : 2D map of the phase scaled to the src wavelength corresponding to tel.OPD + _ tel.PSF : Point Spread Function corresponding to to the tel.src.phase. This is not automatically set, it requires to run tel.computePSF(). + + + ************************** EXEMPLE ************************** + + 1) Create an 8-m diameter circular telescope with a central obstruction of 15% and the pupil sampled with 100 pixels along the diameter. + tel = Telescope(resolution = 100, diameter = 8, centralObstruction = 0.15) + + 2) Create a source object in H band with a magnitude 8 and combine it to the telescope + src = Source(optBand = 'H', magnitude = 8) + src*tel + + 3) Compute the PSF with a zero-padding factor of 2. + tel.computePSF(zeroPaddingFactor = 2) + + """ +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% INITIALIZATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + self.isInitialized = False # Resolution of the telescope + + self.resolution = resolution # Resolution of the telescope + self.D = diameter # Diameter in m + self.pixelSize = self.D/self.resolution # size of the pixels in m + self.centralObstruction = centralObstruction # central obstruction + self.fov = fov # Field of View + self.samplingTime = samplingTime # AO loop speed + self.isPetalFree = False # Flag to remove the petalling effect with ane ELT system. + self.index_pixel_petals = None # indexes of the pixels corresponfong to the M1 petals. They need to be set externally +# Case where the pupil is not input: circular pupil with central obstruction + if pupil is None: + D = self.resolution+1 + x = np.linspace(-self.resolution/2,self.resolution/2,self.resolution) + xx,yy = np.meshgrid(x,x) + circle = xx**2+yy**2 + obs = circle>=(self.centralObstruction*D/2)**2 + self.pupil = circle<(D/2)**2 + self.pupil = self.pupil*obs + else: + print('User-defined pupil, the central obstruction will not be taken into account...') + self.pupil = pupil + + self.pupilReflectivity = self.pupil.astype(float)*pupilReflectivity # A non uniform reflectivity can be input by the user + self.pixelArea = np.sum(self.pupil) # Total number of pixels in the pupil area + self.pupilLogical = np.where(np.reshape(self.pupil,resolution*resolution)>0) # index of valid pixels in the pupil + self.src = Source(optBand = 'V', magnitude = 0, display_properties=False) # temporary source object associated to the telescope object + self.OPD = self.pupil.astype(float) # set the initial OPD + self.OPD_no_pupil = 1+self.pupil.astype(float)*0 # set the initial OPD + self.em_field = self.pupilReflectivity*np.exp(1j*self.src.phase) + self.tag = 'telescope' # tag of the object + self.isPaired = False # indicate if telescope object is paired with an atmosphere object + self.spatialFilter = None + + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% TELESCOPE %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('{: ^18s}'.format('Diameter') + '{: ^18s}'.format(str(self.D)) +'{: ^18s}'.format('[m]' )) + print('{: ^18s}'.format('Resolution') + '{: ^18s}'.format(str(self.resolution)) +'{: ^18s}'.format('[pixels]' )) + print('{: ^18s}'.format('Pixel Size') + '{: ^18s}'.format(str(np.round(self.pixelSize,2))) +'{: ^18s}'.format('[m]' )) + print('{: ^18s}'.format('Surface') + '{: ^18s}'.format(str(np.round(self.pixelArea*self.pixelSize**2))) +'{: ^18s}'.format('[m2]' )) + print('{: ^18s}'.format('Central Obstruction') + '{: ^18s}'.format(str(100*self.centralObstruction)) +'{: ^18s}'.format('[% of diameter]' )) + print('{: ^18s}'.format('Pixels in the pupil') + '{: ^18s}'.format(str(self.pixelArea)) +'{: ^18s}'.format('[pixels]' )) + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + self.isInitialized= True +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PSF COMPUTATION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + def computePSF(self,zeroPaddingFactor=2): + if hasattr(self,'src'): + # number of pixel considered + N = int(zeroPaddingFactor * self.resolution) + center = N//2 + norma = N + + if self.spatialFilter is not None: + self.spatialFilter.set_spatial_filter(zeroPaddingFactor = zeroPaddingFactor) + mask = self.spatialFilter.mask + amp_mask = self.amplitude_filtered + phase = self.phase_filtered + else: + mask = 1 + amp_mask = 1 + phase = self.src.phase + + # zeroPadded support for the FFT + supportPadded = np.zeros([N,N],dtype='complex') + supportPadded [center-self.resolution//2:center+self.resolution//2,center-self.resolution//2:center+self.resolution//2] = amp_mask*self.pupil*self.pupilReflectivity*np.sqrt(self.src.fluxMap)*np.exp(1j*phase) + [xx,yy] = np.meshgrid(np.linspace(0,N-1,N),np.linspace(0,N-1,N)) + self.phasor = np.exp(-(1j*np.pi*(N+1)/N)*(xx+yy)) + + + + # axis in arcsec + self.xPSF_arcsec = [-206265*(self.src.wavelength/self.D) * (self.resolution/2), 206265*(self.src.wavelength/self.D) * (self.resolution/2)] + self.yPSF_arcsec = [-206265*(self.src.wavelength/self.D) * (self.resolution/2), 206265*(self.src.wavelength/self.D) * (self.resolution/2)] + + # axis in radians + self.xPSF_rad = [-(self.src.wavelength/self.D) * (self.resolution/2),(self.src.wavelength/self.D) * (self.resolution/2)] + self.yPSF_rad = [-(self.src.wavelength/self.D) * (self.resolution/2),(self.src.wavelength/self.D) * (self.resolution/2)] + + # PSF computation + self.PSF = (np.abs(np.fft.fft2(supportPadded*self.phasor)*mask/norma)**2) + self.PSF_norma = self.PSF/self.PSF.max() + N_trunc = int(np.floor(2*N/6)) + self.PSF_norma_zoom = self.PSF_norma[N_trunc:-N_trunc,N_trunc:-N_trunc] + + else: + print('Error: no NGS associated to the Telescope. Combine a tel object with an ngs using ngs*tel') + return -1 + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% PSF DISPLAY %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + def showPSF(self,zoom = 1): + # display the full PSF or zoom on the core of the PSF + if hasattr(self, 'PSF'): + print('Displaying the PSF...') + else: + self.computePSF(6) + print('Displaying the PSF...') + + if zoom: + plt.imshow(self.PSF_trunc,extent = [self.xPSF_trunc[0],self.xPSF_trunc[1],self.xPSF_trunc[0],self.xPSF_trunc[1]]) + else: + plt.imshow(self.PSF,extent = [self.xPSF[0],self.xPSF[1],self.xPSF[0],self.xPSF[1]]) + plt.xlabel('[arcsec]') + plt.ylabel('[arcsec]') + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% TELESCOPE PROPERTIES %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + @property + def pupil(self): + return self._pupil + + @pupil.setter + def pupil(self,val): + self._pupil = val.astype(int) + self.pixelArea = np.sum(self._pupil) + tmp = np.reshape(self._pupil,self.resolution**2) + self.pupilLogical = np.where(tmp>0) + self.pupilReflectivity = self.pupil.astype(float) + if self.isInitialized: + print('Warning!: A new pupil is now considered, its reflectivity is considered to be uniform. Assign the proper reflectivity map to tel.pupilReflectivity if required.') + + @property + def OPD(self): + return self._OPD + + @OPD.setter + def OPD(self,val): + self._OPD = val + if type(val) is not list: + if self.src.tag == 'source': + self.src.phase = self._OPD*2*np.pi/self.src.wavelength + if np.ndim(self.OPD)==2: + self.mean_removed_OPD = (self.OPD - np.mean(self.OPD[np.where(self.pupil ==1)]))*self.pupil + else: + if self.src.tag == 'asterism': + for i in range(self.src.n_source): + self.src.src[i].phase = self._OPD*2*np.pi/self.src.src[i].wavelength + else: + raise TypeError('The wrong object was attached to the telescope') + else: + if self.src.tag == 'asterism': + if len(self._OPD)==self.src.n_source: + for i in range(self.src.n_source): + self.src.src[i].phase = self._OPD[i]*2*np.pi/self.src.src[i].wavelength + else: + raise TypeError('A list of OPD cannnot be propagated to a single source') + + + @property + def OPD_no_pupil(self): + return self._OPD_no_pupil + + @OPD_no_pupil.setter + def OPD_no_pupil(self,val): + self._OPD_no_pupil = val + if type(val) is not list: + if self.src.tag == 'source': + self.src.phase_no_pupil = self._OPD_no_pupil*2*np.pi/self.src.wavelength + else: + if self.src.tag == 'asterism': + for i in range(self.src.n_source): + self.src.src[i].phase_no_pupil = self._OPD_no_pupil*2*np.pi/self.src.src[i].wavelength + else: + raise TypeError('The wrong object was attached to the telescope') + else: + if self.src.tag == 'asterism': + if len(self._OPD_no_pupil)==self.src.n_source: + for i in range(self.src.n_source): + self.src.src[i].phase_no_pupil = self._OPD_no_pupil[i]*2*np.pi/self.src.src[i].wavelength + else: + raise TypeError('The lenght of the OPD list ('+str(len(self._OPD_no_pupil))+') does not match the number of sources ('+str(self.src.n_source)+')') + + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% TELESCOPE INTERACTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + def __mul__(self,obj): + # case where multiple objects are considered + if type(obj) is list: + if type(self.OPD) is list: + if len(self.OPD) == len(obj): + for i_obj in range(len(self.OPD)): + tel_tmp = getattr(obj[i_obj], 'telescope') + self.src.src[i_obj]*tel_tmp + tel_tmp.OPD = self.OPD[i_obj] + tel_tmp.OPD_no_pupil = self.OPD_no_pupil[i_obj] + setattr(obj[i_obj],'telescope',tel_tmp) + obj[i_obj].wfs_measure(phase_in = tel_tmp.src.phase) # propagation of the telescope-source phase screen to the pyramid-detector + else: + raise ValueError('Error! There is a mis-match between the number of Sources ('+str(len(self.OPD))+') and the number of WFS ('+str(len(obj))+')') + else: + for i_obj in range(len(obj)): + self*obj[i_obj] + else: + # interaction with WFS object: Propagation of the phase screen + if obj.tag=='pyramid' or obj.tag == 'double_wfs' or obj.tag=='shackHartmann': + if type(self.OPD) is list: + raise ValueError('Error! There is a mis-match between the number of Sources ('+str(len(self.OPD))+') and the number of WFS (1)') + else: + obj.telescope=self # assign the telescope object to the pyramid-telescope object + obj.wfs_measure(phase_in = self.src.phase) # propagation of the telescope-source phase screen to the pyramid-detector + # interaction with detector: computation of the PSF + if obj.tag=='detector': + self.computePSF() + obj.frame = obj.rebin(self.PSF,(obj.resolution,obj.resolution)) + + if obj.tag=='spatialFilter': + self.spatialFilter = obj + N = obj.resolution + EF_in = np.zeros([N,N],dtype='complex') + EF_in [obj.center-self.resolution//2:obj.center+self.resolution//2,obj.center-self.resolution//2:obj.center+self.resolution//2] = self.pupilReflectivity*np.exp(1j*(self.OPD_no_pupil*2*np.pi/self.src.wavelength)) + FP_in = np.fft.fft2(EF_in) + FP_filtered = FP_in*np.fft.fftshift(obj.mask) + em_field = np.fft.ifft2(FP_filtered) + self.em_field_filtered = em_field[obj.center-self.resolution//2:obj.center+self.resolution//2,obj.center-self.resolution//2:obj.center+self.resolution//2] + # self.phase_filtered = np.arctan2(np.imag(self.em_field_filtered),np.real(self.em_field_filtered))*self.pupil + self.phase_filtered = ((np.angle(self.em_field_filtered)))*self.pupil + + self.amplitude_filtered = np.abs(self.em_field_filtered) + return self + + if obj.tag=='deformableMirror': + pupil = np.atleast_3d(self.pupil) + + if self.src.tag == 'source': + self.OPD_no_pupil = obj.dm_propagation(self) + if np.ndim(self.OPD_no_pupil) == 2: + self.OPD = self.OPD_no_pupil*self.pupil + else: + self.OPD =self.OPD_no_pupil*pupil + else: + if self.src.tag == 'asterism': + if len(self.OPD) == self.src.n_source: + for i in range(self.src.n_source): + if obj.altitude is not None: + self.OPD_no_pupil[i] = obj.dm_propagation(self,OPD_in = self.OPD_no_pupil[i], i_source = i ) + else: + self.OPD_no_pupil[i] = obj.dm_propagation(self,OPD_in = self.OPD_no_pupil[i] ) + if np.ndim(self.OPD_no_pupil[i]) == 2: + self.OPD[i] = self.OPD_no_pupil[i]*self.pupil + else: + self.OPD[i] =self.OPD_no_pupil[i]*pupil + self.OPD = self.OPD + self.OPD_no_pupil = self.OPD_no_pupil + + else: + raise TypeError('The lenght of the OPD list ('+str(len(self._OPD_no_pupil))+') does not match the number of sources ('+str(self.src.n_source)+')') + else: + raise TypeError('The wrong object was attached to the telescope') + return self +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% TELESCOPE METHODS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + def resetOPD(self): + # re-initialize the telescope OPD to a flat wavefront + if self.src.tag == 'asterism': + self.OPD = [self.pupil.astype(float) for i in range(self.src.n_source)] + self.OPD_no_pupil = [self.pupil.astype(float)*0 +1 for i in range(self.src.n_source)] + else: + self.OPD = self.pupil.astype(float) + self.OPD_no_pupil = 1+0*self.pupil.astype(float) + + def apply_spiders(self,angle,thickness_spider,offset_X = None, offset_Y=None): + pup = np.copy(self.pupil) + max_offset = self.centralObstruction*self.D/2 - thickness_spider/2 + if offset_X is None: + offset_X = np.zeros(len(angle)) + + if offset_Y is None: + offset_Y = np.zeros(len(angle)) + + if np.max(np.abs(offset_X))>=max_offset or np.max(np.abs(offset_Y))>max_offset: + print('WARNING ! The spider offsets are too large! Weird things could happen!') + for i in range(len(angle)): + angle_val = (angle[i]+90)%360 + x = np.linspace(-self.D/2,self.D/2,self.resolution) + [X,Y] = np.meshgrid(x,x) + X+=offset_X[i] + map_dist = np.abs(X*np.cos(np.deg2rad(angle_val)) + Y*np.sin(np.deg2rad(-angle_val))) + + if 0<=angle_val<90: + map_dist[:self.resolution//2,:] = thickness_spider + if 90<=angle_val<180: + map_dist[:,:self.resolution//2] = thickness_spider + if 180<=angle_val<270: + map_dist[self.resolution//2:,:] = thickness_spider + if 270<=angle_val<360: + map_dist[:,self.resolution//2:] = thickness_spider + pup*= map_dist>thickness_spider/2 + + self.pupil = pup + return + + def removePetalling(self,image = None): + try: + # remove the petalling from image if the index_pixel_petals have been computed externally + if image is None: + petalFreeOPD = np.copy(self.OPD) + if self.index_pixel_petals is None: + print('ERROR : the indexes of the petals have not been set yet!') + print('Returning the current OPD..') + return self.OPD + else: + try: + N = self.index_pixel_petals.shape[2] + # Case with N petals + for i in range(N): + meanValue = np.mean(petalFreeOPD[np.where(np.squeeze(self.index_pixel_petals[:,:,i])==1)]) + # residualValue = meanValue % (self.src.wavelength ) + residualValue = 0 + petalFreeOPD[np.where(np.squeeze(self.index_pixel_petals[:,:,i])==1)] += residualValue- meanValue + except: + # Case with 1 petal + petalFreeOPD[np.where(np.squeeze(self.index_pixel_petals)==1)] -= np.mean(petalFreeOPD[np.where(np.squeeze(self.index_pixel_petals)==1)]) + # update the OPD with the petal-free OPD + self.OPD = petalFreeOPD + else: + petalFreeOPD = image + try: + N = self.index_pixel_petals.shape[2] + for i in range(N): + petalFreeOPD[np.where(np.squeeze(self.index_pixel_petals[:,:,i])==1)]-=np.mean(petalFreeOPD[np.where(np.squeeze(self.index_pixel_petals[:,:,i])==1)]) + except: + petalFreeOPD[np.where(np.squeeze(self.index_pixel_petals)==1)]-=np.mean(petalFreeOPD[np.where(np.squeeze(self.index_pixel_petals)==1)]) + return petalFreeOPD + except: + print('ERROR : the indexes of the petals have not been properly set yet!') + return self.OPD + + def pad(self,resolution_padded): + if np.ndim(self.OPD) == 2: + em_field_padded = np.zeros([resolution_padded,resolution_padded],dtype = complex) + OPD_padded = np.zeros([resolution_padded,resolution_padded],dtype = float) + center = resolution_padded//2 + em_field_padded[center-self.resolution//2:center+self.resolution//2,center-self.resolution//2:center+self.resolution//2] = self.em_field + OPD_padded[center-self.resolution//2:center+self.resolution//2,center-self.resolution//2:center+self.resolution//2] = self.OPD + else: + em_field_padded = np.zeros([resolution_padded,resolution_padded, self.OPD.shape[2]],dtype = complex) + OPD_padded = np.zeros([resolution_padded,resolution_padded,self.OPD.shape[2]],dtype = float) + center = resolution_padded//2 + em_field_padded[center-self.resolution//2:center+self.resolution//2,center-self.resolution//2:center+self.resolution//2,:] = self.em_field + OPD_padded[center-self.resolution//2:center+self.resolution//2,center-self.resolution//2:center+self.resolution//2,:] = self.OPD + return OPD_padded, em_field_padded + + def getPetalOPD(self,petalIndex,image = None): + if image is None: + petal_OPD = np.copy(self.OPD) + else: + petal_OPD = image.copy() + if self.index_pixel_petals is None: + print('ERROR : the indexes of the petals have not been set yet!') + print('Returning the current OPD..') + return self.OPD + else: + self.petalMask = np.zeros(self.pupil.shape) + try: + self.petalMask [np.where(np.squeeze(self.index_pixel_petals[:,:,petalIndex])==1)] = 1 + except: + self.petalMask [np.where(np.squeeze(self.index_pixel_petals)==1)] = 1 + + petal_OPD = petal_OPD * self.petalMask + return petal_OPD + + # Combining with an atmosphere object + def __add__(self,obj): + if obj.tag == 'atmosphere': + self.isPaired = True + self.OPD = obj.OPD.copy() + self.OPD_no_pupil = obj.OPD_no_pupil.copy() + + if self.isPetalFree: + self.removePetalling() + print('Telescope and Atmosphere combined!') + if obj.tag == 'spatialFilter': + self.spatialFilter = obj + self*obj + print('Telescope and Spatial Filter combined!') + + + # Separating from an atmosphere object + def __sub__(self,obj): + if obj.tag == 'atmosphere': + self.isPaired = False + self.resetOPD() + print('Telescope and Atmosphere separated!') + + if obj.tag == 'spatialFilter': + self.spatialFilter = None + print('Telescope and Spatial Filter separated!') + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + def show(self): + attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) + print(self.tag+':') + for a in attributes: + if not(a[0].startswith('__') and a[0].endswith('__')): + if not(a[0].startswith('_')): + if not np.shape(a[1]): + tmp=a[1] + try: + print(' '+str(a[0])+': '+str(tmp.tag)+' object') + except: + print(' '+str(a[0])+': '+str(a[1])) + else: + if np.ndim(a[1])>1: + print(' '+str(a[0])+': '+str(np.shape(a[1]))) + + + + + + diff --git a/AO_modules/Zernike.py b/OOPAO/Zernike.py old mode 100755 new mode 100644 similarity index 97% rename from AO_modules/Zernike.py rename to OOPAO/Zernike.py index e45d12e..b6492d4 --- a/AO_modules/Zernike.py +++ b/OOPAO/Zernike.py @@ -1,102 +1,106 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Feb 19 10:31:33 2020 - -@author: cheritie -""" -import numpy as np -import inspect -import aotools as ao -# ============================================================================= -# CLASS DEFINITION -# ============================================================================= - -class Zernike: - def __init__(self, telObject, J=1): - self.resolution = telObject.resolution - self.D = telObject.D - self.centralObstruction = telObject.centralObstruction - self.nModes = J - - def zernike_tel(self, tel, j): - """ - Creates the Zernike polynomial with radial index, n, and azimuthal index, m. - - Args: - n (int): The radial order of the zernike mode - m (int): The azimuthal order of the zernike mode - N (int): The diameter of the zernike more in pixels - Returns: - ndarray: The Zernike mode - """ - X, Y = np.where(tel.pupil > 0) - - X = ( X-(tel.resolution + tel.resolution%2-1)/2 ) / tel.resolution * tel.D - Y = ( Y-(tel.resolution + tel.resolution%2-1)/2 ) / tel.resolution * tel.D - # ^- to properly allign coordinates relative to the (0,0) for even/odd telescope resolutions - R = np.sqrt(X**2 + Y**2) - R = R/R.max() - theta = np.arctan2(Y, X) - out = np.zeros([tel.pixelArea,j]) - outFullRes = np.zeros([tel.resolution**2, j]) - - for i in range(1, j+1): - n, m = ao.zernike.zernIndex(i+1) - if m == 0: - Z = np.sqrt(n+1) * ao.zernike.zernikeRadialFunc(n, 0, R) - else: - if m > 0: # j is even - Z = np.sqrt(2*(n+1)) * ao.zernike.zernikeRadialFunc(n, m, R) * np.cos(m * theta) - else: #i is odd - m = abs(m) - Z = np.sqrt(2*(n+1)) * ao.zernike.zernikeRadialFunc(n, m, R) * np.sin(m * theta) - - Z -= Z.mean() - Z *= (1/np.std(Z)) - - # clip - out[:, i-1] = Z - outFullRes[tel.pupilLogical, i-1] = Z - - outFullRes = np.reshape( outFullRes, [tel.resolution, tel.resolution, j] ) - return out, outFullRes - - def computeZernike(self, telObject2): - self.modes, self.modesFullRes = self.zernike_tel(telObject2, self.nModes) - # normalize modes - - def modeName(self, index): - modes_names = [ - 'Tip', 'Tilt', 'Defocus', 'Astigmatism (X-shaped)', 'Astigmatism (+-shaped)', - 'Coma vertical', 'Coma horizontal', 'Trefoil vertical', 'Trefoil horizontal', - 'Sphere', 'Secondary astigmatism (X-shaped)', 'Secondary astigmatism (+-shaped)', - 'Quadrofoil vertical', 'Quadrofoil horizontal', - 'Secondary coma horizontal', 'Secondary coma vertical', - 'Secondary trefoil horizontal', 'Secondary trefoil vertical', - 'Pentafoil horizontal', 'Pentafoil vertical' - ] - - if index < 0: - return('Incorrent index!') - elif index >= len(modes_names): - return('Z', index+2) - else: - return(modes_names[index]) - - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - def show(self): - attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) - print(self.tag + ':') - for a in attributes: - if not(a[0].startswith('__') and a[0].endswith('__')): - if not(a[0].startswith('_')): - if not np.shape(a[1]): - tmp=a[1] - try: - print(' '+str(a[0])+': '+str(tmp.tag)+' object') - except: - print(' '+str(a[0])+': '+str(a[1])) - else: - if np.ndim(a[1])>1: +# -*- coding: utf-8 -*- +""" +Created on Wed Feb 19 10:31:33 2020 + +@author: cheritie +""" + +import inspect + +import aotools as ao +import numpy as np + + +# ============================================================================= +# CLASS DEFINITION +# ============================================================================= + +class Zernike: + def __init__(self, telObject, J=1): + self.resolution = telObject.resolution + self.D = telObject.D + self.centralObstruction = telObject.centralObstruction + self.nModes = J + + def zernike_tel(self, tel, j): + """ + Creates the Zernike polynomial with radial index, n, and azimuthal index, m. + + Args: + n (int): The radial order of the zernike mode + m (int): The azimuthal order of the zernike mode + N (int): The diameter of the zernike more in pixels + Returns: + ndarray: The Zernike mode + """ + X, Y = np.where(tel.pupil > 0) + + X = ( X-(tel.resolution + tel.resolution%2-1)/2 ) / tel.resolution * tel.D + Y = ( Y-(tel.resolution + tel.resolution%2-1)/2 ) / tel.resolution * tel.D + # ^- to properly allign coordinates relative to the (0,0) for even/odd telescope resolutions + R = np.sqrt(X**2 + Y**2) + R = R/R.max() + theta = np.arctan2(Y, X) + out = np.zeros([tel.pixelArea,j]) + outFullRes = np.zeros([tel.resolution**2, j]) + + for i in range(1, j+1): + n, m = ao.zernike.zernIndex(i+1) + if m == 0: + Z = np.sqrt(n+1) * ao.zernike.zernikeRadialFunc(n, 0, R) + else: + if m > 0: # j is even + Z = np.sqrt(2*(n+1)) * ao.zernike.zernikeRadialFunc(n, m, R) * np.cos(m * theta) + else: #i is odd + m = abs(m) + Z = np.sqrt(2*(n+1)) * ao.zernike.zernikeRadialFunc(n, m, R) * np.sin(m * theta) + + Z -= Z.mean() + Z *= (1/np.std(Z)) + + # clip + out[:, i-1] = Z + outFullRes[tel.pupilLogical, i-1] = Z + + outFullRes = np.reshape( outFullRes, [tel.resolution, tel.resolution, j] ) + return out, outFullRes + + def computeZernike(self, telObject2): + self.modes, self.modesFullRes = self.zernike_tel(telObject2, self.nModes) + # normalize modes + + def modeName(self, index): + modes_names = [ + 'Tip', 'Tilt', 'Defocus', 'Astigmatism (X-shaped)', 'Astigmatism (+-shaped)', + 'Coma vertical', 'Coma horizontal', 'Trefoil vertical', 'Trefoil horizontal', + 'Sphere', 'Secondary astigmatism (X-shaped)', 'Secondary astigmatism (+-shaped)', + 'Quadrofoil vertical', 'Quadrofoil horizontal', + 'Secondary coma horizontal', 'Secondary coma vertical', + 'Secondary trefoil horizontal', 'Secondary trefoil vertical', + 'Pentafoil horizontal', 'Pentafoil vertical' + ] + + if index < 0: + return('Incorrent index!') + elif index >= len(modes_names): + return('Z', index+2) + else: + return(modes_names[index]) + + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + def show(self): + attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) + print(self.tag + ':') + for a in attributes: + if not(a[0].startswith('__') and a[0].endswith('__')): + if not(a[0].startswith('_')): + if not np.shape(a[1]): + tmp=a[1] + try: + print(' '+str(a[0])+': '+str(tmp.tag)+' object') + except: + print(' '+str(a[0])+': '+str(a[1])) + else: + if np.ndim(a[1])>1: print(' '+str(a[0])+': '+str(np.shape(a[1]))) \ No newline at end of file diff --git a/OOPAO/__init__.py b/OOPAO/__init__.py new file mode 100644 index 0000000..39da95a --- /dev/null +++ b/OOPAO/__init__.py @@ -0,0 +1,40 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri Apr 3 10:56:59 2020 + +@author: cheritie +""" + +import numpy as np + + +print('\n') +print('======================================================================================> ') +print(' ✸ * ° * * ') +print(' ° ✸ ▄███▄ ▄███▄ ▄████▄ ▄███▄ * ▄███▄ => ▄▄▄▄ ') +print(' ✸ ° ██* ██ ██ ██ ██ ██ ██ ██ ██ ██ ====> ▄█▀▀ ▀▀█▄ ') +print(' * ° ✸ ██ ██ ██ ° ██ ██ ██ ██ * ██ ██ ██ ==> █▀ ▄█▀▀█▄ ▀█ ') +print('✸ * ° ██ ██ ██ ██ █████▀ ██▄▄▄██ ██ ██ =========> █▀ █▀ ▄▄ ▀█ ▀█ ') +print(' ✸ ° ██ * ██ ██ ██ ██ ██▀▀▀██ ██ ██ ========> █▄ █▄ ▀▀ ▄█ ▄█ ') +print(' * ✸ ° ██ ██ ██ ██ ██ * ██ ██ ██* ██ => █▄ ▀█▄▄█▀ ▄█ ') +print(' ° * ✸ ▀███▀ ▀███▀ ██ ° ██ ██ ▀███▀ ==> ▀█▄▄ ▄▄█▀ ') +print(' ✸ * * * ▀▀▀▀ ') +print('======================================================================================> ') +print('\n') + +# check the version of numpy libraries +try: + config = np.__config__.blas_mkl_info['libraries'][0] + if config != 'mkl_rt': + print( + '**************************************************************************************************************************************************************') + print( + 'NUMPY WARNING: OOPAO multi-threading requires to use numpy built with mkl library! If you are using AMD or Apple processors the code could be single threaded!') + print( + '**************************************************************************************************************************************************************') +except: + print( + '**************************************************************************************************************************************************************') + print('NUMPY WARNING: mkl blas not found! Multi-threading may not work as expected.') + print( + '**************************************************************************************************************************************************************') \ No newline at end of file diff --git a/AO_modules/calibration/CalibrationVault.py b/OOPAO/calibration/CalibrationVault.py old mode 100755 new mode 100644 similarity index 97% rename from AO_modules/calibration/CalibrationVault.py rename to OOPAO/calibration/CalibrationVault.py index e2b67aa..7627a90 --- a/AO_modules/calibration/CalibrationVault.py +++ b/OOPAO/calibration/CalibrationVault.py @@ -1,108 +1,111 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Mar 2 09:21:48 2020 - -@author: cheritie -""" - -import numpy as np -import matplotlib.pyplot as plt -import inspect -class CalibrationVault(): - def __init__(self,D,nTrunc=0,display=False, print_details = False,invert= True): - if print_details: - print('Computing the SVD...') - if invert: - U,s,V=np.linalg.svd(D,full_matrices=False) - self.s=s - self.S=np.diag(s) - self.eigenValues=s - self.D=U@self.S@V - - self.U=np.transpose(U) - self.V=V - nEigenValues=len(s)-nTrunc - - self.iS=np.diag(1/self.eigenValues) - self.M=np.transpose(self.V)@self.iS@self.U - - self.iStrunc=np.diag(1/self.eigenValues[:nEigenValues]) - self.Vtrunc=self.V[:nEigenValues,:] - self.Utrunc=self.U[:nEigenValues,:] - - self.VtruncT=np.transpose(self.Vtrunc) - self.UtruncT=np.transpose(self.Utrunc) - - self.Mtrunc=self.VtruncT@self.iStrunc@self.Utrunc - self.Dtrunc=self.UtruncT@np.diag(self.eigenValues[:nEigenValues])@self.Vtrunc - - self.cond=self.eigenValues[0]/self.eigenValues[(-nTrunc-1)] - if print_details: - print('Done! The conditionning number is ' + str(self.cond)) - if display: - - plt.figure() - plt.subplot(1,2,1) - plt.imshow(self.D) - plt.colorbar() - plt.title('Cond: '+str(self.cond)) - - plt.subplot(1,2,2) - plt.loglog(self.eigenValues) - plt.plot([0,nEigenValues],[self.eigenValues[nEigenValues-1],self.eigenValues[nEigenValues-1]]) - else: - self.D=D - @property - def nTrunc(self): - return self._nTrunc - - @nTrunc.setter - def nTrunc(self,val): - self._nTrunc=val - - nEigenValues=len(self.s)-self._nTrunc - - self.iStrunc=np.diag(1/self.eigenValues[:nEigenValues]) - - self.Vtrunc=self.V[:nEigenValues,:] - self.Utrunc=self.U[:nEigenValues,:] - - self.VtruncT=np.transpose(self.Vtrunc) - self.UtruncT=np.transpose(self.Utrunc) - - self.Mtrunc=self.VtruncT@self.iStrunc@self.Utrunc - - self.Dtrunc=self.UtruncT@np.diag(self.eigenValues[:nEigenValues])@self.Vtrunc - - self.cond=self.eigenValues[0]/self.eigenValues[(-self.nTrunc-1)] - - plt.close(3456789) - plt.figure(3456789) - plt.subplot(1,2,1) - plt.imshow(self.D.T) - plt.colorbar() - plt.title('Cond: '+str(self.cond)) - - plt.subplot(1,2,2) - plt.loglog(self.eigenValues) - plt.plot([0,nEigenValues],[self.eigenValues[nEigenValues-1],self.eigenValues[nEigenValues-1]]) - -# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - def show(self): - attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) - print(self.tag+':') - for a in attributes: - if not(a[0].startswith('__') and a[0].endswith('__')): - if not(a[0].startswith('_')): - tmp=str(type(a[1])) - if not np.shape(a[1]): - print(' '+str(a[0])+' -- '+tmp[8:-2]) - else: - if np.ndim(a[1])>1: - print(' '+str(a[0])+' -- '+tmp[8:-2]+' : '+str(np.shape(a[1]))) - else: - print(' '+str(a[0])+' -- '+tmp[8:-2]+' : '+str(a[1])) - - +# -*- coding: utf-8 -*- +""" +Created on Mon Mar 2 09:21:48 2020 + +@author: cheritie +""" + +import inspect + +import matplotlib.pyplot as plt +import numpy as np + + +class CalibrationVault(): + def __init__(self,D,nTrunc=0,display=False, print_details = False,invert= True): + if print_details: + print('Computing the SVD...') + if invert: + U,s,V=np.linalg.svd(D,full_matrices=False) + self.s=s + self.S=np.diag(s) + self.eigenValues=s + self.D=U@self.S@V + + self.U=np.transpose(U) + self.V=V + nEigenValues=len(s)-nTrunc + + self.iS=np.diag(1/self.eigenValues) + self.M=np.transpose(self.V)@self.iS@self.U + + self.iStrunc=np.diag(1/self.eigenValues[:nEigenValues]) + self.Vtrunc=self.V[:nEigenValues,:] + self.Utrunc=self.U[:nEigenValues,:] + + self.VtruncT=np.transpose(self.Vtrunc) + self.UtruncT=np.transpose(self.Utrunc) + + self.Mtrunc=self.VtruncT@self.iStrunc@self.Utrunc + self.Dtrunc=self.UtruncT@np.diag(self.eigenValues[:nEigenValues])@self.Vtrunc + + self.cond=self.eigenValues[0]/self.eigenValues[(-nTrunc-1)] + if print_details: + print('Done! The conditionning number is ' + str(self.cond)) + if display: + + plt.figure() + plt.subplot(1,2,1) + plt.imshow(self.D) + plt.colorbar() + plt.title('Cond: '+str(self.cond)) + + plt.subplot(1,2,2) + plt.loglog(self.eigenValues) + plt.plot([0,nEigenValues],[self.eigenValues[nEigenValues-1],self.eigenValues[nEigenValues-1]]) + else: + self.D=D + @property + def nTrunc(self): + return self._nTrunc + + @nTrunc.setter + def nTrunc(self,val): + self._nTrunc=val + + nEigenValues=len(self.s)-self._nTrunc + + self.iStrunc=np.diag(1/self.eigenValues[:nEigenValues]) + + self.Vtrunc=self.V[:nEigenValues,:] + self.Utrunc=self.U[:nEigenValues,:] + + self.VtruncT=np.transpose(self.Vtrunc) + self.UtruncT=np.transpose(self.Utrunc) + + self.Mtrunc=self.VtruncT@self.iStrunc@self.Utrunc + + self.Dtrunc=self.UtruncT@np.diag(self.eigenValues[:nEigenValues])@self.Vtrunc + + self.cond=self.eigenValues[0]/self.eigenValues[(-self.nTrunc-1)] + + plt.close(3456789) + plt.figure(3456789) + plt.subplot(1,2,1) + plt.imshow(self.D.T) + plt.colorbar() + plt.title('Cond: '+str(self.cond)) + + plt.subplot(1,2,2) + plt.loglog(self.eigenValues) + plt.plot([0,nEigenValues],[self.eigenValues[nEigenValues-1],self.eigenValues[nEigenValues-1]]) + +# %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% END %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + def show(self): + attributes = inspect.getmembers(self, lambda a:not(inspect.isroutine(a))) + print(self.tag+':') + for a in attributes: + if not(a[0].startswith('__') and a[0].endswith('__')): + if not(a[0].startswith('_')): + tmp=str(type(a[1])) + if not np.shape(a[1]): + print(' '+str(a[0])+' -- '+tmp[8:-2]) + else: + if np.ndim(a[1])>1: + print(' '+str(a[0])+' -- '+tmp[8:-2]+' : '+str(np.shape(a[1]))) + else: + print(' '+str(a[0])+' -- '+tmp[8:-2]+' : '+str(a[1])) + + \ No newline at end of file diff --git a/AO_modules/calibration/InteractionMatrix.py b/OOPAO/calibration/InteractionMatrix.py similarity index 96% rename from AO_modules/calibration/InteractionMatrix.py rename to OOPAO/calibration/InteractionMatrix.py index 855e2e6..9ee565a 100644 --- a/AO_modules/calibration/InteractionMatrix.py +++ b/OOPAO/calibration/InteractionMatrix.py @@ -1,272 +1,273 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Mar 18 09:26:22 2020 - -@author: cheritie -""" -import numpy as np -import time -from AO_modules.calibration.CalibrationVault import CalibrationVault - -def InteractionMatrix(ngs,atm,tel,dm,wfs,M2C,stroke,phaseOffset=0,nMeasurements=50,noise='off',invert=True,print_time=True): - if wfs.tag=='pyramid' and wfs.gpu_available: - nMeasurements = 1 - print('Pyramid with GPU detected => using single mode measurement to increase speed.') -# disabled noise functionality from WFS - if noise =='off': - wfs.cam.photonNoise = 0 - wfs.cam.readoutNoise = 0 - else: - print('Warning: Keeping the noise configuration for the WFS') - - # separate tel from ATM - tel.isPaired = False - ngs*tel - try: - nModes = M2C.shape[1] - except: - nModes = 1 - intMat = np.zeros([wfs.nSignal,nModes]) - nCycle = int(np.ceil(nModes/nMeasurements)) - nExtra = int(nModes%nMeasurements) - if nMeasurements>nModes: - nMeasurements = nModes - - if np.ndim(phaseOffset)==2: - if nMeasurements !=1: - phaseBuffer = np.tile(phaseOffset[...,None],(1,1,nMeasurements)) - else: - phaseBuffer = phaseOffset - else: - phaseBuffer = phaseOffset - - - for i in range(nCycle): - if nModes>1: - if i==nCycle-1: - if nExtra != 0: - intMatCommands = np.squeeze(M2C[:,-nExtra:]) - try: - phaseBuffer = np.tile(phaseOffset[...,None],(1,1,intMatCommands.shape[-1])) - except: - phaseBuffer = phaseOffset - else: - intMatCommands = np.squeeze(M2C[:,i*nMeasurements:((i+1)*nMeasurements)]) - else: - intMatCommands = np.squeeze(M2C[:,i*nMeasurements:((i+1)*nMeasurements)]) - else: - intMatCommands = np.squeeze(M2C) - - a= time.time() -# push - dm.coefs = intMatCommands*stroke - tel*dm - tel.src.phase+=phaseBuffer - tel*wfs - sp = wfs.signal - - -# pull - dm.coefs=-intMatCommands*stroke - tel*dm - tel.src.phase+=phaseBuffer - tel*wfs - sm = wfs.signal - if i==nCycle-1: - if nExtra !=0: - if nMeasurements==1: - intMat[:,i] = np.squeeze(0.5*(sp-sm)/stroke) - else: - if nExtra ==1: - intMat[:,-nExtra] = np.squeeze(0.5*(sp-sm)/stroke) - else: - intMat[:,-nExtra:] = np.squeeze(0.5*(sp-sm)/stroke) - else: - if nMeasurements==1: - intMat[:,i] = np.squeeze(0.5*(sp-sm)/stroke) - else: - intMat[:,-nMeasurements:] = np.squeeze(0.5*(sp-sm)/stroke) - - - else: - if nMeasurements==1: - intMat[:,i] = np.squeeze(0.5*(sp-sm)/stroke) - else: - intMat[:,i*nMeasurements:((i+1)*nMeasurements)] = np.squeeze(0.5*(sp-sm)/stroke) - intMat = np.squeeze(intMat) - - if print_time: - print(str((i+1)*nMeasurements)+'/'+str(nModes)) - b=time.time() - print('Time elapsed: '+str(b-a)+' s' ) - - out=CalibrationVault(intMat,invert=invert) - - return out - - -def InteractionMatrixFromPhaseScreen(ngs,atm,tel,wfs,phasScreens,stroke,phaseOffset=0,nMeasurements=50,noise='off',invert=True,print_time=True): - - # disabled noise functionality from WFS - if noise =='off': - wfs.cam.photonNoise = 0 - wfs.cam.readoutNoise = 0 - else: - print('Warning: Keeping the noise configuration for the WFS') - - tel.isPaired = False - ngs*tel - try: - nModes = phasScreens.shape[2] - except: - nModes = 1 - intMat = np.zeros([wfs.nSignal,nModes]) - nCycle = int(np.ceil(nModes/nMeasurements)) - nExtra = int(nModes%nMeasurements) - if nMeasurements>nModes: - nMeasurements = nModes - - if np.ndim(phaseOffset)==2: - if nMeasurements !=1: - phaseBuffer = np.tile(phaseOffset[...,None],(1,1,nMeasurements)) - else: - phaseBuffer = phaseOffset - else: - phaseBuffer = phaseOffset - - - for i in range(nCycle): - if nModes>1: - if i==nCycle-1: - if nExtra != 0: - modes_in = np.squeeze(phasScreens[:,:,-nExtra:]) - try: - phaseBuffer = np.tile(phaseOffset[...,None],(1,1,modes_in.shape[-1])) - except: - phaseBuffer = phaseOffset - else: - modes_in = np.squeeze(phasScreens[:,:,i*nMeasurements:((i+1)*nMeasurements)]) - - else: - modes_in = np.squeeze(phasScreens[:,:,i*nMeasurements:((i+1)*nMeasurements)]) - else: - modes_in = np.squeeze(phasScreens) - - a= time.time() -# push - tel.OPD = modes_in*stroke - tel.src.phase+=phaseBuffer - tel*wfs - sp = wfs.signal -# pull - tel.OPD=-modes_in*stroke - tel.src.phase+=phaseBuffer - tel*wfs - sm = wfs.signal - if i==nCycle-1: - if nExtra !=0: - if nMeasurements==1: - intMat[:,i] = np.squeeze(0.5*(sp-sm)/stroke) - else: - if nExtra ==1: - intMat[:,-nExtra] = np.squeeze(0.5*(sp-sm)/stroke) - else: - intMat[:,-nExtra:] = np.squeeze(0.5*(sp-sm)/stroke) - else: - if nMeasurements==1: - intMat[:,i] = np.squeeze(0.5*(sp-sm)/stroke) - else: - intMat[:,-nMeasurements:] = np.squeeze(0.5*(sp-sm)/stroke) - - - else: - if nMeasurements==1: - intMat[:,i] = np.squeeze(0.5*(sp-sm)/stroke) - else: - intMat[:,i*nMeasurements:((i+1)*nMeasurements)] = np.squeeze(0.5*(sp-sm)/stroke) - intMat = np.squeeze(intMat) - if print_time: - print(str((i+1)*nMeasurements)+'/'+str(nModes)) - b=time.time() - print('Time elapsed: '+str(b-a)+' s' ) - - out=CalibrationVault(intMat,invert=invert) - - return out - - - -# def InteractionMatrixOnePass(ngs,atm,tel,dm,wfs,M2C,stroke,phaseOffset=0,nMeasurements=50,noise='off'): -# # disabled noise functionality from WFS -# if noise =='off': -# wfs.cam.photonNoise = 0 -# wfs.cam.readoutNoise = 0 -# else: -# print('Warning: Keeping the noise configuration for the WFS') - -# tel-atm -# ngs*tel -# nModes = M2C.shape[1] -# intMat = np.zeros([wfs.nSignal,nModes]) -# nCycle = int(np.ceil(nModes/nMeasurements)) -# nExtra = int(nModes%nMeasurements) -# if nMeasurements>nModes: -# nMeasurements = nModes - -# if np.ndim(phaseOffset)==2: -# if nMeasurements !=1: -# phaseBuffer = np.tile(phaseOffset[...,None],(1,1,nMeasurements)) -# else: -# phaseBuffer = phaseOffset -# else: -# phaseBuffer = phaseOffset - - -# for i in range(nCycle): -# if i==nCycle-1: -# if nExtra != 0: -# intMatCommands = np.squeeze(M2C[:,-nExtra:]) -# try: -# phaseBuffer = np.tile(phaseOffset[...,None],(1,1,intMatCommands.shape[-1])) -# except: -# phaseBuffer = phaseOffset -# else: -# intMatCommands = np.squeeze(M2C[:,i*nMeasurements:((i+1)*nMeasurements)]) - -# else: -# intMatCommands = np.squeeze(M2C[:,i*nMeasurements:((i+1)*nMeasurements)]) - -# a= time.time() -# # push -# dm.coefs = intMatCommands*stroke -# tel*dm -# tel.src.phase+=phaseBuffer -# tel*wfs -# sp = wfs.signal -# if i==nCycle-1: -# if nExtra !=0: -# if nMeasurements==1: -# intMat[:,i] = np.squeeze(0.5*(sp)/stroke) -# else: -# intMat[:,-nExtra:] = np.squeeze(0.5*(sp)/stroke) -# else: -# if nMeasurements==1: -# intMat[:,i] = np.squeeze(0.5*(sp)/stroke) -# else: -# intMat[:,-nMeasurements:] = np.squeeze(0.5*(sp)/stroke) - - -# else: -# if nMeasurements==1: -# intMat[:,i] = np.squeeze(0.5*(sp)/stroke) -# else: -# intMat[:,i*nMeasurements:((i+1)*nMeasurements)] = np.squeeze(0.5*(sp)/stroke) - -# print(str((i+1)*nMeasurements)+'/'+str(nModes)) -# b=time.time() -# print('Time elapsed: '+str(b-a)+' s' ) - -# out=CalibrationVault(intMat) -# return out - +# -*- coding: utf-8 -*- +""" +Created on Wed Mar 18 09:26:22 2020 + +@author: cheritie +""" +import numpy as np +import time +from .CalibrationVault import CalibrationVault + + +def InteractionMatrix(ngs,atm,tel,dm,wfs,M2C,stroke,phaseOffset=0,nMeasurements=50,noise='off',invert=True,print_time=True): + if wfs.tag=='pyramid' and wfs.gpu_available: + nMeasurements = 1 + print('Pyramid with GPU detected => using single mode measurement to increase speed.') +# disabled noise functionality from WFS + if noise =='off': + wfs.cam.photonNoise = 0 + wfs.cam.readoutNoise = 0 + else: + print('Warning: Keeping the noise configuration for the WFS') + + # separate tel from ATM + tel.isPaired = False + ngs*tel + try: + nModes = M2C.shape[1] + except: + nModes = 1 + intMat = np.zeros([wfs.nSignal,nModes]) + nCycle = int(np.ceil(nModes/nMeasurements)) + nExtra = int(nModes%nMeasurements) + if nMeasurements>nModes: + nMeasurements = nModes + + if np.ndim(phaseOffset)==2: + if nMeasurements !=1: + phaseBuffer = np.tile(phaseOffset[...,None],(1,1,nMeasurements)) + else: + phaseBuffer = phaseOffset + else: + phaseBuffer = phaseOffset + + + for i in range(nCycle): + if nModes>1: + if i==nCycle-1: + if nExtra != 0: + intMatCommands = np.squeeze(M2C[:,-nExtra:]) + try: + phaseBuffer = np.tile(phaseOffset[...,None],(1,1,intMatCommands.shape[-1])) + except: + phaseBuffer = phaseOffset + else: + intMatCommands = np.squeeze(M2C[:,i*nMeasurements:((i+1)*nMeasurements)]) + else: + intMatCommands = np.squeeze(M2C[:,i*nMeasurements:((i+1)*nMeasurements)]) + else: + intMatCommands = np.squeeze(M2C) + + a= time.time() +# push + dm.coefs = intMatCommands*stroke + tel*dm + tel.src.phase+=phaseBuffer + tel*wfs + sp = wfs.signal + + +# pull + dm.coefs=-intMatCommands*stroke + tel*dm + tel.src.phase+=phaseBuffer + tel*wfs + sm = wfs.signal + if i==nCycle-1: + if nExtra !=0: + if nMeasurements==1: + intMat[:,i] = np.squeeze(0.5*(sp-sm)/stroke) + else: + if nExtra ==1: + intMat[:,-nExtra] = np.squeeze(0.5*(sp-sm)/stroke) + else: + intMat[:,-nExtra:] = np.squeeze(0.5*(sp-sm)/stroke) + else: + if nMeasurements==1: + intMat[:,i] = np.squeeze(0.5*(sp-sm)/stroke) + else: + intMat[:,-nMeasurements:] = np.squeeze(0.5*(sp-sm)/stroke) + + + else: + if nMeasurements==1: + intMat[:,i] = np.squeeze(0.5*(sp-sm)/stroke) + else: + intMat[:,i*nMeasurements:((i+1)*nMeasurements)] = np.squeeze(0.5*(sp-sm)/stroke) + intMat = np.squeeze(intMat) + + if print_time: + print(str((i+1)*nMeasurements)+'/'+str(nModes)) + b=time.time() + print('Time elapsed: '+str(b-a)+' s' ) + + out=CalibrationVault(intMat,invert=invert) + + return out + + +def InteractionMatrixFromPhaseScreen(ngs,atm,tel,wfs,phasScreens,stroke,phaseOffset=0,nMeasurements=50,noise='off',invert=True,print_time=True): + + # disabled noise functionality from WFS + if noise =='off': + wfs.cam.photonNoise = 0 + wfs.cam.readoutNoise = 0 + else: + print('Warning: Keeping the noise configuration for the WFS') + + tel.isPaired = False + ngs*tel + try: + nModes = phasScreens.shape[2] + except: + nModes = 1 + intMat = np.zeros([wfs.nSignal,nModes]) + nCycle = int(np.ceil(nModes/nMeasurements)) + nExtra = int(nModes%nMeasurements) + if nMeasurements>nModes: + nMeasurements = nModes + + if np.ndim(phaseOffset)==2: + if nMeasurements !=1: + phaseBuffer = np.tile(phaseOffset[...,None],(1,1,nMeasurements)) + else: + phaseBuffer = phaseOffset + else: + phaseBuffer = phaseOffset + + + for i in range(nCycle): + if nModes>1: + if i==nCycle-1: + if nExtra != 0: + modes_in = np.squeeze(phasScreens[:,:,-nExtra:]) + try: + phaseBuffer = np.tile(phaseOffset[...,None],(1,1,modes_in.shape[-1])) + except: + phaseBuffer = phaseOffset + else: + modes_in = np.squeeze(phasScreens[:,:,i*nMeasurements:((i+1)*nMeasurements)]) + + else: + modes_in = np.squeeze(phasScreens[:,:,i*nMeasurements:((i+1)*nMeasurements)]) + else: + modes_in = np.squeeze(phasScreens) + + a= time.time() +# push + tel.OPD = modes_in*stroke + tel.src.phase+=phaseBuffer + tel*wfs + sp = wfs.signal +# pull + tel.OPD=-modes_in*stroke + tel.src.phase+=phaseBuffer + tel*wfs + sm = wfs.signal + if i==nCycle-1: + if nExtra !=0: + if nMeasurements==1: + intMat[:,i] = np.squeeze(0.5*(sp-sm)/stroke) + else: + if nExtra ==1: + intMat[:,-nExtra] = np.squeeze(0.5*(sp-sm)/stroke) + else: + intMat[:,-nExtra:] = np.squeeze(0.5*(sp-sm)/stroke) + else: + if nMeasurements==1: + intMat[:,i] = np.squeeze(0.5*(sp-sm)/stroke) + else: + intMat[:,-nMeasurements:] = np.squeeze(0.5*(sp-sm)/stroke) + + + else: + if nMeasurements==1: + intMat[:,i] = np.squeeze(0.5*(sp-sm)/stroke) + else: + intMat[:,i*nMeasurements:((i+1)*nMeasurements)] = np.squeeze(0.5*(sp-sm)/stroke) + intMat = np.squeeze(intMat) + if print_time: + print(str((i+1)*nMeasurements)+'/'+str(nModes)) + b=time.time() + print('Time elapsed: '+str(b-a)+' s' ) + + out=CalibrationVault(intMat,invert=invert) + + return out + + + +# def InteractionMatrixOnePass(ngs,atm,tel,dm,wfs,M2C,stroke,phaseOffset=0,nMeasurements=50,noise='off'): +# # disabled noise functionality from WFS +# if noise =='off': +# wfs.cam.photonNoise = 0 +# wfs.cam.readoutNoise = 0 +# else: +# print('Warning: Keeping the noise configuration for the WFS') + +# tel-atm +# ngs*tel +# nModes = M2C.shape[1] +# intMat = np.zeros([wfs.nSignal,nModes]) +# nCycle = int(np.ceil(nModes/nMeasurements)) +# nExtra = int(nModes%nMeasurements) +# if nMeasurements>nModes: +# nMeasurements = nModes + +# if np.ndim(phaseOffset)==2: +# if nMeasurements !=1: +# phaseBuffer = np.tile(phaseOffset[...,None],(1,1,nMeasurements)) +# else: +# phaseBuffer = phaseOffset +# else: +# phaseBuffer = phaseOffset + + +# for i in range(nCycle): +# if i==nCycle-1: +# if nExtra != 0: +# intMatCommands = np.squeeze(M2C[:,-nExtra:]) +# try: +# phaseBuffer = np.tile(phaseOffset[...,None],(1,1,intMatCommands.shape[-1])) +# except: +# phaseBuffer = phaseOffset +# else: +# intMatCommands = np.squeeze(M2C[:,i*nMeasurements:((i+1)*nMeasurements)]) + +# else: +# intMatCommands = np.squeeze(M2C[:,i*nMeasurements:((i+1)*nMeasurements)]) + +# a= time.time() +# # push +# dm.coefs = intMatCommands*stroke +# tel*dm +# tel.src.phase+=phaseBuffer +# tel*wfs +# sp = wfs.signal +# if i==nCycle-1: +# if nExtra !=0: +# if nMeasurements==1: +# intMat[:,i] = np.squeeze(0.5*(sp)/stroke) +# else: +# intMat[:,-nExtra:] = np.squeeze(0.5*(sp)/stroke) +# else: +# if nMeasurements==1: +# intMat[:,i] = np.squeeze(0.5*(sp)/stroke) +# else: +# intMat[:,-nMeasurements:] = np.squeeze(0.5*(sp)/stroke) + + +# else: +# if nMeasurements==1: +# intMat[:,i] = np.squeeze(0.5*(sp)/stroke) +# else: +# intMat[:,i*nMeasurements:((i+1)*nMeasurements)] = np.squeeze(0.5*(sp)/stroke) + +# print(str((i+1)*nMeasurements)+'/'+str(nModes)) +# b=time.time() +# print('Time elapsed: '+str(b-a)+' s' ) + +# out=CalibrationVault(intMat) +# return out + \ No newline at end of file diff --git a/AO_modules/M4_model/__init__.py b/OOPAO/calibration/__init__.py similarity index 92% rename from AO_modules/M4_model/__init__.py rename to OOPAO/calibration/__init__.py index c03131d..5cbfd76 100644 --- a/AO_modules/M4_model/__init__.py +++ b/OOPAO/calibration/__init__.py @@ -1,7 +1,7 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Apr 3 10:56:59 2020 - -@author: cheritie -""" - +# -*- coding: utf-8 -*- +""" +Created on Fri Apr 3 10:56:59 2020 + +@author: cheritie +""" + diff --git a/AO_modules/calibration/ao_calibration.py b/OOPAO/calibration/ao_calibration.py old mode 100755 new mode 100644 similarity index 96% rename from AO_modules/calibration/ao_calibration.py rename to OOPAO/calibration/ao_calibration.py index 8e1cf3a..7d31f34 --- a/AO_modules/calibration/ao_calibration.py +++ b/OOPAO/calibration/ao_calibration.py @@ -1,382 +1,384 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Oct 13 09:47:17 2020 - -@author: cheritie -""" -from astropy.io import fits as pfits -import numpy as np - -from AO_modules.calibration.CalibrationVault import CalibrationVault -from AO_modules.calibration.InteractionMatrix import InteractionMatrix -from AO_modules.tools.tools import emptyClass,createFolder, read_fits, write_fits -import time - - -def ao_calibration_from_ao_obj(ao_obj, nameFolderIntMat = None, nameIntMat = None, nameFolderBasis = None, nameBasis = None, nMeasurements=50, index_modes = None, get_basis = True): - - - # check if the name of the basis is specified otherwise take the nominal name - if nameBasis is None: - if ao_obj.dm.isM4: - initName = 'M2C_M4_' - else: - initName = 'M2C_' - try: - nameBasis = initName+str(ao_obj.param['resolution'])+'_res'+ao_obj.param['extra'] - except: - nameBasis = initName+str(ao_obj.param['resolution'])+'_res' - ao_calib_object = emptyClass() - - # check if a name for the origin folder is specified - if nameFolderBasis is None: - nameFolderBasis = ao_obj.param['pathInput'] - createFolder(nameFolderBasis) - -## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -# get the modal basis : - - try: - print('Loading the KL Modal Basis from: ' + nameFolderBasis+nameBasis ) - M2C = read_fits(nameFolderBasis+ nameBasis+'.fits') - if index_modes is None: - M2C = M2C[:,:ao_obj.param['nModes']] - else: - M2C = M2C[:,index_modes] - - ao_obj.param['modal_basis_filename'] = nameFolderBasis+ nameBasis+'.fits' - - if get_basis: - ao_obj.dm.coefs = M2C - ao_obj.tel*ao_obj.dm - - basis = np.reshape(ao_obj.tel.OPD,[ao_obj.tel.resolution**2,M2C.shape[1]]) - ao_calib_object.basis = basis - - if ao_obj.param['getProjector']: - - print('Computing the pseudo-inverse of the modal basis...') - - cross_product_basis = np.matmul(basis.T,basis) - - non_diagonal_elements = np.sum(np.abs(cross_product_basis))-np.trace(cross_product_basis) - criteria = np.abs(1-np.abs(np.trace(cross_product_basis)-non_diagonal_elements)/np.trace(cross_product_basis)) - if criteria <= 1e-3: - print('Diagonality criteria: ' + str(criteria) + ' -- using the fast computation') - projector = np.diag(1/np.diag(cross_product_basis))@basis.T - else: - print('Diagonality criteria: ' + str(criteria) + ' -- using the slow computation') - projector = np.linalg.pinv(basis) - print('saving for later..') - print('Done!') - - ao_calib_object.projector = projector - - except: - print('ERROR: No file found! Taking a zonal basis instead..' ) - M2C = np.eye(ao_obj.dm.nValidAct) - -## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - if nameFolderIntMat is None: - nameFolderIntMat = ao_obj.param['pathInput']+ao_obj.param['name']+'/' - createFolder(nameFolderIntMat) - -#% get the interaction matrix : - - if nameIntMat is None: - if ao_obj.wfs.tag == 'pyramid' or ao_obj.wfs.tag == 'double_wfs': - try: - # case where the system name has an extra attribute - nameIntMat = 'zonal_interaction_matrix_'+str(ao_obj.param['resolution'])+'_res_'+str(ao_obj.param['modulation'])+'_mod_'+str(ao_obj.param['postProcessing'])+'_psfCentering_'+str(ao_obj.param['psfCentering'])+ao_obj.param['extra'] - except: - nameIntMat = 'zonal_interaction_matrix_'+str(ao_obj.param['resolution'])+'_res_'+str(ao_obj.param['modulation'])+'_mod_'+str(ao_obj.param['postProcessing'])+'_psfCentering_'+str(ao_obj.param['psfCentering']) - - if ao_obj.wfs.tag == 'shackHartmann': - if ao_obj.wfs.is_geometric: - nature = 'geometric' - else: - nature = 'diffractive' - try: - # case where the system name has an extra attribute - nameIntMat = 'zonal_interaction_matrix_'+str(ao_obj.param['resolution'])+'_res_'+str(ao_obj.wfs.nValidSubaperture)+'_subap_'+nature+'_'+ao_obj.param['extra'] - except: - nameIntMat = 'zonal_interaction_matrix_'+str(ao_obj.param['resolution'])+'_res_'+str(ao_obj.wfs.nValidSubaperture)+'_subap_'+nature - try: - print('Loading Interaction matrix '+nameIntMat+'...') - imat = read_fits(nameFolderIntMat+nameIntMat+'.fits') - calib = CalibrationVault(imat@M2C) - print('Done!') - ao_obj.param['interaction_matrix_filename'] = nameFolderIntMat+nameIntMat+'.fits' - - - - except: - print('ERROR! Computingh the zonal interaction matrix') - print('ERROR: No file found! computing imat..' ) - time.sleep(5) - M2C_zon = np.eye(ao_obj.dm.nValidAct) - - stroke =1e-9 # 1 nm amplitude - calib = InteractionMatrix(ao_obj.ngs,ao_obj.atm,ao_obj.tel,ao_obj.dm,ao_obj.wfs,M2C_zon,stroke,phaseOffset = 0,nMeasurements = nMeasurements) - # save output in fits file - hdr=pfits.Header() - hdr['TITLE'] = 'INTERACTION MATRIX' - empty_primary = pfits.PrimaryHDU(header=hdr) - # primary_hdu = pfits.ImageHDU(calib.D.astype(np.float32)) - primary_hdu = pfits.ImageHDU(calib.D) - - hdu = pfits.HDUList([empty_primary, primary_hdu]) - hdu.writeto(nameFolderIntMat + nameIntMat + '.fits', overwrite=True) - calib = CalibrationVault(calib.D@M2C) - -## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -#% get the modal gains matrix : - nameExtra = '_r0_'+str(100*ao_obj.atm.r0)+'_cm_'+ao_obj.param['opticalBand']+'_band_fitting_'+str(ao_obj.param['nModes'])+'_KL' - try: - nameModalGains = 'modal_gains'+ao_obj.param['extra']+nameExtra - except: - nameModalGains = 'modal_gains'+nameExtra - - try: - data_gains = read_fits(nameFolderIntMat+ nameModalGains+'.fits') - - print('Using Modal Gains loaded from '+str(nameFolderIntMat+ nameModalGains+'.fits')) - - except: - data_gains = np.ones(M2C.shape[1]) - print('No Modal Gains found. All gains set to 1') - - ao_calib_object.gOpt = np.diag(1/data_gains) - ao_calib_object.M2C = M2C - ao_calib_object.calib = calib - - return ao_calib_object - -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -# same function using an ao object as an input - -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -def ao_calibration(ngs, tel, atm, dm, wfs, param, nameFolderIntMat = None, nameIntMat = None, nameFolderBasis = None, nameBasis = None, nMeasurements=50, index_modes = None, get_basis = True,input_basis = None): - - # check if the name of the basis is specified otherwise take the nominal name - if nameBasis is None: - if dm.isM4: - initName = 'M2C_M4_' - else: - initName = 'M2C_' - - try: - nameBasis = initName+str(param['resolution'])+'_res'+param['extra'] - except: - nameBasis = initName+str(param['resolution'])+'_res' - - ao_calib_object = emptyClass() - - # check if a name for the origin folder is specified - if nameFolderBasis is None: - nameFolderBasis = param['pathInput'] - createFolder(nameFolderBasis) - -## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -# get the modal basis : - if input_basis is None: - try: - print('Loading the KL Modal Basis from: ' + nameFolderBasis+nameBasis ) - - M2C = read_fits(nameFolderBasis+ nameBasis+'.fits') - param['modal_basis_filename'] = nameFolderBasis+ nameBasis+'.fits' - if index_modes is None: - M2C = M2C[:,:param['nModes']] - else: - M2C = M2C[:,index_modes] - - if get_basis or param['getProjector']: - dm.coefs = M2C - tel*dm - - basis = np.reshape(tel.OPD,[tel.resolution**2,M2C.shape[1]]) - ao_calib_object.basis = basis - - if param['getProjector']: - print('Computing the pseudo-inverse of the modal basis...') - cross_product_basis = np.matmul(basis.T,basis) - non_diagonal_elements = np.sum(np.abs(cross_product_basis))-np.trace(cross_product_basis) - criteria = 1-np.abs(np.trace(cross_product_basis)-non_diagonal_elements)/np.trace(cross_product_basis) - if criteria <= 1e-3: - print('Diagonality criteria: ' + str(criteria) + ' -- using the fast computation') - projector = np.diag(1/np.diag(cross_product_basis))@basis.T - else: - print('Diagonality criteria: ' + str(criteria) + ' -- using the slow computation') - projector = np.linalg.pinv(basis) - ao_calib_object.projector = projector - except: - print('ERROR: No file found! Taking a zonal basis instead..' ) - M2C = np.eye(dm.nValidAct) - else: - M2C = input_basis - if get_basis or param['getProjector']: - dm.coefs = M2C - tel*dm - - basis = np.reshape(tel.OPD,[tel.resolution**2,M2C.shape[1]]) - ao_calib_object.basis = basis - - if param['getProjector']: - print('Computing the pseudo-inverse of the modal basis...') - cross_product_basis = np.matmul(basis.T,basis) - non_diagonal_elements = np.sum(np.abs(cross_product_basis))-np.trace(cross_product_basis) - criteria = 1-np.abs(np.trace(cross_product_basis)-non_diagonal_elements)/np.trace(cross_product_basis) - if criteria <= 1e-3: - print('Diagonality criteria: ' + str(criteria) + ' -- using the fast computation') - projector = np.diag(1/np.diag(cross_product_basis))@basis.T - else: - print('Diagonality criteria: ' + str(criteria) + ' -- using the slow computation') - projector = np.linalg.pinv(basis) - ao_calib_object.projector = projector -## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - if nameFolderIntMat is None: - nameFolderIntMat = param['pathInput']+param['name']+'/' - createFolder(nameFolderIntMat) -#% get the interaction matrix : - - if nameIntMat is None: - - - if wfs.tag == 'pyramid' or wfs.tag == 'double_wfs' : - try: - # case where the system name has an extra attribute - nameIntMat = 'zonal_interaction_matrix_'+str(param['resolution'])+'_res_'+str(param['modulation'])+'_mod_'+str(param['postProcessing'])+'_psfCentering_'+str(param['psfCentering'])+param['extra'] - except: - nameIntMat = 'zonal_interaction_matrix_'+str(param['resolution'])+'_res_'+str(param['modulation'])+'_mod_'+str(param['postProcessing'])+'_psfCentering_'+str(param['psfCentering']) - - if wfs.tag == 'shackHartmann': - if wfs.is_geometric: - nature = 'geometric' - else: - nature = 'diffractive' - try: - # case where the system name has an extra attribute - nameIntMat = 'zonal_interaction_matrix_'+str(param['resolution'])+'_res_'+str(wfs.nValidSubaperture)+'_subap_'+nature+'_'+param['extra'] - except: - nameIntMat = 'zonal_interaction_matrix_'+str(param['resolution'])+'_res_'+str(wfs.nValidSubaperture)+'_subap_'+nature - - - - - try: - print('Loading Interaction matrix '+nameFolderIntMat+nameIntMat+'...') - imat = read_fits(nameFolderIntMat+nameIntMat+'.fits') - calib = CalibrationVault(imat@M2C) - print('Done!') - param['interaction_matrix_filename'] = nameFolderIntMat+nameIntMat+'.fits' - - - except: - - print('ERROR: No file found! computing imat..' ) - time.sleep(5) - M2C_zon = np.eye(dm.nValidAct) - stroke =1e-9 # 1 nm amplitude - calib = InteractionMatrix(ngs, atm, tel, dm, wfs, M2C_zon, stroke, phaseOffset = 0, nMeasurements = nMeasurements) - # save output in fits file - hdr=pfits.Header() - hdr['TITLE'] = 'INTERACTION MATRIX' - empty_primary = pfits.PrimaryHDU(header=hdr) - # primary_hdu = pfits.ImageHDU(calib.D.astype(np.float32)) - primary_hdu = pfits.ImageHDU(calib.D) - hdu = pfits.HDUList([empty_primary, primary_hdu]) - hdu.writeto(nameFolderIntMat+nameIntMat+'.fits',overwrite=True) - calib = CalibrationVault(calib.D@M2C) - -## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -#% get the modal gains matrix : - nameExtra = '_r0_'+str(100*atm.r0)+'_cm_'+param['opticalBand']+'_band_fitting_'+str(param['nModes'])+'_KL' - try: - nameModalGains = 'modal_gains'+param['extra']+nameExtra - except: - nameModalGains = 'modal_gains'+nameExtra - - try: - data_gains = read_fits(nameFolderIntMat+ nameModalGains+'.fits') - - print('Using Modal Gains loaded from '+str(nameFolderIntMat+ nameModalGains+'.fits')) - - except: - data_gains = np.ones(M2C.shape[1]) - print('No Modal Gains found. All gains set to 1') - - - ao_calib_object.gOpt = np.diag(1/data_gains) - - ao_calib_object.M2C = M2C - ao_calib_object.calib = calib - - - - return ao_calib_object - -def get_modal_gains_from_ao_obj(ao_obj, nameFolderIntMat = None): - - if nameFolderIntMat is None: - nameFolderIntMat = ao_obj.param['pathInput']+ao_obj.param['name']+'/' - createFolder(nameFolderIntMat) -## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -#% get the modal gains matrix : - nameExtra = '_r0_'+str(100*ao_obj.atm.r0)+'_cm_'+ao_obj.param['opticalBand']+'_band_fitting_'+str(ao_obj.param['nModes'])+'_KL' - try: - nameModalGains = 'modal_gains'+ao_obj.param['extra']+nameExtra - except: - nameModalGains = 'modal_gains'+nameExtra - print('Looking for Modal Gains loaded from '+str(nameFolderIntMat+ nameModalGains+'.fits')) - try: - data_gains = read_fits(nameFolderIntMat+ nameModalGains+'.fits') - print('Using Modal Gains loaded from '+str(nameFolderIntMat+ nameModalGains+'.fits')) - - except: - data_gains = np.ones(ao_obj.param['nModes']) - print('No Modal Gains found. All gains set to 1') - - gOpt = np.diag(1/data_gains) - - return gOpt - -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -# same function using an ao object as an input - -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - -def get_modal_gains(param, nameFolderIntMat = None,r0 =None): - if r0 is None: - r0 = param['r0'] -## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - if nameFolderIntMat is None: - nameFolderIntMat = param['pathInput']+param['name']+'/' - createFolder(nameFolderIntMat) -#% get the modal gains matrix : - nameExtra = '_r0_'+str(100*r0)+'_cm_'+param['opticalBand']+'_band_fitting_'+str(param['nModes'])+'_KL' - try: - nameModalGains = 'modal_gains'+param['extra']+nameExtra - except: - nameModalGains = 'modal_gains'+nameExtra - - try: - data_gains = read_fits(nameFolderIntMat+ nameModalGains+'.fits') - print('Using Modal Gains loaded from '+str(nameFolderIntMat+ nameModalGains+'.fits')) - - except: - data_gains = np.ones(param['nModes']) - print('No Modal Gains found. All gains set to 1') - gOpt = np.diag(1/data_gains) +# -*- coding: utf-8 -*- +""" +Created on Tue Oct 13 09:47:17 2020 + +@author: cheritie +""" + +import time + +import numpy as np +from astropy.io import fits as pfits + +from .CalibrationVault import CalibrationVault +from .InteractionMatrix import InteractionMatrix +from ..tools.tools import createFolder, emptyClass, read_fits + + +def ao_calibration_from_ao_obj(ao_obj, nameFolderIntMat = None, nameIntMat = None, nameFolderBasis = None, nameBasis = None, nMeasurements=50, index_modes = None, get_basis = True): + + + # check if the name of the basis is specified otherwise take the nominal name + if nameBasis is None: + if ao_obj.dm.isM4: + initName = 'M2C_M4_' + else: + initName = 'M2C_' + try: + nameBasis = initName+str(ao_obj.param['resolution'])+'_res'+ao_obj.param['extra'] + except: + nameBasis = initName+str(ao_obj.param['resolution'])+'_res' + ao_calib_object = emptyClass() + + # check if a name for the origin folder is specified + if nameFolderBasis is None: + nameFolderBasis = ao_obj.param['pathInput'] + createFolder(nameFolderBasis) + +## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# get the modal basis : + + try: + print('Loading the KL Modal Basis from: ' + nameFolderBasis+nameBasis ) + M2C = read_fits(nameFolderBasis+ nameBasis+'.fits') + if index_modes is None: + M2C = M2C[:,:ao_obj.param['nModes']] + else: + M2C = M2C[:,index_modes] + + ao_obj.param['modal_basis_filename'] = nameFolderBasis+ nameBasis+'.fits' + + if get_basis: + ao_obj.dm.coefs = M2C + ao_obj.tel*ao_obj.dm + + basis = np.reshape(ao_obj.tel.OPD,[ao_obj.tel.resolution**2,M2C.shape[1]]) + ao_calib_object.basis = basis + + if ao_obj.param['getProjector']: + + print('Computing the pseudo-inverse of the modal basis...') + + cross_product_basis = np.matmul(basis.T,basis) + + non_diagonal_elements = np.sum(np.abs(cross_product_basis))-np.trace(cross_product_basis) + criteria = np.abs(1-np.abs(np.trace(cross_product_basis)-non_diagonal_elements)/np.trace(cross_product_basis)) + if criteria <= 1e-3: + print('Diagonality criteria: ' + str(criteria) + ' -- using the fast computation') + projector = np.diag(1/np.diag(cross_product_basis))@basis.T + else: + print('Diagonality criteria: ' + str(criteria) + ' -- using the slow computation') + projector = np.linalg.pinv(basis) + print('saving for later..') + print('Done!') + + ao_calib_object.projector = projector + + except: + print('ERROR: No file found! Taking a zonal basis instead..' ) + M2C = np.eye(ao_obj.dm.nValidAct) + +## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + if nameFolderIntMat is None: + nameFolderIntMat = ao_obj.param['pathInput']+ao_obj.param['name']+'/' + createFolder(nameFolderIntMat) + +#% get the interaction matrix : + + if nameIntMat is None: + if ao_obj.wfs.tag == 'pyramid' or ao_obj.wfs.tag == 'double_wfs': + try: + # case where the system name has an extra attribute + nameIntMat = 'zonal_interaction_matrix_'+str(ao_obj.param['resolution'])+'_res_'+str(ao_obj.param['modulation'])+'_mod_'+str(ao_obj.param['postProcessing'])+'_psfCentering_'+str(ao_obj.param['psfCentering'])+ao_obj.param['extra'] + except: + nameIntMat = 'zonal_interaction_matrix_'+str(ao_obj.param['resolution'])+'_res_'+str(ao_obj.param['modulation'])+'_mod_'+str(ao_obj.param['postProcessing'])+'_psfCentering_'+str(ao_obj.param['psfCentering']) + + if ao_obj.wfs.tag == 'shackHartmann': + if ao_obj.wfs.is_geometric: + nature = 'geometric' + else: + nature = 'diffractive' + try: + # case where the system name has an extra attribute + nameIntMat = 'zonal_interaction_matrix_'+str(ao_obj.param['resolution'])+'_res_'+str(ao_obj.wfs.nValidSubaperture)+'_subap_'+nature+'_'+ao_obj.param['extra'] + except: + nameIntMat = 'zonal_interaction_matrix_'+str(ao_obj.param['resolution'])+'_res_'+str(ao_obj.wfs.nValidSubaperture)+'_subap_'+nature + try: + print('Loading Interaction matrix '+nameIntMat+'...') + imat = read_fits(nameFolderIntMat+nameIntMat+'.fits') + calib = CalibrationVault(imat@M2C) + print('Done!') + ao_obj.param['interaction_matrix_filename'] = nameFolderIntMat+nameIntMat+'.fits' + + + + except: + print('ERROR! Computingh the zonal interaction matrix') + print('ERROR: No file found! computing imat..' ) + time.sleep(5) + M2C_zon = np.eye(ao_obj.dm.nValidAct) + + stroke =1e-9 # 1 nm amplitude + calib = InteractionMatrix(ao_obj.ngs,ao_obj.atm,ao_obj.tel,ao_obj.dm,ao_obj.wfs,M2C_zon,stroke,phaseOffset = 0,nMeasurements = nMeasurements) + # save output in fits file + hdr=pfits.Header() + hdr['TITLE'] = 'INTERACTION MATRIX' + empty_primary = pfits.PrimaryHDU(header=hdr) + # primary_hdu = pfits.ImageHDU(calib.D.astype(np.float32)) + primary_hdu = pfits.ImageHDU(calib.D) + + hdu = pfits.HDUList([empty_primary, primary_hdu]) + hdu.writeto(nameFolderIntMat + nameIntMat + '.fits', overwrite=True) + calib = CalibrationVault(calib.D@M2C) + +## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +#% get the modal gains matrix : + nameExtra = '_r0_'+str(100*ao_obj.atm.r0)+'_cm_'+ao_obj.param['opticalBand']+'_band_fitting_'+str(ao_obj.param['nModes'])+'_KL' + try: + nameModalGains = 'modal_gains'+ao_obj.param['extra']+nameExtra + except: + nameModalGains = 'modal_gains'+nameExtra + + try: + data_gains = read_fits(nameFolderIntMat+ nameModalGains+'.fits') + + print('Using Modal Gains loaded from '+str(nameFolderIntMat+ nameModalGains+'.fits')) + + except: + data_gains = np.ones(M2C.shape[1]) + print('No Modal Gains found. All gains set to 1') + + ao_calib_object.gOpt = np.diag(1/data_gains) + ao_calib_object.M2C = M2C + ao_calib_object.calib = calib + + return ao_calib_object + +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +# same function using an ao object as an input + +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +def ao_calibration(ngs, tel, atm, dm, wfs, param, nameFolderIntMat = None, nameIntMat = None, nameFolderBasis = None, nameBasis = None, nMeasurements=50, index_modes = None, get_basis = True,input_basis = None): + + # check if the name of the basis is specified otherwise take the nominal name + if nameBasis is None: + if dm.isM4: + initName = 'M2C_M4_' + else: + initName = 'M2C_' + + try: + nameBasis = initName+str(param['resolution'])+'_res'+param['extra'] + except: + nameBasis = initName+str(param['resolution'])+'_res' + + ao_calib_object = emptyClass() + + # check if a name for the origin folder is specified + if nameFolderBasis is None: + nameFolderBasis = param['pathInput'] + createFolder(nameFolderBasis) + +## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# get the modal basis : + if input_basis is None: + try: + print('Loading the KL Modal Basis from: ' + nameFolderBasis+nameBasis ) + + M2C = read_fits(nameFolderBasis+ nameBasis+'.fits') + param['modal_basis_filename'] = nameFolderBasis+ nameBasis+'.fits' + if index_modes is None: + M2C = M2C[:,:param['nModes']] + else: + M2C = M2C[:,index_modes] + + if get_basis or param['getProjector']: + dm.coefs = M2C + tel*dm + + basis = np.reshape(tel.OPD,[tel.resolution**2,M2C.shape[1]]) + ao_calib_object.basis = basis + + if param['getProjector']: + print('Computing the pseudo-inverse of the modal basis...') + cross_product_basis = np.matmul(basis.T,basis) + non_diagonal_elements = np.sum(np.abs(cross_product_basis))-np.trace(cross_product_basis) + criteria = 1-np.abs(np.trace(cross_product_basis)-non_diagonal_elements)/np.trace(cross_product_basis) + if criteria <= 1e-3: + print('Diagonality criteria: ' + str(criteria) + ' -- using the fast computation') + projector = np.diag(1/np.diag(cross_product_basis))@basis.T + else: + print('Diagonality criteria: ' + str(criteria) + ' -- using the slow computation') + projector = np.linalg.pinv(basis) + ao_calib_object.projector = projector + except: + print('ERROR: No file found! Taking a zonal basis instead..' ) + M2C = np.eye(dm.nValidAct) + else: + M2C = input_basis + if get_basis or param['getProjector']: + dm.coefs = M2C + tel*dm + + basis = np.reshape(tel.OPD,[tel.resolution**2,M2C.shape[1]]) + ao_calib_object.basis = basis + + if param['getProjector']: + print('Computing the pseudo-inverse of the modal basis...') + cross_product_basis = np.matmul(basis.T,basis) + non_diagonal_elements = np.sum(np.abs(cross_product_basis))-np.trace(cross_product_basis) + criteria = 1-np.abs(np.trace(cross_product_basis)-non_diagonal_elements)/np.trace(cross_product_basis) + if criteria <= 1e-3: + print('Diagonality criteria: ' + str(criteria) + ' -- using the fast computation') + projector = np.diag(1/np.diag(cross_product_basis))@basis.T + else: + print('Diagonality criteria: ' + str(criteria) + ' -- using the slow computation') + projector = np.linalg.pinv(basis) + ao_calib_object.projector = projector +## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + if nameFolderIntMat is None: + nameFolderIntMat = param['pathInput']+param['name']+'/' + createFolder(nameFolderIntMat) +#% get the interaction matrix : + + if nameIntMat is None: + + + if wfs.tag == 'pyramid' or wfs.tag == 'double_wfs' : + try: + # case where the system name has an extra attribute + nameIntMat = 'zonal_interaction_matrix_'+str(param['resolution'])+'_res_'+str(param['modulation'])+'_mod_'+str(param['postProcessing'])+'_psfCentering_'+str(param['psfCentering'])+param['extra'] + except: + nameIntMat = 'zonal_interaction_matrix_'+str(param['resolution'])+'_res_'+str(param['modulation'])+'_mod_'+str(param['postProcessing'])+'_psfCentering_'+str(param['psfCentering']) + + if wfs.tag == 'shackHartmann': + if wfs.is_geometric: + nature = 'geometric' + else: + nature = 'diffractive' + try: + # case where the system name has an extra attribute + nameIntMat = 'zonal_interaction_matrix_'+str(param['resolution'])+'_res_'+str(wfs.nValidSubaperture)+'_subap_'+nature+'_'+param['extra'] + except: + nameIntMat = 'zonal_interaction_matrix_'+str(param['resolution'])+'_res_'+str(wfs.nValidSubaperture)+'_subap_'+nature + + + + + try: + print('Loading Interaction matrix '+nameFolderIntMat+nameIntMat+'...') + imat = read_fits(nameFolderIntMat+nameIntMat+'.fits') + calib = CalibrationVault(imat@M2C) + print('Done!') + param['interaction_matrix_filename'] = nameFolderIntMat+nameIntMat+'.fits' + + + except: + + print('ERROR: No file found! computing imat..' ) + time.sleep(5) + M2C_zon = np.eye(dm.nValidAct) + stroke =1e-9 # 1 nm amplitude + calib = InteractionMatrix(ngs, atm, tel, dm, wfs, M2C_zon, stroke, phaseOffset = 0, nMeasurements = nMeasurements) + # save output in fits file + hdr=pfits.Header() + hdr['TITLE'] = 'INTERACTION MATRIX' + empty_primary = pfits.PrimaryHDU(header=hdr) + # primary_hdu = pfits.ImageHDU(calib.D.astype(np.float32)) + primary_hdu = pfits.ImageHDU(calib.D) + hdu = pfits.HDUList([empty_primary, primary_hdu]) + hdu.writeto(nameFolderIntMat+nameIntMat+'.fits',overwrite=True) + calib = CalibrationVault(calib.D@M2C) + +## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +#% get the modal gains matrix : + nameExtra = '_r0_'+str(100*atm.r0)+'_cm_'+param['opticalBand']+'_band_fitting_'+str(param['nModes'])+'_KL' + try: + nameModalGains = 'modal_gains'+param['extra']+nameExtra + except: + nameModalGains = 'modal_gains'+nameExtra + + try: + data_gains = read_fits(nameFolderIntMat+ nameModalGains+'.fits') + + print('Using Modal Gains loaded from '+str(nameFolderIntMat+ nameModalGains+'.fits')) + + except: + data_gains = np.ones(M2C.shape[1]) + print('No Modal Gains found. All gains set to 1') + + + ao_calib_object.gOpt = np.diag(1/data_gains) + + ao_calib_object.M2C = M2C + ao_calib_object.calib = calib + + + + return ao_calib_object + +def get_modal_gains_from_ao_obj(ao_obj, nameFolderIntMat = None): + + if nameFolderIntMat is None: + nameFolderIntMat = ao_obj.param['pathInput']+ao_obj.param['name']+'/' + createFolder(nameFolderIntMat) +## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +#% get the modal gains matrix : + nameExtra = '_r0_'+str(100*ao_obj.atm.r0)+'_cm_'+ao_obj.param['opticalBand']+'_band_fitting_'+str(ao_obj.param['nModes'])+'_KL' + try: + nameModalGains = 'modal_gains'+ao_obj.param['extra']+nameExtra + except: + nameModalGains = 'modal_gains'+nameExtra + print('Looking for Modal Gains loaded from '+str(nameFolderIntMat+ nameModalGains+'.fits')) + try: + data_gains = read_fits(nameFolderIntMat+ nameModalGains+'.fits') + print('Using Modal Gains loaded from '+str(nameFolderIntMat+ nameModalGains+'.fits')) + + except: + data_gains = np.ones(ao_obj.param['nModes']) + print('No Modal Gains found. All gains set to 1') + + gOpt = np.diag(1/data_gains) + + return gOpt + +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +# same function using an ao object as an input + +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +def get_modal_gains(param, nameFolderIntMat = None,r0 =None): + if r0 is None: + r0 = param['r0'] +## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + if nameFolderIntMat is None: + nameFolderIntMat = param['pathInput']+param['name']+'/' + createFolder(nameFolderIntMat) +#% get the modal gains matrix : + nameExtra = '_r0_'+str(100*r0)+'_cm_'+param['opticalBand']+'_band_fitting_'+str(param['nModes'])+'_KL' + try: + nameModalGains = 'modal_gains'+param['extra']+nameExtra + except: + nameModalGains = 'modal_gains'+nameExtra + + try: + data_gains = read_fits(nameFolderIntMat+ nameModalGains+'.fits') + print('Using Modal Gains loaded from '+str(nameFolderIntMat+ nameModalGains+'.fits')) + + except: + data_gains = np.ones(param['nModes']) + print('No Modal Gains found. All gains set to 1') + gOpt = np.diag(1/data_gains) return gOpt \ No newline at end of file diff --git a/AO_modules/calibration/ao_cockpit_psim.py b/OOPAO/calibration/ao_cockpit_psim.py old mode 100755 new mode 100644 similarity index 99% rename from AO_modules/calibration/ao_cockpit_psim.py rename to OOPAO/calibration/ao_cockpit_psim.py index 4f89472..64a50e4 --- a/AO_modules/calibration/ao_cockpit_psim.py +++ b/OOPAO/calibration/ao_cockpit_psim.py @@ -5,30 +5,18 @@ @author: cverinau """ - -import numpy as np -#import proper as prp import math -import mpmath -import numba -from numba import jit -from math import factorial -import os -import psutil -import matplotlib.pyplot as plt -import time import multiprocessing as mp -import pyfftw -import numexpr as ne -from astropy.io import fits - -import pdb - +import os import pickle +import time +from math import factorial -import sys - - +import matplotlib.pyplot as plt +import numexpr as ne +import numpy as np +import psutil +import pyfftw def sizeof_fmt(num, suffix='B'): diff --git a/AO_modules/calibration/compute_KL_modal_basis.py b/OOPAO/calibration/compute_KL_modal_basis.py old mode 100755 new mode 100644 similarity index 95% rename from AO_modules/calibration/compute_KL_modal_basis.py rename to OOPAO/calibration/compute_KL_modal_basis.py index e2693c4..b08bf87 --- a/AO_modules/calibration/compute_KL_modal_basis.py +++ b/OOPAO/calibration/compute_KL_modal_basis.py @@ -1,273 +1,272 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Oct 21 10:57:29 2020 - -@author: cheritie -""" - -# modules for the KL basis computation: -import numpy as np -from astropy.io import fits as pfits -from AO_modules.tools.tools import createFolder -import AO_modules.calibration.ao_cockpit_psim as aou - - - -def compute_M2C(telescope, atmosphere, deformableMirror, param, nameFolder = None, nameFile = None,remove_piston = False,HHtName = None, baseName = None, SpM_2D = None, nZer = 3, SZ=None, mem_available = None, NDIVL = None, computeSpM = True, ortho_spm = True, computeSB = True, computeKL = True, minimF = False, P2F = None, alpha = None, beta = None, nmo = None, IF_2D = None, IFma = None, returnSB = False, returnHHt = False, recompute_cov = False,extra_name = ''): - - """ - - HHtName = None extension for the HHt Covariance file - - baseName = None extension to the filename for basis saving - - SpM_2D = None 2D Specific modes [dim,dim,nspm], if None then automatic - - nZer = 3 number of zernike (PTT,...) for automatic computation - - SZ = None telescope.resolutione of FFts for HHt (By default SZ=2*dim) - - mem_available = None Memory allocated for HHt computation (default is 50GB) - - NDIVL = None Subdiv. of HHt task in ~NDIVL**2. None:-> mem_available - - computeSpM = True Flag to compute Specific modes - - ortho_spm = True Flag to orthonormalize specific modes (QR decomposition) - - computeSB = True Flag to compute the Seed Basis - - computeKL = True Flag to compute the KL basis - - minimF = False Flag to minimize Forces - - P2F = None Stiffness matrix (loaded by default) - - alpha = None Force regularization parameter (expert) - - beta = None Position damping parameter (expert) - - nmo = None Number of modes to compute - - IF_2D = None 2D Influence Functions (only for speeding up) - - IFma = None Serial Influence Functions (only for speeding up) - - returnSB = False Flag to return also the Seed Basis (w/ or w/o KL) - - """ - - if nmo is None: - nmo=param['nModes'] - - if deformableMirror.isM4: - initName = 'M2C_M4_' - else: - initName = 'M2C_' - if baseName is not None: - initName = initName + baseName+'_' - if nameFolder is None: - nameFolder = param['pathInput'] - - createFolder(nameFolder) - - if nameFile is None: - try: - nameFile = initName + str(param['resolution'])+'_res'+param['extra']+extra_name - except: - nameFile = initName + str(param['resolution'])+'_res'+extra_name - - - # the function takes as an input an object with obj.tel, obj.atm,obj. - diameter = telescope.D - r0 = atmosphere.r0 - L0 = atmosphere.L0 - pupil = telescope.pupil - - telescope.isPaired = False # separate from eventual atmosphere - - if IF_2D is None: - deformableMirror.coefs = np.eye(deformableMirror.nValidAct) # assign dm coefs to get the cube of IF in OPD - print('COMPUTING TEL*DM...') - print(' ') - telescope*deformableMirror # propagate to get the OPD of the IFS after reflection - - print('PREPARING IF_2D...') - print(' ') - IF_2D = np.moveaxis(telescope.OPD,-1,0) - - nact = IF_2D.shape[0] - - print('Computing Specific Modes ...') - print(' ') - GEO = aou.mkp(telescope.resolution/telescope.resolution*diameter,telescope.resolution,diameter,0.) - - if nZer is not None and SpM_2D is None: - SpM_2D = aou.give_zernike(GEO, diameter, nZer) - nspm = nZer - if SpM_2D is not None: - nspm=SpM_2D.shape[2] - - if SZ is None: - SZ = int(2*telescope.resolution) ## SZ=1110 for dxo=0.06944 and SZ=1542 for dxo=0.05 - - print('COMPUTING VON KARMAN 2D PSD...') - print(' ') - PSD_atm , df, pterm = aou.VK_DSP_up(diameter,r0,L0,SZ,telescope.resolution,1,pupil) - -#%% ---------- EVALUATE SPLIT OF WORK UPON MEMORY AVAILABLE ---------- - - -#%% ----------COMPUTE HHt COVARIANCE MATRIX (OR LOAD EXISTING ONE) ---------- - #pdb.set_trace() - - if recompute_cov is False: - try: - #HHt, PSD_atm, df = aou.load(nameFolder+'HHt_PSD_df_'+HHtName+'_r'+str(r0)+'_SZ'+str(SZ)+'.pkl') - HHt, PSD_atm, df = aou.load(nameFolder+'HHt_PSD_df_'+HHtName+'.pkl') - print('LOADED COV MAT HHt...') - print(' ') - except: - recompute_cov = True - - if recompute_cov is True: - print('COMPUTING COV MAT HHt...') - print(' ') - #pdb.set_trace() - if mem_available is None: - mem_available=100.e9 - if NDIVL is None: - mem,NDIVL=aou.estimate_ndivl(SZ,telescope.resolution,nact,mem_available) - if NDIVL == 0: - NDIVL = 1 - BLOCKL=nact//NDIVL - REST=nact-BLOCKL*NDIVL - HHt = aou.DO_HHt(IF_2D,PSD_atm,df,pupil,BLOCKL,REST,SZ,0) - try: - aou.save(nameFolder+'HHt_PSD_df_'+HHtName+'.pkl',[HHt, PSD_atm, df]) - except: - aou.save(nameFolder+'HHt_PSD_df_'+initName+'r'+str(r0)+'_SZ'+str(SZ)+'.pkl',[HHt, PSD_atm, df]) - - -#%% ----------PRECOMPUTE MOST USED QUANTITIES ---------- - if computeSpM == True or computeSB == True or computeKL == True: - - ## VALID OPD POINTS IN PUPIL - idxpup=np.where(pupil==1) - tpup=len(idxpup[0]) - - ## Matrix of serialized IFs - if IFma is None: - print('SERIALIZING IFs...') - print(' ') - IFma=np.matrix(aou.vectorifyb(IF_2D,idxpup)) - - ## Matrix of serialized Special modes - print('SERIALIZING Specific Modes...') - print(' ') - Tspm=np.matrix(aou.vectorify(SpM_2D,idxpup)) - - ## CROSS-PRODUCT OF IFs - print('COMPUTING IFs CROSS PRODUCT...') - print(' ') - DELTA=IFma.T @ IFma - -#%% ----------COMPUTE SPECIFIC MODES BASIS ---------- - if minimF == True: - if P2F is None: - P2F=np.float64(pfits.getdata(param['pathInput']+'P2F.fits'))*1.e6 #( in N/m) - P2Ff=np.zeros([nact,nact],dtype=np.float64) - nap=nact//6 - for k in range(0,6): - P2Ff[k*nap:(k+1)*nap,k*nap:(k+1)*nap] = P2F.copy() - - K=np.asmatrix(P2Ff) - del P2Ff - - - if alpha is None: - alpha = 1.e-18 - if beta is None: - beta=1.e-6 - - if computeSpM == True and minimF == True: - print('BUILDING FORCE-OPTIMIZED SPECIFIC MODES...') - print(' ') - check=1 - amp_check=1.e-6 - - SpM = aou.build_SpecificBasis_F(Tspm,IFma,DELTA,K,alpha,ortho_spm,check,amp_check) -# SpM_opd = IFma @ SpM - - print('CHECKING ORTHONORMALITY OF SPECIFIC MODES...') - print(' ') - - DELTA_SpM_opd = SpM.T @ DELTA @ SpM - print('Orthonormality error for SpM = ', np.max(np.abs(DELTA_SpM_opd/tpup-np.eye(nspm)))) - - - if computeSpM == True and minimF == False: - check=1 - amp_check=1.e-6 - lim=1.e-3 - SpM = aou.build_SpecificBasis_C(Tspm,IFma,DELTA,lim,ortho_spm,check,amp_check) - - print('CHECKING ORTHONORMALITY OF SPECIFIC MODES...') - print(' ') - DELTA_SpM_opd = SpM.T @ DELTA @ SpM - print('Orthonormality error for SpM = ', np.max(np.abs(DELTA_SpM_opd/tpup-np.eye(nspm)))) - - -#%% ----------COMPUTE SEED BASIS ---------- - if computeKL == True: - computeSB = True - - if computeSB == True: - #pdb.set_trace() - if minimF == False: - print('BUILDING SEED BASIS ...') - print(' ') - lim=1.e-3 - SB = aou.build_SeedBasis_C(IFma, SpM,DELTA,lim) - nSB=SB.shape[1] - DELTA_SB = SB.T @ DELTA @ SB - print('Orthonormality error for '+str(nSB)+' modes of the Seed Basis = ',np.max(np.abs(DELTA_SB[0:nSB,0:nSB]/tpup-np.eye(nSB)))) - - if minimF == True: - print('BUILDING FORCE OPTIMIZED SEED BASIS ...') - print(' ') - SB = aou.build_SeedBasis_F(IFma, SpM, K, beta) - nSB=SB.shape[1] - DELTA_SB = SB.T @ DELTA @ SB - print('Orthonormality error for '+str(nmo)+' modes of the Seed Basis = ',np.max(np.abs(DELTA_SB[0:nmo,0:nmo]/tpup-np.eye(nmo)))) - - if computeKL == False: - BASIS=np.asmatrix(np.zeros([nact,nspm+nSB],dtype=np.float64)) - BASIS[:,0:nspm] = SpM - BASIS[:,nspm:] = SB - if remove_piston == True: - BASIS = np.asarray(BASIS[:,1:]) - print('Piston removed from the modal basis!' ) - -#%% ----------COMPUTE KL BASIS ---------- - - if computeKL == True: - check=1 - if nmo>SB.shape[1]: - print('WARNING: Number of modes requested too high, taking the maximum value possible!') - nmoKL = SB.shape[1] - else: - nmoKL = nmo - KL=aou.build_KLBasis(HHt,SB,DELTA,nmoKL,check) - #pdb.set_trace() - DELTA_KL = KL.T @ DELTA @ KL - print('Orthonormality error for '+str(nmoKL)+' modes of the KL Basis = ',np.max(np.abs(DELTA_KL[0:nmoKL,0:nmoKL]/tpup-np.eye(nmoKL)))) - - - BASIS=np.asmatrix(np.zeros([nact,nspm+nmoKL],dtype=np.float64)) - BASIS[:,0:nspm] = SpM - BASIS[:,nspm:] = KL - if remove_piston == True: - BASIS = np.asarray(BASIS[:,1:]) - print('Piston removed from the modal basis!' ) -# save output in fits file - hdr=pfits.Header() - hdr['TITLE'] = initName+'_KL' #'M4_KL' - empty_primary = pfits.PrimaryHDU(header=hdr) - ## CAREFUL THE CUBE IS SAVED AS A NON SPARSE MATRIX - primary_hdu = pfits.ImageHDU(BASIS) - hdu = pfits.HDUList([empty_primary, primary_hdu]) - hdu.writeto(nameFolder+nameFile+'.fits',overwrite=True) - return np.asarray(BASIS) - - if returnSB == True: - hdr=pfits.Header() - hdr['TITLE'] = initName+'_SB' #'M4_KL' - empty_primary = pfits.PrimaryHDU(header=hdr) - ## CAREFUL THE CUBE IS SAVED AS A NON SPARSE MATRIX - primary_hdu = pfits.ImageHDU(BASIS) - hdu = pfits.HDUList([empty_primary, primary_hdu]) - hdu.writeto(nameFolder+nameFile+'.fits',overwrite=True) - - return np.asarray(BASIS),SB +# -*- coding: utf-8 -*- +""" +Created on Wed Oct 21 10:57:29 2020 + +@author: cheritie +""" + +import numpy as np +from astropy.io import fits as pfits + +import ao_cockpit_psim as aou +from ..tools.tools import createFolder + + +def compute_M2C(telescope, atmosphere, deformableMirror, param, nameFolder = None, nameFile = None,remove_piston = False,HHtName = None, baseName = None, SpM_2D = None, nZer = 3, SZ=None, mem_available = None, NDIVL = None, computeSpM = True, ortho_spm = True, computeSB = True, computeKL = True, minimF = False, P2F = None, alpha = None, beta = None, nmo = None, IF_2D = None, IFma = None, returnSB = False, returnHHt = False, recompute_cov = False,extra_name = ''): + + """ + - HHtName = None extension for the HHt Covariance file + - baseName = None extension to the filename for basis saving + - SpM_2D = None 2D Specific modes [dim,dim,nspm], if None then automatic + - nZer = 3 number of zernike (PTT,...) for automatic computation + - SZ = None telescope.resolutione of FFts for HHt (By default SZ=2*dim) + - mem_available = None Memory allocated for HHt computation (default is 50GB) + - NDIVL = None Subdiv. of HHt task in ~NDIVL**2. None:-> mem_available + - computeSpM = True Flag to compute Specific modes + - ortho_spm = True Flag to orthonormalize specific modes (QR decomposition) + - computeSB = True Flag to compute the Seed Basis + - computeKL = True Flag to compute the KL basis + - minimF = False Flag to minimize Forces + - P2F = None Stiffness matrix (loaded by default) + - alpha = None Force regularization parameter (expert) + - beta = None Position damping parameter (expert) + - nmo = None Number of modes to compute + - IF_2D = None 2D Influence Functions (only for speeding up) + - IFma = None Serial Influence Functions (only for speeding up) + - returnSB = False Flag to return also the Seed Basis (w/ or w/o KL) + + """ + + if nmo is None: + nmo=param['nModes'] + + if deformableMirror.isM4: + initName = 'M2C_M4_' + else: + initName = 'M2C_' + if baseName is not None: + initName = initName + baseName+'_' + if nameFolder is None: + nameFolder = param['pathInput'] + + createFolder(nameFolder) + + if nameFile is None: + try: + nameFile = initName + str(param['resolution'])+'_res'+param['extra']+extra_name + except: + nameFile = initName + str(param['resolution'])+'_res'+extra_name + + + # the function takes as an input an object with obj.tel, obj.atm,obj. + diameter = telescope.D + r0 = atmosphere.r0 + L0 = atmosphere.L0 + pupil = telescope.pupil + + telescope.isPaired = False # separate from eventual atmosphere + + if IF_2D is None: + deformableMirror.coefs = np.eye(deformableMirror.nValidAct) # assign dm coefs to get the cube of IF in OPD + print('COMPUTING TEL*DM...') + print(' ') + telescope*deformableMirror # propagate to get the OPD of the IFS after reflection + + print('PREPARING IF_2D...') + print(' ') + IF_2D = np.moveaxis(telescope.OPD,-1,0) + + nact = IF_2D.shape[0] + + print('Computing Specific Modes ...') + print(' ') + GEO = aou.mkp(telescope.resolution/telescope.resolution*diameter,telescope.resolution,diameter,0.) + + if nZer is not None and SpM_2D is None: + SpM_2D = aou.give_zernike(GEO, diameter, nZer) + nspm = nZer + if SpM_2D is not None: + nspm=SpM_2D.shape[2] + + if SZ is None: + SZ = int(2*telescope.resolution) ## SZ=1110 for dxo=0.06944 and SZ=1542 for dxo=0.05 + + print('COMPUTING VON KARMAN 2D PSD...') + print(' ') + PSD_atm , df, pterm = aou.VK_DSP_up(diameter,r0,L0,SZ,telescope.resolution,1,pupil) + +#%% ---------- EVALUATE SPLIT OF WORK UPON MEMORY AVAILABLE ---------- + + +#%% ----------COMPUTE HHt COVARIANCE MATRIX (OR LOAD EXISTING ONE) ---------- + #pdb.set_trace() + + if recompute_cov is False: + try: + #HHt, PSD_atm, df = aou.load(nameFolder+'HHt_PSD_df_'+HHtName+'_r'+str(r0)+'_SZ'+str(SZ)+'.pkl') + HHt, PSD_atm, df = aou.load(nameFolder+'HHt_PSD_df_'+HHtName+'.pkl') + print('LOADED COV MAT HHt...') + print(' ') + except: + recompute_cov = True + + if recompute_cov is True: + print('COMPUTING COV MAT HHt...') + print(' ') + #pdb.set_trace() + if mem_available is None: + mem_available=100.e9 + if NDIVL is None: + mem,NDIVL=aou.estimate_ndivl(SZ,telescope.resolution,nact,mem_available) + if NDIVL == 0: + NDIVL = 1 + BLOCKL=nact//NDIVL + REST=nact-BLOCKL*NDIVL + HHt = aou.DO_HHt(IF_2D,PSD_atm,df,pupil,BLOCKL,REST,SZ,0) + try: + aou.save(nameFolder+'HHt_PSD_df_'+HHtName+'.pkl',[HHt, PSD_atm, df]) + except: + aou.save(nameFolder+'HHt_PSD_df_'+initName+'r'+str(r0)+'_SZ'+str(SZ)+'.pkl',[HHt, PSD_atm, df]) + + +#%% ----------PRECOMPUTE MOST USED QUANTITIES ---------- + if computeSpM == True or computeSB == True or computeKL == True: + + ## VALID OPD POINTS IN PUPIL + idxpup=np.where(pupil==1) + tpup=len(idxpup[0]) + + ## Matrix of serialized IFs + if IFma is None: + print('SERIALIZING IFs...') + print(' ') + IFma=np.matrix(aou.vectorifyb(IF_2D,idxpup)) + + ## Matrix of serialized Special modes + print('SERIALIZING Specific Modes...') + print(' ') + Tspm=np.matrix(aou.vectorify(SpM_2D,idxpup)) + + ## CROSS-PRODUCT OF IFs + print('COMPUTING IFs CROSS PRODUCT...') + print(' ') + DELTA=IFma.T @ IFma + +#%% ----------COMPUTE SPECIFIC MODES BASIS ---------- + if minimF == True: + if P2F is None: + P2F=np.float64(pfits.getdata(param['pathInput']+'P2F.fits'))*1.e6 #( in N/m) + P2Ff=np.zeros([nact,nact],dtype=np.float64) + nap=nact//6 + for k in range(0,6): + P2Ff[k*nap:(k+1)*nap,k*nap:(k+1)*nap] = P2F.copy() + + K=np.asmatrix(P2Ff) + del P2Ff + + + if alpha is None: + alpha = 1.e-18 + if beta is None: + beta=1.e-6 + + if computeSpM == True and minimF == True: + print('BUILDING FORCE-OPTIMIZED SPECIFIC MODES...') + print(' ') + check=1 + amp_check=1.e-6 + + SpM = aou.build_SpecificBasis_F(Tspm,IFma,DELTA,K,alpha,ortho_spm,check,amp_check) +# SpM_opd = IFma @ SpM + + print('CHECKING ORTHONORMALITY OF SPECIFIC MODES...') + print(' ') + + DELTA_SpM_opd = SpM.T @ DELTA @ SpM + print('Orthonormality error for SpM = ', np.max(np.abs(DELTA_SpM_opd/tpup-np.eye(nspm)))) + + + if computeSpM == True and minimF == False: + check=1 + amp_check=1.e-6 + lim=1.e-3 + SpM = aou.build_SpecificBasis_C(Tspm,IFma,DELTA,lim,ortho_spm,check,amp_check) + + print('CHECKING ORTHONORMALITY OF SPECIFIC MODES...') + print(' ') + DELTA_SpM_opd = SpM.T @ DELTA @ SpM + print('Orthonormality error for SpM = ', np.max(np.abs(DELTA_SpM_opd/tpup-np.eye(nspm)))) + + +#%% ----------COMPUTE SEED BASIS ---------- + if computeKL == True: + computeSB = True + + if computeSB == True: + #pdb.set_trace() + if minimF == False: + print('BUILDING SEED BASIS ...') + print(' ') + lim=1.e-3 + SB = aou.build_SeedBasis_C(IFma, SpM,DELTA,lim) + nSB=SB.shape[1] + DELTA_SB = SB.T @ DELTA @ SB + print('Orthonormality error for '+str(nSB)+' modes of the Seed Basis = ',np.max(np.abs(DELTA_SB[0:nSB,0:nSB]/tpup-np.eye(nSB)))) + + if minimF == True: + print('BUILDING FORCE OPTIMIZED SEED BASIS ...') + print(' ') + SB = aou.build_SeedBasis_F(IFma, SpM, K, beta) + nSB=SB.shape[1] + DELTA_SB = SB.T @ DELTA @ SB + print('Orthonormality error for '+str(nmo)+' modes of the Seed Basis = ',np.max(np.abs(DELTA_SB[0:nmo,0:nmo]/tpup-np.eye(nmo)))) + + if computeKL == False: + BASIS=np.asmatrix(np.zeros([nact,nspm+nSB],dtype=np.float64)) + BASIS[:,0:nspm] = SpM + BASIS[:,nspm:] = SB + if remove_piston == True: + BASIS = np.asarray(BASIS[:,1:]) + print('Piston removed from the modal basis!' ) + +#%% ----------COMPUTE KL BASIS ---------- + + if computeKL == True: + check=1 + if nmo>SB.shape[1]: + print('WARNING: Number of modes requested too high, taking the maximum value possible!') + nmoKL = SB.shape[1] + else: + nmoKL = nmo + KL=aou.build_KLBasis(HHt,SB,DELTA,nmoKL,check) + #pdb.set_trace() + DELTA_KL = KL.T @ DELTA @ KL + print('Orthonormality error for '+str(nmoKL)+' modes of the KL Basis = ',np.max(np.abs(DELTA_KL[0:nmoKL,0:nmoKL]/tpup-np.eye(nmoKL)))) + + + BASIS=np.asmatrix(np.zeros([nact,nspm+nmoKL],dtype=np.float64)) + BASIS[:,0:nspm] = SpM + BASIS[:,nspm:] = KL + if remove_piston == True: + BASIS = np.asarray(BASIS[:,1:]) + print('Piston removed from the modal basis!' ) +# save output in fits file + hdr=pfits.Header() + hdr['TITLE'] = initName+'_KL' #'M4_KL' + empty_primary = pfits.PrimaryHDU(header=hdr) + ## CAREFUL THE CUBE IS SAVED AS A NON SPARSE MATRIX + primary_hdu = pfits.ImageHDU(BASIS) + hdu = pfits.HDUList([empty_primary, primary_hdu]) + hdu.writeto(nameFolder+nameFile+'.fits',overwrite=True) + return np.asarray(BASIS) + + if returnSB == True: + hdr=pfits.Header() + hdr['TITLE'] = initName+'_SB' #'M4_KL' + empty_primary = pfits.PrimaryHDU(header=hdr) + ## CAREFUL THE CUBE IS SAVED AS A NON SPARSE MATRIX + primary_hdu = pfits.ImageHDU(BASIS) + hdu = pfits.HDUList([empty_primary, primary_hdu]) + hdu.writeto(nameFolder+nameFile+'.fits',overwrite=True) + + return np.asarray(BASIS),SB diff --git a/AO_modules/calibration/getFittingError.py b/OOPAO/calibration/getFittingError.py old mode 100755 new mode 100644 similarity index 96% rename from AO_modules/calibration/getFittingError.py rename to OOPAO/calibration/getFittingError.py index e8e64ba..245297d --- a/AO_modules/calibration/getFittingError.py +++ b/OOPAO/calibration/getFittingError.py @@ -1,80 +1,80 @@ -# -*- coding: utf-8 -*- -""" -Created on Thu Oct 29 17:15:06 2020 - -@author: cheritie -""" - -import numpy as np -import matplotlib.pyplot as plt - - -def getFittingError(OPD, proj, basis, display = True): -# compute projector - phi_turb = np.reshape(OPD,OPD.shape[0]*OPD.shape[1]) - phi_corr = np.matmul(basis, np.matmul(proj,phi_turb)) - - OPD_fitting_2D = np.reshape(phi_turb-phi_corr,[OPD.shape[0],OPD.shape[1]]) - OPD_corr_2D = np.reshape(phi_corr,[OPD.shape[0],OPD.shape[1]]) - OPD_turb_2D = np.reshape(phi_turb,[OPD.shape[0],OPD.shape[1]]) - - if display: - - plt.figure() - plt.subplot(131) - plt.imshow(1e9*OPD_fitting_2D) - plt.title('Fitting Error OPD [nm]') - - plt.colorbar() - - plt.subplot(132) - plt.imshow(1e9*OPD_turb_2D) - plt.colorbar() - plt.title('Turbulence OPD [nm]') - - plt.subplot(133) - plt.imshow(1e9*OPD_corr_2D) - plt.colorbar() - plt.title('DM OPD [nm]') - - plt.show() - - return OPD_fitting_2D,OPD_corr_2D,OPD_turb_2D - -def getFittingError_dm(OPD, proj, tel,dm, M2C, display = True): - tel.resetOPD() - # compute projector - OPD_turb = np.reshape(OPD,OPD.shape[0]*OPD.shape[1]) - coefs_dm =M2C@np.matmul(proj,OPD_turb) - dm.coefs = -coefs_dm - tel.isPaired = True - tel.OPD = OPD - - tel*dm - - OPD_fitting_2D = tel.OPD - OPD_corr_2D = dm.OPD*tel.pupil - OPD_turb_2D = np.reshape(OPD_turb,[OPD.shape[0],OPD.shape[1]]) - - if display: - - plt.figure() - plt.subplot(131) - plt.imshow(1e9*OPD_fitting_2D) - plt.title('Fitting Error OPD [nm]') - - plt.colorbar() - - plt.subplot(132) - plt.imshow(1e9*OPD_turb_2D) - plt.colorbar() - plt.title('Turbulence OPD [nm]') - - plt.subplot(133) - plt.imshow(1e9*OPD_corr_2D) - plt.colorbar() - plt.title('DM OPD [nm]') - - plt.show() - +# -*- coding: utf-8 -*- +""" +Created on Thu Oct 29 17:15:06 2020 + +@author: cheritie +""" + +import numpy as np +import matplotlib.pyplot as plt + + +def getFittingError(OPD, proj, basis, display = True): +# compute projector + phi_turb = np.reshape(OPD,OPD.shape[0]*OPD.shape[1]) + phi_corr = np.matmul(basis, np.matmul(proj,phi_turb)) + + OPD_fitting_2D = np.reshape(phi_turb-phi_corr,[OPD.shape[0],OPD.shape[1]]) + OPD_corr_2D = np.reshape(phi_corr,[OPD.shape[0],OPD.shape[1]]) + OPD_turb_2D = np.reshape(phi_turb,[OPD.shape[0],OPD.shape[1]]) + + if display: + + plt.figure() + plt.subplot(131) + plt.imshow(1e9*OPD_fitting_2D) + plt.title('Fitting Error OPD [nm]') + + plt.colorbar() + + plt.subplot(132) + plt.imshow(1e9*OPD_turb_2D) + plt.colorbar() + plt.title('Turbulence OPD [nm]') + + plt.subplot(133) + plt.imshow(1e9*OPD_corr_2D) + plt.colorbar() + plt.title('DM OPD [nm]') + + plt.show() + + return OPD_fitting_2D,OPD_corr_2D,OPD_turb_2D + +def getFittingError_dm(OPD, proj, tel,dm, M2C, display = True): + tel.resetOPD() + # compute projector + OPD_turb = np.reshape(OPD,OPD.shape[0]*OPD.shape[1]) + coefs_dm =M2C@np.matmul(proj,OPD_turb) + dm.coefs = -coefs_dm + tel.isPaired = True + tel.OPD = OPD + + tel*dm + + OPD_fitting_2D = tel.OPD + OPD_corr_2D = dm.OPD*tel.pupil + OPD_turb_2D = np.reshape(OPD_turb,[OPD.shape[0],OPD.shape[1]]) + + if display: + + plt.figure() + plt.subplot(131) + plt.imshow(1e9*OPD_fitting_2D) + plt.title('Fitting Error OPD [nm]') + + plt.colorbar() + + plt.subplot(132) + plt.imshow(1e9*OPD_turb_2D) + plt.colorbar() + plt.title('Turbulence OPD [nm]') + + plt.subplot(133) + plt.imshow(1e9*OPD_corr_2D) + plt.colorbar() + plt.title('DM OPD [nm]') + + plt.show() + return OPD_fitting_2D,OPD_corr_2D,OPD_turb_2D, coefs_dm \ No newline at end of file diff --git a/AO_modules/calibration/get_fast_atmosphere.py b/OOPAO/calibration/get_fast_atmosphere.py similarity index 88% rename from AO_modules/calibration/get_fast_atmosphere.py rename to OOPAO/calibration/get_fast_atmosphere.py index dd38504..44a2cd7 100644 --- a/AO_modules/calibration/get_fast_atmosphere.py +++ b/OOPAO/calibration/get_fast_atmosphere.py @@ -1,46 +1,42 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Jul 1 08:44:13 2022 - -@author: cheritie -""" - - - -from AO_modules.Telescope import Telescope -from AO_modules.Source import Source -from AO_modules.Atmosphere import Atmosphere - - - - -def get_fast_atmosphere(obj,param,speed_factor): - # create the Telescope object - tel_fast = Telescope(resolution = param['resolution'],\ - diameter = param['diameter'],\ - samplingTime = obj.tel.samplingTime/speed_factor,\ - centralObstruction = param['centralObstruction']) - - # create the Source object - ngs_fast = Source(optBand = param['opticalBand'],\ - magnitude = 0) - - # combine the NGS to the telescope using '*' operator: - ngs_fast*tel_fast - - # create the Atmosphere object - atm_fast = Atmosphere(telescope = tel_fast,\ - r0 = param['r0'],\ - L0 = param['L0'],\ - windSpeed = param['windSpeed'],\ - fractionalR0 = param['fractionnalR0'],\ - windDirection = param['windDirection'],\ - altitude = param['altitude'],\ - param = param) - - # initialize atmosphere - atm_fast.initializeAtmosphere(tel_fast) - atm_fast.update() - - return tel_fast,atm_fast - +# -*- coding: utf-8 -*- +""" +Created on Fri Jul 1 08:44:13 2022 + +@author: cheritie +""" + +from ..Telescope import Telescope +from ..Source import Source +from ..Atmosphere import Atmosphere + + +def get_fast_atmosphere(obj,param,speed_factor): + # create the Telescope object + tel_fast = Telescope(resolution = param['resolution'],\ + diameter = param['diameter'],\ + samplingTime = obj.tel.samplingTime/speed_factor,\ + centralObstruction = param['centralObstruction']) + + # create the Source object + ngs_fast = Source(optBand = param['opticalBand'],\ + magnitude = 0) + + # combine the NGS to the telescope using '*' operator: + ngs_fast*tel_fast + + # create the Atmosphere object + atm_fast = Atmosphere(telescope = tel_fast,\ + r0 = param['r0'],\ + L0 = param['L0'],\ + windSpeed = param['windSpeed'],\ + fractionalR0 = param['fractionnalR0'],\ + windDirection = param['windDirection'],\ + altitude = param['altitude'],\ + param = param) + + # initialize atmosphere + atm_fast.initializeAtmosphere(tel_fast) + atm_fast.update() + + return tel_fast,atm_fast + diff --git a/AO_modules/calibration/get_modal_basis.py b/OOPAO/calibration/get_modal_basis.py old mode 100755 new mode 100644 similarity index 96% rename from AO_modules/calibration/get_modal_basis.py rename to OOPAO/calibration/get_modal_basis.py index 2214c7c..3950c27 --- a/AO_modules/calibration/get_modal_basis.py +++ b/OOPAO/calibration/get_modal_basis.py @@ -1,157 +1,159 @@ -# -*- coding: utf-8 -*- -""" -Created on Thu Feb 11 14:21:00 2021 - -@author: cheritie -""" -import numpy as np -from AO_modules.tools.tools import emptyClass,createFolder -from astropy.io import fits as pfits - - -def get_modal_basis_from_ao_obj(ao_obj, nameFolderBasis = None, nameBasis = None): - - # check if the name of the basis is specified otherwise take the nominal name - if nameBasis is None: - if ao_obj.dm.isM4: - initName = 'M2C_M4_' - else: - initName = 'M2C_' - try: - nameBasis = initName+str(ao_obj.param['resolution'])+'_res'+ao_obj.param['extra'] - except: - nameBasis = initName+str(ao_obj.param['resolution'])+'_res' - - ao_calib_object = emptyClass() - - # check if a name for the origin folder is specified - if nameFolderBasis is None: - nameFolderBasis = ao_obj.param['pathInput'] - createFolder(nameFolderBasis) - -## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -# get the modal basis : - - try: - print('Loading the KL Modal Basis from: ' + nameFolderBasis+nameBasis ) - - hdu=pfits.open(nameFolderBasis+ nameBasis+'.fits') - try: - M2C = hdu[1].data - except: - M2C = hdu[0].data - - M2C = M2C[:,:ao_obj.param['nModes']] - - ao_obj.dm.coefs = M2C - ao_obj.tel*ao_obj.dm - - basis = np.reshape(ao_obj.tel.OPD,[ao_obj.tel.resolution**2,M2C.shape[1]]) - ao_calib_object.M2C = M2C - ao_calib_object.basis = basis - - if ao_obj.param['getProjector']: - print('Computing the pseudo-inverse of the modal basis...') - - cross_product_basis = np.matmul(basis.T,basis) - - non_diagonal_elements = np.sum(np.abs(cross_product_basis))-np.trace(cross_product_basis) - criteria = np.abs(np.trace(cross_product_basis)-non_diagonal_elements)/np.trace(cross_product_basis) - if criteria <= 1e-3: - print('Diagonality criteria: ' + str(criteria) + ' -- using the fast computation') - projector = np.diag(1/np.diag(cross_product_basis))@basis.T - else: - print('Diagonality criteria: ' + str(criteria) + ' -- using the slow computation') - projector = np.linalg.pinv(basis) - ao_calib_object.projector = projector - - except: - print('ERROR: No file found! Taking a zonal basis instead..' ) - M2C = np.eye(ao_obj.dm.nValidAct) - ao_calib_object.M2C = M2C - - return ao_calib_object -#%% -# -# -# Same functions without ao-object -# -# -#%% - -def get_modal_basis(ngs, tel, atm, dm, wfs, param, nameFolderBasis = None, nameBasis = None): - - # check if the name of the basis is specified otherwise take the nominal name - if nameBasis is None: - if dm.isM4: - initName = 'M2C_M4_' - else: - initName = 'M2C_' - - try: - nameBasis = initName+str(param['resolution'])+'_res'+param['extra'] - except: - nameBasis = initName+str(param['resolution'])+'_res' - - ao_calib_object = emptyClass() - - # check if a name for the origin folder is specified - if nameFolderBasis is None: - nameFolderBasis = param['pathInput'] - createFolder(nameFolderBasis) - -## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -# get the modal basis : - - try: - print('Loading the KL Modal Basis from: ' + nameFolderBasis+nameBasis ) - - hdu=pfits.open(nameFolderBasis+ nameBasis+'.fits') - M2C = hdu[1].data - - M2C = M2C[:,:param['nModes']] - - dm.coefs = M2C - tel*dm - - basis = np.reshape(tel.OPD,[tel.resolution**2,M2C.shape[1]]) - ao_calib_object.basis = basis - ao_calib_object.M2C = M2C - - if param['getProjector']: - print('Computing the pseudo-inverse of the modal basis...') - - cross_product_basis = np.matmul(basis.T,basis) - - non_diagonal_elements = np.sum(np.abs(cross_product_basis))-np.trace(cross_product_basis) - criteria = np.abs(np.trace(cross_product_basis)-non_diagonal_elements)/np.trace(cross_product_basis) - if criteria <= 1e-3: - print('Diagonality criteria: ' + str(criteria) + ' -- using the fast computation') - projector = np.diag(1/np.diag(cross_product_basis))@basis.T - else: - print('Diagonality criteria: ' + str(criteria) + ' -- using the slow computation') - projector = np.linalg.pinv(basis) - ao_calib_object.projector = projector - except: - print('ERROR: No file found! Taking a zonal basis instead..' ) - M2C = np.eye(dm.nValidAct) - - - return ao_calib_object - - -def get_projector(basis): - print('Computing the pseudo-inverse of the modal basis...') - - cross_product_basis = np.matmul(basis.T,basis) - - non_diagonal_elements = np.sum(np.abs(cross_product_basis))-np.trace(cross_product_basis) - criteria = np.abs(np.trace(cross_product_basis)-non_diagonal_elements)/np.trace(cross_product_basis) - if criteria <= 1e-3: - print('Diagonality criteria: ' + str(criteria) + ' -- using the fast computation') - projector = np.diag(1/np.diag(cross_product_basis))@basis.T - else: - print('Diagonality criteria: ' + str(criteria) + ' -- using the slow computation') - projector = np.linalg.pinv(basis) - +# -*- coding: utf-8 -*- +""" +Created on Thu Feb 11 14:21:00 2021 + +@author: cheritie +""" + +import numpy as np +from astropy.io import fits as pfits + +from ..tools.tools import createFolder, emptyClass + + +def get_modal_basis_from_ao_obj(ao_obj, nameFolderBasis = None, nameBasis = None): + + # check if the name of the basis is specified otherwise take the nominal name + if nameBasis is None: + if ao_obj.dm.isM4: + initName = 'M2C_M4_' + else: + initName = 'M2C_' + try: + nameBasis = initName+str(ao_obj.param['resolution'])+'_res'+ao_obj.param['extra'] + except: + nameBasis = initName+str(ao_obj.param['resolution'])+'_res' + + ao_calib_object = emptyClass() + + # check if a name for the origin folder is specified + if nameFolderBasis is None: + nameFolderBasis = ao_obj.param['pathInput'] + createFolder(nameFolderBasis) + +## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# get the modal basis : + + try: + print('Loading the KL Modal Basis from: ' + nameFolderBasis+nameBasis ) + + hdu=pfits.open(nameFolderBasis+ nameBasis+'.fits') + try: + M2C = hdu[1].data + except: + M2C = hdu[0].data + + M2C = M2C[:,:ao_obj.param['nModes']] + + ao_obj.dm.coefs = M2C + ao_obj.tel*ao_obj.dm + + basis = np.reshape(ao_obj.tel.OPD,[ao_obj.tel.resolution**2,M2C.shape[1]]) + ao_calib_object.M2C = M2C + ao_calib_object.basis = basis + + if ao_obj.param['getProjector']: + print('Computing the pseudo-inverse of the modal basis...') + + cross_product_basis = np.matmul(basis.T,basis) + + non_diagonal_elements = np.sum(np.abs(cross_product_basis))-np.trace(cross_product_basis) + criteria = np.abs(np.trace(cross_product_basis)-non_diagonal_elements)/np.trace(cross_product_basis) + if criteria <= 1e-3: + print('Diagonality criteria: ' + str(criteria) + ' -- using the fast computation') + projector = np.diag(1/np.diag(cross_product_basis))@basis.T + else: + print('Diagonality criteria: ' + str(criteria) + ' -- using the slow computation') + projector = np.linalg.pinv(basis) + ao_calib_object.projector = projector + + except: + print('ERROR: No file found! Taking a zonal basis instead..' ) + M2C = np.eye(ao_obj.dm.nValidAct) + ao_calib_object.M2C = M2C + + return ao_calib_object +#%% +# +# +# Same functions without ao-object +# +# +#%% + +def get_modal_basis(ngs, tel, atm, dm, wfs, param, nameFolderBasis = None, nameBasis = None): + + # check if the name of the basis is specified otherwise take the nominal name + if nameBasis is None: + if dm.isM4: + initName = 'M2C_M4_' + else: + initName = 'M2C_' + + try: + nameBasis = initName+str(param['resolution'])+'_res'+param['extra'] + except: + nameBasis = initName+str(param['resolution'])+'_res' + + ao_calib_object = emptyClass() + + # check if a name for the origin folder is specified + if nameFolderBasis is None: + nameFolderBasis = param['pathInput'] + createFolder(nameFolderBasis) + +## %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +# get the modal basis : + + try: + print('Loading the KL Modal Basis from: ' + nameFolderBasis+nameBasis ) + + hdu=pfits.open(nameFolderBasis+ nameBasis+'.fits') + M2C = hdu[1].data + + M2C = M2C[:,:param['nModes']] + + dm.coefs = M2C + tel*dm + + basis = np.reshape(tel.OPD,[tel.resolution**2,M2C.shape[1]]) + ao_calib_object.basis = basis + ao_calib_object.M2C = M2C + + if param['getProjector']: + print('Computing the pseudo-inverse of the modal basis...') + + cross_product_basis = np.matmul(basis.T,basis) + + non_diagonal_elements = np.sum(np.abs(cross_product_basis))-np.trace(cross_product_basis) + criteria = np.abs(np.trace(cross_product_basis)-non_diagonal_elements)/np.trace(cross_product_basis) + if criteria <= 1e-3: + print('Diagonality criteria: ' + str(criteria) + ' -- using the fast computation') + projector = np.diag(1/np.diag(cross_product_basis))@basis.T + else: + print('Diagonality criteria: ' + str(criteria) + ' -- using the slow computation') + projector = np.linalg.pinv(basis) + ao_calib_object.projector = projector + except: + print('ERROR: No file found! Taking a zonal basis instead..' ) + M2C = np.eye(dm.nValidAct) + + + return ao_calib_object + + +def get_projector(basis): + print('Computing the pseudo-inverse of the modal basis...') + + cross_product_basis = np.matmul(basis.T,basis) + + non_diagonal_elements = np.sum(np.abs(cross_product_basis))-np.trace(cross_product_basis) + criteria = np.abs(np.trace(cross_product_basis)-non_diagonal_elements)/np.trace(cross_product_basis) + if criteria <= 1e-3: + print('Diagonality criteria: ' + str(criteria) + ' -- using the fast computation') + projector = np.diag(1/np.diag(cross_product_basis))@basis.T + else: + print('Diagonality criteria: ' + str(criteria) + ' -- using the slow computation') + projector = np.linalg.pinv(basis) + return projector \ No newline at end of file diff --git a/AO_modules/calibration/initialization_AO.py b/OOPAO/calibration/initialization_AO.py old mode 100755 new mode 100644 similarity index 87% rename from AO_modules/calibration/initialization_AO.py rename to OOPAO/calibration/initialization_AO.py index 9178ed6..1976dd0 --- a/AO_modules/calibration/initialization_AO.py +++ b/OOPAO/calibration/initialization_AO.py @@ -1,110 +1,110 @@ - -# -*- coding: utf-8 -*- -""" -Created on Wed Jun 24 16:41:21 2020 - -@author: cheritie -""" -# local modules -from AO_modules.Telescope import Telescope -from AO_modules.Source import Source -from AO_modules.Atmosphere import Atmosphere -from AO_modules.Pyramid import Pyramid -from AO_modules.DeformableMirror import DeformableMirror -from AO_modules.MisRegistration import MisRegistration -from AO_modules.tools.set_paralleling_setup import set_paralleling_setup - - -def run_initialization_basic_AO(param): - - #%% ----------------------- TELESCOPE ---------------------------------- - - # create the Telescope object - tel = Telescope(resolution = param['resolution'],\ - diameter = param['diameter'],\ - samplingTime = param['samplingTime'],\ - centralObstruction = param['centralObstruction']) - - #%% ----------------------- NGS ---------------------------------- - # create the Source object - ngs=Source(optBand = param['opticalBand'],\ - magnitude = param['magnitude']) - - # combine the NGS to the telescope using '*' operator: - ngs*tel - - - - #%% ----------------------- ATMOSPHERE ---------------------------------- - - # create the Atmosphere object - atm=Atmosphere(telescope = tel,\ - r0 = param['r0'],\ - L0 = param['L0'],\ - windSpeed = param['windSpeed'],\ - fractionalR0 = param['fractionnalR0'],\ - windDirection = param['windDirection'],\ - altitude = param['altitude'],\ - param = param) - - # initialize atmosphere - atm.initializeAtmosphere(tel) - - # pairing the telescope with the atmosphere using '+' operator - tel+atm - # separating the tel and atmosphere using '-' operator - tel-atm - - #%% ----------------------- DEFORMABLE MIRROR ---------------------------------- - # mis-registrations object - misReg = MisRegistration(param) - # if no coordonates specified, create a cartesian dm - dm=DeformableMirror(telescope = tel,\ - nSubap = param['nSubaperture'],\ - mechCoupling = param['mechanicalCoupling'],\ - misReg = misReg) - - - #%% ----------------------- PYRAMID WFS ---------------------------------- - - # make sure tel and atm are separated to initialize the PWFS - tel-atm - try: - print('Using a user-defined zero-padding for the PWFS:', param['zeroPadding']) - except: - param['zeroPadding']=0 - print('Using the default zero-padding for the PWFS') - # create the Pyramid Object - wfs = Pyramid(nSubap = param['nSubaperture'],\ - telescope = tel,\ - modulation = param['modulation'],\ - lightRatio = param['lightThreshold'],\ - pupilSeparationRatio = param['pupilSeparationRatio'],\ - calibModulation = param['calibrationModulation'],\ - psfCentering = param['psfCentering'],\ - edgePixel = param['edgePixel'],\ - unitCalibration = param['unitCalibration'],\ - extraModulationFactor = param['extraModulationFactor'],\ - postProcessing = param['postProcessing'],\ - zeroPadding = param['zeroPadding']) - - set_paralleling_setup(wfs, ELT = False) - # propagate the light through the WFS - tel*wfs - - class emptyClass(): - pass - # save output as sub-classes - simulationObject = emptyClass() - - simulationObject.tel = tel - simulationObject.atm = atm - simulationObject.ngs = ngs - simulationObject.dm = dm - simulationObject.wfs = wfs - simulationObject.param = param - - - return simulationObject - - + +# -*- coding: utf-8 -*- +""" +Created on Wed Jun 24 16:41:21 2020 + +@author: cheritie +""" + +from OOPAO.Telescope import Telescope +from OOPAO.Source import Source +from OOPAO.Atmosphere import Atmosphere +from OOPAO.Pyramid import Pyramid +from OOPAO.DeformableMirror import DeformableMirror +from OOPAO.MisRegistration import MisRegistration +from OOPAO.tools.set_paralleling_setup import set_paralleling_setup + + +def run_initialization_basic_AO(param): + + #%% ----------------------- TELESCOPE ---------------------------------- + + # create the Telescope object + tel = Telescope(resolution = param['resolution'],\ + diameter = param['diameter'],\ + samplingTime = param['samplingTime'],\ + centralObstruction = param['centralObstruction']) + + #%% ----------------------- NGS ---------------------------------- + # create the Source object + ngs=Source(optBand = param['opticalBand'],\ + magnitude = param['magnitude']) + + # combine the NGS to the telescope using '*' operator: + ngs*tel + + + + #%% ----------------------- ATMOSPHERE ---------------------------------- + + # create the Atmosphere object + atm=Atmosphere(telescope = tel,\ + r0 = param['r0'],\ + L0 = param['L0'],\ + windSpeed = param['windSpeed'],\ + fractionalR0 = param['fractionnalR0'],\ + windDirection = param['windDirection'],\ + altitude = param['altitude'],\ + param = param) + + # initialize atmosphere + atm.initializeAtmosphere(tel) + + # pairing the telescope with the atmosphere using '+' operator + tel+atm + # separating the tel and atmosphere using '-' operator + tel-atm + + #%% ----------------------- DEFORMABLE MIRROR ---------------------------------- + # mis-registrations object + misReg = MisRegistration(param) + # if no coordonates specified, create a cartesian dm + dm=DeformableMirror(telescope = tel,\ + nSubap = param['nSubaperture'],\ + mechCoupling = param['mechanicalCoupling'],\ + misReg = misReg) + + + #%% ----------------------- PYRAMID WFS ---------------------------------- + + # make sure tel and atm are separated to initialize the PWFS + tel-atm + try: + print('Using a user-defined zero-padding for the PWFS:', param['zeroPadding']) + except: + param['zeroPadding']=0 + print('Using the default zero-padding for the PWFS') + # create the Pyramid Object + wfs = Pyramid(nSubap = param['nSubaperture'],\ + telescope = tel,\ + modulation = param['modulation'],\ + lightRatio = param['lightThreshold'],\ + pupilSeparationRatio = param['pupilSeparationRatio'],\ + calibModulation = param['calibrationModulation'],\ + psfCentering = param['psfCentering'],\ + edgePixel = param['edgePixel'],\ + unitCalibration = param['unitCalibration'],\ + extraModulationFactor = param['extraModulationFactor'],\ + postProcessing = param['postProcessing'],\ + zeroPadding = param['zeroPadding']) + + set_paralleling_setup(wfs, ELT = False) + # propagate the light through the WFS + tel*wfs + + class emptyClass(): + pass + # save output as sub-classes + simulationObject = emptyClass() + + simulationObject.tel = tel + simulationObject.atm = atm + simulationObject.ngs = ngs + simulationObject.dm = dm + simulationObject.wfs = wfs + simulationObject.param = param + + + return simulationObject + + diff --git a/AO_modules/calibration/initialization_AO_PWFS.py b/OOPAO/calibration/initialization_AO_PWFS.py similarity index 88% rename from AO_modules/calibration/initialization_AO_PWFS.py rename to OOPAO/calibration/initialization_AO_PWFS.py index df0b835..9eafa90 100644 --- a/AO_modules/calibration/initialization_AO_PWFS.py +++ b/OOPAO/calibration/initialization_AO_PWFS.py @@ -1,156 +1,156 @@ - -# -*- coding: utf-8 -*- -""" -Created on Wed Jun 24 16:41:21 2020 - -@author: cheritie -""" -# local modules -from AO_modules.Telescope import Telescope -from AO_modules.Source import Source -from AO_modules.Atmosphere import Atmosphere -from AO_modules.Pyramid import Pyramid -from AO_modules.DeformableMirror import DeformableMirror -from AO_modules.MisRegistration import MisRegistration -from AO_modules.tools.set_paralleling_setup import set_paralleling_setup -import numpy as np -from AO_modules.calibration.compute_KL_modal_basis import compute_M2C -from AO_modules.calibration.ao_calibration import ao_calibration -from AO_modules.tools.tools import * - -def run_initialization_AO_PWFS(param): - - #%% ----------------------- TELESCOPE ---------------------------------- - - # create the Telescope object - tel = Telescope(resolution = param['resolution'],\ - diameter = param['diameter'],\ - samplingTime = param['samplingTime'],\ - centralObstruction = param['centralObstruction']) - - #%% ----------------------- NGS ---------------------------------- - # create the Source object - ngs=Source(optBand = param['opticalBand'],\ - magnitude = param['magnitude']) - - # combine the NGS to the telescope using '*' operator: - ngs*tel - - - - #%% ----------------------- ATMOSPHERE ---------------------------------- - - # create the Atmosphere object - atm=Atmosphere(telescope = tel,\ - r0 = param['r0'],\ - L0 = param['L0'],\ - windSpeed = param['windSpeed'],\ - fractionalR0 = param['fractionnalR0'],\ - windDirection = param['windDirection'],\ - altitude = param['altitude'],\ - param = param) - - # initialize atmosphere - atm.initializeAtmosphere(tel) - - # pairing the telescope with the atmosphere using '+' operator - tel+atm - # separating the tel and atmosphere using '-' operator - tel-atm - - #%% ----------------------- DEFORMABLE MIRROR ---------------------------------- - # mis-registrations object - misReg = MisRegistration(param) - # if no coordonates specified, create a cartesian dm - dm=DeformableMirror(telescope = tel,\ - nSubap = param['nSubaperture'],\ - mechCoupling = param['mechanicalCoupling'],\ - misReg = misReg) - - - #%% ----------------------- PYRAMID WFS ---------------------------------- - - # make sure tel and atm are separated to initialize the PWFS - tel-atm - try: - print('Using a user-defined zero-padding for the PWFS:', param['zeroPadding']) - except: - param['zeroPadding']=0 - print('Using the default zero-padding for the PWFS') - # create the Pyramid Object - wfs = Pyramid(nSubap = param['nSubaperture'],\ - telescope = tel,\ - modulation = param['modulation'],\ - lightRatio = param['lightThreshold'],\ - pupilSeparationRatio = param['pupilSeparationRatio'],\ - calibModulation = param['calibrationModulation'],\ - psfCentering = param['psfCentering'],\ - edgePixel = param['edgePixel'],\ - postProcessing = param['postProcessing']) - # set_paralleling_setup(wfs, ELT = False) - # propagate the light through the WFS - tel*wfs - #%% ----------------------- Modal Basis ---------------------------------- - - try: - M2C_KL = read_fits(param['pathInput']+param['modal_basis_name']+'.fits') - print('Succesfully loaded KL modal basis') - except: - print('Computing KL modal basis ...') - - M2C_KL = compute_M2C( telescope = tel,\ - atmosphere = atm,\ - deformableMirror = dm,\ - param = param,\ - nameFolder = None,\ - nameFile = None,\ - remove_piston = True,\ - HHtName = None,\ - baseName = None ,\ - mem_available = 8.1e9,\ - minimF = False,\ - nmo = 1000,\ - ortho_spm = True,\ - SZ = np.int(2*tel.OPD.shape[0]),\ - nZer = 3,\ - NDIVL = 1) - print('Done') - - -#%% ----------------------- Calibration ---------------------------------- - - ao_calib = ao_calibration(param = param,\ - ngs = ngs,\ - tel = tel,\ - atm = atm,\ - dm = dm,\ - wfs = wfs,\ - nameFolderIntMat = None,\ - nameIntMat = None,\ - nameFolderBasis = None,\ - nameBasis = param['modal_basis_name'],\ - nMeasurements = 1,\ - get_basis = True) - - - - - class emptyClass(): - pass - # save output as sub-classes - simulationObject = emptyClass() - - simulationObject.tel = tel - simulationObject.atm = atm - - simulationObject.ngs = ngs - simulationObject.dm = dm - simulationObject.wfs = wfs - simulationObject.param = param - simulationObject.calib = ao_calib.calib - simulationObject.M2C = ao_calib.M2C - simulationObject.gOpt = ao_calib.gOpt - simulationObject.projector = ao_calib.projector - simulationObject.basis = ao_calib.basis - - return simulationObject + +# -*- coding: utf-8 -*- +""" +Created on Wed Jun 24 16:41:21 2020 + +@author: cheritie +""" +import numpy as np + +from .ao_calibration import ao_calibration +from .compute_KL_modal_basis import compute_M2C +from ..Atmosphere import Atmosphere +from ..DeformableMirror import DeformableMirror +from ..MisRegistration import MisRegistration +from ..Pyramid import Pyramid +from ..Source import Source +from ..Telescope import Telescope +from ..tools.tools import read_fits + + +def run_initialization_AO_PWFS(param): + + #%% ----------------------- TELESCOPE ---------------------------------- + + # create the Telescope object + tel = Telescope(resolution = param['resolution'],\ + diameter = param['diameter'],\ + samplingTime = param['samplingTime'],\ + centralObstruction = param['centralObstruction']) + + #%% ----------------------- NGS ---------------------------------- + # create the Source object + ngs=Source(optBand = param['opticalBand'],\ + magnitude = param['magnitude']) + + # combine the NGS to the telescope using '*' operator: + ngs*tel + + + + #%% ----------------------- ATMOSPHERE ---------------------------------- + + # create the Atmosphere object + atm=Atmosphere(telescope = tel,\ + r0 = param['r0'],\ + L0 = param['L0'],\ + windSpeed = param['windSpeed'],\ + fractionalR0 = param['fractionnalR0'],\ + windDirection = param['windDirection'],\ + altitude = param['altitude'],\ + param = param) + + # initialize atmosphere + atm.initializeAtmosphere(tel) + + # pairing the telescope with the atmosphere using '+' operator + tel+atm + # separating the tel and atmosphere using '-' operator + tel-atm + + #%% ----------------------- DEFORMABLE MIRROR ---------------------------------- + # mis-registrations object + misReg = MisRegistration(param) + # if no coordonates specified, create a cartesian dm + dm=DeformableMirror(telescope = tel,\ + nSubap = param['nSubaperture'],\ + mechCoupling = param['mechanicalCoupling'],\ + misReg = misReg) + + + #%% ----------------------- PYRAMID WFS ---------------------------------- + + # make sure tel and atm are separated to initialize the PWFS + tel-atm + try: + print('Using a user-defined zero-padding for the PWFS:', param['zeroPadding']) + except: + param['zeroPadding']=0 + print('Using the default zero-padding for the PWFS') + # create the Pyramid Object + wfs = Pyramid(nSubap = param['nSubaperture'],\ + telescope = tel,\ + modulation = param['modulation'],\ + lightRatio = param['lightThreshold'],\ + pupilSeparationRatio = param['pupilSeparationRatio'],\ + calibModulation = param['calibrationModulation'],\ + psfCentering = param['psfCentering'],\ + edgePixel = param['edgePixel'],\ + postProcessing = param['postProcessing']) + # set_paralleling_setup(wfs, ELT = False) + # propagate the light through the WFS + tel*wfs + #%% ----------------------- Modal Basis ---------------------------------- + + try: + M2C_KL = read_fits(param['pathInput']+param['modal_basis_name']+'.fits') + print('Succesfully loaded KL modal basis') + except: + print('Computing KL modal basis ...') + + M2C_KL = compute_M2C( telescope = tel,\ + atmosphere = atm,\ + deformableMirror = dm,\ + param = param,\ + nameFolder = None,\ + nameFile = None,\ + remove_piston = True,\ + HHtName = None,\ + baseName = None ,\ + mem_available = 8.1e9,\ + minimF = False,\ + nmo = 1000,\ + ortho_spm = True,\ + SZ = np.int(2*tel.OPD.shape[0]),\ + nZer = 3,\ + NDIVL = 1) + print('Done') + + +#%% ----------------------- Calibration ---------------------------------- + + ao_calib = ao_calibration(param = param,\ + ngs = ngs,\ + tel = tel,\ + atm = atm,\ + dm = dm,\ + wfs = wfs,\ + nameFolderIntMat = None,\ + nameIntMat = None,\ + nameFolderBasis = None,\ + nameBasis = param['modal_basis_name'],\ + nMeasurements = 1,\ + get_basis = True) + + + + + class emptyClass(): + pass + # save output as sub-classes + simulationObject = emptyClass() + + simulationObject.tel = tel + simulationObject.atm = atm + + simulationObject.ngs = ngs + simulationObject.dm = dm + simulationObject.wfs = wfs + simulationObject.param = param + simulationObject.calib = ao_calib.calib + simulationObject.M2C = ao_calib.M2C + simulationObject.gOpt = ao_calib.gOpt + simulationObject.projector = ao_calib.projector + simulationObject.basis = ao_calib.basis + + return simulationObject diff --git a/AO_modules/calibration/initialization_AO_SHWFS.py b/OOPAO/calibration/initialization_AO_SHWFS.py similarity index 89% rename from AO_modules/calibration/initialization_AO_SHWFS.py rename to OOPAO/calibration/initialization_AO_SHWFS.py index 83aca81..6e8696b 100644 --- a/AO_modules/calibration/initialization_AO_SHWFS.py +++ b/OOPAO/calibration/initialization_AO_SHWFS.py @@ -1,174 +1,176 @@ - -# -*- coding: utf-8 -*- -""" -Created on Wed Jun 24 16:41:21 2020 - -@author: cheritie -""" -# local modules -from AO_modules.Telescope import Telescope -from AO_modules.Source import Source -from AO_modules.Atmosphere import Atmosphere -from AO_modules.ShackHartmann import ShackHartmann -from AO_modules.DeformableMirror import DeformableMirror -from AO_modules.MisRegistration import MisRegistration -from AO_modules.tools.set_paralleling_setup import set_paralleling_setup -import numpy as np -from AO_modules.calibration.compute_KL_modal_basis import compute_M2C -from AO_modules.calibration.ao_calibration import ao_calibration -from AO_modules.tools.tools import * -def run_initialization_AO_SHWFS(param): - - #%% ----------------------- TELESCOPE ---------------------------------- - - # create the Telescope object - tel = Telescope(resolution = param['resolution'],\ - diameter = param['diameter'],\ - samplingTime = param['samplingTime'],\ - centralObstruction = param['centralObstruction']) - - #%% ----------------------- NGS ---------------------------------- - # create the Source object - ngs=Source(optBand = param['opticalBand'],\ - magnitude = param['magnitude']) - - # combine the NGS to the telescope using '*' operator: - ngs*tel - - - - #%% ----------------------- ATMOSPHERE ---------------------------------- - - # create the Atmosphere object - atm=Atmosphere(telescope = tel,\ - r0 = param['r0'],\ - L0 = param['L0'],\ - windSpeed = param['windSpeed'],\ - fractionalR0 = param['fractionnalR0'],\ - windDirection = param['windDirection'],\ - altitude = param['altitude'],\ - param = param) - - # initialize atmosphere - atm.initializeAtmosphere(tel) - - # pairing the telescope with the atmosphere using '+' operator - tel+atm - # separating the tel and atmosphere using '-' operator - tel-atm - - #%% ----------------------- DEFORMABLE MIRROR ---------------------------------- - # mis-registrations object - misReg = MisRegistration(param) - # if no coordonates specified, create a cartesian dm - dm=DeformableMirror(telescope = tel,\ - nSubap = param['nSubaperture'],\ - mechCoupling = param['mechanicalCoupling'],\ - misReg = misReg) - - - #%% ----------------------- PYRAMID WFS ---------------------------------- - - # make sure tel and atm are separated to initialize the PWFS - tel-atm - - wfs = ShackHartmann(nSubap = param['nSubaperture'],\ - telescope = tel,\ - lightRatio = param['lightThreshold'] ,\ - is_geometric = param['is_geometric']) - - # propagate the light through the WFS - tel*wfs - - - #%% ----------------------- Modal Basis ---------------------------------- - - try: - M2C_KL = read_fits(param['pathInput']+param['modal_basis_name']+'.fits') - print('Succesfully loaded KL modal basis') - except: - print('Computing KL modal basis ...') - - M2C_KL = compute_M2C( telescope = tel,\ - atmosphere = atm,\ - deformableMirror = dm,\ - param = param,\ - nameFolder = None,\ - nameFile = None,\ - remove_piston = True,\ - HHtName = None,\ - baseName = None ,\ - mem_available = 8.1e9,\ - minimF = False,\ - nmo = 1000,\ - ortho_spm = True,\ - SZ = np.int(2*tel.OPD.shape[0]),\ - nZer = 3,\ - NDIVL = 1) - print('Done') - - -#%% ----------------------- Calibration ---------------------------------- - wfs.is_geometric = True - ao_calib = ao_calibration(param = param,\ - ngs = ngs,\ - tel = tel,\ - atm = atm,\ - dm = dm,\ - wfs = wfs,\ - nameFolderIntMat = None,\ - nameIntMat = None,\ - nameFolderBasis = None,\ - nameBasis = param['modal_basis_name'],\ - nMeasurements = 100,\ - get_basis = False) - wfs.is_geometric = False - - #%% ----------------------- FASTER LOOP ---------------------------------- - - # create the Telescope object - tel_fast= Telescope(resolution = param['resolution'],\ - diameter = param['diameter'],\ - samplingTime = 1/3000,\ - centralObstruction = param['centralObstruction']) - - # create the Source object - ngs_fast=Source(optBand = param['opticalBand'],\ - magnitude = 0) - - - - # combine the NGS to the telescope using '*' operator: - ngs_fast*tel_fast - - # create the Atmosphere object - atm_fast = Atmosphere(telescope = tel_fast,\ - r0 = param['r0'],\ - L0 = param['L0'],\ - windSpeed = param['windSpeed'],\ - fractionalR0 = param['fractionnalR0'],\ - windDirection = param['windDirection'],\ - altitude = param['altitude']) - # initialize atmosphere - atm_fast.initializeAtmosphere(tel_fast) - atm_fast.update() - class emptyClass(): - pass - # save output as sub-classes - simulationObject = emptyClass() - - simulationObject.tel = tel - simulationObject.atm = atm - simulationObject.tel_fast = tel_fast - simulationObject.atm_fast = atm_fast - simulationObject.ngs = ngs - simulationObject.dm = dm - simulationObject.wfs = wfs - simulationObject.param = param - simulationObject.calib = ao_calib.calib - simulationObject.M2C = ao_calib.M2C - simulationObject.gOpt = ao_calib.gOpt - - return simulationObject - - + +# -*- coding: utf-8 -*- +""" +Created on Wed Jun 24 16:41:21 2020 + +@author: cheritie +""" + +import numpy as np + +from .ao_calibration import ao_calibration +from .compute_KL_modal_basis import compute_M2C +from ..Atmosphere import Atmosphere +from ..DeformableMirror import DeformableMirror +from ..MisRegistration import MisRegistration +from ..ShackHartmann import ShackHartmann +from ..Source import Source +from ..Telescope import Telescope +from ..tools.tools import read_fits + + +def run_initialization_AO_SHWFS(param): + + #%% ----------------------- TELESCOPE ---------------------------------- + + # create the Telescope object + tel = Telescope(resolution = param['resolution'],\ + diameter = param['diameter'],\ + samplingTime = param['samplingTime'],\ + centralObstruction = param['centralObstruction']) + + #%% ----------------------- NGS ---------------------------------- + # create the Source object + ngs=Source(optBand = param['opticalBand'],\ + magnitude = param['magnitude']) + + # combine the NGS to the telescope using '*' operator: + ngs*tel + + + + #%% ----------------------- ATMOSPHERE ---------------------------------- + + # create the Atmosphere object + atm=Atmosphere(telescope = tel,\ + r0 = param['r0'],\ + L0 = param['L0'],\ + windSpeed = param['windSpeed'],\ + fractionalR0 = param['fractionnalR0'],\ + windDirection = param['windDirection'],\ + altitude = param['altitude'],\ + param = param) + + # initialize atmosphere + atm.initializeAtmosphere(tel) + + # pairing the telescope with the atmosphere using '+' operator + tel+atm + # separating the tel and atmosphere using '-' operator + tel-atm + + #%% ----------------------- DEFORMABLE MIRROR ---------------------------------- + # mis-registrations object + misReg = MisRegistration(param) + # if no coordonates specified, create a cartesian dm + dm=DeformableMirror(telescope = tel,\ + nSubap = param['nSubaperture'],\ + mechCoupling = param['mechanicalCoupling'],\ + misReg = misReg) + + + #%% ----------------------- PYRAMID WFS ---------------------------------- + + # make sure tel and atm are separated to initialize the PWFS + tel-atm + + wfs = ShackHartmann(nSubap = param['nSubaperture'],\ + telescope = tel,\ + lightRatio = param['lightThreshold'] ,\ + is_geometric = param['is_geometric']) + + # propagate the light through the WFS + tel*wfs + + + #%% ----------------------- Modal Basis ---------------------------------- + + try: + M2C_KL = read_fits(param['pathInput']+param['modal_basis_name']+'.fits') + print('Succesfully loaded KL modal basis') + except: + print('Computing KL modal basis ...') + + M2C_KL = compute_M2C( telescope = tel,\ + atmosphere = atm,\ + deformableMirror = dm,\ + param = param,\ + nameFolder = None,\ + nameFile = None,\ + remove_piston = True,\ + HHtName = None,\ + baseName = None ,\ + mem_available = 8.1e9,\ + minimF = False,\ + nmo = 1000,\ + ortho_spm = True,\ + SZ = np.int(2*tel.OPD.shape[0]),\ + nZer = 3,\ + NDIVL = 1) + print('Done') + + +#%% ----------------------- Calibration ---------------------------------- + wfs.is_geometric = True + ao_calib = ao_calibration(param = param,\ + ngs = ngs,\ + tel = tel,\ + atm = atm,\ + dm = dm,\ + wfs = wfs,\ + nameFolderIntMat = None,\ + nameIntMat = None,\ + nameFolderBasis = None,\ + nameBasis = param['modal_basis_name'],\ + nMeasurements = 100,\ + get_basis = False) + wfs.is_geometric = False + + #%% ----------------------- FASTER LOOP ---------------------------------- + + # create the Telescope object + tel_fast= Telescope(resolution = param['resolution'],\ + diameter = param['diameter'],\ + samplingTime = 1/3000,\ + centralObstruction = param['centralObstruction']) + + # create the Source object + ngs_fast=Source(optBand = param['opticalBand'],\ + magnitude = 0) + + + + # combine the NGS to the telescope using '*' operator: + ngs_fast*tel_fast + + # create the Atmosphere object + atm_fast = Atmosphere(telescope = tel_fast,\ + r0 = param['r0'],\ + L0 = param['L0'],\ + windSpeed = param['windSpeed'],\ + fractionalR0 = param['fractionnalR0'],\ + windDirection = param['windDirection'],\ + altitude = param['altitude']) + # initialize atmosphere + atm_fast.initializeAtmosphere(tel_fast) + atm_fast.update() + class emptyClass(): + pass + # save output as sub-classes + simulationObject = emptyClass() + + simulationObject.tel = tel + simulationObject.atm = atm + simulationObject.tel_fast = tel_fast + simulationObject.atm_fast = atm_fast + simulationObject.ngs = ngs + simulationObject.dm = dm + simulationObject.wfs = wfs + simulationObject.param = param + simulationObject.calib = ao_calib.calib + simulationObject.M2C = ao_calib.M2C + simulationObject.gOpt = ao_calib.gOpt + + return simulationObject + + diff --git a/OOPAO/closed_loop/__init__.py b/OOPAO/closed_loop/__init__.py new file mode 100644 index 0000000..822b683 --- /dev/null +++ b/OOPAO/closed_loop/__init__.py @@ -0,0 +1,6 @@ +# -*- coding: utf-8 -*- +""" +Created on Fri Apr 3 10:56:59 2020 + +@author: cheritie +""" diff --git a/AO_modules/closed_loop/run_cl.py b/OOPAO/closed_loop/run_cl.py old mode 100755 new mode 100644 similarity index 95% rename from AO_modules/closed_loop/run_cl.py rename to OOPAO/closed_loop/run_cl.py index 0f13465..98ab721 --- a/AO_modules/closed_loop/run_cl.py +++ b/OOPAO/closed_loop/run_cl.py @@ -1,330 +1,330 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Nov 16 15:18:31 2020 - -@author: cheritie -""" - -import matplotlib.pyplot as plt -import numpy as np -import time - -# local modules -from AO_modules.calibration.CalibrationVault import CalibrationVault -from AO_modules.MisRegistration import MisRegistration -from AO_modules.tools.tools import createFolder -from AO_modules.mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration - - -##################################################################################################################################################### -# # # # # # # # # # # # # # # # # -# # # # # # # # # # # # # # -# # # # # # # # # # # # # # # # # # # # # -# # # # # # # # # # # # # -# # # # # # # # # # # # # # # # # # # -##################################################################################################################################################### - -def run_cl(param,obj): - - nLoop = param['nLoop'] - gain_cl = param['gainCL'] - - #% ------------------------------------ Noise Update ------------------------------------ - # set magnitude of the NGS - obj.ngs.nPhoton = param['nPhotonPerSubaperture'] * (1/obj.tel.samplingTime)/((obj.tel.D/param['nSubaperture'] )**2) - - extraName ='' - # photon Noise - obj.wfs.cam.photonNoise = param['photonNoise'] - if obj.wfs.cam.photonNoise: - extraName+=('photonNoise_') - - # RON - obj.wfs.cam.readoutNoise = param['readoutNoise'] - - if obj.wfs.cam.readoutNoise: - extraName+=('readoutNoise_') - - - #% ------------------------------------ Calibration ------------------------------------ - if obj.calib.D.shape[1] != param['nModes']: - calib_cl = CalibrationVault(obj.calib.D[:,:param['nModes']]) - else: - calib_cl = obj.calib - M2C_cl = obj.M2C_cl - - #%% ------------------------------------ mis-registrations ------------------------------------ - - misRegistration_cl = MisRegistration(param) - - misRegistration_cl.show() - obj.dm.misReg.show() - -# case with no mis-registration - if misRegistration_cl == obj.dm.misReg: - dm_cl = obj.dm - else: - dm_cl = applyMisRegistration(obj.tel,misRegistration_cl,param) - - #%% ------------------------------------ closed loop data to be saved ------------------------------------ - - wfs_signals = np.zeros([nLoop,obj.wfs.nSignal]) - dm_commands = np.zeros([nLoop,obj.dm.nValidAct]) - modal_coefficients_res = np.zeros([nLoop,obj.M2C_cl.shape[1]]) - modal_coefficients_turb = np.zeros([nLoop,obj.M2C_cl.shape[1]]) - ao_residuals = np.zeros(nLoop) - ao_turbulence = np.zeros(nLoop) - petalling_nm = np.zeros([6,nLoop]) - - #%% ------------------------------------ destination folder ------------------------------------ - - misregistrationFolder = misRegistration_cl.misRegName - destinationFolder = param['pathOutput']+ '/'+param['name'] +'/'+ misregistrationFolder + '/' + extraName + '/' - createFolder(destinationFolder) - - gain_cl = param['gainCL'] - - # combine with atmosphere - obj.tel+obj.atm - plt.close('all') - - # initialize DM commands - dm_cl.coefs=0 - - #%% ------------------------------------ Modal Gains ------------------------------------ - - ModalGainsMatrix = obj.gOpt - - # Loop initialization - reconstructor = np.matmul(M2C_cl,np.matmul(ModalGainsMatrix,calib_cl.M)) - - - #%% - # propagate through th whole system - obj.ngs*obj.tel*dm_cl*obj.wfs - - # setup the display - if obj.display: - plt.figure(79) - plt.ion() - - ax1=plt.subplot(2,2,1) - im_atm=ax1.imshow(obj.tel.src.phase) - plt.colorbar(im_atm) - plt.title('Turbulence phase') - - - ax2=plt.subplot(2,2,2) - im_residual=ax2.imshow(obj.tel.src.phase) - plt.colorbar(im_residual) - plt.title('Residual Phase') - - ax3=plt.subplot(2,2,3) - ax3.plot(np.diag(obj.gOpt)) - plt.title('Modal Gains') - - ax4 = plt.subplot(2,2,4) - obj.tel.computePSF(zeroPaddingFactor = 4) - psf_cl=ax4.imshow(np.log(np.abs(obj.tel.PSF))) - plt.colorbar(psf_cl) - plt.title('CL PSF') - - if obj.displayPetals: - if obj.dm.isM4: - plt.figure(80) - ax_p0 = plt.subplot(2,3,1) - p = obj.tel.getPetalOPD(0) - pet_0 = ax_p0.imshow(p) - plt.colorbar(pet_0) - plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') - - ax_p1 = plt.subplot(2,3,2) - p = obj.tel.getPetalOPD(1) - pet_1 = ax_p1.imshow(p) - plt.colorbar(pet_1) - plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') - - ax_p2 = plt.subplot(2,3,3) - p = obj.tel.getPetalOPD(2) - pet_2 = ax_p2.imshow(p) - plt.colorbar(pet_2) - plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') - - ax_p3 = plt.subplot(2,3,4) - p = obj.tel.getPetalOPD(3) - pet_3 = ax_p3.imshow(p) - plt.colorbar(pet_3) - plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') - - ax_p4 = plt.subplot(2,3,5) - p = obj.tel.getPetalOPD(4) - pet_4 = ax_p4.imshow(p) - plt.colorbar(pet_4) - plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') - - ax_p5 = plt.subplot(2,3,6) - p = obj.tel.getPetalOPD(5) - pet_5 = ax_p5.imshow(p) - plt.colorbar(pet_5) - plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') - - -#%% - wfsSignal = np.zeros(obj.wfs.nSignal) - - - for i_loop in range(nLoop): - a= time.time() - # update atmospheric phase screen - obj.atm.update() - - # save phase variance - ao_turbulence[i_loop]=np.std(obj.tel.OPD[np.where(obj.tel.pupil>0)])*1e9 - - # save turbulent phase - turbPhase=obj.tel.src.phase - turb_OPD = np.reshape(obj.tel.OPD,obj.tel.resolution**2) - try: - modal_coefficients_turb[i_loop,:] = np.matmul(obj.projector, turb_OPD) - save_modal_coef = True - except: - save_modal_coef = False - if i_loop == 0: - print('Error - no projector for the modal basis..') - # propagate to the WFS with the CL commands applied - obj.tel*dm_cl*obj.wfs - - dm_cl.coefs=dm_cl.coefs-gain_cl*np.matmul(reconstructor,wfsSignal) - - dm_commands[i_loop,:] = dm_cl.coefs - - # # petal kicker - # if i_loop==100: - # # compute the mean value of the recorded commands - # mean_command_value = np.zeros(6) - # for i_petal in range(6): - - # mean_command_value[i_petal] = np.mean(dm_commands[50:i_loop,i_petal*892:(i_petal*892+811)]) - # print(mean_command_value[i_petal]) - # dm_cl.coefs[i_petal*892:(i_petal*892+811)] -= mean_command_value[i_petal] - - - if obj.tel.isPetalFree: - dm_cl.OPD = obj.tel.removePetalling(dm_cl.OPD) - ao_residuals[i_loop]=np.std(obj.tel.OPD[np.where(obj.tel.pupil>0)])*1e9 - - # store the slopes after computing the commands => 2 frames delay - wfsSignal=obj.wfs.signal - b= time.time() - - res_OPD = np.reshape(obj.tel.OPD,obj.tel.resolution**2) - try: - modal_coefficients_res[i_loop,:] = np.matmul(obj.projector, res_OPD) - except: - if i_loop==0: - print('Error - no projector for the modal basis..') - - if obj.display==True: - # turbulence phase - im_atm.set_data(turbPhase) - im_atm.set_clim(vmin=turbPhase.min(),vmax=turbPhase.max()) - - # residual phase - tmp = obj.tel.src.phase - tmp-=np.mean(tmp[obj.tel.pupil]) - im_residual.set_data(tmp) - im_residual.set_clim(vmin=tmp.min(),vmax=tmp.max()) - - obj.tel.computePSF(zeroPaddingFactor = 6) - tmp = obj.tel.PSF_trunc/obj.tel.PSF_trunc.max() - psf_cl.set_data(np.log(np.abs(tmp))) - psf_cl.set_clim(vmin=-5,vmax=0) - plt.draw() - plt.show() - plt.pause(0.001) - - if obj.displayPetals: - if obj.dm.isM4: - p = obj.tel.getPetalOPD(0)*1e9 - pet_0.set_data(p) - pet_0.set_clim(vmin=p.min(),vmax=p.max()) - ax_p0.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') - - p = obj.tel.getPetalOPD(1)*1e9 - pet_1.set_data(p) - pet_1.set_clim(vmin=p.min(),vmax=p.max()) - ax_p1.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') - - p = obj.tel.getPetalOPD(2)*1e9 - pet_2.set_data(p) - pet_2.set_clim(vmin=p.min(),vmax=p.max()) - ax_p2.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') - - p = obj.tel.getPetalOPD(3)*1e9 - pet_3.set_data(p) - pet_3.set_clim(vmin=p.min(),vmax=p.max()) - ax_p3.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') - - p = obj.tel.getPetalOPD(4)*1e9 - pet_4.set_data(p) - pet_4.set_clim(vmin=p.min(),vmax=p.max()) - ax_p4.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') - - - p = obj.tel.getPetalOPD(5)*1e9 - pet_5.set_data(p) - pet_5.set_clim(vmin=p.min(),vmax=p.max()) - ax_p5.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') - - plt.draw() - plt.show() - plt.pause(0.001) - if obj.printPetals: - if obj.dm.isM4: - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - # remove mean OPD - obj.tel.OPD[np.where(obj.tel.pupil == 1)] -= np.mean(obj.tel.OPD[np.where(obj.tel.pupil == 1)]) - open_loop_modal_command = np.matmul(calib_cl.M,wfsSignal) - open_loop_modal_command -= np.mean(open_loop_modal_command) - open_loop_LO_filtered = M2C_cl[:,2:] @ open_loop_modal_command[2:] - for i_petal in range(6): - petal = obj.tel.getPetalOPD(i_petal)*1e9 - petal_nm = np.round(np.mean(petal[np.where(obj.tel.petalMask == 1)]),5) - mean_command_value = np.mean(open_loop_LO_filtered[i_petal*892:(i_petal*892+811)]) - - print('Petal '+str(i_petal+1) +': ' + str(petal_nm)) - print('mean command '+str(i_petal+1) +': ' + str(mean_command_value)) - - petalling_nm[i_petal,i_loop] = petal_nm - - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - - print('Loop'+str(i_loop)+'/'+str(nLoop)+'Turbulence: '+str(ao_turbulence[i_loop])+' -- Residual:' +str(ao_residuals[i_loop])+ '\n') - print('Elapsed Time: ' + str(b-a) + ' s ') - - dataCL = dict() - dataCL['ao_residual'] = ao_residuals - dataCL['ao_turbulence'] = ao_turbulence - if save_modal_coef: - dataCL['modal_coeff_res'] = modal_coefficients_res - dataCL['modal_coeff_turb'] = modal_coefficients_turb - dataCL['parameterFile'] = param - if obj.printPetals: - dataCL['petalling'] = petalling_nm - dataCL['destinationFolder'] = destinationFolder - dataCL['last_residual_OPD'] = np.reshape(res_OPD,[obj.tel.resolution,obj.tel.resolution]) - - try: - if obj.perf_only: - return dataCL - else: - dataCL['wfs_signals'] = wfs_signals - dataCL['dmCommands'] = dm_commands - except: - dataCL['wfs_signals'] = wfs_signals - dataCL['dmCommands'] = dm_commands - # dataCL['m4_cube_map'] = np.reshape(np.sum((dm_cl.modes)**3,axis=1),[obj.tel.resolution,obj.tel.resolution]) - return dataCL - - - +# -*- coding: utf-8 -*- +""" +Created on Mon Nov 16 15:18:31 2020 + +@author: cheritie +""" + +import time + +import matplotlib.pyplot as plt +import numpy as np + +from ..MisRegistration import MisRegistration +from ..calibration.CalibrationVault import CalibrationVault +from ..mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration +from ..tools.tools import createFolder + + +##################################################################################################################################################### +# # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # +##################################################################################################################################################### + +def run_cl(param,obj): + + nLoop = param['nLoop'] + gain_cl = param['gainCL'] + + #% ------------------------------------ Noise Update ------------------------------------ + # set magnitude of the NGS + obj.ngs.nPhoton = param['nPhotonPerSubaperture'] * (1/obj.tel.samplingTime)/((obj.tel.D/param['nSubaperture'] )**2) + + extraName ='' + # photon Noise + obj.wfs.cam.photonNoise = param['photonNoise'] + if obj.wfs.cam.photonNoise: + extraName+=('photonNoise_') + + # RON + obj.wfs.cam.readoutNoise = param['readoutNoise'] + + if obj.wfs.cam.readoutNoise: + extraName+=('readoutNoise_') + + + #% ------------------------------------ Calibration ------------------------------------ + if obj.calib.D.shape[1] != param['nModes']: + calib_cl = CalibrationVault(obj.calib.D[:,:param['nModes']]) + else: + calib_cl = obj.calib + M2C_cl = obj.M2C_cl + + #%% ------------------------------------ mis-registrations ------------------------------------ + + misRegistration_cl = MisRegistration(param) + + misRegistration_cl.show() + obj.dm.misReg.show() + +# case with no mis-registration + if misRegistration_cl == obj.dm.misReg: + dm_cl = obj.dm + else: + dm_cl = applyMisRegistration(obj.tel,misRegistration_cl,param) + + #%% ------------------------------------ closed loop data to be saved ------------------------------------ + + wfs_signals = np.zeros([nLoop,obj.wfs.nSignal]) + dm_commands = np.zeros([nLoop,obj.dm.nValidAct]) + modal_coefficients_res = np.zeros([nLoop,obj.M2C_cl.shape[1]]) + modal_coefficients_turb = np.zeros([nLoop,obj.M2C_cl.shape[1]]) + ao_residuals = np.zeros(nLoop) + ao_turbulence = np.zeros(nLoop) + petalling_nm = np.zeros([6,nLoop]) + + #%% ------------------------------------ destination folder ------------------------------------ + + misregistrationFolder = misRegistration_cl.misRegName + destinationFolder = param['pathOutput']+ '/'+param['name'] +'/'+ misregistrationFolder + '/' + extraName + '/' + createFolder(destinationFolder) + + gain_cl = param['gainCL'] + + # combine with atmosphere + obj.tel+obj.atm + plt.close('all') + + # initialize DM commands + dm_cl.coefs=0 + + #%% ------------------------------------ Modal Gains ------------------------------------ + + ModalGainsMatrix = obj.gOpt + + # Loop initialization + reconstructor = np.matmul(M2C_cl,np.matmul(ModalGainsMatrix,calib_cl.M)) + + + #%% + # propagate through th whole system + obj.ngs*obj.tel*dm_cl*obj.wfs + + # setup the display + if obj.display: + plt.figure(79) + plt.ion() + + ax1=plt.subplot(2,2,1) + im_atm=ax1.imshow(obj.tel.src.phase) + plt.colorbar(im_atm) + plt.title('Turbulence phase') + + + ax2=plt.subplot(2,2,2) + im_residual=ax2.imshow(obj.tel.src.phase) + plt.colorbar(im_residual) + plt.title('Residual Phase') + + ax3=plt.subplot(2,2,3) + ax3.plot(np.diag(obj.gOpt)) + plt.title('Modal Gains') + + ax4 = plt.subplot(2,2,4) + obj.tel.computePSF(zeroPaddingFactor = 4) + psf_cl=ax4.imshow(np.log(np.abs(obj.tel.PSF))) + plt.colorbar(psf_cl) + plt.title('CL PSF') + + if obj.displayPetals: + if obj.dm.isM4: + plt.figure(80) + ax_p0 = plt.subplot(2,3,1) + p = obj.tel.getPetalOPD(0) + pet_0 = ax_p0.imshow(p) + plt.colorbar(pet_0) + plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') + + ax_p1 = plt.subplot(2,3,2) + p = obj.tel.getPetalOPD(1) + pet_1 = ax_p1.imshow(p) + plt.colorbar(pet_1) + plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') + + ax_p2 = plt.subplot(2,3,3) + p = obj.tel.getPetalOPD(2) + pet_2 = ax_p2.imshow(p) + plt.colorbar(pet_2) + plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') + + ax_p3 = plt.subplot(2,3,4) + p = obj.tel.getPetalOPD(3) + pet_3 = ax_p3.imshow(p) + plt.colorbar(pet_3) + plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') + + ax_p4 = plt.subplot(2,3,5) + p = obj.tel.getPetalOPD(4) + pet_4 = ax_p4.imshow(p) + plt.colorbar(pet_4) + plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') + + ax_p5 = plt.subplot(2,3,6) + p = obj.tel.getPetalOPD(5) + pet_5 = ax_p5.imshow(p) + plt.colorbar(pet_5) + plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') + + +#%% + wfsSignal = np.zeros(obj.wfs.nSignal) + + + for i_loop in range(nLoop): + a= time.time() + # update atmospheric phase screen + obj.atm.update() + + # save phase variance + ao_turbulence[i_loop]=np.std(obj.tel.OPD[np.where(obj.tel.pupil>0)])*1e9 + + # save turbulent phase + turbPhase=obj.tel.src.phase + turb_OPD = np.reshape(obj.tel.OPD,obj.tel.resolution**2) + try: + modal_coefficients_turb[i_loop,:] = np.matmul(obj.projector, turb_OPD) + save_modal_coef = True + except: + save_modal_coef = False + if i_loop == 0: + print('Error - no projector for the modal basis..') + # propagate to the WFS with the CL commands applied + obj.tel*dm_cl*obj.wfs + + dm_cl.coefs=dm_cl.coefs-gain_cl*np.matmul(reconstructor,wfsSignal) + + dm_commands[i_loop,:] = dm_cl.coefs + + # # petal kicker + # if i_loop==100: + # # compute the mean value of the recorded commands + # mean_command_value = np.zeros(6) + # for i_petal in range(6): + + # mean_command_value[i_petal] = np.mean(dm_commands[50:i_loop,i_petal*892:(i_petal*892+811)]) + # print(mean_command_value[i_petal]) + # dm_cl.coefs[i_petal*892:(i_petal*892+811)] -= mean_command_value[i_petal] + + + if obj.tel.isPetalFree: + dm_cl.OPD = obj.tel.removePetalling(dm_cl.OPD) + ao_residuals[i_loop]=np.std(obj.tel.OPD[np.where(obj.tel.pupil>0)])*1e9 + + # store the slopes after computing the commands => 2 frames delay + wfsSignal=obj.wfs.signal + b= time.time() + + res_OPD = np.reshape(obj.tel.OPD,obj.tel.resolution**2) + try: + modal_coefficients_res[i_loop,:] = np.matmul(obj.projector, res_OPD) + except: + if i_loop==0: + print('Error - no projector for the modal basis..') + + if obj.display==True: + # turbulence phase + im_atm.set_data(turbPhase) + im_atm.set_clim(vmin=turbPhase.min(),vmax=turbPhase.max()) + + # residual phase + tmp = obj.tel.src.phase + tmp-=np.mean(tmp[obj.tel.pupil]) + im_residual.set_data(tmp) + im_residual.set_clim(vmin=tmp.min(),vmax=tmp.max()) + + obj.tel.computePSF(zeroPaddingFactor = 6) + tmp = obj.tel.PSF_trunc/obj.tel.PSF_trunc.max() + psf_cl.set_data(np.log(np.abs(tmp))) + psf_cl.set_clim(vmin=-5,vmax=0) + plt.draw() + plt.show() + plt.pause(0.001) + + if obj.displayPetals: + if obj.dm.isM4: + p = obj.tel.getPetalOPD(0)*1e9 + pet_0.set_data(p) + pet_0.set_clim(vmin=p.min(),vmax=p.max()) + ax_p0.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') + + p = obj.tel.getPetalOPD(1)*1e9 + pet_1.set_data(p) + pet_1.set_clim(vmin=p.min(),vmax=p.max()) + ax_p1.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') + + p = obj.tel.getPetalOPD(2)*1e9 + pet_2.set_data(p) + pet_2.set_clim(vmin=p.min(),vmax=p.max()) + ax_p2.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') + + p = obj.tel.getPetalOPD(3)*1e9 + pet_3.set_data(p) + pet_3.set_clim(vmin=p.min(),vmax=p.max()) + ax_p3.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') + + p = obj.tel.getPetalOPD(4)*1e9 + pet_4.set_data(p) + pet_4.set_clim(vmin=p.min(),vmax=p.max()) + ax_p4.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') + + + p = obj.tel.getPetalOPD(5)*1e9 + pet_5.set_data(p) + pet_5.set_clim(vmin=p.min(),vmax=p.max()) + ax_p5.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') + + plt.draw() + plt.show() + plt.pause(0.001) + if obj.printPetals: + if obj.dm.isM4: + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + # remove mean OPD + obj.tel.OPD[np.where(obj.tel.pupil == 1)] -= np.mean(obj.tel.OPD[np.where(obj.tel.pupil == 1)]) + open_loop_modal_command = np.matmul(calib_cl.M,wfsSignal) + open_loop_modal_command -= np.mean(open_loop_modal_command) + open_loop_LO_filtered = M2C_cl[:,2:] @ open_loop_modal_command[2:] + for i_petal in range(6): + petal = obj.tel.getPetalOPD(i_petal)*1e9 + petal_nm = np.round(np.mean(petal[np.where(obj.tel.petalMask == 1)]),5) + mean_command_value = np.mean(open_loop_LO_filtered[i_petal*892:(i_petal*892+811)]) + + print('Petal '+str(i_petal+1) +': ' + str(petal_nm)) + print('mean command '+str(i_petal+1) +': ' + str(mean_command_value)) + + petalling_nm[i_petal,i_loop] = petal_nm + + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + + print('Loop'+str(i_loop)+'/'+str(nLoop)+'Turbulence: '+str(ao_turbulence[i_loop])+' -- Residual:' +str(ao_residuals[i_loop])+ '\n') + print('Elapsed Time: ' + str(b-a) + ' s ') + + dataCL = dict() + dataCL['ao_residual'] = ao_residuals + dataCL['ao_turbulence'] = ao_turbulence + if save_modal_coef: + dataCL['modal_coeff_res'] = modal_coefficients_res + dataCL['modal_coeff_turb'] = modal_coefficients_turb + dataCL['parameterFile'] = param + if obj.printPetals: + dataCL['petalling'] = petalling_nm + dataCL['destinationFolder'] = destinationFolder + dataCL['last_residual_OPD'] = np.reshape(res_OPD,[obj.tel.resolution,obj.tel.resolution]) + + try: + if obj.perf_only: + return dataCL + else: + dataCL['wfs_signals'] = wfs_signals + dataCL['dmCommands'] = dm_commands + except: + dataCL['wfs_signals'] = wfs_signals + dataCL['dmCommands'] = dm_commands + # dataCL['m4_cube_map'] = np.reshape(np.sum((dm_cl.modes)**3,axis=1),[obj.tel.resolution,obj.tel.resolution]) + return dataCL + + + diff --git a/AO_modules/closed_loop/run_cl_long_push_pull.py b/OOPAO/closed_loop/run_cl_long_push_pull.py similarity index 94% rename from AO_modules/closed_loop/run_cl_long_push_pull.py rename to OOPAO/closed_loop/run_cl_long_push_pull.py index f7762bc..58fc53d 100644 --- a/AO_modules/closed_loop/run_cl_long_push_pull.py +++ b/OOPAO/closed_loop/run_cl_long_push_pull.py @@ -1,283 +1,283 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Jun 14 11:32:08 2021 - -@author: cheritie -""" - -import matplotlib.pyplot as plt -import numpy as np -import time - -# local modules -from AO_modules.calibration.CalibrationVault import CalibrationVault -from AO_modules.MisRegistration import MisRegistration -from AO_modules.tools.tools import createFolder, emptyClass -from AO_modules.mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration - - -##################################################################################################################################################### -# # # # # # # # # # # # # # # # # -# # # # # # # # # # # # # # -# # # # # # # # # # # # # # # # # # # # # -# # # # # # # # # # # # # -# # # # # # # # # # # # # # # # # # # -##################################################################################################################################################### - -def run_cl_long_push_pull(param,obj): - - - gain_cl = param['gainCL'] - - #% ------------------------------------ Noise Update ------------------------------------ - - # set magnitude of the NGS - obj.ngs.nPhoton = param['nPhotonPerSubaperture'] * (1/obj.tel.samplingTime)/((obj.tel.D/param['nSubaperture'] )**2) - - extraName ='' - # photon Noise - obj.wfs.cam.photonNoise = param['photonNoise'] - if obj.wfs.cam.photonNoise: - extraName+=('photonNoise_') - - # RON - obj.wfs.cam.readoutNoise = param['readoutNoise'] - - if obj.wfs.cam.readoutNoise: - extraName+=('readoutNoise_') - - - #% ------------------------------------ Calibration ------------------------------------ - - calib_cl = CalibrationVault(obj.calib.D[:,:param['nModes']]) - M2C_cl = obj.M2C_cl - M2C_cl_inv = np.linalg.pinv(M2C_cl) - #% ------------------------------------ M4 Saturations ------------------------------------ - - obj.P2F_full = np.zeros([6*892,6*892]) - try: - for i in range(6): - obj.P2F_full[i*892:(i+1)*892,i*892:(i+1)*892] = obj.P2F - obj.get_forces = True - print('Stiffness matrix properly attached to the ao-object!') - - except: - print('No stiffness matrix attached to the ao-object.. computation of the M4 forces not considered') - obj.get_forces = False - #%% ------------------------------------ mis-registrations ------------------------------------ - - misRegistration_cl = MisRegistration(param) - - misRegistration_cl.show() - obj.dm.misReg.show() - -# case with no mis-registration - if misRegistration_cl == obj.dm.misReg: - dm_cl = obj.dm - else: - dm_cl = applyMisRegistration(obj.tel,misRegistration_cl,param) - - - - #%% - def push_pull(obj, dm_cl, wfsSignal, i_loop,cl_data): - - sp = np.zeros(obj.wfs.nSignal) - sm = np.zeros(obj.wfs.nSignal) - - # push - for i_length in range(obj.push_pull_duration): - # update of the atmosphere - obj.atm.update() - cl_data.ao_turbulence[i_loop+i_length ]=np.std(obj.atm.OPD[np.where(obj.tel.pupil>0)])*1e9 - - # get modal coefficients from DM - modal_coeffs = M2C_cl_inv @ dm_cl.coefs - # force a push/pull on the desired mode - modal_coeffs[obj.push_pull_index_mode[0]] = obj.push_pull_amplitude[0] - print('Push '+str(i_length)) - # apply it on the dm - dm_cl.coefs = M2C_cl@modal_coeffs - # propagate - obj.tel*dm_cl*obj.wfs - - if obj.get_forces: - cl_data.dm_forces[i_loop+i_length,:] = obj.P2F_full@dm_cl.coefs - - cl_data.ao_residuals[i_loop+i_length]=np.std(obj.tel.OPD[np.where(obj.tel.pupil>0)])*1e9 - # control law - dm_cl.coefs -= np.matmul(M2C_cl,obj.push_pull_gains@np.matmul(reconstructor,wfsSignal)) - # buffer of the wfs signal to simulate delay - wfsSignal = obj.wfs.signal - if i_length>=obj.start_pp: - print('saving wfs signal') - sp+=obj.wfs.signal - print('Loop '+str(i_loop+i_length)+'/'+str(cl_data.nLoop)+' Turbulence: '+str(cl_data.ao_turbulence[i_loop+i_length])+' -- Residual:' +str(cl_data.ao_residuals[i_loop+i_length])+ '\n') - print('Elapsed Time: ' + str(b-a) + ' s ') - if obj.display==True: - cl_plot([obj.atm.OPD, obj.tel.OPD, [np.arange(param['nModes']),np.diag(obj.push_pull_gains)],[np.arange(i_loop+i_length),cl_data.ao_residuals[:i_loop+i_length]]], plt_obj = plt_obj) - if plt_obj.keep_going is False: - print('Loop stopped by the user') - break - plt.pause(0.01) - - for i_length in range(obj.push_pull_duration): - # update of the atmosphere - obj.atm.update() - cl_data.ao_turbulence[i_loop+i_length ]=np.std(obj.atm.OPD[np.where(obj.tel.pupil>0)])*1e9 - - # get modal coefficients from DM - modal_coeffs = M2C_cl_inv @ dm_cl.coefs - # force a push/pull on the desired mode - modal_coeffs[obj.push_pull_index_mode[0]] = -obj.push_pull_amplitude[0] - print('Pull '+str(i_length)) - # apply it on the dm - dm_cl.coefs = M2C_cl@modal_coeffs - # propagate - obj.tel*dm_cl*obj.wfs - cl_data.ao_residuals[i_loop+i_length+obj.push_pull_duration]=np.std(obj.tel.OPD[np.where(obj.tel.pupil>0)])*1e9 - # control law - dm_cl.coefs -= np.matmul(M2C_cl,obj.push_pull_gains@np.matmul(reconstructor,wfsSignal)) - - if obj.get_forces: - cl_data.dm_forces[i_loop+i_length,:] = obj.P2F_full@dm_cl.coefs - - # buffer of the wfs signal to simulate delay - wfsSignal = obj.wfs.signal - if i_length>=obj.start_pp: - print('saving wfs signal') - sm+=obj.wfs.signal - print('Loop '+str(i_loop+i_length+obj.push_pull_duration)+'/'+str(cl_data.nLoop)+' Turbulence: '+str(cl_data.ao_turbulence[i_loop+i_length+obj.push_pull_duration])+' -- Residual:' +str(cl_data.ao_residuals[i_loop+i_length+obj.push_pull_duration])+ '\n') - print('Elapsed Time: ' + str(b-a) + ' s ') - if obj.display==True: - cl_plot([obj.atm.OPD, obj.tel.OPD, [np.arange(param['nModes']),np.diag(obj.push_pull_gains)],[np.arange(i_loop+i_length+obj.push_pull_duration),cl_data.ao_residuals[:i_loop+i_length+obj.push_pull_duration]]], plt_obj = plt_obj) - if plt_obj.keep_going is False: - print('Loop stopped by the user') - break - plt.pause(0.01) - sp /= (obj.push_pull_duration-obj.start_pp) - sm /= (obj.push_pull_duration-obj.start_pp) - push_pull_signal = 0.5*(sp-sm)/obj.push_pull_amplitude[0] - - return push_pull_signal - - - cl_data = emptyClass() - cl_data.nLoop = obj.n_bootstrap + obj.n_push_pull * (2*obj.push_pull_duration + obj.n_closed_loop) - cl_data.ao_residuals = np.zeros(cl_data.nLoop ) - cl_data.ao_turbulence = np.zeros(cl_data.nLoop ) - cl_data.dm_forces = np.zeros([cl_data.nLoop,obj.dm.nValidAct]) - - #% ------------------------------------ destination folder ------------------------------------ - misregistrationFolder = misRegistration_cl.misRegName - destinationFolder = param['pathOutput']+ '/'+param['name'] +'/'+ misregistrationFolder + '/' + extraName + '/' - createFolder(destinationFolder) - - #% ------------------------------------ initialization ------------------------------------ - - # combine with atmosphere - obj.tel+obj.atm - plt.close('all') - - # initialize DM commands and closed loop gain - dm_cl.coefs = 0 - gain_cl = param['gainCL'] - - # propagate through th whole system - obj.ngs*obj.tel*dm_cl*obj.wfs - - #% ------------------------------------ variable to save data ------------------------------------ - wfsSignal = np.zeros(obj.wfs.nSignal) - pp_index = np.linspace(obj.n_bootstrap,cl_data.nLoop-obj.n_closed_loop,obj.n_push_pull).astype(int) - pp_signal = [] - - #% ------------------------------------ Modal Gains ------------------------------------ - ModalGainsMatrix = obj.gOpt - # Loop initialization - reconstructor = np.matmul(ModalGainsMatrix,calib_cl.M) - - - #% ------------------------------------ setu the display ------------------------------------ - if obj.display: - from AO_modules.tools.displayTools import cl_plot - plt.close('all') - list_fig = [obj.atm.OPD, obj.tel.OPD, [np.arange(param['nModes']),np.diag(obj.gOpt)],[np.arange(cl_data.nLoop),cl_data.ao_residuals]] - - type_fig = ['imshow','imshow','plot','plot'] - - list_title = ['atm OPD [m]','tel OPD [m]','Optical Gains','WFE [nm]'] - - plt_obj = cl_plot(list_fig,plt_obj = None, type_fig = type_fig, fig_number = 20, list_ratio = [[1,1,1],[1,1]], list_title = list_title) - - #%% - - keep_going = True - i_loop=-1 - while keep_going: - i_loop+=1 - a= time.time() - # update atmospheric phase screen - obj.atm.update() - - # save phase variance - cl_data.ao_turbulence[i_loop ]=np.std(obj.tel.OPD[np.where(obj.tel.pupil>0)])*1e9 - - # propagate to the WFS with the CL commands applied - obj.tel*dm_cl*obj.wfs - - dm_cl.coefs=dm_cl.coefs-gain_cl*np.matmul(M2C_cl,np.matmul(reconstructor,wfsSignal)) - - cl_data.ao_residuals[i_loop ]=np.std(obj.tel.OPD[np.where(obj.tel.pupil>0)])*1e9 - - if obj.get_forces: - cl_data.dm_forces[i_loop,:] = obj.P2F_full@dm_cl.coefs - - # store the slopes after computing the commands => 2 frames delay - wfsSignal=obj.wfs.signal - b= time.time() - - if obj.display==True: - if i_loop>1: - if i_loop >2500: - cl_plot([obj.atm.OPD, obj.tel.OPD, [np.arange(param['nModes']),np.diag(obj.gOpt)],[np.arange(25,i_loop,1),cl_data.ao_residuals[25:i_loop ]]], plt_obj = plt_obj) - else: - cl_plot([obj.atm.OPD, obj.tel.OPD, [np.arange(param['nModes']),np.diag(obj.gOpt)],[np.arange(i_loop),cl_data.ao_residuals[:i_loop ]]], plt_obj = plt_obj) - - if plt_obj.keep_going is False: - print('Loop stopped by the user') - break - plt.pause(0.001) - - print('Loop '+str(i_loop)+'/'+str(cl_data.nLoop)+' Turbulence: '+str(cl_data.ao_turbulence[i_loop ])+' -- Residual:' +str(cl_data.ao_residuals[i_loop ])+ '\n') - print('Elapsed Time: ' + str(b-a) + ' s ') - if i_loop in pp_index: - print('Applying the push-pull sequence!') - tmp = push_pull(obj, dm_cl, wfsSignal,i_loop,cl_data) - pp_signal.append(tmp) - - i_loop +=obj.push_pull_duration*2 -1 - print(i_loop) - - if i_loop == cl_data.nLoop-1: - keep_going = False - - push_pull_signal = np.mean(np.asarray(pp_signal), axis = 0 ) - res_OPD = np.reshape(obj.tel.OPD,obj.tel.resolution**2) - -#%% - - dataCL = dict() - dataCL['ao_residual' ] = cl_data.ao_residuals - dataCL['ao_turbulence' ] = cl_data.ao_turbulence - dataCL['dm_forces' ] = cl_data.dm_forces - dataCL['on_sky_signal' ] = push_pull_signal - dataCL['on_sky_signal_list' ] = np.asarray(pp_signal) - - dataCL['destinationFolder'] = destinationFolder - dataCL['last_residual_OPD'] = np.reshape(res_OPD,[obj.tel.resolution,obj.tel.resolution]) - - - return dataCL - - - +# -*- coding: utf-8 -*- +""" +Created on Mon Jun 14 11:32:08 2021 + +@author: cheritie +""" + +import time + +import matplotlib.pyplot as plt +import numpy as np + +from ..MisRegistration import MisRegistration +from ..calibration.CalibrationVault import CalibrationVault +from ..mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration +from ..tools.tools import createFolder, emptyClass + + +##################################################################################################################################################### +# # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # +##################################################################################################################################################### + +def run_cl_long_push_pull(param,obj): + + + gain_cl = param['gainCL'] + + #% ------------------------------------ Noise Update ------------------------------------ + + # set magnitude of the NGS + obj.ngs.nPhoton = param['nPhotonPerSubaperture'] * (1/obj.tel.samplingTime)/((obj.tel.D/param['nSubaperture'] )**2) + + extraName ='' + # photon Noise + obj.wfs.cam.photonNoise = param['photonNoise'] + if obj.wfs.cam.photonNoise: + extraName+=('photonNoise_') + + # RON + obj.wfs.cam.readoutNoise = param['readoutNoise'] + + if obj.wfs.cam.readoutNoise: + extraName+=('readoutNoise_') + + + #% ------------------------------------ Calibration ------------------------------------ + + calib_cl = CalibrationVault(obj.calib.D[:,:param['nModes']]) + M2C_cl = obj.M2C_cl + M2C_cl_inv = np.linalg.pinv(M2C_cl) + #% ------------------------------------ M4 Saturations ------------------------------------ + + obj.P2F_full = np.zeros([6*892,6*892]) + try: + for i in range(6): + obj.P2F_full[i*892:(i+1)*892,i*892:(i+1)*892] = obj.P2F + obj.get_forces = True + print('Stiffness matrix properly attached to the ao-object!') + + except: + print('No stiffness matrix attached to the ao-object.. computation of the M4 forces not considered') + obj.get_forces = False + #%% ------------------------------------ mis-registrations ------------------------------------ + + misRegistration_cl = MisRegistration(param) + + misRegistration_cl.show() + obj.dm.misReg.show() + +# case with no mis-registration + if misRegistration_cl == obj.dm.misReg: + dm_cl = obj.dm + else: + dm_cl = applyMisRegistration(obj.tel,misRegistration_cl,param) + + + + #%% + def push_pull(obj, dm_cl, wfsSignal, i_loop,cl_data): + + sp = np.zeros(obj.wfs.nSignal) + sm = np.zeros(obj.wfs.nSignal) + + # push + for i_length in range(obj.push_pull_duration): + # update of the atmosphere + obj.atm.update() + cl_data.ao_turbulence[i_loop+i_length ]=np.std(obj.atm.OPD[np.where(obj.tel.pupil>0)])*1e9 + + # get modal coefficients from DM + modal_coeffs = M2C_cl_inv @ dm_cl.coefs + # force a push/pull on the desired mode + modal_coeffs[obj.push_pull_index_mode[0]] = obj.push_pull_amplitude[0] + print('Push '+str(i_length)) + # apply it on the dm + dm_cl.coefs = M2C_cl@modal_coeffs + # propagate + obj.tel*dm_cl*obj.wfs + + if obj.get_forces: + cl_data.dm_forces[i_loop+i_length,:] = obj.P2F_full@dm_cl.coefs + + cl_data.ao_residuals[i_loop+i_length]=np.std(obj.tel.OPD[np.where(obj.tel.pupil>0)])*1e9 + # control law + dm_cl.coefs -= np.matmul(M2C_cl,obj.push_pull_gains@np.matmul(reconstructor,wfsSignal)) + # buffer of the wfs signal to simulate delay + wfsSignal = obj.wfs.signal + if i_length>=obj.start_pp: + print('saving wfs signal') + sp+=obj.wfs.signal + print('Loop '+str(i_loop+i_length)+'/'+str(cl_data.nLoop)+' Turbulence: '+str(cl_data.ao_turbulence[i_loop+i_length])+' -- Residual:' +str(cl_data.ao_residuals[i_loop+i_length])+ '\n') + print('Elapsed Time: ' + str(b-a) + ' s ') + if obj.display==True: + cl_plot([obj.atm.OPD, obj.tel.OPD, [np.arange(param['nModes']),np.diag(obj.push_pull_gains)],[np.arange(i_loop+i_length),cl_data.ao_residuals[:i_loop+i_length]]], plt_obj = plt_obj) + if plt_obj.keep_going is False: + print('Loop stopped by the user') + break + plt.pause(0.01) + + for i_length in range(obj.push_pull_duration): + # update of the atmosphere + obj.atm.update() + cl_data.ao_turbulence[i_loop+i_length ]=np.std(obj.atm.OPD[np.where(obj.tel.pupil>0)])*1e9 + + # get modal coefficients from DM + modal_coeffs = M2C_cl_inv @ dm_cl.coefs + # force a push/pull on the desired mode + modal_coeffs[obj.push_pull_index_mode[0]] = -obj.push_pull_amplitude[0] + print('Pull '+str(i_length)) + # apply it on the dm + dm_cl.coefs = M2C_cl@modal_coeffs + # propagate + obj.tel*dm_cl*obj.wfs + cl_data.ao_residuals[i_loop+i_length+obj.push_pull_duration]=np.std(obj.tel.OPD[np.where(obj.tel.pupil>0)])*1e9 + # control law + dm_cl.coefs -= np.matmul(M2C_cl,obj.push_pull_gains@np.matmul(reconstructor,wfsSignal)) + + if obj.get_forces: + cl_data.dm_forces[i_loop+i_length,:] = obj.P2F_full@dm_cl.coefs + + # buffer of the wfs signal to simulate delay + wfsSignal = obj.wfs.signal + if i_length>=obj.start_pp: + print('saving wfs signal') + sm+=obj.wfs.signal + print('Loop '+str(i_loop+i_length+obj.push_pull_duration)+'/'+str(cl_data.nLoop)+' Turbulence: '+str(cl_data.ao_turbulence[i_loop+i_length+obj.push_pull_duration])+' -- Residual:' +str(cl_data.ao_residuals[i_loop+i_length+obj.push_pull_duration])+ '\n') + print('Elapsed Time: ' + str(b-a) + ' s ') + if obj.display==True: + cl_plot([obj.atm.OPD, obj.tel.OPD, [np.arange(param['nModes']),np.diag(obj.push_pull_gains)],[np.arange(i_loop+i_length+obj.push_pull_duration),cl_data.ao_residuals[:i_loop+i_length+obj.push_pull_duration]]], plt_obj = plt_obj) + if plt_obj.keep_going is False: + print('Loop stopped by the user') + break + plt.pause(0.01) + sp /= (obj.push_pull_duration-obj.start_pp) + sm /= (obj.push_pull_duration-obj.start_pp) + push_pull_signal = 0.5*(sp-sm)/obj.push_pull_amplitude[0] + + return push_pull_signal + + + cl_data = emptyClass() + cl_data.nLoop = obj.n_bootstrap + obj.n_push_pull * (2*obj.push_pull_duration + obj.n_closed_loop) + cl_data.ao_residuals = np.zeros(cl_data.nLoop ) + cl_data.ao_turbulence = np.zeros(cl_data.nLoop ) + cl_data.dm_forces = np.zeros([cl_data.nLoop,obj.dm.nValidAct]) + + #% ------------------------------------ destination folder ------------------------------------ + misregistrationFolder = misRegistration_cl.misRegName + destinationFolder = param['pathOutput']+ '/'+param['name'] +'/'+ misregistrationFolder + '/' + extraName + '/' + createFolder(destinationFolder) + + #% ------------------------------------ initialization ------------------------------------ + + # combine with atmosphere + obj.tel+obj.atm + plt.close('all') + + # initialize DM commands and closed loop gain + dm_cl.coefs = 0 + gain_cl = param['gainCL'] + + # propagate through th whole system + obj.ngs*obj.tel*dm_cl*obj.wfs + + #% ------------------------------------ variable to save data ------------------------------------ + wfsSignal = np.zeros(obj.wfs.nSignal) + pp_index = np.linspace(obj.n_bootstrap,cl_data.nLoop-obj.n_closed_loop,obj.n_push_pull).astype(int) + pp_signal = [] + + #% ------------------------------------ Modal Gains ------------------------------------ + ModalGainsMatrix = obj.gOpt + # Loop initialization + reconstructor = np.matmul(ModalGainsMatrix,calib_cl.M) + + + #% ------------------------------------ setu the display ------------------------------------ + if obj.display: + from AO_modules.tools.displayTools import cl_plot + plt.close('all') + list_fig = [obj.atm.OPD, obj.tel.OPD, [np.arange(param['nModes']),np.diag(obj.gOpt)],[np.arange(cl_data.nLoop),cl_data.ao_residuals]] + + type_fig = ['imshow','imshow','plot','plot'] + + list_title = ['atm OPD [m]','tel OPD [m]','Optical Gains','WFE [nm]'] + + plt_obj = cl_plot(list_fig,plt_obj = None, type_fig = type_fig, fig_number = 20, list_ratio = [[1,1,1],[1,1]], list_title = list_title) + + #%% + + keep_going = True + i_loop=-1 + while keep_going: + i_loop+=1 + a= time.time() + # update atmospheric phase screen + obj.atm.update() + + # save phase variance + cl_data.ao_turbulence[i_loop ]=np.std(obj.tel.OPD[np.where(obj.tel.pupil>0)])*1e9 + + # propagate to the WFS with the CL commands applied + obj.tel*dm_cl*obj.wfs + + dm_cl.coefs=dm_cl.coefs-gain_cl*np.matmul(M2C_cl,np.matmul(reconstructor,wfsSignal)) + + cl_data.ao_residuals[i_loop ]=np.std(obj.tel.OPD[np.where(obj.tel.pupil>0)])*1e9 + + if obj.get_forces: + cl_data.dm_forces[i_loop,:] = obj.P2F_full@dm_cl.coefs + + # store the slopes after computing the commands => 2 frames delay + wfsSignal=obj.wfs.signal + b= time.time() + + if obj.display==True: + if i_loop>1: + if i_loop >2500: + cl_plot([obj.atm.OPD, obj.tel.OPD, [np.arange(param['nModes']),np.diag(obj.gOpt)],[np.arange(25,i_loop,1),cl_data.ao_residuals[25:i_loop ]]], plt_obj = plt_obj) + else: + cl_plot([obj.atm.OPD, obj.tel.OPD, [np.arange(param['nModes']),np.diag(obj.gOpt)],[np.arange(i_loop),cl_data.ao_residuals[:i_loop ]]], plt_obj = plt_obj) + + if plt_obj.keep_going is False: + print('Loop stopped by the user') + break + plt.pause(0.001) + + print('Loop '+str(i_loop)+'/'+str(cl_data.nLoop)+' Turbulence: '+str(cl_data.ao_turbulence[i_loop ])+' -- Residual:' +str(cl_data.ao_residuals[i_loop ])+ '\n') + print('Elapsed Time: ' + str(b-a) + ' s ') + if i_loop in pp_index: + print('Applying the push-pull sequence!') + tmp = push_pull(obj, dm_cl, wfsSignal,i_loop,cl_data) + pp_signal.append(tmp) + + i_loop +=obj.push_pull_duration*2 -1 + print(i_loop) + + if i_loop == cl_data.nLoop-1: + keep_going = False + + push_pull_signal = np.mean(np.asarray(pp_signal), axis = 0 ) + res_OPD = np.reshape(obj.tel.OPD,obj.tel.resolution**2) + +#%% + + dataCL = dict() + dataCL['ao_residual' ] = cl_data.ao_residuals + dataCL['ao_turbulence' ] = cl_data.ao_turbulence + dataCL['dm_forces' ] = cl_data.dm_forces + dataCL['on_sky_signal' ] = push_pull_signal + dataCL['on_sky_signal_list' ] = np.asarray(pp_signal) + + dataCL['destinationFolder'] = destinationFolder + dataCL['last_residual_OPD'] = np.reshape(res_OPD,[obj.tel.resolution,obj.tel.resolution]) + + + return dataCL + + + diff --git a/AO_modules/closed_loop/run_cl_sinusoidal_modulation.py b/OOPAO/closed_loop/run_cl_sinusoidal_modulation.py similarity index 96% rename from AO_modules/closed_loop/run_cl_sinusoidal_modulation.py rename to OOPAO/closed_loop/run_cl_sinusoidal_modulation.py index 468b87a..85d9cc5 100644 --- a/AO_modules/closed_loop/run_cl_sinusoidal_modulation.py +++ b/OOPAO/closed_loop/run_cl_sinusoidal_modulation.py @@ -1,528 +1,528 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Nov 16 15:18:31 2020 - -@author: cheritie -""" - -import matplotlib.pyplot as plt -import numpy as np -import time - -# local modules -from AO_modules.calibration.CalibrationVault import CalibrationVault -from AO_modules.MisRegistration import MisRegistration -from AO_modules.tools.tools import createFolder, emptyClass -from AO_modules.mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration - - -##################################################################################################################################################### -# # # # # # # # # # # # # # # # # -# # # # # # # # # # # # # # -# # # # # # # # # # # # # # # # # # # # # -# # # # # # # # # # # # # -# # # # # # # # # # # # # # # # # # # -##################################################################################################################################################### - -def run_cl_sinusoidal_modulation(param,obj): - - - gain_cl = param['gainCL'] - - #% ------------------------------------ Noise Update ------------------------------------ - - - # set magnitude of the NGS - obj.ngs.nPhoton = param['nPhotonPerSubaperture'] * (1/obj.tel.samplingTime)/((obj.tel.D/param['nSubaperture'] )**2) - - extraName ='' - # photon Noise - obj.wfs.cam.photonNoise = param['photonNoise'] - if obj.wfs.cam.photonNoise: - extraName+=('photonNoise_') - - # RON - obj.wfs.cam.readoutNoise = param['readoutNoise'] - - if obj.wfs.cam.readoutNoise: - extraName+=('readoutNoise_') - - - #% ------------------------------------ Calibration ------------------------------------ - - - M2C_cl = obj.M2C_cl - - calib_cl = CalibrationVault(obj.calib.D[:,:param['nModes']]) - - - #% ------------------------------------ M4 Saturations ------------------------------------ - - P2F_full = np.zeros([6*892,6*892]) - try: - for i in range(6): - P2F_full[i*892:(i+1)*892,i*892:(i+1)*892] = obj.P2F - get_forces = True - print('Stiffness matrix properly attached to the ao-object!') - - except: - print('No stiffness matrix attached to the ao-object.. computation of the M4 forces not considered') - get_forces = False - #%% ------------------------------------ mis-registrations ------------------------------------ - - misRegistration_cl = MisRegistration(param) - - misRegistration_cl.show() - obj.dm.misReg.show() - -# case with no mis-registration - if misRegistration_cl == obj.dm.misReg: - print('No need to update M4') - else: - obj.dm = applyMisRegistration(obj.tel,misRegistration_cl,param) - - #%% modulation signal - - obj.n_iteration_per_mode = np.zeros(obj.n_multiplexed_modulated_modes, dtype = int) - obj.n_iteration_per_period = np.zeros(obj.n_multiplexed_modulated_modes, dtype = int) - obj.modulation_index_frequency = np.zeros(obj.n_multiplexed_modulated_modes, dtype = int) - - for i_mode in range(obj.n_multiplexed_modulated_modes): - period = 1/obj.modulation_frequency[i_mode] - n_period = obj.n_modulation_period - - - criterion = period/obj.tel.samplingTime - target = int(np.round(criterion)) - period = target*obj.tel.samplingTime - print('Mode '+str(obj.modulation_index_mode[i_mode])+' -- Requested frequency: '+str(obj.modulation_frequency[i_mode]) + ' Hz' ) - obj.modulation_frequency[i_mode] = 1/period - print('Mode '+str(obj.modulation_index_mode[i_mode])+' -- Updated frequency: '+str(obj.modulation_frequency[i_mode]) + ' Hz' ) - - criterion = period/obj.tel.samplingTime - - N_tmp = int(np.ceil(n_period*criterion)) - criteria = True - - while criteria: - u = (np.fft.fftfreq(N_tmp, d =obj.tel.samplingTime)) - if obj.modulation_frequency[i_mode] in u: - criteria = False - index_freq = list(u).index(obj.modulation_frequency[i_mode]) - print(u[index_freq]) - else: - N_tmp+=1 - print('Mode '+str(obj.modulation_index_mode[i_mode])+' -- Sinusoidal modulation sampled with '+str(criterion) + ' points per period -- total of iterations required: '+str(N_tmp)) - obj.n_iteration_per_period[i_mode] = int(criterion) - obj.n_iteration_per_mode[i_mode] = int(N_tmp) - - # TAKING THE MAXIMUM NUMBER OF ITERATION - N = int(np.max(obj.n_iteration_per_mode)) - N_real = N+obj.n_iteration_per_period[i_mode] - - obj.modulation_signal = np.zeros([obj.n_multiplexed_modulated_modes, N_real]) - obj.amp_demo_theo = np.zeros([obj.n_multiplexed_modulated_modes]) - - - t = np.linspace(0,N_real*obj.tel.samplingTime,N_real, endpoint = True) - - t_measurement = np.linspace(0,N*obj.tel.samplingTime,N_real, endpoint = True) - - obj.sin = np.sin(t_measurement*obj.modulation_frequency[i_mode]*2.0*np.pi) - obj.cos = np.cos(t_measurement*obj.modulation_frequency[i_mode]*2.0*np.pi) - - for i_mode in range(obj.n_multiplexed_modulated_modes): - u = (np.fft.fftfreq(N, d =obj.tel.samplingTime)) - index_tmp = list(u).index(obj.modulation_frequency[i_mode]) - obj.modulation_signal[i_mode,:] = (obj.modulation_amplitude[i_mode])*np.sin(t*obj.modulation_frequency[i_mode]*2.0*np.pi + obj.modulation_phase_shift[i_mode]) - - fft_theo = np.fft.fft(obj.modulation_signal[i_mode,obj.n_iteration_per_period[i_mode]:]) - obj.amp_demo_theo[i_mode] = 2.0/N_real * np.abs(fft_theo[index_tmp]) - - nLoop = N_real+param['nLoop'] - #%% ------------------------------------ closed loop data to be saved ------------------------------------ - - dm_forces = np.zeros([nLoop,obj.dm.nValidAct]) - modal_coefficients_res = np.zeros([nLoop,obj.M2C_cl.shape[1]]) - modal_coefficients_turb = np.zeros([nLoop,obj.M2C_cl.shape[1]]) - ao_residuals = np.zeros(nLoop) - ao_turbulence = np.zeros(nLoop) - petalling_nm = np.zeros([6,nLoop]) - res_OPD_buff = np.zeros([nLoop,obj.tel.resolution,obj.tel.resolution]) - # buffer for demodulations - buffer_dm = np.zeros([obj.dm.nValidAct,N]) - buffer_wfs = np.zeros([obj.wfs.nSignal,N]) - - #%% ------------------------------------ destination folder ------------------------------------ - - misregistrationFolder = misRegistration_cl.misRegName - destinationFolder = param['pathOutput']+ '/'+param['name'] +'/'+ misregistrationFolder + '/' + extraName + '/' - createFolder(destinationFolder) - - gain_cl = param['gainCL'] - - # combine with atmosphere - obj.tel+obj.atm - plt.close('all') - - # initialize DM commands - obj.dm.coefs=0 - - #%% ------------------------------------ Modal Gains ------------------------------------ - - - ModalGainsMatrix = obj.gOpt - - # Loop initialization -# reconstructor = np.matmul(M2C_cl,np.matmul(ModalGainsMatrix,calib_cl.M)) - reconstructor = np.matmul(ModalGainsMatrix,calib_cl.M) - - - #%% - # propagate through th whole system - obj.ngs*obj.tel*obj.dm*obj.wfs - - # setup the display - if obj.display: - from AO_modules.tools.displayTools import cl_plot - plt.close('all') - list_fig = [obj.atm.OPD, obj.tel.OPD, [np.arange(param['nModes']),np.diag(obj.gOpt)],[np.arange(nLoop),ao_residuals]] - - type_fig = ['imshow','imshow','plot','plot'] - - list_title = ['atm OPD [m]','tel OPD [m]','Optical Gains','WFE [nm]'] - - plt_obj = cl_plot(list_fig,plt_obj = None, type_fig = type_fig, fig_number = 20, list_ratio = [[1,1,1],[1,1]], list_title = list_title) - - - if obj.displayPetals: - if obj.dm.isM4: - plt.figure(80) - ax_p0 = plt.subplot(2,3,1) - p = obj.tel.getPetalOPD(0) - pet_0 = ax_p0.imshow(p) - plt.colorbar(pet_0) - plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') - - ax_p1 = plt.subplot(2,3,2) - p = obj.tel.getPetalOPD(1) - pet_1 = ax_p1.imshow(p) - plt.colorbar(pet_1) - plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') - - ax_p2 = plt.subplot(2,3,3) - p = obj.tel.getPetalOPD(2) - pet_2 = ax_p2.imshow(p) - plt.colorbar(pet_2) - plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') - - ax_p3 = plt.subplot(2,3,4) - p = obj.tel.getPetalOPD(3) - pet_3 = ax_p3.imshow(p) - plt.colorbar(pet_3) - plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') - - ax_p4 = plt.subplot(2,3,5) - p = obj.tel.getPetalOPD(4) - pet_4 = ax_p4.imshow(p) - plt.colorbar(pet_4) - plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') - - ax_p5 = plt.subplot(2,3,6) - p = obj.tel.getPetalOPD(5) - pet_5 = ax_p5.imshow(p) - plt.colorbar(pet_5) - plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') - - -#%% - wfsSignal = np.zeros(obj.wfs.nSignal) - count = -1 - count_bootstrap = -1 - - - for i_loop in range(nLoop): - a= time.time() - # update atmospheric phase screen - obj.atm.update() - - # save phase variance - ao_turbulence[i_loop]=np.std(obj.tel.OPD[np.where(obj.tel.pupil>0)])*1e9 - - # save turbulent phase - turbPhase=obj.tel.src.phase - turb_OPD = np.reshape(obj.tel.OPD,obj.tel.resolution**2) - - try: - modal_coefficients_turb[i_loop,:] = obj.projector @ turb_OPD - save_modal_coef = True - except: - save_modal_coef = False - if i_loop == 0: - print('Error - no projector for the modal basis..') - - - # apply the modulation of the modes - if i_loop>=param['nLoop']: - count_bootstrap+=1 - print('Applying sinusoidal perturbation at '+str(obj.modulation_frequency[i_mode]) + ' Hz') - - if count_bootstrap>= obj.n_iteration_per_period[0]: - count+=1 - dm_perturbation_in = np.zeros(obj.dm.nValidAct) - if count_bootstrap==0: - dm_perturbation_out = np.zeros(obj.dm.nValidAct) - - for i_mode in range(obj.n_multiplexed_modulated_modes): - dm_perturbation_in += obj.modulation_M2C[:,obj.modulation_index_mode[i_mode]]*obj.modulation_signal[i_mode,count_bootstrap] - - obj.dm.coefs+= dm_perturbation_in - # propagate to the WFS with the CL commands applied - obj.tel*obj.dm*obj.wfs - - if i_loop == param['nLoop']-1: - res_OPD_to_save = obj.tel.OPD - - if i_loop>=param['nLoop']: - - if count_bootstrap>= obj.n_iteration_per_period[0]: - buffer_dm[:,count] = obj.dm.coefs - - res_OPD_buff[i_loop,:,:] = obj.tel.OPD.copy() - obj.dm.coefs=obj.dm.coefs-gain_cl*np.matmul(M2C_cl,np.matmul(reconstructor,wfsSignal)) - - if get_forces: - dm_forces[i_loop,:] = P2F_full@obj.dm.coefs - - - if i_loop>=param['nLoop']: - if count_bootstrap>= obj.n_iteration_per_period[0]: - print('Acquirring WFS signal') - buffer_wfs[:,count] = obj.wfs.signal - dm_perturbation_out = np.zeros(obj.dm.nValidAct) - - for i_mode in range(obj.n_multiplexed_modulated_modes): - dm_perturbation_out -= obj.modulation_M2C[:,obj.modulation_index_mode[i_mode]]*obj.modulation_signal[i_mode,count_bootstrap]*obj.modulation_OG[i_mode] - obj.dm.coefs+= dm_perturbation_out - - if obj.tel.isPetalFree: - obj.dm.OPD = obj.tel.removePetalling(obj.dm.OPD) - ao_residuals[i_loop]=np.std(obj.tel.OPD[np.where(obj.tel.pupil>0)])*1e9 - - # store the slopes after computing the commands => 2 frames delay - wfsSignal=obj.wfs.signal - b= time.time() - - res_OPD = np.reshape(obj.tel.OPD,obj.tel.resolution**2) - try: - modal_coefficients_res[i_loop,:] = obj.projector @ res_OPD - except: - if i_loop==0: - print('Error - no projector for the modal basis..') - - if obj.display==True: - if i_loop>1: - if i_loop >25000: - cl_plot([obj.atm.OPD, obj.tel.OPD, [np.arange(param['nModes']),np.diag(obj.gOpt)],[np.arange(25,i_loop,1),ao_residuals[25:i_loop]]], plt_obj = plt_obj) - else: - cl_plot([obj.atm.OPD, obj.tel.OPD, [np.arange(param['nModes']),np.diag(obj.gOpt)],[np.arange(i_loop),ao_residuals[:i_loop]]], plt_obj = plt_obj) - - if plt_obj.keep_going is False: - print('Loop stopped by the user') - break - plt.pause(0.001) - - if obj.displayPetals: - if obj.dm.isM4: - p = obj.tel.getPetalOPD(0)*1e9 - pet_0.set_data(p) - pet_0.set_clim(vmin=p.min(),vmax=p.max()) - ax_p0.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') - - p = obj.tel.getPetalOPD(1)*1e9 - pet_1.set_data(p) - pet_1.set_clim(vmin=p.min(),vmax=p.max()) - ax_p1.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') - - p = obj.tel.getPetalOPD(2)*1e9 - pet_2.set_data(p) - pet_2.set_clim(vmin=p.min(),vmax=p.max()) - ax_p2.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') - - p = obj.tel.getPetalOPD(3)*1e9 - pet_3.set_data(p) - pet_3.set_clim(vmin=p.min(),vmax=p.max()) - ax_p3.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') - - p = obj.tel.getPetalOPD(4)*1e9 - pet_4.set_data(p) - pet_4.set_clim(vmin=p.min(),vmax=p.max()) - ax_p4.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') - - - p = obj.tel.getPetalOPD(5)*1e9 - pet_5.set_data(p) - pet_5.set_clim(vmin=p.min(),vmax=p.max()) - ax_p5.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') - - plt.draw() - plt.show() - plt.pause(0.001) - if obj.printPetals: - if obj.dm.isM4: - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - # remove mean OPD - obj.tel.OPD[np.where(obj.tel.pupil == 1)] -= np.mean(obj.tel.OPD[np.where(obj.tel.pupil == 1)]) - for i_petal in range(6): - petal = obj.tel.getPetalOPD(i_petal)*1e9 - petal_nm = np.round(np.mean(petal[np.where(obj.tel.petalMask == 1)]),5) - print('Petal '+str(i_petal+1) +': ' + str(petal_nm)) - petalling_nm[i_petal,i_loop] = petal_nm - - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - - print('Loop '+str(i_loop)+'/'+str(nLoop)+' Turbulence: '+str(ao_turbulence[i_loop])+' -- Residual:' +str(ao_residuals[i_loop])+ '\n') - print('Elapsed Time: ' + str(b-a) + ' s ') - - - - -#%% DEMODULATION AMPLITUDE - - modal_coefs = np.linalg.pinv(obj.modulation_M2C)@buffer_dm - - - -#%% SLOPESMAPS DEMODULATION - from joblib import Parallel,delayed - - - - def demodulate_function(obj, buffer_wfs, modal_coefs, i_mode, n_period): - # test that the n_period can be requested - N_requested = int((n_period) *obj.n_iteration_per_period[i_mode]) - - criteria = True - - while criteria: - u = (np.fft.fftfreq(N_requested, d =obj.tel.samplingTime)) - if obj.modulation_frequency[i_mode] in u: - criteria = False - index_freq_temp = list(u).index(obj.modulation_frequency[i_mode]) - else: - N_requested+=1 - - print('number of period condsidered: ' + str(N_requested/obj.n_iteration_per_period[i_mode])) - - - def demodulate(temporal_series_wfs): - - y_f = np.fft.fft(temporal_series_wfs[:N_requested]) - - y_f_mod = np.abs(y_f) - - y_f_phi = np.arctan2(np.real(y_f[index_freq_temp]),np.imag(y_f[index_freq_temp])) - - amp = 2.*y_f_mod[index_freq_temp]/N_requested - - return amp, y_f_phi - - fft_modal_coefs = np.fft.fft(modal_coefs[obj.modulation_index_mode[i_mode],:N_requested]) - - - # start = len(obj.modulation_signal[obj.modulation_index_mode[i_mode]])-N_requested - - - fft_frequency_vector = np.fft.fftfreq(len(fft_modal_coefs), d =obj.tel.samplingTime) - - tmp_demodulated_amplitude = 2.0/N_requested * np.abs(fft_modal_coefs[index_freq_temp]) - - # tmp_demodulated_amplitude = np.abs(fft_modal_coefs[index_freq_temp])/np.abs(fft_theo[index_freq_temp]) - - - def job_loop_demodulate(): - Q = Parallel(n_jobs=4,prefer='threads')(delayed(demodulate)(i) for i in (buffer_wfs[:,:N_requested])) - return Q - - maps = np.asarray(job_loop_demodulate()) - - tmp_demodulated_phase = maps[:,1] - tmp_demodulated_wfs_signal = (maps[:,0]*(np.sign(np.sin(tmp_demodulated_phase))))/tmp_demodulated_amplitude - - return tmp_demodulated_phase,tmp_demodulated_wfs_signal, tmp_demodulated_amplitude, N_requested, fft_modal_coefs,fft_frequency_vector - - demodulated_phase = np.zeros([obj.n_multiplexed_modulated_modes,obj.wfs.nSignal]) - demodulated_wfs_signal = np.zeros([obj.n_multiplexed_modulated_modes,obj.wfs.nSignal]) - demodulated_amplitude = np.zeros([obj.n_multiplexed_modulated_modes]) - - fft_frequency_vector = [] - fft_modal_coefs = [] - for i_mode in range(obj.n_multiplexed_modulated_modes): - tmp_demodulated_phase,tmp_demodulated_wfs_signal, tmp_demodulated_amplitude,N,tmp_fft_modal_coefs,tmp_fft_frequency_vector= demodulate_function(obj, buffer_wfs, modal_coefs,i_mode, n_period = obj.n_modulation_period) - - demodulated_phase[i_mode,:] = tmp_demodulated_phase - demodulated_wfs_signal[i_mode,:] = tmp_demodulated_wfs_signal - demodulated_amplitude[i_mode] = tmp_demodulated_amplitude - fft_frequency_vector.append(tmp_fft_frequency_vector) - fft_modal_coefs.append(tmp_fft_modal_coefs) - - - -#%% - - dataCL = dict() - dataCL['ao_residual' ] = ao_residuals - dataCL['ao_turbulence' ] = ao_turbulence - dataCL['demodulate_function' ] = demodulate_function - dataCL['modulation_signal' ] = obj.modulation_signal - dataCL['modulation_frequency' ] = obj.modulation_frequency - - dataCL['demodulated_phase' ] = demodulated_phase - dataCL['demodulated_amplitude' ] = demodulated_amplitude - dataCL['demodulated_amplitude_theo' ] = obj.amp_demo_theo - dataCL['demodulated_amplitude_ratio'] = demodulated_amplitude / obj.amp_demo_theo - - dataCL['demodulated_wfs_signal' ] = demodulated_wfs_signal - dataCL['fft_modal_coefs' ] = fft_modal_coefs - dataCL['fft_frequency_vector' ] = fft_frequency_vector - # dataCL['dm_forces'] = dm_forces - # dataCL['wfs_signals'] = buffer_wfs - dataCL['modal_coeff'] = modal_coefs - # dataCL['dm_commands'] = buffer_dm - - - try: - if obj.save_residual_phase: - dataCL['residual_phase'] = res_OPD_buff - except: - dataCL['residual_phase'] = None - - - - if save_modal_coef: - dataCL['modal_coeff_res'] = modal_coefficients_res - dataCL['modal_coeff_turb'] = modal_coefficients_turb - - dataCL['parameterFile'] = param - if obj.printPetals: - dataCL['petalling'] = petalling_nm - dataCL['destinationFolder'] = destinationFolder - dataCL['last_residual_OPD'] = res_OPD_to_save - - try: - if obj.perf_only: - return dataCL - else: - dataCL['wfs_signals'] = buffer_wfs - dataCL['dm_commands'] = buffer_dm - dataCL['dm_forces'] = dm_forces - return dataCL - - except: - dataCL['wfs_signals'] = buffer_wfs - dataCL['dmCommands'] = buffer_dm - # dataCL['m4_cube_map'] = np.reshape(np.sum((obj.dm.modes)**3,axis=1),[obj.tel.resolution,obj.tel.resolution]) - return dataCL - - - +# -*- coding: utf-8 -*- +""" +Created on Mon Nov 16 15:18:31 2020 + +@author: cheritie +""" + +import time + +import matplotlib.pyplot as plt +import numpy as np + +from ..MisRegistration import MisRegistration +from ..calibration.CalibrationVault import CalibrationVault +from ..mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration +from ..tools.tools import createFolder + + +##################################################################################################################################################### +# # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # +##################################################################################################################################################### + +def run_cl_sinusoidal_modulation(param,obj): + + + gain_cl = param['gainCL'] + + #% ------------------------------------ Noise Update ------------------------------------ + + + # set magnitude of the NGS + obj.ngs.nPhoton = param['nPhotonPerSubaperture'] * (1/obj.tel.samplingTime)/((obj.tel.D/param['nSubaperture'] )**2) + + extraName ='' + # photon Noise + obj.wfs.cam.photonNoise = param['photonNoise'] + if obj.wfs.cam.photonNoise: + extraName+=('photonNoise_') + + # RON + obj.wfs.cam.readoutNoise = param['readoutNoise'] + + if obj.wfs.cam.readoutNoise: + extraName+=('readoutNoise_') + + + #% ------------------------------------ Calibration ------------------------------------ + + + M2C_cl = obj.M2C_cl + + calib_cl = CalibrationVault(obj.calib.D[:,:param['nModes']]) + + + #% ------------------------------------ M4 Saturations ------------------------------------ + + P2F_full = np.zeros([6*892,6*892]) + try: + for i in range(6): + P2F_full[i*892:(i+1)*892,i*892:(i+1)*892] = obj.P2F + get_forces = True + print('Stiffness matrix properly attached to the ao-object!') + + except: + print('No stiffness matrix attached to the ao-object.. computation of the M4 forces not considered') + get_forces = False + #%% ------------------------------------ mis-registrations ------------------------------------ + + misRegistration_cl = MisRegistration(param) + + misRegistration_cl.show() + obj.dm.misReg.show() + +# case with no mis-registration + if misRegistration_cl == obj.dm.misReg: + print('No need to update M4') + else: + obj.dm = applyMisRegistration(obj.tel,misRegistration_cl,param) + + #%% modulation signal + + obj.n_iteration_per_mode = np.zeros(obj.n_multiplexed_modulated_modes, dtype = int) + obj.n_iteration_per_period = np.zeros(obj.n_multiplexed_modulated_modes, dtype = int) + obj.modulation_index_frequency = np.zeros(obj.n_multiplexed_modulated_modes, dtype = int) + + for i_mode in range(obj.n_multiplexed_modulated_modes): + period = 1/obj.modulation_frequency[i_mode] + n_period = obj.n_modulation_period + + + criterion = period/obj.tel.samplingTime + target = int(np.round(criterion)) + period = target*obj.tel.samplingTime + print('Mode '+str(obj.modulation_index_mode[i_mode])+' -- Requested frequency: '+str(obj.modulation_frequency[i_mode]) + ' Hz' ) + obj.modulation_frequency[i_mode] = 1/period + print('Mode '+str(obj.modulation_index_mode[i_mode])+' -- Updated frequency: '+str(obj.modulation_frequency[i_mode]) + ' Hz' ) + + criterion = period/obj.tel.samplingTime + + N_tmp = int(np.ceil(n_period*criterion)) + criteria = True + + while criteria: + u = (np.fft.fftfreq(N_tmp, d =obj.tel.samplingTime)) + if obj.modulation_frequency[i_mode] in u: + criteria = False + index_freq = list(u).index(obj.modulation_frequency[i_mode]) + print(u[index_freq]) + else: + N_tmp+=1 + print('Mode '+str(obj.modulation_index_mode[i_mode])+' -- Sinusoidal modulation sampled with '+str(criterion) + ' points per period -- total of iterations required: '+str(N_tmp)) + obj.n_iteration_per_period[i_mode] = int(criterion) + obj.n_iteration_per_mode[i_mode] = int(N_tmp) + + # TAKING THE MAXIMUM NUMBER OF ITERATION + N = int(np.max(obj.n_iteration_per_mode)) + N_real = N+obj.n_iteration_per_period[i_mode] + + obj.modulation_signal = np.zeros([obj.n_multiplexed_modulated_modes, N_real]) + obj.amp_demo_theo = np.zeros([obj.n_multiplexed_modulated_modes]) + + + t = np.linspace(0,N_real*obj.tel.samplingTime,N_real, endpoint = True) + + t_measurement = np.linspace(0,N*obj.tel.samplingTime,N_real, endpoint = True) + + obj.sin = np.sin(t_measurement*obj.modulation_frequency[i_mode]*2.0*np.pi) + obj.cos = np.cos(t_measurement*obj.modulation_frequency[i_mode]*2.0*np.pi) + + for i_mode in range(obj.n_multiplexed_modulated_modes): + u = (np.fft.fftfreq(N, d =obj.tel.samplingTime)) + index_tmp = list(u).index(obj.modulation_frequency[i_mode]) + obj.modulation_signal[i_mode,:] = (obj.modulation_amplitude[i_mode])*np.sin(t*obj.modulation_frequency[i_mode]*2.0*np.pi + obj.modulation_phase_shift[i_mode]) + + fft_theo = np.fft.fft(obj.modulation_signal[i_mode,obj.n_iteration_per_period[i_mode]:]) + obj.amp_demo_theo[i_mode] = 2.0/N_real * np.abs(fft_theo[index_tmp]) + + nLoop = N_real+param['nLoop'] + #%% ------------------------------------ closed loop data to be saved ------------------------------------ + + dm_forces = np.zeros([nLoop,obj.dm.nValidAct]) + modal_coefficients_res = np.zeros([nLoop,obj.M2C_cl.shape[1]]) + modal_coefficients_turb = np.zeros([nLoop,obj.M2C_cl.shape[1]]) + ao_residuals = np.zeros(nLoop) + ao_turbulence = np.zeros(nLoop) + petalling_nm = np.zeros([6,nLoop]) + res_OPD_buff = np.zeros([nLoop,obj.tel.resolution,obj.tel.resolution]) + # buffer for demodulations + buffer_dm = np.zeros([obj.dm.nValidAct,N]) + buffer_wfs = np.zeros([obj.wfs.nSignal,N]) + + #%% ------------------------------------ destination folder ------------------------------------ + + misregistrationFolder = misRegistration_cl.misRegName + destinationFolder = param['pathOutput']+ '/'+param['name'] +'/'+ misregistrationFolder + '/' + extraName + '/' + createFolder(destinationFolder) + + gain_cl = param['gainCL'] + + # combine with atmosphere + obj.tel+obj.atm + plt.close('all') + + # initialize DM commands + obj.dm.coefs=0 + + #%% ------------------------------------ Modal Gains ------------------------------------ + + + ModalGainsMatrix = obj.gOpt + + # Loop initialization +# reconstructor = np.matmul(M2C_cl,np.matmul(ModalGainsMatrix,calib_cl.M)) + reconstructor = np.matmul(ModalGainsMatrix,calib_cl.M) + + + #%% + # propagate through th whole system + obj.ngs*obj.tel*obj.dm*obj.wfs + + # setup the display + if obj.display: + from AO_modules.tools.displayTools import cl_plot + plt.close('all') + list_fig = [obj.atm.OPD, obj.tel.OPD, [np.arange(param['nModes']),np.diag(obj.gOpt)],[np.arange(nLoop),ao_residuals]] + + type_fig = ['imshow','imshow','plot','plot'] + + list_title = ['atm OPD [m]','tel OPD [m]','Optical Gains','WFE [nm]'] + + plt_obj = cl_plot(list_fig,plt_obj = None, type_fig = type_fig, fig_number = 20, list_ratio = [[1,1,1],[1,1]], list_title = list_title) + + + if obj.displayPetals: + if obj.dm.isM4: + plt.figure(80) + ax_p0 = plt.subplot(2,3,1) + p = obj.tel.getPetalOPD(0) + pet_0 = ax_p0.imshow(p) + plt.colorbar(pet_0) + plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') + + ax_p1 = plt.subplot(2,3,2) + p = obj.tel.getPetalOPD(1) + pet_1 = ax_p1.imshow(p) + plt.colorbar(pet_1) + plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') + + ax_p2 = plt.subplot(2,3,3) + p = obj.tel.getPetalOPD(2) + pet_2 = ax_p2.imshow(p) + plt.colorbar(pet_2) + plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') + + ax_p3 = plt.subplot(2,3,4) + p = obj.tel.getPetalOPD(3) + pet_3 = ax_p3.imshow(p) + plt.colorbar(pet_3) + plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') + + ax_p4 = plt.subplot(2,3,5) + p = obj.tel.getPetalOPD(4) + pet_4 = ax_p4.imshow(p) + plt.colorbar(pet_4) + plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') + + ax_p5 = plt.subplot(2,3,6) + p = obj.tel.getPetalOPD(5) + pet_5 = ax_p5.imshow(p) + plt.colorbar(pet_5) + plt.title('Petal mean value:' + str(1e9*np.mean(p[np.where(obj.tel.petalMask == 1)])) + ' nm') + + +#%% + wfsSignal = np.zeros(obj.wfs.nSignal) + count = -1 + count_bootstrap = -1 + + + for i_loop in range(nLoop): + a= time.time() + # update atmospheric phase screen + obj.atm.update() + + # save phase variance + ao_turbulence[i_loop]=np.std(obj.tel.OPD[np.where(obj.tel.pupil>0)])*1e9 + + # save turbulent phase + turbPhase=obj.tel.src.phase + turb_OPD = np.reshape(obj.tel.OPD,obj.tel.resolution**2) + + try: + modal_coefficients_turb[i_loop,:] = obj.projector @ turb_OPD + save_modal_coef = True + except: + save_modal_coef = False + if i_loop == 0: + print('Error - no projector for the modal basis..') + + + # apply the modulation of the modes + if i_loop>=param['nLoop']: + count_bootstrap+=1 + print('Applying sinusoidal perturbation at '+str(obj.modulation_frequency[i_mode]) + ' Hz') + + if count_bootstrap>= obj.n_iteration_per_period[0]: + count+=1 + dm_perturbation_in = np.zeros(obj.dm.nValidAct) + if count_bootstrap==0: + dm_perturbation_out = np.zeros(obj.dm.nValidAct) + + for i_mode in range(obj.n_multiplexed_modulated_modes): + dm_perturbation_in += obj.modulation_M2C[:,obj.modulation_index_mode[i_mode]]*obj.modulation_signal[i_mode,count_bootstrap] + + obj.dm.coefs+= dm_perturbation_in + # propagate to the WFS with the CL commands applied + obj.tel*obj.dm*obj.wfs + + if i_loop == param['nLoop']-1: + res_OPD_to_save = obj.tel.OPD + + if i_loop>=param['nLoop']: + + if count_bootstrap>= obj.n_iteration_per_period[0]: + buffer_dm[:,count] = obj.dm.coefs + + res_OPD_buff[i_loop,:,:] = obj.tel.OPD.copy() + obj.dm.coefs=obj.dm.coefs-gain_cl*np.matmul(M2C_cl,np.matmul(reconstructor,wfsSignal)) + + if get_forces: + dm_forces[i_loop,:] = P2F_full@obj.dm.coefs + + + if i_loop>=param['nLoop']: + if count_bootstrap>= obj.n_iteration_per_period[0]: + print('Acquirring WFS signal') + buffer_wfs[:,count] = obj.wfs.signal + dm_perturbation_out = np.zeros(obj.dm.nValidAct) + + for i_mode in range(obj.n_multiplexed_modulated_modes): + dm_perturbation_out -= obj.modulation_M2C[:,obj.modulation_index_mode[i_mode]]*obj.modulation_signal[i_mode,count_bootstrap]*obj.modulation_OG[i_mode] + obj.dm.coefs+= dm_perturbation_out + + if obj.tel.isPetalFree: + obj.dm.OPD = obj.tel.removePetalling(obj.dm.OPD) + ao_residuals[i_loop]=np.std(obj.tel.OPD[np.where(obj.tel.pupil>0)])*1e9 + + # store the slopes after computing the commands => 2 frames delay + wfsSignal=obj.wfs.signal + b= time.time() + + res_OPD = np.reshape(obj.tel.OPD,obj.tel.resolution**2) + try: + modal_coefficients_res[i_loop,:] = obj.projector @ res_OPD + except: + if i_loop==0: + print('Error - no projector for the modal basis..') + + if obj.display==True: + if i_loop>1: + if i_loop >25000: + cl_plot([obj.atm.OPD, obj.tel.OPD, [np.arange(param['nModes']),np.diag(obj.gOpt)],[np.arange(25,i_loop,1),ao_residuals[25:i_loop]]], plt_obj = plt_obj) + else: + cl_plot([obj.atm.OPD, obj.tel.OPD, [np.arange(param['nModes']),np.diag(obj.gOpt)],[np.arange(i_loop),ao_residuals[:i_loop]]], plt_obj = plt_obj) + + if plt_obj.keep_going is False: + print('Loop stopped by the user') + break + plt.pause(0.001) + + if obj.displayPetals: + if obj.dm.isM4: + p = obj.tel.getPetalOPD(0)*1e9 + pet_0.set_data(p) + pet_0.set_clim(vmin=p.min(),vmax=p.max()) + ax_p0.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') + + p = obj.tel.getPetalOPD(1)*1e9 + pet_1.set_data(p) + pet_1.set_clim(vmin=p.min(),vmax=p.max()) + ax_p1.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') + + p = obj.tel.getPetalOPD(2)*1e9 + pet_2.set_data(p) + pet_2.set_clim(vmin=p.min(),vmax=p.max()) + ax_p2.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') + + p = obj.tel.getPetalOPD(3)*1e9 + pet_3.set_data(p) + pet_3.set_clim(vmin=p.min(),vmax=p.max()) + ax_p3.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') + + p = obj.tel.getPetalOPD(4)*1e9 + pet_4.set_data(p) + pet_4.set_clim(vmin=p.min(),vmax=p.max()) + ax_p4.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') + + + p = obj.tel.getPetalOPD(5)*1e9 + pet_5.set_data(p) + pet_5.set_clim(vmin=p.min(),vmax=p.max()) + ax_p5.set_title('Petal mean value:' + str(np.round(np.mean(p[np.where(obj.tel.petalMask == 1)]),5)) + ' nm') + + plt.draw() + plt.show() + plt.pause(0.001) + if obj.printPetals: + if obj.dm.isM4: + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + # remove mean OPD + obj.tel.OPD[np.where(obj.tel.pupil == 1)] -= np.mean(obj.tel.OPD[np.where(obj.tel.pupil == 1)]) + for i_petal in range(6): + petal = obj.tel.getPetalOPD(i_petal)*1e9 + petal_nm = np.round(np.mean(petal[np.where(obj.tel.petalMask == 1)]),5) + print('Petal '+str(i_petal+1) +': ' + str(petal_nm)) + petalling_nm[i_petal,i_loop] = petal_nm + + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + + print('Loop '+str(i_loop)+'/'+str(nLoop)+' Turbulence: '+str(ao_turbulence[i_loop])+' -- Residual:' +str(ao_residuals[i_loop])+ '\n') + print('Elapsed Time: ' + str(b-a) + ' s ') + + + + +#%% DEMODULATION AMPLITUDE + + modal_coefs = np.linalg.pinv(obj.modulation_M2C)@buffer_dm + + + +#%% SLOPESMAPS DEMODULATION + from joblib import Parallel,delayed + + + + def demodulate_function(obj, buffer_wfs, modal_coefs, i_mode, n_period): + # test that the n_period can be requested + N_requested = int((n_period) *obj.n_iteration_per_period[i_mode]) + + criteria = True + + while criteria: + u = (np.fft.fftfreq(N_requested, d =obj.tel.samplingTime)) + if obj.modulation_frequency[i_mode] in u: + criteria = False + index_freq_temp = list(u).index(obj.modulation_frequency[i_mode]) + else: + N_requested+=1 + + print('number of period condsidered: ' + str(N_requested/obj.n_iteration_per_period[i_mode])) + + + def demodulate(temporal_series_wfs): + + y_f = np.fft.fft(temporal_series_wfs[:N_requested]) + + y_f_mod = np.abs(y_f) + + y_f_phi = np.arctan2(np.real(y_f[index_freq_temp]),np.imag(y_f[index_freq_temp])) + + amp = 2.*y_f_mod[index_freq_temp]/N_requested + + return amp, y_f_phi + + fft_modal_coefs = np.fft.fft(modal_coefs[obj.modulation_index_mode[i_mode],:N_requested]) + + + # start = len(obj.modulation_signal[obj.modulation_index_mode[i_mode]])-N_requested + + + fft_frequency_vector = np.fft.fftfreq(len(fft_modal_coefs), d =obj.tel.samplingTime) + + tmp_demodulated_amplitude = 2.0/N_requested * np.abs(fft_modal_coefs[index_freq_temp]) + + # tmp_demodulated_amplitude = np.abs(fft_modal_coefs[index_freq_temp])/np.abs(fft_theo[index_freq_temp]) + + + def job_loop_demodulate(): + Q = Parallel(n_jobs=4,prefer='threads')(delayed(demodulate)(i) for i in (buffer_wfs[:,:N_requested])) + return Q + + maps = np.asarray(job_loop_demodulate()) + + tmp_demodulated_phase = maps[:,1] + tmp_demodulated_wfs_signal = (maps[:,0]*(np.sign(np.sin(tmp_demodulated_phase))))/tmp_demodulated_amplitude + + return tmp_demodulated_phase,tmp_demodulated_wfs_signal, tmp_demodulated_amplitude, N_requested, fft_modal_coefs,fft_frequency_vector + + demodulated_phase = np.zeros([obj.n_multiplexed_modulated_modes,obj.wfs.nSignal]) + demodulated_wfs_signal = np.zeros([obj.n_multiplexed_modulated_modes,obj.wfs.nSignal]) + demodulated_amplitude = np.zeros([obj.n_multiplexed_modulated_modes]) + + fft_frequency_vector = [] + fft_modal_coefs = [] + for i_mode in range(obj.n_multiplexed_modulated_modes): + tmp_demodulated_phase,tmp_demodulated_wfs_signal, tmp_demodulated_amplitude,N,tmp_fft_modal_coefs,tmp_fft_frequency_vector= demodulate_function(obj, buffer_wfs, modal_coefs,i_mode, n_period = obj.n_modulation_period) + + demodulated_phase[i_mode,:] = tmp_demodulated_phase + demodulated_wfs_signal[i_mode,:] = tmp_demodulated_wfs_signal + demodulated_amplitude[i_mode] = tmp_demodulated_amplitude + fft_frequency_vector.append(tmp_fft_frequency_vector) + fft_modal_coefs.append(tmp_fft_modal_coefs) + + + +#%% + + dataCL = dict() + dataCL['ao_residual' ] = ao_residuals + dataCL['ao_turbulence' ] = ao_turbulence + dataCL['demodulate_function' ] = demodulate_function + dataCL['modulation_signal' ] = obj.modulation_signal + dataCL['modulation_frequency' ] = obj.modulation_frequency + + dataCL['demodulated_phase' ] = demodulated_phase + dataCL['demodulated_amplitude' ] = demodulated_amplitude + dataCL['demodulated_amplitude_theo' ] = obj.amp_demo_theo + dataCL['demodulated_amplitude_ratio'] = demodulated_amplitude / obj.amp_demo_theo + + dataCL['demodulated_wfs_signal' ] = demodulated_wfs_signal + dataCL['fft_modal_coefs' ] = fft_modal_coefs + dataCL['fft_frequency_vector' ] = fft_frequency_vector + # dataCL['dm_forces'] = dm_forces + # dataCL['wfs_signals'] = buffer_wfs + dataCL['modal_coeff'] = modal_coefs + # dataCL['dm_commands'] = buffer_dm + + + try: + if obj.save_residual_phase: + dataCL['residual_phase'] = res_OPD_buff + except: + dataCL['residual_phase'] = None + + + + if save_modal_coef: + dataCL['modal_coeff_res'] = modal_coefficients_res + dataCL['modal_coeff_turb'] = modal_coefficients_turb + + dataCL['parameterFile'] = param + if obj.printPetals: + dataCL['petalling'] = petalling_nm + dataCL['destinationFolder'] = destinationFolder + dataCL['last_residual_OPD'] = res_OPD_to_save + + try: + if obj.perf_only: + return dataCL + else: + dataCL['wfs_signals'] = buffer_wfs + dataCL['dm_commands'] = buffer_dm + dataCL['dm_forces'] = dm_forces + return dataCL + + except: + dataCL['wfs_signals'] = buffer_wfs + dataCL['dmCommands'] = buffer_dm + # dataCL['m4_cube_map'] = np.reshape(np.sum((obj.dm.modes)**3,axis=1),[obj.tel.resolution,obj.tel.resolution]) + return dataCL + + + diff --git a/AO_modules/closed_loop/run_cl_two_stages.py b/OOPAO/closed_loop/run_cl_two_stages.py similarity index 92% rename from AO_modules/closed_loop/run_cl_two_stages.py rename to OOPAO/closed_loop/run_cl_two_stages.py index 21c60cf..08eb23f 100644 --- a/AO_modules/closed_loop/run_cl_two_stages.py +++ b/OOPAO/closed_loop/run_cl_two_stages.py @@ -1,250 +1,248 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Nov 16 15:18:31 2020 - -@author: cheritie -""" - -import matplotlib.pyplot as plt -import numpy as np -import time -import scipy -from astropy.io import fits as pfits -# local modules -from AO_modules.calibration.CalibrationVault import CalibrationVault -from AO_modules.MisRegistration import MisRegistration -from AO_modules.tools.tools import createFolder,write_fits -from AO_modules.mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration -from AO_modules.tools.interpolate_influence_functions import interpolate_influence_functions - - -##################################################################################################################################################### -# # # # # # # # # # # # # # # # # -# # # # # # # # # # # # # # -# # # # # # # # # # # # # # # # # # # # # -# # # # # # # # # # # # # -# # # # # # # # # # # # # # # # # # # -##################################################################################################################################################### - -def run_cl_two_stages(param,obj,speed_factor = 2,filename_phase_screen = None, extra_name = '', destination_folder = None, get_le_psf = False): - - nLoop = param['nLoop'] - gain_cl = param['gainCL'] - - #% ------------------------------------ Noise Update ------------------------------------ - # set magnitude of the NGS - # obj.ngs.nPhoton = param['nPhotonPerSubaperture'] * (1/obj.tel.samplingTime)/((obj.tel.D/param['nSubaperture'] )**2) - obj.ngs.magnitude = param['magnitude'] - obj.ngs.nPhoton = obj.ngs.zeroPoint*10**(-0.4*obj.ngs.magnitude) - - extraName ='' - # photon Noise - obj.wfs.cam.photonNoise = param['photonNoise'] - if obj.wfs.cam.photonNoise: - extraName+=('photonNoise_') - - # RON - obj.wfs.cam.readoutNoise = param['readoutNoise'] - - if obj.wfs.cam.readoutNoise: - extraName+=('readoutNoise_') - - - #% ------------------------------------ Calibration ------------------------------------ - if obj.calib.D.shape[1] != param['nModes']: - calib_cl = CalibrationVault(obj.calib.D[:,:param['nModes']]) - else: - calib_cl = obj.calib - M2C_cl = obj.M2C - - - #%% - # def interpolation_function(image): - # image = np.reshape(image,[1,image.shape[0],image.shape[1]]) - # misReg = MisRegistration() - # # parameters for the interpolation - # nRes = image.shape[1] - # pixel_size_in = obj.tel.D/nRes # pixel size in [m] of the input influence function - # resolution_out = 1152 # resolution in pixels of the input influence function - # pixel_size_out = obj.tel.D/resolution_out # resolution in pixels of the output influence function - # mis_registration = misReg # mis-registration object to apply with respect to the input influence function - # coordinates_in = np.zeros([100,2]) # coordinated in [m] of the input influence function - - # P_out,coord_out = interpolate_influence_functions(image,pixel_size_in,pixel_size_out, resolution_out, mis_registration,coordinates_in) - - # return (np.squeeze(P_out.astype(int))) - #%% ------------------------------------ mis-registrations ------------------------------------ - - misRegistration_cl = MisRegistration(param) - - # misRegistration_cl.show() - # obj.dm.misReg.show() - -# case with no mis-registration - if misRegistration_cl == obj.dm.misReg: - dm_cl = obj.dm - else: - dm_cl = applyMisRegistration(obj.tel,misRegistration_cl,param) - - #%% ------------------------------------ closed loop data to be saved ------------------------------------ - - wfs_signals = np.zeros([nLoop,obj.wfs.nSignal]) - dm_commands = np.zeros([nLoop,obj.dm.nValidAct]) - ao_residuals = np.zeros(nLoop) - ao_turbulence = np.zeros(nLoop) - residual_phase_screen = np.zeros([nLoop,obj.tel.resolution,obj.tel.resolution],dtype=np.float32) - - #%% ------------------------------------ destination folder ------------------------------------ - - misregistrationFolder = misRegistration_cl.misRegName - if destination_folder is None: - destination_folder = param['pathOutput']+ '/'+param['name'] +'/'+obj.wfs.tag + '/' - createFolder(destination_folder) - - gain_cl = param['gainCL'] - - # combine with atmosphere - obj.tel_fast+obj.atm_fast - plt.close('all') - - # initialize DM commands - dm_cl.coefs=0 - - #%% ------------------------------------ Modal Gains ------------------------------------ - - ModalGainsMatrix = obj.gOpt - - # Loop initialization - reconstructor = np.matmul(M2C_cl,np.matmul(ModalGainsMatrix,calib_cl.M)) - - - #%% - # propagate through th whole system - obj.ngs*obj.tel*dm_cl*obj.wfs - - obj.ngs*obj.tel_fast*dm_cl - - # setup the display - if obj.display: - plt.figure(79) - plt.ion() - - ax1=plt.subplot(1,3,1) - im_atm = ax1.imshow(obj.tel.src.phase) - plt.colorbar(im_atm,fraction=0.046, pad=0.04) - plt.title('Turbulence OPD [nm]') - - - ax2=plt.subplot(1,3,2) - im_residual_1 = ax2.imshow(obj.tel.src.phase) - plt.colorbar(im_residual_1,fraction=0.046, pad=0.04) - plt.title('Residual OPD First stage [nm]') - - ax3=plt.subplot(1,3,3) - im_residual_2 = ax3.imshow(obj.tel.src.phase) - plt.colorbar(im_residual_2,fraction=0.046, pad=0.04) - plt.title('Intermediate OPD [nm]') - -#%% - wfsSignal = np.zeros(obj.wfs.nSignal) - - OPD_buffer = [] - obj.tel_fast.isPaired = True - obj.tel.isPaired = True - OPD_first_stage_res = obj.atm_fast.OPD - - PSF_LE = [] - - for i_loop in range(nLoop): - - a= time.time() - # update atmospheric phase screen - obj.atm_fast.update() - - # skipping the first phase screen - if i_loop>0: - OPD_buffer.append(obj.atm_fast.OPD_no_pupil) - - # save phase variance - ao_turbulence[i_loop]=np.std(obj.atm_fast.OPD[np.where(obj.tel.pupil>0)])*1e9 - - # save turbulent phase - OPD_turb = obj.tel_fast.OPD.copy() - - if len(OPD_buffer)==speed_factor: - OPD_first_stage = np.mean(OPD_buffer,axis=0) - obj.tel.OPD_no_pupil = OPD_first_stage.copy() - obj.tel.OPD = OPD_first_stage.copy()*obj.tel.pupil - - # propagate to the WFS with the CL commands applied - obj.tel*dm_cl*obj.wfs - - OPD_first_stage_res = obj.tel.OPD.copy() - # reinitialize phase buffer - OPD_buffer = [] - - dm_cl.coefs = dm_cl.coefs-gain_cl*np.matmul(reconstructor,wfsSignal) - wfsSignal = obj.wfs.signal - if get_le_psf: - if i_loop >100: - obj.tel.computePSF(zeroPaddingFactor = 4) - PSF_LE.append(obj.tel.PSF) - - # else: - obj.tel_fast*dm_cl - - ao_residuals[i_loop]=np.std(obj.tel_fast.OPD[np.where(obj.tel.pupil>0)])*1e9 - - b= time.time() - - mean_rem_OPD = obj.tel_fast.OPD.copy() - mean_rem_OPD[np.where(obj.tel.pupil>0)] -= np.mean(mean_rem_OPD[np.where(obj.tel.pupil>0)]) - - residual_phase_screen[i_loop,:,:] = mean_rem_OPD - dm_commands[i_loop,:] = dm_cl.coefs.copy() - wfs_signals[i_loop,:] = wfsSignal.copy() - - if obj.display==True: - # turbulence phase - im_atm.set_data(OPD_turb) - im_atm.set_clim(vmin=OPD_turb.min(),vmax=OPD_turb.max()) - - # residual phase - tmp = obj.tel_fast.OPD.copy() - # tmp-=np.mean(tmp[obj.tel.pupil]) - im_residual_2.set_data(tmp) - im_residual_2.set_clim(vmin=tmp.min(),vmax=tmp.max()) - - tmp = OPD_first_stage_res - # tmp-=np.mean(tmp[obj.tel.pupil]) - im_residual_1.set_data(tmp) - im_residual_1.set_clim(vmin=tmp.min(),vmax=tmp.max()) - plt.draw() - - plt.show() - plt.pause(0.001) - - print('Loop'+str(i_loop)+'/'+str(nLoop)+'Turbulence: '+str(ao_turbulence[i_loop])+' -- Residual:' +str(ao_residuals[i_loop])+ '\n') - print('Elapsed Time: ' + str(b-a) + ' s ') - - dataCL = dict() - dataCL['ao_residual'] = ao_residuals - dataCL['ao_turbulence'] = ao_turbulence - dataCL['parameterFile'] = param - dataCL['destination_folder'] = destination_folder - dataCL['long_exposure_psf'] = np.mean(np.asarray(PSF_LE),axis =0) - - if filename_phase_screen is None: - filename_phase_screen = 'seeing_'+str(np.round(obj.atm.seeingArcsec,2))+'_magnitude_'+str(np.round(obj.ngs.magnitude))+\ - '_nKL_'+str(param['nModes'])+'_nLoop_'+str(param['nLoop'])+'_windspeed_'+str(np.round(np.mean(obj.atm_fast.windSpeed)))+\ - '_stg1_'+str(np.round(0.001/obj.tel.samplingTime,2))+'_kHz_stg2_'+str(np.round(0.001/obj.tel_fast.samplingTime,2))+'_kHz' - - dataCL['filename'] = filename_phase_screen+extra_name - dataCL['dm_commands'] = dm_commands - dataCL['residual_phase_screen'] = residual_phase_screen - dataCL['wfs_signals'] = wfs_signals - - return dataCL - - - +# -*- coding: utf-8 -*- +""" +Created on Mon Nov 16 15:18:31 2020 + +@author: cheritie +""" + +import time + +import matplotlib.pyplot as plt +import numpy as np + +from ..MisRegistration import MisRegistration +from ..calibration.CalibrationVault import CalibrationVault +from ..mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration +from ..tools.tools import createFolder + + +##################################################################################################################################################### +# # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # +##################################################################################################################################################### + +def run_cl_two_stages(param,obj,speed_factor = 2,filename_phase_screen = None, extra_name = '', destination_folder = None, get_le_psf = False): + + nLoop = param['nLoop'] + gain_cl = param['gainCL'] + + #% ------------------------------------ Noise Update ------------------------------------ + # set magnitude of the NGS + # obj.ngs.nPhoton = param['nPhotonPerSubaperture'] * (1/obj.tel.samplingTime)/((obj.tel.D/param['nSubaperture'] )**2) + obj.ngs.magnitude = param['magnitude'] + obj.ngs.nPhoton = obj.ngs.zeroPoint*10**(-0.4*obj.ngs.magnitude) + + extraName ='' + # photon Noise + obj.wfs.cam.photonNoise = param['photonNoise'] + if obj.wfs.cam.photonNoise: + extraName+=('photonNoise_') + + # RON + obj.wfs.cam.readoutNoise = param['readoutNoise'] + + if obj.wfs.cam.readoutNoise: + extraName+=('readoutNoise_') + + + #% ------------------------------------ Calibration ------------------------------------ + if obj.calib.D.shape[1] != param['nModes']: + calib_cl = CalibrationVault(obj.calib.D[:,:param['nModes']]) + else: + calib_cl = obj.calib + M2C_cl = obj.M2C + + + #%% + # def interpolation_function(image): + # image = np.reshape(image,[1,image.shape[0],image.shape[1]]) + # misReg = MisRegistration() + # # parameters for the interpolation + # nRes = image.shape[1] + # pixel_size_in = obj.tel.D/nRes # pixel size in [m] of the input influence function + # resolution_out = 1152 # resolution in pixels of the input influence function + # pixel_size_out = obj.tel.D/resolution_out # resolution in pixels of the output influence function + # mis_registration = misReg # mis-registration object to apply with respect to the input influence function + # coordinates_in = np.zeros([100,2]) # coordinated in [m] of the input influence function + + # P_out,coord_out = interpolate_influence_functions(image,pixel_size_in,pixel_size_out, resolution_out, mis_registration,coordinates_in) + + # return (np.squeeze(P_out.astype(int))) + #%% ------------------------------------ mis-registrations ------------------------------------ + + misRegistration_cl = MisRegistration(param) + + # misRegistration_cl.show() + # obj.dm.misReg.show() + +# case with no mis-registration + if misRegistration_cl == obj.dm.misReg: + dm_cl = obj.dm + else: + dm_cl = applyMisRegistration(obj.tel,misRegistration_cl,param) + + #%% ------------------------------------ closed loop data to be saved ------------------------------------ + + wfs_signals = np.zeros([nLoop,obj.wfs.nSignal]) + dm_commands = np.zeros([nLoop,obj.dm.nValidAct]) + ao_residuals = np.zeros(nLoop) + ao_turbulence = np.zeros(nLoop) + residual_phase_screen = np.zeros([nLoop,obj.tel.resolution,obj.tel.resolution],dtype=np.float32) + + #%% ------------------------------------ destination folder ------------------------------------ + + misregistrationFolder = misRegistration_cl.misRegName + if destination_folder is None: + destination_folder = param['pathOutput']+ '/'+param['name'] +'/'+obj.wfs.tag + '/' + createFolder(destination_folder) + + gain_cl = param['gainCL'] + + # combine with atmosphere + obj.tel_fast+obj.atm_fast + plt.close('all') + + # initialize DM commands + dm_cl.coefs=0 + + #%% ------------------------------------ Modal Gains ------------------------------------ + + ModalGainsMatrix = obj.gOpt + + # Loop initialization + reconstructor = np.matmul(M2C_cl,np.matmul(ModalGainsMatrix,calib_cl.M)) + + + #%% + # propagate through th whole system + obj.ngs*obj.tel*dm_cl*obj.wfs + + obj.ngs*obj.tel_fast*dm_cl + + # setup the display + if obj.display: + plt.figure(79) + plt.ion() + + ax1=plt.subplot(1,3,1) + im_atm = ax1.imshow(obj.tel.src.phase) + plt.colorbar(im_atm,fraction=0.046, pad=0.04) + plt.title('Turbulence OPD [nm]') + + + ax2=plt.subplot(1,3,2) + im_residual_1 = ax2.imshow(obj.tel.src.phase) + plt.colorbar(im_residual_1,fraction=0.046, pad=0.04) + plt.title('Residual OPD First stage [nm]') + + ax3=plt.subplot(1,3,3) + im_residual_2 = ax3.imshow(obj.tel.src.phase) + plt.colorbar(im_residual_2,fraction=0.046, pad=0.04) + plt.title('Intermediate OPD [nm]') + +#%% + wfsSignal = np.zeros(obj.wfs.nSignal) + + OPD_buffer = [] + obj.tel_fast.isPaired = True + obj.tel.isPaired = True + OPD_first_stage_res = obj.atm_fast.OPD + + PSF_LE = [] + + for i_loop in range(nLoop): + + a= time.time() + # update atmospheric phase screen + obj.atm_fast.update() + + # skipping the first phase screen + if i_loop>0: + OPD_buffer.append(obj.atm_fast.OPD_no_pupil) + + # save phase variance + ao_turbulence[i_loop]=np.std(obj.atm_fast.OPD[np.where(obj.tel.pupil>0)])*1e9 + + # save turbulent phase + OPD_turb = obj.tel_fast.OPD.copy() + + if len(OPD_buffer)==speed_factor: + OPD_first_stage = np.mean(OPD_buffer,axis=0) + obj.tel.OPD_no_pupil = OPD_first_stage.copy() + obj.tel.OPD = OPD_first_stage.copy()*obj.tel.pupil + + # propagate to the WFS with the CL commands applied + obj.tel*dm_cl*obj.wfs + + OPD_first_stage_res = obj.tel.OPD.copy() + # reinitialize phase buffer + OPD_buffer = [] + + dm_cl.coefs = dm_cl.coefs-gain_cl*np.matmul(reconstructor,wfsSignal) + wfsSignal = obj.wfs.signal + if get_le_psf: + if i_loop >100: + obj.tel.computePSF(zeroPaddingFactor = 4) + PSF_LE.append(obj.tel.PSF) + + # else: + obj.tel_fast*dm_cl + + ao_residuals[i_loop]=np.std(obj.tel_fast.OPD[np.where(obj.tel.pupil>0)])*1e9 + + b= time.time() + + mean_rem_OPD = obj.tel_fast.OPD.copy() + mean_rem_OPD[np.where(obj.tel.pupil>0)] -= np.mean(mean_rem_OPD[np.where(obj.tel.pupil>0)]) + + residual_phase_screen[i_loop,:,:] = mean_rem_OPD + dm_commands[i_loop,:] = dm_cl.coefs.copy() + wfs_signals[i_loop,:] = wfsSignal.copy() + + if obj.display==True: + # turbulence phase + im_atm.set_data(OPD_turb) + im_atm.set_clim(vmin=OPD_turb.min(),vmax=OPD_turb.max()) + + # residual phase + tmp = obj.tel_fast.OPD.copy() + # tmp-=np.mean(tmp[obj.tel.pupil]) + im_residual_2.set_data(tmp) + im_residual_2.set_clim(vmin=tmp.min(),vmax=tmp.max()) + + tmp = OPD_first_stage_res + # tmp-=np.mean(tmp[obj.tel.pupil]) + im_residual_1.set_data(tmp) + im_residual_1.set_clim(vmin=tmp.min(),vmax=tmp.max()) + plt.draw() + + plt.show() + plt.pause(0.001) + + print('Loop'+str(i_loop)+'/'+str(nLoop)+'Turbulence: '+str(ao_turbulence[i_loop])+' -- Residual:' +str(ao_residuals[i_loop])+ '\n') + print('Elapsed Time: ' + str(b-a) + ' s ') + + dataCL = dict() + dataCL['ao_residual'] = ao_residuals + dataCL['ao_turbulence'] = ao_turbulence + dataCL['parameterFile'] = param + dataCL['destination_folder'] = destination_folder + dataCL['long_exposure_psf'] = np.mean(np.asarray(PSF_LE),axis =0) + + if filename_phase_screen is None: + filename_phase_screen = 'seeing_'+str(np.round(obj.atm.seeingArcsec,2))+'_magnitude_'+str(np.round(obj.ngs.magnitude))+\ + '_nKL_'+str(param['nModes'])+'_nLoop_'+str(param['nLoop'])+'_windspeed_'+str(np.round(np.mean(obj.atm_fast.windSpeed)))+\ + '_stg1_'+str(np.round(0.001/obj.tel.samplingTime,2))+'_kHz_stg2_'+str(np.round(0.001/obj.tel_fast.samplingTime,2))+'_kHz' + + dataCL['filename'] = filename_phase_screen+extra_name + dataCL['dm_commands'] = dm_commands + dataCL['residual_phase_screen'] = residual_phase_screen + dataCL['wfs_signals'] = wfs_signals + + return dataCL + + + diff --git a/AO_modules/closed_loop/run_cl_two_stages_atm_change.py b/OOPAO/closed_loop/run_cl_two_stages_atm_change.py similarity index 93% rename from AO_modules/closed_loop/run_cl_two_stages_atm_change.py rename to OOPAO/closed_loop/run_cl_two_stages_atm_change.py index 407ce1a..dcbfa6f 100644 --- a/AO_modules/closed_loop/run_cl_two_stages_atm_change.py +++ b/OOPAO/closed_loop/run_cl_two_stages_atm_change.py @@ -1,302 +1,300 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Jul 11 11:39:36 2022 - -@author: cheritie -""" - -# -*- coding: utf-8 -*- -""" -Created on Mon Nov 16 15:18:31 2020 - -@author: cheritie -""" - -import matplotlib.pyplot as plt -import numpy as np -import time -import scipy -from astropy.io import fits as pfits -# local modules -from AO_modules.calibration.CalibrationVault import CalibrationVault -from AO_modules.MisRegistration import MisRegistration -from AO_modules.tools.tools import createFolder,write_fits -from AO_modules.mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration -from AO_modules.tools.interpolate_influence_functions import interpolate_influence_functions - - -##################################################################################################################################################### -# # # # # # # # # # # # # # # # # -# # # # # # # # # # # # # # -# # # # # # # # # # # # # # # # # # # # # -# # # # # # # # # # # # # -# # # # # # # # # # # # # # # # # # # -##################################################################################################################################################### - -def run_cl_two_stages_atm_change(param,obj,start_change,n_transition,func_change_wind_speed = None ,func_change_wind_direction = None ,func_change_r0 = None,speed_factor = 2,filename_phase_screen = None, extra_name = '', destination_folder = None, get_le_psf = False): - - ref_wind_direction = np.asarray(obj.atm_fast.windDirection) - ref_wind_speed = np.asarray(obj.atm_fast.windSpeed) - ref_r0 = np.copy(obj.atm_fast.r0) - ref_seeing = np.copy(obj.atm_fast.seeingArcsec) - end_change = start_change + n_transition - nLoop = param['nLoop'] - gain_cl = param['gainCL'] - - #% ------------------------------------ Noise Update ------------------------------------ - # set magnitude of the NGS - # obj.ngs.nPhoton = param['nPhotonPerSubaperture'] * (1/obj.tel.samplingTime)/((obj.tel.D/param['nSubaperture'] )**2) - obj.ngs.magnitude = param['magnitude'] - obj.ngs.nPhoton = obj.ngs.zeroPoint*10**(-0.4*obj.ngs.magnitude) - - extraName ='' - # photon Noise - obj.wfs.cam.photonNoise = param['photonNoise'] - if obj.wfs.cam.photonNoise: - extraName+=('photonNoise_') - - # RON - obj.wfs.cam.readoutNoise = param['readoutNoise'] - - if obj.wfs.cam.readoutNoise: - extraName+=('readoutNoise_') - - - #% ------------------------------------ Calibration ------------------------------------ - if obj.calib.D.shape[1] != param['nModes']: - calib_cl = CalibrationVault(obj.calib.D[:,:param['nModes']]) - else: - calib_cl = obj.calib - M2C_cl = obj.M2C - - - #%% - # def interpolation_function(image): - # image = np.reshape(image,[1,image.shape[0],image.shape[1]]) - # misReg = MisRegistration() - # # parameters for the interpolation - # nRes = image.shape[1] - # pixel_size_in = obj.tel.D/nRes # pixel size in [m] of the input influence function - # resolution_out = 1152 # resolution in pixels of the input influence function - # pixel_size_out = obj.tel.D/resolution_out # resolution in pixels of the output influence function - # mis_registration = misReg # mis-registration object to apply with respect to the input influence function - # coordinates_in = np.zeros([100,2]) # coordinated in [m] of the input influence function - - # P_out,coord_out = interpolate_influence_functions(image,pixel_size_in,pixel_size_out, resolution_out, mis_registration,coordinates_in) - - # return (np.squeeze(P_out.astype(int))) - #%% ------------------------------------ mis-registrations ------------------------------------ - - misRegistration_cl = MisRegistration(param) - - # misRegistration_cl.show() - # obj.dm.misReg.show() - -# case with no mis-registration - if misRegistration_cl == obj.dm.misReg: - dm_cl = obj.dm - else: - dm_cl = applyMisRegistration(obj.tel,misRegistration_cl,param) - - #%% ------------------------------------ closed loop data to be saved ------------------------------------ - - wfs_signals = np.zeros([nLoop,obj.wfs.nSignal]) - dm_commands = np.zeros([nLoop,obj.dm.nValidAct]) - ao_residuals = np.zeros(nLoop) - ao_turbulence = np.zeros(nLoop) - residual_phase_screen = np.zeros([nLoop,obj.tel.resolution,obj.tel.resolution],dtype=np.float32) - - #%% ------------------------------------ destination folder ------------------------------------ - - misregistrationFolder = misRegistration_cl.misRegName - if destination_folder is None: - destination_folder = param['pathOutput']+ '/'+param['name'] +'/'+obj.wfs.tag + '/' - createFolder(destination_folder) - - gain_cl = param['gainCL'] - - # combine with atmosphere - obj.tel_fast+obj.atm_fast - plt.close('all') - - # initialize DM commands - dm_cl.coefs=0 - - #%% ------------------------------------ Modal Gains ------------------------------------ - - ModalGainsMatrix = obj.gOpt - - # Loop initialization - reconstructor = np.matmul(M2C_cl,np.matmul(ModalGainsMatrix,calib_cl.M)) - - - #%% - # propagate through th whole system - obj.ngs*obj.tel*dm_cl*obj.wfs - - obj.ngs*obj.tel_fast*dm_cl - - # setup the display - if obj.display: - plt.figure(79) - plt.ion() - - ax1=plt.subplot(1,3,1) - im_atm = ax1.imshow(obj.tel.src.phase) - plt.colorbar(im_atm,fraction=0.046, pad=0.04) - plt.title('Turbulence OPD [nm]') - - - ax2=plt.subplot(1,3,2) - im_residual_1 = ax2.imshow(obj.tel.src.phase) - plt.colorbar(im_residual_1,fraction=0.046, pad=0.04) - plt.title('Residual OPD First stage [nm]') - - ax3=plt.subplot(1,3,3) - im_residual_2 = ax3.imshow(obj.tel.src.phase) - plt.colorbar(im_residual_2,fraction=0.046, pad=0.04) - plt.title('Intermediate OPD [nm]') - -#%% - wfsSignal = np.zeros(obj.wfs.nSignal) - - OPD_buffer = [] - obj.tel_fast.isPaired = True - obj.tel.isPaired = True - OPD_first_stage_res = obj.atm_fast.OPD - - PSF_LE = [] - count_change = 0 - - for i_loop in range(nLoop): - if i_loop>start_change and count_change< n_transition: - # update wind direction - if func_change_wind_direction is not None: - obj.atm_fast.windDirection = list(ref_wind_direction+func_change_wind_direction[:,count_change]) - # update wind speed - if func_change_wind_speed is not None: - obj.atm_fast.windSpeed = list(ref_wind_speed+func_change_wind_speed[:,count_change]) - # update r0 - if func_change_r0 is not None: - obj.atm_fast.r0 = ref_r0+func_change_r0[count_change] - - count_change+=1 - obj.atm_fast.print_atm() - - a= time.time() - # update atmospheric phase screen - obj.atm_fast.update() - - # skipping the first phase screen - if i_loop>0: - OPD_buffer.append(obj.atm_fast.OPD_no_pupil) - - # save phase variance - ao_turbulence[i_loop]=np.std(obj.atm_fast.OPD[np.where(obj.tel.pupil>0)])*1e9 - - # save turbulent phase - OPD_turb = obj.tel_fast.OPD - - if len(OPD_buffer)==speed_factor: - OPD_first_stage = np.mean(OPD_buffer,axis=0) - obj.tel.OPD_no_pupil = OPD_first_stage.copy() - obj.tel.OPD = OPD_first_stage.copy()*obj.tel.pupil - # propagate to the WFS with the CL commands applied - obj.tel*dm_cl*obj.wfs - - OPD_first_stage_res = obj.tel.OPD - # reinitialize phase buffer - OPD_buffer = [] - - dm_cl.coefs = dm_cl.coefs-gain_cl*np.matmul(reconstructor,wfsSignal) - wfsSignal = obj.wfs.signal - if get_le_psf: - if i_loop >100: - obj.tel.computePSF(zeroPaddingFactor = 4) - PSF_LE.append(obj.tel.PSF) - - # else: - obj.tel_fast*dm_cl - - ao_residuals[i_loop]=np.std(obj.tel_fast.OPD[np.where(obj.tel.pupil>0)])*1e9 - - b= time.time() - - mean_rem_OPD = obj.tel_fast.OPD - mean_rem_OPD[np.where(obj.tel.pupil>0)] -= np.mean(mean_rem_OPD[np.where(obj.tel.pupil>0)]) - - residual_phase_screen[i_loop,:,:] = mean_rem_OPD - dm_commands[i_loop,:] = dm_cl.coefs.copy() - wfs_signals[i_loop,:] = wfsSignal.copy() - - if obj.display==True: - # turbulence phase - im_atm.set_data(OPD_turb) - im_atm.set_clim(vmin=OPD_turb.min(),vmax=OPD_turb.max()) - - # residual phase - tmp = obj.tel_fast.OPD - # tmp-=np.mean(tmp[obj.tel.pupil]) - im_residual_2.set_data(tmp) - im_residual_2.set_clim(vmin=tmp.min(),vmax=tmp.max()) - - tmp = OPD_first_stage_res - # tmp-=np.mean(tmp[obj.tel.pupil]) - im_residual_1.set_data(tmp) - im_residual_1.set_clim(vmin=tmp.min(),vmax=tmp.max()) - plt.draw() - - plt.show() - plt.pause(0.001) - - print('Loop'+str(i_loop)+'/'+str(nLoop)+'Turbulence: '+str(ao_turbulence[i_loop])+' -- Residual:' +str(ao_residuals[i_loop])+ '\n') - print('Elapsed Time: ' + str(b-a) + ' s ') - - dataCL = dict() - dataCL['ao_residual'] = ao_residuals - dataCL['ao_turbulence'] = ao_turbulence - dataCL['parameterFile'] = param - dataCL['destination_folder'] = destination_folder - dataCL['long_exposure_psf'] = np.mean(np.asarray(PSF_LE),axis =0) - - if filename_phase_screen is None: - filename_phase_screen ='_magnitude_'+str(np.round(obj.ngs.magnitude))+\ - '_nLoop_'+str(param['nLoop'])+\ - '_seeing_1_'+str(np.round(ref_seeing,2))+'_to_'+str(np.round(obj.atm_fast.seeingArcsec,2))+\ - '_speed_1_'+str(np.round(np.mean(ref_wind_speed)))+'_to_'+str(np.round(np.mean(obj.atm_fast.windSpeed)))+\ - '_direction_1_'+str(np.round(np.mean(ref_wind_direction)))+'_to_'+str(np.round(np.mean(obj.atm_fast.windDirection)))+\ - '_stg1_'+str(np.round(0.001/obj.tel.samplingTime,2))+'_kHz_stg2_'+str(np.round(0.001/obj.tel_fast.samplingTime,2))+'_kHz' - - dataCL['filename'] = filename_phase_screen+extra_name - dataCL['dm_commands'] = dm_commands - dataCL['residual_phase_screen'] = residual_phase_screen - dataCL['wfs_signals'] = wfs_signals - - # hdr = pfits.Header() - # hdr['TITLE'] = 'phase screens' - # primary_hdu = pfits.PrimaryHDU(residual_phase_screen) - # hdu = pfits.HDUList([primary_hdu]) - # # save output - # hdu.writeto(destination_folder+filename_phase_screen+'.fits',overwrite=True) - # hdu.close() - - # hdr = pfits.Header() - # hdr['TITLE'] = 'wfs signals' - # primary_hdu = pfits.PrimaryHDU(wfs_signals) - # hdu = pfits.HDUList([primary_hdu]) - # # save output - # hdu.writeto(destination_folder+filename_phase_screen+'_wfs_signals.fits',overwrite=True) - # hdu.close() - - # hdr = pfits.Header() - # hdr['TITLE'] = 'dm commands' - # primary_hdu = pfits.PrimaryHDU(dm_commands) - # hdu = pfits.HDUList([primary_hdu]) - # # save output - # hdu.writeto(destination_folder+filename_phase_screen+'_dm_commands.fits',overwrite=True) - # hdu.close() - - return dataCL - - - +# -*- coding: utf-8 -*- +""" +Created on Mon Jul 11 11:39:36 2022 + +@author: cheritie +""" + +# -*- coding: utf-8 -*- +""" +Created on Mon Nov 16 15:18:31 2020 + +@author: cheritie +""" + +import time + +import matplotlib.pyplot as plt +import numpy as np + +from ..MisRegistration import MisRegistration +from ..calibration.CalibrationVault import CalibrationVault +from ..mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration +from ..tools.tools import createFolder + + +##################################################################################################################################################### +# # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # # # +# # # # # # # # # # # # # +# # # # # # # # # # # # # # # # # # # +##################################################################################################################################################### + +def run_cl_two_stages_atm_change(param,obj,start_change,n_transition,func_change_wind_speed = None ,func_change_wind_direction = None ,func_change_r0 = None,speed_factor = 2,filename_phase_screen = None, extra_name = '', destination_folder = None, get_le_psf = False): + + ref_wind_direction = np.asarray(obj.atm_fast.windDirection) + ref_wind_speed = np.asarray(obj.atm_fast.windSpeed) + ref_r0 = np.copy(obj.atm_fast.r0) + ref_seeing = np.copy(obj.atm_fast.seeingArcsec) + end_change = start_change + n_transition + nLoop = param['nLoop'] + gain_cl = param['gainCL'] + + #% ------------------------------------ Noise Update ------------------------------------ + # set magnitude of the NGS + # obj.ngs.nPhoton = param['nPhotonPerSubaperture'] * (1/obj.tel.samplingTime)/((obj.tel.D/param['nSubaperture'] )**2) + obj.ngs.magnitude = param['magnitude'] + obj.ngs.nPhoton = obj.ngs.zeroPoint*10**(-0.4*obj.ngs.magnitude) + + extraName ='' + # photon Noise + obj.wfs.cam.photonNoise = param['photonNoise'] + if obj.wfs.cam.photonNoise: + extraName+=('photonNoise_') + + # RON + obj.wfs.cam.readoutNoise = param['readoutNoise'] + + if obj.wfs.cam.readoutNoise: + extraName+=('readoutNoise_') + + + #% ------------------------------------ Calibration ------------------------------------ + if obj.calib.D.shape[1] != param['nModes']: + calib_cl = CalibrationVault(obj.calib.D[:,:param['nModes']]) + else: + calib_cl = obj.calib + M2C_cl = obj.M2C + + + #%% + # def interpolation_function(image): + # image = np.reshape(image,[1,image.shape[0],image.shape[1]]) + # misReg = MisRegistration() + # # parameters for the interpolation + # nRes = image.shape[1] + # pixel_size_in = obj.tel.D/nRes # pixel size in [m] of the input influence function + # resolution_out = 1152 # resolution in pixels of the input influence function + # pixel_size_out = obj.tel.D/resolution_out # resolution in pixels of the output influence function + # mis_registration = misReg # mis-registration object to apply with respect to the input influence function + # coordinates_in = np.zeros([100,2]) # coordinated in [m] of the input influence function + + # P_out,coord_out = interpolate_influence_functions(image,pixel_size_in,pixel_size_out, resolution_out, mis_registration,coordinates_in) + + # return (np.squeeze(P_out.astype(int))) + #%% ------------------------------------ mis-registrations ------------------------------------ + + misRegistration_cl = MisRegistration(param) + + # misRegistration_cl.show() + # obj.dm.misReg.show() + +# case with no mis-registration + if misRegistration_cl == obj.dm.misReg: + dm_cl = obj.dm + else: + dm_cl = applyMisRegistration(obj.tel,misRegistration_cl,param) + + #%% ------------------------------------ closed loop data to be saved ------------------------------------ + + wfs_signals = np.zeros([nLoop,obj.wfs.nSignal]) + dm_commands = np.zeros([nLoop,obj.dm.nValidAct]) + ao_residuals = np.zeros(nLoop) + ao_turbulence = np.zeros(nLoop) + residual_phase_screen = np.zeros([nLoop,obj.tel.resolution,obj.tel.resolution],dtype=np.float32) + + #%% ------------------------------------ destination folder ------------------------------------ + + misregistrationFolder = misRegistration_cl.misRegName + if destination_folder is None: + destination_folder = param['pathOutput']+ '/'+param['name'] +'/'+obj.wfs.tag + '/' + createFolder(destination_folder) + + gain_cl = param['gainCL'] + + # combine with atmosphere + obj.tel_fast+obj.atm_fast + plt.close('all') + + # initialize DM commands + dm_cl.coefs=0 + + #%% ------------------------------------ Modal Gains ------------------------------------ + + ModalGainsMatrix = obj.gOpt + + # Loop initialization + reconstructor = np.matmul(M2C_cl,np.matmul(ModalGainsMatrix,calib_cl.M)) + + + #%% + # propagate through th whole system + obj.ngs*obj.tel*dm_cl*obj.wfs + + obj.ngs*obj.tel_fast*dm_cl + + # setup the display + if obj.display: + plt.figure(79) + plt.ion() + + ax1=plt.subplot(1,3,1) + im_atm = ax1.imshow(obj.tel.src.phase) + plt.colorbar(im_atm,fraction=0.046, pad=0.04) + plt.title('Turbulence OPD [nm]') + + + ax2=plt.subplot(1,3,2) + im_residual_1 = ax2.imshow(obj.tel.src.phase) + plt.colorbar(im_residual_1,fraction=0.046, pad=0.04) + plt.title('Residual OPD First stage [nm]') + + ax3=plt.subplot(1,3,3) + im_residual_2 = ax3.imshow(obj.tel.src.phase) + plt.colorbar(im_residual_2,fraction=0.046, pad=0.04) + plt.title('Intermediate OPD [nm]') + +#%% + wfsSignal = np.zeros(obj.wfs.nSignal) + + OPD_buffer = [] + obj.tel_fast.isPaired = True + obj.tel.isPaired = True + OPD_first_stage_res = obj.atm_fast.OPD + + PSF_LE = [] + count_change = 0 + + for i_loop in range(nLoop): + if i_loop>start_change and count_change< n_transition: + # update wind direction + if func_change_wind_direction is not None: + obj.atm_fast.windDirection = list(ref_wind_direction+func_change_wind_direction[:,count_change]) + # update wind speed + if func_change_wind_speed is not None: + obj.atm_fast.windSpeed = list(ref_wind_speed+func_change_wind_speed[:,count_change]) + # update r0 + if func_change_r0 is not None: + obj.atm_fast.r0 = ref_r0+func_change_r0[count_change] + + count_change+=1 + obj.atm_fast.print_atm() + + a= time.time() + # update atmospheric phase screen + obj.atm_fast.update() + + # skipping the first phase screen + if i_loop>0: + OPD_buffer.append(obj.atm_fast.OPD_no_pupil) + + # save phase variance + ao_turbulence[i_loop]=np.std(obj.atm_fast.OPD[np.where(obj.tel.pupil>0)])*1e9 + + # save turbulent phase + OPD_turb = obj.tel_fast.OPD + + if len(OPD_buffer)==speed_factor: + OPD_first_stage = np.mean(OPD_buffer,axis=0) + obj.tel.OPD_no_pupil = OPD_first_stage.copy() + obj.tel.OPD = OPD_first_stage.copy()*obj.tel.pupil + # propagate to the WFS with the CL commands applied + obj.tel*dm_cl*obj.wfs + + OPD_first_stage_res = obj.tel.OPD + # reinitialize phase buffer + OPD_buffer = [] + + dm_cl.coefs = dm_cl.coefs-gain_cl*np.matmul(reconstructor,wfsSignal) + wfsSignal = obj.wfs.signal + if get_le_psf: + if i_loop >100: + obj.tel.computePSF(zeroPaddingFactor = 4) + PSF_LE.append(obj.tel.PSF) + + # else: + obj.tel_fast*dm_cl + + ao_residuals[i_loop]=np.std(obj.tel_fast.OPD[np.where(obj.tel.pupil>0)])*1e9 + + b= time.time() + + mean_rem_OPD = obj.tel_fast.OPD + mean_rem_OPD[np.where(obj.tel.pupil>0)] -= np.mean(mean_rem_OPD[np.where(obj.tel.pupil>0)]) + + residual_phase_screen[i_loop,:,:] = mean_rem_OPD + dm_commands[i_loop,:] = dm_cl.coefs.copy() + wfs_signals[i_loop,:] = wfsSignal.copy() + + if obj.display==True: + # turbulence phase + im_atm.set_data(OPD_turb) + im_atm.set_clim(vmin=OPD_turb.min(),vmax=OPD_turb.max()) + + # residual phase + tmp = obj.tel_fast.OPD + # tmp-=np.mean(tmp[obj.tel.pupil]) + im_residual_2.set_data(tmp) + im_residual_2.set_clim(vmin=tmp.min(),vmax=tmp.max()) + + tmp = OPD_first_stage_res + # tmp-=np.mean(tmp[obj.tel.pupil]) + im_residual_1.set_data(tmp) + im_residual_1.set_clim(vmin=tmp.min(),vmax=tmp.max()) + plt.draw() + + plt.show() + plt.pause(0.001) + + print('Loop'+str(i_loop)+'/'+str(nLoop)+'Turbulence: '+str(ao_turbulence[i_loop])+' -- Residual:' +str(ao_residuals[i_loop])+ '\n') + print('Elapsed Time: ' + str(b-a) + ' s ') + + dataCL = dict() + dataCL['ao_residual'] = ao_residuals + dataCL['ao_turbulence'] = ao_turbulence + dataCL['parameterFile'] = param + dataCL['destination_folder'] = destination_folder + dataCL['long_exposure_psf'] = np.mean(np.asarray(PSF_LE),axis =0) + + if filename_phase_screen is None: + filename_phase_screen ='_magnitude_'+str(np.round(obj.ngs.magnitude))+\ + '_nLoop_'+str(param['nLoop'])+\ + '_seeing_1_'+str(np.round(ref_seeing,2))+'_to_'+str(np.round(obj.atm_fast.seeingArcsec,2))+\ + '_speed_1_'+str(np.round(np.mean(ref_wind_speed)))+'_to_'+str(np.round(np.mean(obj.atm_fast.windSpeed)))+\ + '_direction_1_'+str(np.round(np.mean(ref_wind_direction)))+'_to_'+str(np.round(np.mean(obj.atm_fast.windDirection)))+\ + '_stg1_'+str(np.round(0.001/obj.tel.samplingTime,2))+'_kHz_stg2_'+str(np.round(0.001/obj.tel_fast.samplingTime,2))+'_kHz' + + dataCL['filename'] = filename_phase_screen+extra_name + dataCL['dm_commands'] = dm_commands + dataCL['residual_phase_screen'] = residual_phase_screen + dataCL['wfs_signals'] = wfs_signals + + # hdr = pfits.Header() + # hdr['TITLE'] = 'phase screens' + # primary_hdu = pfits.PrimaryHDU(residual_phase_screen) + # hdu = pfits.HDUList([primary_hdu]) + # # save output + # hdu.writeto(destination_folder+filename_phase_screen+'.fits',overwrite=True) + # hdu.close() + + # hdr = pfits.Header() + # hdr['TITLE'] = 'wfs signals' + # primary_hdu = pfits.PrimaryHDU(wfs_signals) + # hdu = pfits.HDUList([primary_hdu]) + # # save output + # hdu.writeto(destination_folder+filename_phase_screen+'_wfs_signals.fits',overwrite=True) + # hdu.close() + + # hdr = pfits.Header() + # hdr['TITLE'] = 'dm commands' + # primary_hdu = pfits.PrimaryHDU(dm_commands) + # hdu = pfits.HDUList([primary_hdu]) + # # save output + # hdu.writeto(destination_folder+filename_phase_screen+'_dm_commands.fits',overwrite=True) + # hdu.close() + + return dataCL + + + diff --git a/AO_modules/__init__.py b/OOPAO/mis_registration_identification_algorithm/__init__.py old mode 100755 new mode 100644 similarity index 92% rename from AO_modules/__init__.py rename to OOPAO/mis_registration_identification_algorithm/__init__.py index c03131d..5cbfd76 --- a/AO_modules/__init__.py +++ b/OOPAO/mis_registration_identification_algorithm/__init__.py @@ -1,7 +1,7 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Apr 3 10:56:59 2020 - -@author: cheritie -""" - +# -*- coding: utf-8 -*- +""" +Created on Fri Apr 3 10:56:59 2020 + +@author: cheritie +""" + diff --git a/AO_modules/mis_registration_identification_algorithm/applyMisRegistration.py b/OOPAO/mis_registration_identification_algorithm/applyMisRegistration.py old mode 100755 new mode 100644 similarity index 92% rename from AO_modules/mis_registration_identification_algorithm/applyMisRegistration.py rename to OOPAO/mis_registration_identification_algorithm/applyMisRegistration.py index c6c782d..a54b9fe --- a/AO_modules/mis_registration_identification_algorithm/applyMisRegistration.py +++ b/OOPAO/mis_registration_identification_algorithm/applyMisRegistration.py @@ -1,139 +1,134 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Aug 25 15:03:13 2020 - -@author: cheritie -""" - -import numpy as np -from AO_modules.DeformableMirror import DeformableMirror -from astropy.io import fits as pfits -from joblib import Parallel, delayed -from AO_modules.tools.interpolateGeometricalTransformation import rotateImageMatrix,rotation,translationImageMatrix,translation,anamorphosis,anamorphosisImageMatrix -import time -from AO_modules.MisRegistration import MisRegistration -import skimage.transform as sk -import scipy.ndimage as sp - - -def applyMisRegistration(tel,misRegistration_tmp,param, wfs = None, extra_dm_mis_registration = None,print_dm_properties=True,floating_precision=64): - if extra_dm_mis_registration is None: - extra_dm_mis_registration = MisRegistration() - try: - if param['pitch'] is None: - pitch = 0 - else: - pitch = param['pitch'] - except: - pitch = 0 - if wfs is None: - - # case synthetic DM - with user-defined coordinates - try: - if param['dm_coordinates'] is None: - coordinates = 0 - else: - coordinates = param['dm_coordinates'] - except: - coordinates = 0 - - - - try: - if param['isM4'] is True: - # case with M4 - dm_tmp = DeformableMirror(telescope = tel,\ - nSubap = param['nSubaperture'],\ - mechCoupling = param['mechanicalCoupling'],\ - coordinates = coordinates,\ - pitch = 0,\ - misReg = misRegistration_tmp + extra_dm_mis_registration,\ - M4_param = param,\ - print_dm_properties = print_dm_properties,\ - floating_precision =floating_precision) - if print_dm_properties: - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('Mis-Registrations Applied on M4!') - - else: - from users.cheritie.model_LBT.lbt_tools import get_influence_functions - - modes, coord, M2C, validAct = get_influence_functions(telescope = tel,\ - misReg = misRegistration_tmp + extra_dm_mis_registration,\ - filename_IF = param['filename_if'],\ - filename_mir_modes = param['filename_mir_modes'],\ - filename_coordinates = param['filename_coord'],\ - filename_M2C = param['filename_m2c']) - param['isM4'] = False - - dm_tmp = DeformableMirror(telescope = tel,\ - nSubap = param['nSubaperture'],\ - mechCoupling = param['mechanicalCoupling'],\ - coordinates = coord,\ - pitch = 0,\ - misReg = misRegistration_tmp + extra_dm_mis_registration,\ - modes = np.reshape(modes,[tel.resolution**2,modes.shape[2]]),\ - M4_param = param,\ - print_dm_properties = print_dm_properties) - if print_dm_properties: - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('Mis-Registrations Applied on user-defined DM!') - - - except: - # default case - - dm_tmp = DeformableMirror(telescope = tel,\ - nSubap = param['nSubaperture'],\ - mechCoupling = param['mechanicalCoupling'],\ - coordinates = coordinates,\ - pitch = pitch,\ - misReg = misRegistration_tmp + extra_dm_mis_registration,\ - print_dm_properties = print_dm_properties) - if print_dm_properties: - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('Mis-Registrations Applied on Synthetic DM!') - else: - if wfs.tag == 'pyramid': - misRegistration_wfs = MisRegistration() - misRegistration_wfs.shiftX = misRegistration_tmp.shiftX - misRegistration_wfs.shiftY = misRegistration_tmp.shiftY - - wfs.apply_shift_wfs( misRegistration_wfs.shiftX, misRegistration_wfs.shiftY) - - - misRegistration_dm = MisRegistration() - misRegistration_dm.rotationAngle = misRegistration_tmp.rotationAngle - misRegistration_dm.tangentialScaling = misRegistration_tmp.tangentialScaling - misRegistration_dm.radialScaling = misRegistration_tmp.radialScaling - - dm_tmp = applyMisRegistration(tel,misRegistration_dm + extra_dm_mis_registration, param, wfs = None, print_dm_properties = print_dm_properties) - if print_dm_properties: - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('Mis-Registrations Applied on both DM and WFS!') - else: - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('Wrong object passed as a wfs.. aplying the mis-registrations to the DM only') - dm_tmp = applyMisRegistration(tel,misRegistration_tmp + extra_dm_mis_registration, param, wfs = None) - - return dm_tmp - - - - - - -# def apply_shift_wfs(wfs,sx,sy): -# if wfs.tag =='pyramid': -# sx *= 1/(wfs.telescope.pixelSize*(wfs.telescope.resolution/wfs.nSubap)) -# sy *= 1/(wfs.telescope.pixelSize*(wfs.telescope.resolution/wfs.nSubap)) -# tmp = np.ones([wfs.nRes,wfs.nRes]) -# tmp[:,0] = 0 -# Tip = (sp.morphology.distance_transform_edt(tmp)) -# Tilt = (sp.morphology.distance_transform_edt(np.transpose(tmp))) - -# # normalize the TT to apply the modulation in terms of lambda/D -# Tip = (wfs.telRes/wfs.nSubap)*(((Tip/Tip.max())-0.5)*2*np.pi) -# Tilt = (wfs.telRes/wfs.nSubap)*(((Tilt/Tilt.max())-0.5)*2*np.pi) - +# -*- coding: utf-8 -*- +""" +Created on Tue Aug 25 15:03:13 2020 + +@author: cheritie +""" + +import numpy as np + +from ..DeformableMirror import DeformableMirror +from ..MisRegistration import MisRegistration + + +def applyMisRegistration(tel,misRegistration_tmp,param, wfs = None, extra_dm_mis_registration = None,print_dm_properties=True,floating_precision=64): + if extra_dm_mis_registration is None: + extra_dm_mis_registration = MisRegistration() + try: + if param['pitch'] is None: + pitch = 0 + else: + pitch = param['pitch'] + except: + pitch = 0 + if wfs is None: + + # case synthetic DM - with user-defined coordinates + try: + if param['dm_coordinates'] is None: + coordinates = 0 + else: + coordinates = param['dm_coordinates'] + except: + coordinates = 0 + + + + try: + if param['isM4'] is True: + # case with M4 + dm_tmp = DeformableMirror(telescope = tel,\ + nSubap = param['nSubaperture'],\ + mechCoupling = param['mechanicalCoupling'],\ + coordinates = coordinates,\ + pitch = 0,\ + misReg = misRegistration_tmp + extra_dm_mis_registration,\ + M4_param = param,\ + print_dm_properties = print_dm_properties,\ + floating_precision =floating_precision) + if print_dm_properties: + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('Mis-Registrations Applied on M4!') + + else: + from users.cheritie.model_LBT.lbt_tools import get_influence_functions + + modes, coord, M2C, validAct = get_influence_functions(telescope = tel,\ + misReg = misRegistration_tmp + extra_dm_mis_registration,\ + filename_IF = param['filename_if'],\ + filename_mir_modes = param['filename_mir_modes'],\ + filename_coordinates = param['filename_coord'],\ + filename_M2C = param['filename_m2c']) + param['isM4'] = False + + dm_tmp = DeformableMirror(telescope = tel,\ + nSubap = param['nSubaperture'],\ + mechCoupling = param['mechanicalCoupling'],\ + coordinates = coord,\ + pitch = 0,\ + misReg = misRegistration_tmp + extra_dm_mis_registration,\ + modes = np.reshape(modes,[tel.resolution**2,modes.shape[2]]),\ + M4_param = param,\ + print_dm_properties = print_dm_properties) + if print_dm_properties: + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('Mis-Registrations Applied on user-defined DM!') + + + except: + # default case + + dm_tmp = DeformableMirror(telescope = tel,\ + nSubap = param['nSubaperture'],\ + mechCoupling = param['mechanicalCoupling'],\ + coordinates = coordinates,\ + pitch = pitch,\ + misReg = misRegistration_tmp + extra_dm_mis_registration,\ + print_dm_properties = print_dm_properties) + if print_dm_properties: + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('Mis-Registrations Applied on Synthetic DM!') + else: + if wfs.tag == 'pyramid': + misRegistration_wfs = MisRegistration() + misRegistration_wfs.shiftX = misRegistration_tmp.shiftX + misRegistration_wfs.shiftY = misRegistration_tmp.shiftY + + wfs.apply_shift_wfs( misRegistration_wfs.shiftX, misRegistration_wfs.shiftY) + + + misRegistration_dm = MisRegistration() + misRegistration_dm.rotationAngle = misRegistration_tmp.rotationAngle + misRegistration_dm.tangentialScaling = misRegistration_tmp.tangentialScaling + misRegistration_dm.radialScaling = misRegistration_tmp.radialScaling + + dm_tmp = applyMisRegistration(tel,misRegistration_dm + extra_dm_mis_registration, param, wfs = None, print_dm_properties = print_dm_properties) + if print_dm_properties: + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('Mis-Registrations Applied on both DM and WFS!') + else: + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('Wrong object passed as a wfs.. aplying the mis-registrations to the DM only') + dm_tmp = applyMisRegistration(tel,misRegistration_tmp + extra_dm_mis_registration, param, wfs = None) + + return dm_tmp + + + + + + +# def apply_shift_wfs(wfs,sx,sy): +# if wfs.tag =='pyramid': +# sx *= 1/(wfs.telescope.pixelSize*(wfs.telescope.resolution/wfs.nSubap)) +# sy *= 1/(wfs.telescope.pixelSize*(wfs.telescope.resolution/wfs.nSubap)) +# tmp = np.ones([wfs.nRes,wfs.nRes]) +# tmp[:,0] = 0 +# Tip = (sp.morphology.distance_transform_edt(tmp)) +# Tilt = (sp.morphology.distance_transform_edt(np.transpose(tmp))) + +# # normalize the TT to apply the modulation in terms of lambda/D +# Tip = (wfs.telRes/wfs.nSubap)*(((Tip/Tip.max())-0.5)*2*np.pi) +# Tilt = (wfs.telRes/wfs.nSubap)*(((Tilt/Tilt.max())-0.5)*2*np.pi) + # wfs.mask = wfs.convert_for_gpu(np.exp(1j*(wfs.initial_m+sx*Tip+sy*Tilt))) \ No newline at end of file diff --git a/AO_modules/mis_registration_identification_algorithm/computeMetaSensitivyMatrix.py b/OOPAO/mis_registration_identification_algorithm/computeMetaSensitivyMatrix.py old mode 100755 new mode 100644 similarity index 93% rename from AO_modules/mis_registration_identification_algorithm/computeMetaSensitivyMatrix.py rename to OOPAO/mis_registration_identification_algorithm/computeMetaSensitivyMatrix.py index 5bc421a..b55d896 --- a/AO_modules/mis_registration_identification_algorithm/computeMetaSensitivyMatrix.py +++ b/OOPAO/mis_registration_identification_algorithm/computeMetaSensitivyMatrix.py @@ -1,215 +1,221 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Aug 21 10:22:55 2020 - -@author: cheritie -""" - -from AO_modules.calibration.CalibrationVault import CalibrationVault -from AO_modules.calibration.InteractionMatrix import InteractionMatrix,InteractionMatrixFromPhaseScreen -from AO_modules.MisRegistration import MisRegistration -from astropy.io import fits as pfits -import numpy as np -from AO_modules.tools.tools import createFolder -from AO_modules.mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration -from AO_modules.tools.interpolateGeometricalTransformation import rotateImageMatrix,rotation,translationImageMatrix,translation,anamorphosis,anamorphosisImageMatrix -import skimage.transform as sk -""" -def computeMetaSensitivityMatrix(nameFolder,nameSystem,tel,atm,ngs,dm_0,pitch,wfs,basis,misRegistrationZeroPoint,epsilonMisRegistration,param): - - Compute the set of sensitivity matrices required to identify the mis-registrations. - - %%%%%%%%%%%%%%%% -- INPUTS -- %%%%%%%%%%%%%%%% - _ nameFolder : folder to store the sensitivity matrices. - _ nameSystem : name of the AO system considered. For instance 'ELT_96x96_R_band' - _ tel : telescope object - _ atm : atmosphere object - _ ngs : source object - _ dm_0 : deformable mirror with reference configuration of mis-registrations - _ pitch : pitch of the dm in [m] - _ wfs : wfs object - _ basis : basis to use to compute the sensitivity matrices. Basis should be an object with the following fields: - basis.modes : [nActuator x nModes] matrix containing the commands to apply the modal basis on the dm - basis.extra : extra name to name the sensitivity matrices for instance 'KL' - _ misRegistrationZeroPoint : mis-registration around which you want to compute the sensitivity matrices - _ epsilonMisRegistration : epsilon value to apply - _ param : dictionnary used as parameter file - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - The function returns the meta-sensitivity matrix that contains all the individual sensitivity matrices reshaped as a vector and concatenated. - - %%%%%%%%%%%%%%%% -- OUTPUTS -- %%%%%%%%%%%%%%%% - _ metaSensitivityMatrix : meta-sensitivity matrix stored as a calibration object. the matrix is accessible with metaSensitivityMatrix.D and its pseudo inverse with metaSensitivityMatrix.M - _ calib_0 : interaction matrix corresponding to misRegistrationZeroPoint stored as a calibration object. - -""" -def computeMetaSensitivityMatrix(nameFolder, nameSystem, tel, atm, ngs, dm_0, pitch, wfs, basis, misRegistrationZeroPoint, epsilonMisRegistration, param, wfs_mis_registrated = None,save_sensitivity_matrices=True,fast = False, n_mis_reg = 3, recompute_sensitivity = False): - #%% -------------------- CREATION OF THE DESTINATION FOLDER -------------------- - homeFolder = misRegistrationZeroPoint.misRegName+'/' - intMat_name = 'im' - if basis.modes.shape == np.shape(np.eye(dm_0.nValidAct)): - - comparison = basis.modes == np.eye(dm_0.nValidAct) - if comparison.all(): - foldername = nameFolder+nameSystem+homeFolder+'zon/' - extraName = '' - - else: - foldername = nameFolder+nameSystem+homeFolder+'mod/' - extraName = '_'+basis.extra - else: - foldername = nameFolder+nameSystem+homeFolder+'mod/' - extraName = '_'+basis.extra - - if save_sensitivity_matrices: - createFolder(foldername) - - - - #%% -------------------- COMPUTATION OF THE INTERACTION MATRICES FOLDER -------------------- - epsilonMisRegistration_name = ['dX','dY','dRot','dmX','dmY'] - epsilonMisRegistration_field = ['shiftX','shiftY','rotationAngle','radialScaling','tangentialScaling'] - epsilonMisRegistration_name = epsilonMisRegistration_name[:n_mis_reg] - epsilonMisRegistration_field = epsilonMisRegistration_field[:n_mis_reg] - try: - meta_matrix =np.zeros([wfs.nSignal*basis.modes.shape[1],int(len(epsilonMisRegistration_name))]) - except: - meta_matrix =np.zeros([wfs.nSignal,int(len(epsilonMisRegistration_name))]) - - for i in range(len(epsilonMisRegistration_name)): - # name for the matrices - name_0 = foldername + intMat_name +'_0'+ extraName+'.fits' - name_p = foldername + intMat_name +'_'+ epsilonMisRegistration_name[i]+'_p_'+str(np.abs(epsilonMisRegistration.shiftX))+ extraName+'.fits' - name_n = foldername + intMat_name +'_'+ epsilonMisRegistration_name[i]+'_m_'+str(np.abs(epsilonMisRegistration.shiftX))+ extraName+'.fits' - - stroke = 1e-12 - - - #%% -------------------- CENTERED INTERACTION MATRIX -------------------- - try: - if recompute_sensitivity is False: - - hdu = pfits.open(name_0) - calib_0 = CalibrationVault(hdu[1].data,invert=False) - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - print('WARNING: you are loading existing data from \n') - print(str(name_0)+'/n') - print('Make sure that the loaded data correspond to your AO system!') - print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - else: - hdu = pfits.open(name_0+'_volontary_error') - - except: - calib_0 = InteractionMatrix(ngs, atm, tel, dm_0, wfs, basis.modes ,stroke, phaseOffset=0, nMeasurements=50,invert=False,print_time=False) - - # save output in fits file - if save_sensitivity_matrices: - hdr=pfits.Header() - hdr['TITLE'] = 'INTERACTION MATRIX - INITIAL POINT' - empty_primary = pfits.PrimaryHDU(header=hdr) - primary_hdu = pfits.ImageHDU(calib_0.D) - hdu = pfits.HDUList([empty_primary, primary_hdu]) - hdu.writeto(name_0,overwrite=True) - - #%% -------------------- POSITIVE MIS-REGISTRATION -------------------- - try: - if recompute_sensitivity is False: - hdu = pfits.open(name_p) - calib_tmp_p = CalibrationVault(hdu[1].data,invert=False) - else: - hdu = pfits.open(name_0+'_volontary_error') - except: - # set the mis-registration value - misRegistration_tmp = MisRegistration(misRegistrationZeroPoint) - - setattr(misRegistration_tmp,epsilonMisRegistration_field[i],getattr(misRegistration_tmp,epsilonMisRegistration_field[i]) + getattr(epsilonMisRegistration,epsilonMisRegistration_field[i])) - if fast: - dm_0.coefs = np.squeeze(basis.modes) - tel*dm_0 - input_modes_0 = dm_0.OPD - input_modes_cp = input_modes_0.copy() - input_modes_cp = tel.pupil*apply_mis_reg(tel,input_modes_0, misRegistration_tmp) - - calib_tmp_p = InteractionMatrixFromPhaseScreen(ngs,atm,tel,wfs,input_modes_cp,stroke,phaseOffset=0,nMeasurements=50,invert=False,print_time=False) - - else: - # compute new deformable mirror - dm_tmp = applyMisRegistration(tel,misRegistration_tmp,param, wfs = wfs_mis_registrated,print_dm_properties=False, floating_precision=dm_0.floating_precision) - # compute the interaction matrix for the positive mis-registration - calib_tmp_p = InteractionMatrix(ngs, atm, tel, dm_tmp, wfs, basis.modes, stroke, phaseOffset=0, nMeasurements=50,invert=False,print_time=False) - del dm_tmp - - # save output in fits file - if save_sensitivity_matrices: - hdr=pfits.Header() - hdr['TITLE'] = 'INTERACTION MATRIX - POSITIVE MIS-REGISTRATION' - empty_primary = pfits.PrimaryHDU(header=hdr) - primary_hdu = pfits.ImageHDU(calib_tmp_p.D) - hdu = pfits.HDUList([empty_primary, primary_hdu]) - hdu.writeto(name_p,overwrite=True) - - #%% -------------------- NEGATIVE MIS-REGISTRATION -------------------- - try: - if recompute_sensitivity is False: - hdu = pfits.open(name_n) - calib_tmp_n = CalibrationVault(hdu[1].data,invert=False) - else: - hdu = pfits.open(name_0+'_volontary_error') - except: - # set the mis-registration value - misRegistration_tmp = MisRegistration(misRegistrationZeroPoint) - setattr(misRegistration_tmp,epsilonMisRegistration_field[i], getattr(misRegistration_tmp,epsilonMisRegistration_field[i]) - getattr(epsilonMisRegistration,epsilonMisRegistration_field[i])) - if fast: - dm_0.coefs = np.squeeze(basis.modes) - tel*dm_0 - input_modes_0 = dm_0.OPD - input_modes_cp = input_modes_0.copy() - input_modes_cp = tel.pupil*apply_mis_reg(tel,input_modes_0, misRegistration_tmp) - - calib_tmp_n = InteractionMatrixFromPhaseScreen(ngs,atm,tel,wfs,input_modes_cp,stroke,phaseOffset=0,nMeasurements=50,invert=False,print_time=False) - else: - # compute new deformable mirror - dm_tmp = applyMisRegistration(tel,misRegistration_tmp,param, wfs = wfs_mis_registrated,print_dm_properties=False, floating_precision=dm_0.floating_precision) - # compute the interaction matrix for the negative mis-registration - calib_tmp_n = InteractionMatrix(ngs, atm, tel, dm_tmp, wfs, basis.modes, stroke, phaseOffset=0, nMeasurements=50,invert=False,print_time=False) - del dm_tmp - - # save output in fits file - if save_sensitivity_matrices: - hdr=pfits.Header() - hdr['TITLE'] = 'INTERACTION MATRIX - NEGATIVE MIS-REGISTRATION' - empty_primary = pfits.PrimaryHDU(header=hdr) - primary_hdu = pfits.ImageHDU(calib_tmp_n.D) - hdu = pfits.HDUList([empty_primary, primary_hdu]) - hdu.writeto(name_n,overwrite=True) - - #%% -------------------- COMPUTE THE META SENSITIVITY MATRIX -------------------- - # reshape the matrix as a row vector -# print('The value of the epsilon mis-registration is '+ str(getattr(epsilonMisRegistration,epsilonMisRegistration_field[i]))) - try: - row_meta_matrix = np.reshape(((calib_tmp_p.D -calib_tmp_n.D)/2.)/getattr(epsilonMisRegistration,epsilonMisRegistration_field[i]),[calib_tmp_p.D.shape[0]*calib_tmp_p.D.shape[1]]) - except: - row_meta_matrix = np.reshape(((calib_tmp_p.D -calib_tmp_n.D)/2.)/getattr(epsilonMisRegistration,epsilonMisRegistration_field[i]),[calib_tmp_p.D.shape[0]]) - meta_matrix[:,i] = row_meta_matrix - - metaSensitivityMatrix = CalibrationVault(meta_matrix) - - return metaSensitivityMatrix, calib_0 - -def apply_mis_reg(tel,map_2d, misReg): - pixelsize = tel.D/tel.resolution - tel.resetOPD() - # 2) transformations for the mis-registration - anamMatrix = anamorphosisImageMatrix(tel.OPD,misReg.anamorphosisAngle,[1+misReg.radialScaling,1+misReg.tangentialScaling]) - rotMatrix = rotateImageMatrix(tel.OPD,misReg.rotationAngle) - shiftMatrix = translationImageMatrix(tel.OPD,[misReg.shiftY/pixelsize,misReg.shiftX/pixelsize]) #units are in m - - # 3) Global transformation matrix - transformationMatrix = anamMatrix + rotMatrix + shiftMatrix - - def globalTransformation(image): - output = sk.warp(image,(transformationMatrix).inverse,order=3) - return output - out = globalTransformation(map_2d) +# -*- coding: utf-8 -*- +""" +Created on Fri Aug 21 10:22:55 2020 + +@author: cheritie +""" + +import numpy as np +import skimage.transform as sk +from astropy.io import fits as pfits + +from ..MisRegistration import MisRegistration +from ..calibration.CalibrationVault import CalibrationVault +from ..calibration.InteractionMatrix import InteractionMatrix, InteractionMatrixFromPhaseScreen +from ..mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration +from ..tools.interpolateGeometricalTransformation import (anamorphosisImageMatrix, + rotateImageMatrix, + translationImageMatrix) +from ..tools.tools import createFolder + +""" +def computeMetaSensitivityMatrix(nameFolder,nameSystem,tel,atm,ngs,dm_0,pitch,wfs,basis,misRegistrationZeroPoint,epsilonMisRegistration,param): + + Compute the set of sensitivity matrices required to identify the mis-registrations. + + %%%%%%%%%%%%%%%% -- INPUTS -- %%%%%%%%%%%%%%%% + _ nameFolder : folder to store the sensitivity matrices. + _ nameSystem : name of the AO system considered. For instance 'ELT_96x96_R_band' + _ tel : telescope object + _ atm : atmosphere object + _ ngs : source object + _ dm_0 : deformable mirror with reference configuration of mis-registrations + _ pitch : pitch of the dm in [m] + _ wfs : wfs object + _ basis : basis to use to compute the sensitivity matrices. Basis should be an object with the following fields: + basis.modes : [nActuator x nModes] matrix containing the commands to apply the modal basis on the dm + basis.extra : extra name to name the sensitivity matrices for instance 'KL' + _ misRegistrationZeroPoint : mis-registration around which you want to compute the sensitivity matrices + _ epsilonMisRegistration : epsilon value to apply + _ param : dictionnary used as parameter file + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + The function returns the meta-sensitivity matrix that contains all the individual sensitivity matrices reshaped as a vector and concatenated. + + %%%%%%%%%%%%%%%% -- OUTPUTS -- %%%%%%%%%%%%%%%% + _ metaSensitivityMatrix : meta-sensitivity matrix stored as a calibration object. the matrix is accessible with metaSensitivityMatrix.D and its pseudo inverse with metaSensitivityMatrix.M + _ calib_0 : interaction matrix corresponding to misRegistrationZeroPoint stored as a calibration object. + +""" + + +def computeMetaSensitivityMatrix(nameFolder, nameSystem, tel, atm, ngs, dm_0, pitch, wfs, basis, misRegistrationZeroPoint, epsilonMisRegistration, param, wfs_mis_registrated = None,save_sensitivity_matrices=True,fast = False, n_mis_reg = 3, recompute_sensitivity = False): + #%% -------------------- CREATION OF THE DESTINATION FOLDER -------------------- + homeFolder = misRegistrationZeroPoint.misRegName+'/' + intMat_name = 'im' + if basis.modes.shape == np.shape(np.eye(dm_0.nValidAct)): + + comparison = basis.modes == np.eye(dm_0.nValidAct) + if comparison.all(): + foldername = nameFolder+nameSystem+homeFolder+'zon/' + extraName = '' + + else: + foldername = nameFolder+nameSystem+homeFolder+'mod/' + extraName = '_'+basis.extra + else: + foldername = nameFolder+nameSystem+homeFolder+'mod/' + extraName = '_'+basis.extra + + if save_sensitivity_matrices: + createFolder(foldername) + + + + #%% -------------------- COMPUTATION OF THE INTERACTION MATRICES FOLDER -------------------- + epsilonMisRegistration_name = ['dX','dY','dRot','dmX','dmY'] + epsilonMisRegistration_field = ['shiftX','shiftY','rotationAngle','radialScaling','tangentialScaling'] + epsilonMisRegistration_name = epsilonMisRegistration_name[:n_mis_reg] + epsilonMisRegistration_field = epsilonMisRegistration_field[:n_mis_reg] + try: + meta_matrix =np.zeros([wfs.nSignal*basis.modes.shape[1],int(len(epsilonMisRegistration_name))]) + except: + meta_matrix =np.zeros([wfs.nSignal,int(len(epsilonMisRegistration_name))]) + + for i in range(len(epsilonMisRegistration_name)): + # name for the matrices + name_0 = foldername + intMat_name +'_0'+ extraName+'.fits' + name_p = foldername + intMat_name +'_'+ epsilonMisRegistration_name[i]+'_p_'+str(np.abs(epsilonMisRegistration.shiftX))+ extraName+'.fits' + name_n = foldername + intMat_name +'_'+ epsilonMisRegistration_name[i]+'_m_'+str(np.abs(epsilonMisRegistration.shiftX))+ extraName+'.fits' + + stroke = 1e-12 + + + #%% -------------------- CENTERED INTERACTION MATRIX -------------------- + try: + if recompute_sensitivity is False: + + hdu = pfits.open(name_0) + calib_0 = CalibrationVault(hdu[1].data,invert=False) + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + print('WARNING: you are loading existing data from \n') + print(str(name_0)+'/n') + print('Make sure that the loaded data correspond to your AO system!') + print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') + else: + hdu = pfits.open(name_0+'_volontary_error') + + except: + calib_0 = InteractionMatrix(ngs, atm, tel, dm_0, wfs, basis.modes ,stroke, phaseOffset=0, nMeasurements=50,invert=False,print_time=False) + + # save output in fits file + if save_sensitivity_matrices: + hdr=pfits.Header() + hdr['TITLE'] = 'INTERACTION MATRIX - INITIAL POINT' + empty_primary = pfits.PrimaryHDU(header=hdr) + primary_hdu = pfits.ImageHDU(calib_0.D) + hdu = pfits.HDUList([empty_primary, primary_hdu]) + hdu.writeto(name_0,overwrite=True) + + #%% -------------------- POSITIVE MIS-REGISTRATION -------------------- + try: + if recompute_sensitivity is False: + hdu = pfits.open(name_p) + calib_tmp_p = CalibrationVault(hdu[1].data,invert=False) + else: + hdu = pfits.open(name_0+'_volontary_error') + except: + # set the mis-registration value + misRegistration_tmp = MisRegistration(misRegistrationZeroPoint) + + setattr(misRegistration_tmp,epsilonMisRegistration_field[i],getattr(misRegistration_tmp,epsilonMisRegistration_field[i]) + getattr(epsilonMisRegistration,epsilonMisRegistration_field[i])) + if fast: + dm_0.coefs = np.squeeze(basis.modes) + tel*dm_0 + input_modes_0 = dm_0.OPD + input_modes_cp = input_modes_0.copy() + input_modes_cp = tel.pupil*apply_mis_reg(tel,input_modes_0, misRegistration_tmp) + + calib_tmp_p = InteractionMatrixFromPhaseScreen(ngs,atm,tel,wfs,input_modes_cp,stroke,phaseOffset=0,nMeasurements=50,invert=False,print_time=False) + + else: + # compute new deformable mirror + dm_tmp = applyMisRegistration(tel,misRegistration_tmp,param, wfs = wfs_mis_registrated,print_dm_properties=False, floating_precision=dm_0.floating_precision) + # compute the interaction matrix for the positive mis-registration + calib_tmp_p = InteractionMatrix(ngs, atm, tel, dm_tmp, wfs, basis.modes, stroke, phaseOffset=0, nMeasurements=50,invert=False,print_time=False) + del dm_tmp + + # save output in fits file + if save_sensitivity_matrices: + hdr=pfits.Header() + hdr['TITLE'] = 'INTERACTION MATRIX - POSITIVE MIS-REGISTRATION' + empty_primary = pfits.PrimaryHDU(header=hdr) + primary_hdu = pfits.ImageHDU(calib_tmp_p.D) + hdu = pfits.HDUList([empty_primary, primary_hdu]) + hdu.writeto(name_p,overwrite=True) + + #%% -------------------- NEGATIVE MIS-REGISTRATION -------------------- + try: + if recompute_sensitivity is False: + hdu = pfits.open(name_n) + calib_tmp_n = CalibrationVault(hdu[1].data,invert=False) + else: + hdu = pfits.open(name_0+'_volontary_error') + except: + # set the mis-registration value + misRegistration_tmp = MisRegistration(misRegistrationZeroPoint) + setattr(misRegistration_tmp,epsilonMisRegistration_field[i], getattr(misRegistration_tmp,epsilonMisRegistration_field[i]) - getattr(epsilonMisRegistration,epsilonMisRegistration_field[i])) + if fast: + dm_0.coefs = np.squeeze(basis.modes) + tel*dm_0 + input_modes_0 = dm_0.OPD + input_modes_cp = input_modes_0.copy() + input_modes_cp = tel.pupil*apply_mis_reg(tel,input_modes_0, misRegistration_tmp) + + calib_tmp_n = InteractionMatrixFromPhaseScreen(ngs,atm,tel,wfs,input_modes_cp,stroke,phaseOffset=0,nMeasurements=50,invert=False,print_time=False) + else: + # compute new deformable mirror + dm_tmp = applyMisRegistration(tel,misRegistration_tmp,param, wfs = wfs_mis_registrated,print_dm_properties=False, floating_precision=dm_0.floating_precision) + # compute the interaction matrix for the negative mis-registration + calib_tmp_n = InteractionMatrix(ngs, atm, tel, dm_tmp, wfs, basis.modes, stroke, phaseOffset=0, nMeasurements=50,invert=False,print_time=False) + del dm_tmp + + # save output in fits file + if save_sensitivity_matrices: + hdr=pfits.Header() + hdr['TITLE'] = 'INTERACTION MATRIX - NEGATIVE MIS-REGISTRATION' + empty_primary = pfits.PrimaryHDU(header=hdr) + primary_hdu = pfits.ImageHDU(calib_tmp_n.D) + hdu = pfits.HDUList([empty_primary, primary_hdu]) + hdu.writeto(name_n,overwrite=True) + + #%% -------------------- COMPUTE THE META SENSITIVITY MATRIX -------------------- + # reshape the matrix as a row vector +# print('The value of the epsilon mis-registration is '+ str(getattr(epsilonMisRegistration,epsilonMisRegistration_field[i]))) + try: + row_meta_matrix = np.reshape(((calib_tmp_p.D -calib_tmp_n.D)/2.)/getattr(epsilonMisRegistration,epsilonMisRegistration_field[i]),[calib_tmp_p.D.shape[0]*calib_tmp_p.D.shape[1]]) + except: + row_meta_matrix = np.reshape(((calib_tmp_p.D -calib_tmp_n.D)/2.)/getattr(epsilonMisRegistration,epsilonMisRegistration_field[i]),[calib_tmp_p.D.shape[0]]) + meta_matrix[:,i] = row_meta_matrix + + metaSensitivityMatrix = CalibrationVault(meta_matrix) + + return metaSensitivityMatrix, calib_0 + +def apply_mis_reg(tel,map_2d, misReg): + pixelsize = tel.D/tel.resolution + tel.resetOPD() + # 2) transformations for the mis-registration + anamMatrix = anamorphosisImageMatrix(tel.OPD,misReg.anamorphosisAngle,[1+misReg.radialScaling,1+misReg.tangentialScaling]) + rotMatrix = rotateImageMatrix(tel.OPD,misReg.rotationAngle) + shiftMatrix = translationImageMatrix(tel.OPD,[misReg.shiftY/pixelsize,misReg.shiftX/pixelsize]) #units are in m + + # 3) Global transformation matrix + transformationMatrix = anamMatrix + rotMatrix + shiftMatrix + + def globalTransformation(image): + output = sk.warp(image,(transformationMatrix).inverse,order=3) + return output + out = globalTransformation(map_2d) return out \ No newline at end of file diff --git a/AO_modules/mis_registration_identification_algorithm/estimateMisRegistration.py b/OOPAO/mis_registration_identification_algorithm/estimateMisRegistration.py old mode 100755 new mode 100644 similarity index 94% rename from AO_modules/mis_registration_identification_algorithm/estimateMisRegistration.py rename to OOPAO/mis_registration_identification_algorithm/estimateMisRegistration.py index fadf96b..9eb80c9 --- a/AO_modules/mis_registration_identification_algorithm/estimateMisRegistration.py +++ b/OOPAO/mis_registration_identification_algorithm/estimateMisRegistration.py @@ -1,281 +1,283 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Aug 25 14:35:26 2020 - -@author: cheritie -""" -import matplotlib.pyplot as plt -import numpy as np -import scipy.ndimage as sp - -from AO_modules.calibration.InteractionMatrix import InteractionMatrix -from AO_modules.MisRegistration import MisRegistration - -from AO_modules.mis_registration_identification_algorithm.computeMetaSensitivyMatrix import computeMetaSensitivityMatrix -from AO_modules.mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration -from AO_modules.tools.interpolateGeometricalTransformation import rotateImageMatrix,rotation,translationImageMatrix,translation,anamorphosis,anamorphosisImageMatrix - -import skimage.transform as sk -""" -def estimateMisRegistration(nameFolder, nameSystem, tel, atm, ngs, dm_0, wfs, basis, calib_in, misRegistrationZeroPoint, epsilonMisRegistration, param, precision = 3, gainEstimation = 1, return_all = False): - - Compute the set of sensitivity matrices required to identify the mis-registrations. - - %%%%%%%%%%%%%%%% -- INPUTS -- %%%%%%%%%%%%%%%% - _ nameFolder : folder to store the sensitivity matrices. - _ nameSystem : name of the AO system considered. For instance 'ELT_96x96_R_band' - _ tel : telescope object - _ atm : atmosphere object - _ ngs : source object - _ dm_0 : deformable mirror with reference configuration of mis-registrations - _ pitch : pitch of the dm in [m] - _ wfs : wfs object - _ basis : basis to use to compute the sensitivity matrices. Basis should be an object with the following fields: - - basis.modes : [nActuator x nModes] matrix containing the commands to apply the modal basis on the dm - basis.indexModes : indexes of the modes considered in the basis. This is used to name the sensitivity matrices - basis.extra : extra name to name the sensitivity matrices for instance 'KL' - - _ precision : precision to round the parameter estimation. Equivalent to np.round(misReg_estimation,precision) - _ gainEstimation : gain to apply after one estimation. eventually allows to avoid overshoots. - _ return_all : if true, returns all the estimations at every step of the algorithm - _ misRegistrationZeroPoint : mis-registration around which you want to compute the sensitivity matrices - _ epsilonMisRegistration : epsilon value to apply - _ param : dictionnary used as parameter file - - %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - - The function returns the meta-sensitivity matrix that contains all the individual sensitivity matrices reshaped as a vector and concatenated. - - %%%%%%%%%%%%%%%% -- OUTPUTS -- %%%%%%%%%%%%%%%% - _ misRegistration_out : mis-registration object corresponding to the convergence value - _ scalingFactor_values : scaling factor (for each mode of the modal basis) for each iteration to take into consideration eventual gains variation between data and model - _ misRegistration_values : mis-registration values for each iteration - - -""" -def estimateMisRegistration(nameFolder, nameSystem, tel, atm, ngs, dm_0, wfs, basis, calib_in, misRegistrationZeroPoint, epsilonMisRegistration, param, precision = 3, gainEstimation = 1, sensitivity_matrices = None, return_all = False, fast = False, wfs_mis_registrated = None, nIteration = 3): - - #%% ---------- LOAD/COMPUTE SENSITIVITY MATRICES -------------------- - # compute the sensitivity matrices. if the data already exits, the files will be loaded - - # WARNING: The data are loaded only if the name of the requeste files matches the ones in argument of this function. - # make sure that these files are well corresponding to the system you are working with. - - if sensitivity_matrices is None: - [metaMatrix,calib_0] = computeMetaSensitivityMatrix(nameFolder = nameFolder,\ - nameSystem = nameSystem,\ - tel = tel,\ - atm = atm,\ - ngs = ngs,\ - dm_0 = dm_0,\ - pitch = dm_0.pitch,\ - wfs = wfs,\ - basis = basis,\ - misRegistrationZeroPoint = misRegistrationZeroPoint,\ - epsilonMisRegistration = epsilonMisRegistration,\ - param = param,\ - wfs_mis_registrated = wfs_mis_registrated) - else: - metaMatrix = sensitivity_matrices - - - #%% ---------- ITERATIVE ESTIMATION OF THE PARAMETERS -------------------- - stroke = 1e-12 - criteria = 0 - n_mis_reg = metaMatrix.M.shape[0] - misRegEstBuffer = np.zeros(n_mis_reg) - scalingFactor_values = [1] - misRegistration_values = [np.zeros(n_mis_reg)] - - epsilonMisRegistration_field = ['shiftX','shiftY','rotationAngle','radialScaling','tangentialScaling'] - - i_iter=0 - tel.isPaired = False - misRegistration_out = MisRegistration(misRegistrationZeroPoint) - - if fast: - from AO_modules.calibration.InteractionMatrix import InteractionMatrixFromPhaseScreen - - dm_0.coefs = np.squeeze(basis.modes) - tel*dm_0 - input_modes_0 = dm_0.OPD - input_modes_cp = input_modes_0.copy() - while criteria ==0: - i_iter=i_iter+1 - # temporary deformable mirror - if np.ndim(input_modes_0)==2: - if wfs_mis_registrated is not None: - misRegistration_wfs = MisRegistration() - misRegistration_wfs.shiftX = misRegistration_out.shiftX - misRegistration_wfs.shiftY = misRegistration_out.shiftY - - misRegistration_dm = MisRegistration() - misRegistration_dm.rotationAngle = misRegistration_out.rotationAngle - - wfs.apply_shift_wfs( misRegistration_wfs.shiftX , misRegistration_wfs.shiftY) - - input_modes_cp = tel.pupil*apply_mis_reg(tel,input_modes_0, misRegistration_dm) - else: - input_modes_cp = tel.pupil*apply_mis_reg(tel,input_modes_0, misRegistration_out) - else: - - for i_modes in range(input_modes_0.shape[2]): - if wfs_mis_registrated is not None: - - misRegistration_wfs = MisRegistration() - misRegistration_wfs.shiftX = misRegistration_out.shiftX - misRegistration_wfs.shiftY = misRegistration_out.shiftY - - misRegistration_dm = MisRegistration() - misRegistration_dm.rotationAngle = misRegistration_out.rotationAngle - - wfs.apply_shift_wfs(misRegistration_wfs.shiftX, misRegistration_wfs.shiftY) - - input_modes_cp[:,:,i_modes] = tel.pupil*apply_mis_reg(tel,input_modes_0[:,:,i_modes], misRegistration_dm) - else: - input_modes_cp[:,:,i_modes] = tel.pupil*apply_mis_reg(tel,input_modes_0[:,:,i_modes], misRegistration_out) - - - # temporary interaction matrix - calib_tmp = InteractionMatrixFromPhaseScreen(ngs,atm,tel,wfs,input_modes_cp,stroke,phaseOffset=0,nMeasurements=5,invert=False,print_time=False) - # temporary scaling factor - try: - scalingFactor_tmp = np.round(np.diag(calib_tmp.D.T@calib_in.D)/ np.diag(calib_tmp.D.T@calib_tmp.D),precision) - contain_nan = False - for i in scalingFactor_tmp: - if(np.isnan(i)): - contain_nan = True - break - if contain_nan: - print('Nan Warning !!') - scalingFactor_tmp[:] = 1 - - - # temporary mis-registration - misReg_tmp = gainEstimation*np.matmul(metaMatrix.M,np.reshape( calib_in.D@np.diag(1/scalingFactor_tmp) - calib_tmp.D ,calib_in.D.shape[0]*calib_in.D.shape[1])) - except: - scalingFactor_tmp = np.round(np.sum(np.squeeze(calib_tmp.D)*np.squeeze(calib_in.D))/ np.sum(np.squeeze(calib_tmp.D)*np.squeeze(calib_tmp.D)),precision) - contain_nan = False - if np.isnan(scalingFactor_tmp): - scalingFactor_tmp = 1 - contain_nan = True - print('Nan Warning !!') - - - # temporary mis-registration - misReg_tmp = gainEstimation*np.matmul(metaMatrix.M,np.squeeze((np.squeeze(calib_in.D)*(1/scalingFactor_tmp)) - np.squeeze(calib_tmp.D))) - # cumulative mis-registration - misRegEstBuffer+= np.round(misReg_tmp,precision) - - # define the next working point to adjust the scaling factor - for i_mis_reg in range(n_mis_reg): - setattr(misRegistration_out, epsilonMisRegistration_field[i_mis_reg], getattr(misRegistration_out, epsilonMisRegistration_field[i_mis_reg]) + np.round(misReg_tmp[i_mis_reg],precision)) - - # save the data for each iteration - scalingFactor_values.append(np.copy(scalingFactor_tmp)) - misRegistration_values.append(np.copy(misRegEstBuffer)) - - - if i_iter==nIteration: - criteria =1 - - else: - while criteria ==0: - i_iter=i_iter+1 - # temporary deformable mirror - dm_tmp = applyMisRegistration(tel,misRegistration_out,param, wfs = wfs_mis_registrated,print_dm_properties=False,floating_precision=dm_0.floating_precision) - - # temporary interaction matrix - calib_tmp = InteractionMatrix(ngs,atm,tel,dm_tmp,wfs,basis.modes,stroke,phaseOffset=0,nMeasurements=50,invert=False,print_time=False) - # erase dm_tmp to free memory - del dm_tmp - # temporary scaling factor - try: - scalingFactor_tmp = np.round(np.diag(calib_tmp.D.T@calib_in.D)/ np.diag(calib_tmp.D.T@calib_tmp.D),precision) - contain_nan = False - for i in scalingFactor_tmp: - if(np.isnan(i)): - contain_nan = True - break - if contain_nan: - print('Nan Warning !!') - scalingFactor_tmp[:] = 1 - # temporary mis-registration - misReg_tmp = gainEstimation*np.matmul(metaMatrix.M,np.reshape( calib_in.D@np.diag(1/scalingFactor_tmp) - calib_tmp.D ,calib_in.D.shape[0]*calib_in.D.shape[1])) - - except: - scalingFactor_tmp = np.round(np.sum(np.squeeze(calib_tmp.D)*np.squeeze(calib_in.D))/ np.sum(np.squeeze(calib_tmp.D)*np.squeeze(calib_tmp.D)),precision) - contain_nan = False - if np.isnan(scalingFactor_tmp): - scalingFactor_tmp = 1 - contain_nan = True - print('Nan Warning !!') - # temporary mis-registration - misReg_tmp = gainEstimation*np.matmul(metaMatrix.M,np.squeeze((np.squeeze(calib_in.D)*(1/scalingFactor_tmp)) - np.squeeze(calib_tmp.D))) - # cumulative mis-registration - misRegEstBuffer+= np.round(misReg_tmp,precision) - - # define the next working point to adjust the scaling factor - if contain_nan is False: - for i_mis_reg in range(n_mis_reg): - setattr(misRegistration_out, epsilonMisRegistration_field[i_mis_reg], getattr(misRegistration_out, epsilonMisRegistration_field[i_mis_reg]) + np.round(misReg_tmp[i_mis_reg],precision)) - - - # save the data for each iteration - scalingFactor_values.append(np.copy(scalingFactor_tmp)) - misRegistration_values.append(np.copy(misRegEstBuffer)) - - - if i_iter==nIteration: - criteria =1 - - misRegistration_out.shiftX = np.round(misRegistration_out.shiftX,precision) - misRegistration_out.shiftY = np.round(misRegistration_out.shiftY,precision) - misRegistration_out.rotationAngle = np.round(misRegistration_out.rotationAngle,precision) - misRegistration_out.radialScaling = np.round(misRegistration_out.radialScaling,precision) - misRegistration_out.tangentialScaling = np.round(misRegistration_out.tangentialScaling,precision) - - # values for validity - - tolerance = [dm_0.pitch/50,dm_0.pitch/50,np.rad2deg(np.arctan((dm_0.pitch/50)/(tel.D/2))),0.05,0.05] - - diff = np.abs(misRegistration_values[-1]-misRegistration_values[-2]) - - # in case of nan - diff[np.where(np.isnan(diff))] = 10000 - if np.argwhere(diff-tolerance[:n_mis_reg]>0).size==0 or contain_nan: - # validity of the mis-reg - validity_flag = True - else: - validity_flag = False - misRegistration_out.shiftX = 0*np.round(misRegistration_out.shiftX,precision) - misRegistration_out.shiftY = 0*np.round(misRegistration_out.shiftY,precision) - misRegistration_out.rotationAngle = 0*np.round(misRegistration_out.rotationAngle,precision) - misRegistration_out.radialScaling = 0*np.round(misRegistration_out.radialScaling,precision) - misRegistration_out.tangentialScaling = 0*np.round(misRegistration_out.tangentialScaling,precision) - - if return_all: - return misRegistration_out, scalingFactor_values, misRegistration_values,validity_flag - else: - return misRegistration_out - - - -def apply_mis_reg(tel,map_2d, misReg): - pixelsize = tel.D/tel.resolution - tel.resetOPD() - # 2) transformations for the mis-registration - anamMatrix = anamorphosisImageMatrix(tel.OPD,misReg.anamorphosisAngle,[1+misReg.radialScaling,1+misReg.tangentialScaling]) - rotMatrix = rotateImageMatrix(tel.OPD,misReg.rotationAngle) - shiftMatrix = translationImageMatrix(tel.OPD,[misReg.shiftY/pixelsize,misReg.shiftX/pixelsize]) #units are in m - - # 3) Global transformation matrix - transformationMatrix = anamMatrix + rotMatrix + shiftMatrix - - def globalTransformation(image): - output = sk.warp(image,(transformationMatrix).inverse,order=3) - return output - out = globalTransformation(map_2d) - return out - +# -*- coding: utf-8 -*- +""" +Created on Tue Aug 25 14:35:26 2020 + +@author: cheritie +""" + +import numpy as np +import skimage.transform as sk + +from ..MisRegistration import MisRegistration +from ..calibration.InteractionMatrix import InteractionMatrix +from ..mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration +from ..mis_registration_identification_algorithm.computeMetaSensitivyMatrix import computeMetaSensitivityMatrix +from ..tools.interpolateGeometricalTransformation import (anamorphosisImageMatrix, + rotateImageMatrix, + translationImageMatrix) + +""" +def estimateMisRegistration(nameFolder, nameSystem, tel, atm, ngs, dm_0, wfs, basis, calib_in, misRegistrationZeroPoint, epsilonMisRegistration, param, precision = 3, gainEstimation = 1, return_all = False): + + Compute the set of sensitivity matrices required to identify the mis-registrations. + + %%%%%%%%%%%%%%%% -- INPUTS -- %%%%%%%%%%%%%%%% + _ nameFolder : folder to store the sensitivity matrices. + _ nameSystem : name of the AO system considered. For instance 'ELT_96x96_R_band' + _ tel : telescope object + _ atm : atmosphere object + _ ngs : source object + _ dm_0 : deformable mirror with reference configuration of mis-registrations + _ pitch : pitch of the dm in [m] + _ wfs : wfs object + _ basis : basis to use to compute the sensitivity matrices. Basis should be an object with the following fields: + + basis.modes : [nActuator x nModes] matrix containing the commands to apply the modal basis on the dm + basis.indexModes : indexes of the modes considered in the basis. This is used to name the sensitivity matrices + basis.extra : extra name to name the sensitivity matrices for instance 'KL' + + _ precision : precision to round the parameter estimation. Equivalent to np.round(misReg_estimation,precision) + _ gainEstimation : gain to apply after one estimation. eventually allows to avoid overshoots. + _ return_all : if true, returns all the estimations at every step of the algorithm + _ misRegistrationZeroPoint : mis-registration around which you want to compute the sensitivity matrices + _ epsilonMisRegistration : epsilon value to apply + _ param : dictionnary used as parameter file + + %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + The function returns the meta-sensitivity matrix that contains all the individual sensitivity matrices reshaped as a vector and concatenated. + + %%%%%%%%%%%%%%%% -- OUTPUTS -- %%%%%%%%%%%%%%%% + _ misRegistration_out : mis-registration object corresponding to the convergence value + _ scalingFactor_values : scaling factor (for each mode of the modal basis) for each iteration to take into consideration eventual gains variation between data and model + _ misRegistration_values : mis-registration values for each iteration + + +""" + + +def estimateMisRegistration(nameFolder, nameSystem, tel, atm, ngs, dm_0, wfs, basis, calib_in, misRegistrationZeroPoint, epsilonMisRegistration, param, precision = 3, gainEstimation = 1, sensitivity_matrices = None, return_all = False, fast = False, wfs_mis_registrated = None, nIteration = 3): + + #%% ---------- LOAD/COMPUTE SENSITIVITY MATRICES -------------------- + # compute the sensitivity matrices. if the data already exits, the files will be loaded + + # WARNING: The data are loaded only if the name of the requeste files matches the ones in argument of this function. + # make sure that these files are well corresponding to the system you are working with. + + if sensitivity_matrices is None: + [metaMatrix,calib_0] = computeMetaSensitivityMatrix(nameFolder = nameFolder,\ + nameSystem = nameSystem,\ + tel = tel,\ + atm = atm,\ + ngs = ngs,\ + dm_0 = dm_0,\ + pitch = dm_0.pitch,\ + wfs = wfs,\ + basis = basis,\ + misRegistrationZeroPoint = misRegistrationZeroPoint,\ + epsilonMisRegistration = epsilonMisRegistration,\ + param = param,\ + wfs_mis_registrated = wfs_mis_registrated) + else: + metaMatrix = sensitivity_matrices + + + #%% ---------- ITERATIVE ESTIMATION OF THE PARAMETERS -------------------- + stroke = 1e-12 + criteria = 0 + n_mis_reg = metaMatrix.M.shape[0] + misRegEstBuffer = np.zeros(n_mis_reg) + scalingFactor_values = [1] + misRegistration_values = [np.zeros(n_mis_reg)] + + epsilonMisRegistration_field = ['shiftX','shiftY','rotationAngle','radialScaling','tangentialScaling'] + + i_iter=0 + tel.isPaired = False + misRegistration_out = MisRegistration(misRegistrationZeroPoint) + + if fast: + from AO_modules.calibration.InteractionMatrix import InteractionMatrixFromPhaseScreen + + dm_0.coefs = np.squeeze(basis.modes) + tel*dm_0 + input_modes_0 = dm_0.OPD + input_modes_cp = input_modes_0.copy() + while criteria ==0: + i_iter=i_iter+1 + # temporary deformable mirror + if np.ndim(input_modes_0)==2: + if wfs_mis_registrated is not None: + misRegistration_wfs = MisRegistration() + misRegistration_wfs.shiftX = misRegistration_out.shiftX + misRegistration_wfs.shiftY = misRegistration_out.shiftY + + misRegistration_dm = MisRegistration() + misRegistration_dm.rotationAngle = misRegistration_out.rotationAngle + + wfs.apply_shift_wfs( misRegistration_wfs.shiftX , misRegistration_wfs.shiftY) + + input_modes_cp = tel.pupil*apply_mis_reg(tel,input_modes_0, misRegistration_dm) + else: + input_modes_cp = tel.pupil*apply_mis_reg(tel,input_modes_0, misRegistration_out) + else: + + for i_modes in range(input_modes_0.shape[2]): + if wfs_mis_registrated is not None: + + misRegistration_wfs = MisRegistration() + misRegistration_wfs.shiftX = misRegistration_out.shiftX + misRegistration_wfs.shiftY = misRegistration_out.shiftY + + misRegistration_dm = MisRegistration() + misRegistration_dm.rotationAngle = misRegistration_out.rotationAngle + + wfs.apply_shift_wfs(misRegistration_wfs.shiftX, misRegistration_wfs.shiftY) + + input_modes_cp[:,:,i_modes] = tel.pupil*apply_mis_reg(tel,input_modes_0[:,:,i_modes], misRegistration_dm) + else: + input_modes_cp[:,:,i_modes] = tel.pupil*apply_mis_reg(tel,input_modes_0[:,:,i_modes], misRegistration_out) + + + # temporary interaction matrix + calib_tmp = InteractionMatrixFromPhaseScreen(ngs,atm,tel,wfs,input_modes_cp,stroke,phaseOffset=0,nMeasurements=5,invert=False,print_time=False) + # temporary scaling factor + try: + scalingFactor_tmp = np.round(np.diag(calib_tmp.D.T@calib_in.D)/ np.diag(calib_tmp.D.T@calib_tmp.D),precision) + contain_nan = False + for i in scalingFactor_tmp: + if(np.isnan(i)): + contain_nan = True + break + if contain_nan: + print('Nan Warning !!') + scalingFactor_tmp[:] = 1 + + + # temporary mis-registration + misReg_tmp = gainEstimation*np.matmul(metaMatrix.M,np.reshape( calib_in.D@np.diag(1/scalingFactor_tmp) - calib_tmp.D ,calib_in.D.shape[0]*calib_in.D.shape[1])) + except: + scalingFactor_tmp = np.round(np.sum(np.squeeze(calib_tmp.D)*np.squeeze(calib_in.D))/ np.sum(np.squeeze(calib_tmp.D)*np.squeeze(calib_tmp.D)),precision) + contain_nan = False + if np.isnan(scalingFactor_tmp): + scalingFactor_tmp = 1 + contain_nan = True + print('Nan Warning !!') + + + # temporary mis-registration + misReg_tmp = gainEstimation*np.matmul(metaMatrix.M,np.squeeze((np.squeeze(calib_in.D)*(1/scalingFactor_tmp)) - np.squeeze(calib_tmp.D))) + # cumulative mis-registration + misRegEstBuffer+= np.round(misReg_tmp,precision) + + # define the next working point to adjust the scaling factor + for i_mis_reg in range(n_mis_reg): + setattr(misRegistration_out, epsilonMisRegistration_field[i_mis_reg], getattr(misRegistration_out, epsilonMisRegistration_field[i_mis_reg]) + np.round(misReg_tmp[i_mis_reg],precision)) + + # save the data for each iteration + scalingFactor_values.append(np.copy(scalingFactor_tmp)) + misRegistration_values.append(np.copy(misRegEstBuffer)) + + + if i_iter==nIteration: + criteria =1 + + else: + while criteria ==0: + i_iter=i_iter+1 + # temporary deformable mirror + dm_tmp = applyMisRegistration(tel,misRegistration_out,param, wfs = wfs_mis_registrated,print_dm_properties=False,floating_precision=dm_0.floating_precision) + + # temporary interaction matrix + calib_tmp = InteractionMatrix(ngs,atm,tel,dm_tmp,wfs,basis.modes,stroke,phaseOffset=0,nMeasurements=50,invert=False,print_time=False) + # erase dm_tmp to free memory + del dm_tmp + # temporary scaling factor + try: + scalingFactor_tmp = np.round(np.diag(calib_tmp.D.T@calib_in.D)/ np.diag(calib_tmp.D.T@calib_tmp.D),precision) + contain_nan = False + for i in scalingFactor_tmp: + if(np.isnan(i)): + contain_nan = True + break + if contain_nan: + print('Nan Warning !!') + scalingFactor_tmp[:] = 1 + # temporary mis-registration + misReg_tmp = gainEstimation*np.matmul(metaMatrix.M,np.reshape( calib_in.D@np.diag(1/scalingFactor_tmp) - calib_tmp.D ,calib_in.D.shape[0]*calib_in.D.shape[1])) + + except: + scalingFactor_tmp = np.round(np.sum(np.squeeze(calib_tmp.D)*np.squeeze(calib_in.D))/ np.sum(np.squeeze(calib_tmp.D)*np.squeeze(calib_tmp.D)),precision) + contain_nan = False + if np.isnan(scalingFactor_tmp): + scalingFactor_tmp = 1 + contain_nan = True + print('Nan Warning !!') + # temporary mis-registration + misReg_tmp = gainEstimation*np.matmul(metaMatrix.M,np.squeeze((np.squeeze(calib_in.D)*(1/scalingFactor_tmp)) - np.squeeze(calib_tmp.D))) + # cumulative mis-registration + misRegEstBuffer+= np.round(misReg_tmp,precision) + + # define the next working point to adjust the scaling factor + if contain_nan is False: + for i_mis_reg in range(n_mis_reg): + setattr(misRegistration_out, epsilonMisRegistration_field[i_mis_reg], getattr(misRegistration_out, epsilonMisRegistration_field[i_mis_reg]) + np.round(misReg_tmp[i_mis_reg],precision)) + + + # save the data for each iteration + scalingFactor_values.append(np.copy(scalingFactor_tmp)) + misRegistration_values.append(np.copy(misRegEstBuffer)) + + + if i_iter==nIteration: + criteria =1 + + misRegistration_out.shiftX = np.round(misRegistration_out.shiftX,precision) + misRegistration_out.shiftY = np.round(misRegistration_out.shiftY,precision) + misRegistration_out.rotationAngle = np.round(misRegistration_out.rotationAngle,precision) + misRegistration_out.radialScaling = np.round(misRegistration_out.radialScaling,precision) + misRegistration_out.tangentialScaling = np.round(misRegistration_out.tangentialScaling,precision) + + # values for validity + + tolerance = [dm_0.pitch/50,dm_0.pitch/50,np.rad2deg(np.arctan((dm_0.pitch/50)/(tel.D/2))),0.05,0.05] + + diff = np.abs(misRegistration_values[-1]-misRegistration_values[-2]) + + # in case of nan + diff[np.where(np.isnan(diff))] = 10000 + if np.argwhere(diff-tolerance[:n_mis_reg]>0).size==0 or contain_nan: + # validity of the mis-reg + validity_flag = True + else: + validity_flag = False + misRegistration_out.shiftX = 0*np.round(misRegistration_out.shiftX,precision) + misRegistration_out.shiftY = 0*np.round(misRegistration_out.shiftY,precision) + misRegistration_out.rotationAngle = 0*np.round(misRegistration_out.rotationAngle,precision) + misRegistration_out.radialScaling = 0*np.round(misRegistration_out.radialScaling,precision) + misRegistration_out.tangentialScaling = 0*np.round(misRegistration_out.tangentialScaling,precision) + + if return_all: + return misRegistration_out, scalingFactor_values, misRegistration_values,validity_flag + else: + return misRegistration_out + + + +def apply_mis_reg(tel,map_2d, misReg): + pixelsize = tel.D/tel.resolution + tel.resetOPD() + # 2) transformations for the mis-registration + anamMatrix = anamorphosisImageMatrix(tel.OPD,misReg.anamorphosisAngle,[1+misReg.radialScaling,1+misReg.tangentialScaling]) + rotMatrix = rotateImageMatrix(tel.OPD,misReg.rotationAngle) + shiftMatrix = translationImageMatrix(tel.OPD,[misReg.shiftY/pixelsize,misReg.shiftX/pixelsize]) #units are in m + + # 3) Global transformation matrix + transformationMatrix = anamMatrix + rotMatrix + shiftMatrix + + def globalTransformation(image): + output = sk.warp(image,(transformationMatrix).inverse,order=3) + return output + out = globalTransformation(map_2d) + return out + diff --git a/AO_modules/phaseStats.py b/OOPAO/phaseStats.py old mode 100755 new mode 100644 similarity index 95% rename from AO_modules/phaseStats.py rename to OOPAO/phaseStats.py index 4a88551..33167d9 --- a/AO_modules/phaseStats.py +++ b/OOPAO/phaseStats.py @@ -1,319 +1,318 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Aug 18 08:49:50 2020 - -@author: cheritie -""" - -import numpy as np -import scipy as sp -from AO_modules.tools.tools import * - -import jsonpickle -import json - -import random -import time -from numpy.random import RandomState - - - -def gamma(x): - return np.math.gamma(x) - -def variance(atm): - # compute the phase variance from an atmosphere object - L0r0Ratio = (atm.L0/atm.r0)**(5./3) - - out = ( (24.*gamma(6./5))**(5./6) )*\ - ( gamma(11./6)*gamma(5./6) )/\ - ( 2*np.pi**(8./3) )*\ - ( L0r0Ratio ) - - out = np.sum(atm.cn2)*out - return out - - -def covariance(rho,atm): - # compute the phase covariance from the baseline rho and an atmosphere object - L0r0Ratio = (atm.L0/atm.r0)**(5./3) - - cst = ( (24.*gamma(6./5)/5)**(5./6) )*\ - ( gamma(11./6)/((2.**(5./6)) )*\ - ( np.pi**(8./3)) )*\ - ( L0r0Ratio ) - - out = ( np.ones(rho.shape) )*\ - ( (24.*gamma(6./5)/5)**(5./6) )*\ - ( gamma(11./6)*gamma(5./6) )/\ - ( 2*np.pi**(8./3) )*\ - ( L0r0Ratio ) - - index = np.where(rho!=0) - - u = 2*np.pi*rho[index]/atm.L0 - out[index] = cst*u**(5./6)*sp.special.kv(5./6,u) - return out - - - -def spectrum(f,atm): - # compute the phase power spectral density from the spatial frequency f and an atmsphere object - out = ( (24.*gamma(6./5)/5)**(5./6) )*\ - ( gamma(11./6)**2 )/\ - ( 2*np.pi**(11./3) )*\ - ( atm.r0**(-5/3) ) - - out = out * (f**2 + 1./atm.L0**2)**(-11/6) - return out - - -def makeCovarianceMatrix(rho1,rho2,atm): - - rho = np.abs(bsxfunMinus(rho1,rho2)) - - L0r0ratio = (atm.L0/atm.r0_def)**(5./3) - - cst = (24.*np.math.gamma(6./5)/5)**(5./6) * (np.math.gamma(11./6)/((2.**(5./6))*np.pi**(8./3)))*L0r0ratio - - out = np.ones(rho.shape)*((24.*np.math.gamma(6./5)/5)**(5./6))*(np.math.gamma(11./6)*np.math.gamma(5./6)/(2*np.pi**(8./3))) * L0r0ratio - - index = np.where(rho!=0) - - u = 2*np.pi*rho[index]/atm.L0 - - - if atm.param is None: - sp_kv = sp.special.kv(5./6,u) - - else: - try: - print('Loading pre-computed data...') - name_data = 'sp_kv_L0_'+str(atm.param['L0'])+'_m_shape_'+str(len(rho1))+'x'+str(len(rho2))+'.json' - - location_data = atm.param['pathInput'] + atm.param['name'] + '/sk_v/' - - try: - with open(location_data+name_data ) as f: - C = json.load(f) - data_loaded = jsonpickle.decode(C) - except: - createFolder(location_data) - with open(location_data+name_data ) as f: - C = json.load(f) - data_loaded = jsonpickle.decode(C) - - sp_kv = data_loaded['sp_kv'] - - - except: - print('Something went wrong.. re-computing sp_kv ...') - name_data = 'sp_kv_L0_'+str(atm.param['L0'])+'_m_shape_'+str(len(rho1))+'x'+str(len(rho2))+'.json' - location_data = atm.param['pathInput'] + atm.param['name'] + '/sk_v/' - - sp_kv = sp.special.kv(5./6,u) - - print('saving for future...') - data = dict() - data['sp_kv'] = sp_kv - data_encoded = jsonpickle.encode(data) - - try: - with open(location_data+name_data, 'w') as f: - json.dump(data_encoded, f) - except: - createFolder(location_data) - with open(location_data+name_data, 'w') as f: - json.dump(data_encoded, f) - - - - out[index] = cst*u**(5./6)*sp_kv - - - return out - -#def fourierPhaseScreen(atm,D,resolution): -# -# N = int(8* resolution) -# -# L = (N-1)*D/(resolution-1) -# -# -# fx = np.fft.fftshift(np.fft.fftfreq(N)) -# fy = np.fft.fftshift(np.fft.fftfreq(N)) -# -# fx,fy = np.meshgrid(fx,fy) -# f_r,f_phi = cart2pol(fx,fx) -# -# f_r = np.fft.fftshift(f_r*(N-1)/L/2) -# f_r+=f_r.T -# -# psdRoot = np.sqrt(spectrum(f_r,atm)) -# index = np.where(f_r==0) -# psdRoot[index]=0 -# -# # low order correc -# -# fourierSampling = 1./L -# -# u = np.arange(resolution+1) -# WNF = np.fft.fft2(np.random.randn(N,N))/N -# -# outMap = psdRoot*WNF -# outMap = np.real(np.fft.ifft2(outMap))*fourierSampling*N**2 -# -# out = outMap[u[0]:u[-1],u[0]:u[-1]] -# -# -# return out - -def ift2(G, delta_f): - """ - ------------ Function adapted from aotools ---------------------- - - Wrapper for inverse fourier transform - - Parameters: - G: data to transform - delta_f: pixel seperation - FFT (FFT object, optional): An accelerated FFT object - """ - - N = G.shape[0] - -# g = np.fft.fftshift( np.fft.fft2( np.fft.fftshift(G) ) ) * (N * delta_f)**2 - g = np.fft.fftshift( np.fft.fft2( np.fft.fftshift(G) ) ) - - return g - - -def ft_phase_screen(atm, N, delta, l0 =1e-10,seed=None): - ''' - ------------ Function adapted from aotools ---------------------- - - Creates a random phase screen with Von Karmen statistics. - (Schmidt 2010) - - Parameters: - r0 (float): r0 parameter of scrn in metres - N (int): Size of phase scrn in pxls - delta (float): size in Metres of each pxl - L0 (float): Size of outer-scale in metres - l0 (float): inner scale in metres - - Returns: - ndarray: np array representing phase screen - ''' - delta = float(delta) - r0 = float(atm.r0) - L0 = float(atm.L0) - R = random.SystemRandom(time.time()) - if seed is None: - seed = int(R.random()*100000) - randomState = RandomState(seed) - - del_f = 1./(N*delta) - - fx = np.arange(-N/2.,N/2.) * del_f - - (fx,fy) = np.meshgrid(fx,fx) - f = np.sqrt(fx**2 + fy**2) - - fm = 5.92/l0/(2*np.pi) - f0 = 1./L0 - - PSD_phi = (0.023*r0**(-5./3.) * np.exp(-1*((f/fm)**2)) / - ( ( (f**2) + (f0**2) )**(11./6) ) ) - - PSD_phi[int(N/2), int(N/2)] = 0 - - cn = ( (randomState.normal(size=(N,N)) + 1j* randomState.normal(size=(N,N)) ) - * np.sqrt(PSD_phi)*del_f ) - - phs = ift2(cn,1).real - - return phs - - - - - - - -def ft_sh_phase_screen(atm, N, delta, l0= 1e-10, seed=None): - - """ - ------------ Function adapted from aotools ---------------------- - - Creates a random phase screen with Von Karmen statistics with added - sub-harmonics to augment tip-tilt modes. - (Schmidt 2010) - - Args: - r0 (float): r0 parameter of scrn in metres - N (int): Size of phase scrn in pxls - delta (float): size in Metres of each pxl - L0 (float): Size of outer-scale in metres - l0 (float): inner scale in metres - - Returns: - ndarray: np array representing phase screen - """ - delta = float(delta) - r0 = float(atm.r0) - L0 = float(atm.L0) - R = random.SystemRandom(time.time()) - if seed is None: - seed = int(R.random()*100000) - randomState = RandomState(seed) - - D = N*delta - # high-frequency screen from FFT method - phs_hi = ft_phase_screen(atm, N, delta, seed=seed) - - # spatial grid [m] - coords = np.arange(-N/2,N/2)*delta - x, y = np.meshgrid(coords,coords) - - # initialize low-freq screen - phs_lo = np.zeros(phs_hi.shape) - - # loop over frequency grids with spacing 1/(3^p*L) - for p in range(1,4): - # setup the PSD - del_f = 1 / (3**p*D) #frequency grid spacing [1/m] - fx = np.arange(-1,2) * del_f - - # frequency grid [1/m] - fx, fy = np.meshgrid(fx,fx) - f = np.sqrt(fx**2 + fy**2) # polar grid - - fm = 5.92/l0/(2*np.pi) # inner scale frequency [1/m] - f0 = 1./L0 - - # outer scale frequency [1/m] - # modified von Karman atmospheric phase PSD - PSD_phi = (0.023*r0**(-5./3) - * np.exp(-1*(f/fm)**2) / ((f**2 + f0**2)**(11./6)) ) - PSD_phi[1,1] = 0 - - # random draws of Fourier coefficients - cn = ( (randomState.normal(size=(3,3)) - + 1j*randomState.normal(size=(3,3)) ) - * np.sqrt(PSD_phi)*del_f ) - SH = np.zeros((N,N),dtype="complex") - # loop over frequencies on this grid - for i in range(0,2): - for j in range(0,2): - - SH += cn[i,j] * np.exp(1j*2*np.pi*(fx[i,j]*x+fy[i,j]*y)) - - phs_lo = phs_lo + SH - # accumulate subharmonics - - phs_lo = phs_lo.real - phs_lo.real.mean() - - phs = phs_lo+phs_hi - +# -*- coding: utf-8 -*- +""" +Created on Tue Aug 18 08:49:50 2020 + +@author: cheritie +""" + +import json +import random +import time + +import jsonpickle +import numpy as np +import scipy as sp +from numpy.random import RandomState + +from .tools.tools import bsxfunMinus, createFolder + + +def gamma(x): + return np.math.gamma(x) + +def variance(atm): + # compute the phase variance from an atmosphere object + L0r0Ratio = (atm.L0/atm.r0)**(5./3) + + out = ( (24.*gamma(6./5))**(5./6) )*\ + ( gamma(11./6)*gamma(5./6) )/\ + ( 2*np.pi**(8./3) )*\ + ( L0r0Ratio ) + + out = np.sum(atm.cn2)*out + return out + + +def covariance(rho,atm): + # compute the phase covariance from the baseline rho and an atmosphere object + L0r0Ratio = (atm.L0/atm.r0)**(5./3) + + cst = ( (24.*gamma(6./5)/5)**(5./6) )*\ + ( gamma(11./6)/((2.**(5./6)) )*\ + ( np.pi**(8./3)) )*\ + ( L0r0Ratio ) + + out = ( np.ones(rho.shape) )*\ + ( (24.*gamma(6./5)/5)**(5./6) )*\ + ( gamma(11./6)*gamma(5./6) )/\ + ( 2*np.pi**(8./3) )*\ + ( L0r0Ratio ) + + index = np.where(rho!=0) + + u = 2*np.pi*rho[index]/atm.L0 + out[index] = cst*u**(5./6)*sp.special.kv(5./6,u) + return out + + + +def spectrum(f,atm): + # compute the phase power spectral density from the spatial frequency f and an atmsphere object + out = ( (24.*gamma(6./5)/5)**(5./6) )*\ + ( gamma(11./6)**2 )/\ + ( 2*np.pi**(11./3) )*\ + ( atm.r0**(-5/3) ) + + out = out * (f**2 + 1./atm.L0**2)**(-11/6) + return out + + +def makeCovarianceMatrix(rho1,rho2,atm): + + rho = np.abs(bsxfunMinus(rho1,rho2)) + + L0r0ratio = (atm.L0/atm.r0_def)**(5./3) + + cst = (24.*np.math.gamma(6./5)/5)**(5./6) * (np.math.gamma(11./6)/((2.**(5./6))*np.pi**(8./3)))*L0r0ratio + + out = np.ones(rho.shape)*((24.*np.math.gamma(6./5)/5)**(5./6))*(np.math.gamma(11./6)*np.math.gamma(5./6)/(2*np.pi**(8./3))) * L0r0ratio + + index = np.where(rho!=0) + + u = 2*np.pi*rho[index]/atm.L0 + + + if atm.param is None: + sp_kv = sp.special.kv(5./6,u) + + else: + try: + print('Loading pre-computed data...') + name_data = 'sp_kv_L0_'+str(atm.param['L0'])+'_m_shape_'+str(len(rho1))+'x'+str(len(rho2))+'.json' + + location_data = atm.param['pathInput'] + atm.param['name'] + '/sk_v/' + + try: + with open(location_data+name_data ) as f: + C = json.load(f) + data_loaded = jsonpickle.decode(C) + except: + createFolder(location_data) + with open(location_data+name_data ) as f: + C = json.load(f) + data_loaded = jsonpickle.decode(C) + + sp_kv = data_loaded['sp_kv'] + + + except: + print('Something went wrong.. re-computing sp_kv ...') + name_data = 'sp_kv_L0_'+str(atm.param['L0'])+'_m_shape_'+str(len(rho1))+'x'+str(len(rho2))+'.json' + location_data = atm.param['pathInput'] + atm.param['name'] + '/sk_v/' + + sp_kv = sp.special.kv(5./6,u) + + print('saving for future...') + data = dict() + data['sp_kv'] = sp_kv + data_encoded = jsonpickle.encode(data) + + try: + with open(location_data+name_data, 'w') as f: + json.dump(data_encoded, f) + except: + createFolder(location_data) + with open(location_data+name_data, 'w') as f: + json.dump(data_encoded, f) + + + + out[index] = cst*u**(5./6)*sp_kv + + + return out + +#def fourierPhaseScreen(atm,D,resolution): +# +# N = int(8* resolution) +# +# L = (N-1)*D/(resolution-1) +# +# +# fx = np.fft.fftshift(np.fft.fftfreq(N)) +# fy = np.fft.fftshift(np.fft.fftfreq(N)) +# +# fx,fy = np.meshgrid(fx,fy) +# f_r,f_phi = cart2pol(fx,fx) +# +# f_r = np.fft.fftshift(f_r*(N-1)/L/2) +# f_r+=f_r.T +# +# psdRoot = np.sqrt(spectrum(f_r,atm)) +# index = np.where(f_r==0) +# psdRoot[index]=0 +# +# # low order correc +# +# fourierSampling = 1./L +# +# u = np.arange(resolution+1) +# WNF = np.fft.fft2(np.random.randn(N,N))/N +# +# outMap = psdRoot*WNF +# outMap = np.real(np.fft.ifft2(outMap))*fourierSampling*N**2 +# +# out = outMap[u[0]:u[-1],u[0]:u[-1]] +# +# +# return out + +def ift2(G, delta_f): + """ + ------------ Function adapted from aotools ---------------------- + + Wrapper for inverse fourier transform + + Parameters: + G: data to transform + delta_f: pixel seperation + FFT (FFT object, optional): An accelerated FFT object + """ + + N = G.shape[0] + +# g = np.fft.fftshift( np.fft.fft2( np.fft.fftshift(G) ) ) * (N * delta_f)**2 + g = np.fft.fftshift( np.fft.fft2( np.fft.fftshift(G) ) ) + + return g + + +def ft_phase_screen(atm, N, delta, l0 =1e-10,seed=None): + ''' + ------------ Function adapted from aotools ---------------------- + + Creates a random phase screen with Von Karmen statistics. + (Schmidt 2010) + + Parameters: + r0 (float): r0 parameter of scrn in metres + N (int): Size of phase scrn in pxls + delta (float): size in Metres of each pxl + L0 (float): Size of outer-scale in metres + l0 (float): inner scale in metres + + Returns: + ndarray: np array representing phase screen + ''' + delta = float(delta) + r0 = float(atm.r0) + L0 = float(atm.L0) + R = random.SystemRandom(time.time()) + if seed is None: + seed = int(R.random()*100000) + randomState = RandomState(seed) + + del_f = 1./(N*delta) + + fx = np.arange(-N/2.,N/2.) * del_f + + (fx,fy) = np.meshgrid(fx,fx) + f = np.sqrt(fx**2 + fy**2) + + fm = 5.92/l0/(2*np.pi) + f0 = 1./L0 + + PSD_phi = (0.023*r0**(-5./3.) * np.exp(-1*((f/fm)**2)) / + ( ( (f**2) + (f0**2) )**(11./6) ) ) + + PSD_phi[int(N/2), int(N/2)] = 0 + + cn = ( (randomState.normal(size=(N,N)) + 1j* randomState.normal(size=(N,N)) ) + * np.sqrt(PSD_phi)*del_f ) + + phs = ift2(cn,1).real + + return phs + + + + + + + +def ft_sh_phase_screen(atm, N, delta, l0= 1e-10, seed=None): + + """ + ------------ Function adapted from aotools ---------------------- + + Creates a random phase screen with Von Karmen statistics with added + sub-harmonics to augment tip-tilt modes. + (Schmidt 2010) + + Args: + r0 (float): r0 parameter of scrn in metres + N (int): Size of phase scrn in pxls + delta (float): size in Metres of each pxl + L0 (float): Size of outer-scale in metres + l0 (float): inner scale in metres + + Returns: + ndarray: np array representing phase screen + """ + delta = float(delta) + r0 = float(atm.r0) + L0 = float(atm.L0) + R = random.SystemRandom(time.time()) + if seed is None: + seed = int(R.random()*100000) + randomState = RandomState(seed) + + D = N*delta + # high-frequency screen from FFT method + phs_hi = ft_phase_screen(atm, N, delta, seed=seed) + + # spatial grid [m] + coords = np.arange(-N/2,N/2)*delta + x, y = np.meshgrid(coords,coords) + + # initialize low-freq screen + phs_lo = np.zeros(phs_hi.shape) + + # loop over frequency grids with spacing 1/(3^p*L) + for p in range(1,4): + # setup the PSD + del_f = 1 / (3**p*D) #frequency grid spacing [1/m] + fx = np.arange(-1,2) * del_f + + # frequency grid [1/m] + fx, fy = np.meshgrid(fx,fx) + f = np.sqrt(fx**2 + fy**2) # polar grid + + fm = 5.92/l0/(2*np.pi) # inner scale frequency [1/m] + f0 = 1./L0 + + # outer scale frequency [1/m] + # modified von Karman atmospheric phase PSD + PSD_phi = (0.023*r0**(-5./3) + * np.exp(-1*(f/fm)**2) / ((f**2 + f0**2)**(11./6)) ) + PSD_phi[1,1] = 0 + + # random draws of Fourier coefficients + cn = ( (randomState.normal(size=(3,3)) + + 1j*randomState.normal(size=(3,3)) ) + * np.sqrt(PSD_phi)*del_f ) + SH = np.zeros((N,N),dtype="complex") + # loop over frequencies on this grid + for i in range(0,2): + for j in range(0,2): + + SH += cn[i,j] * np.exp(1j*2*np.pi*(fx[i,j]*x+fy[i,j]*y)) + + phs_lo = phs_lo + SH + # accumulate subharmonics + + phs_lo = phs_lo.real - phs_lo.real.mean() + + phs = phs_lo+phs_hi + return phs \ No newline at end of file diff --git a/AO_modules/calibration/__init__.py b/OOPAO/tools/__init__.py similarity index 92% rename from AO_modules/calibration/__init__.py rename to OOPAO/tools/__init__.py index c03131d..5cbfd76 100644 --- a/AO_modules/calibration/__init__.py +++ b/OOPAO/tools/__init__.py @@ -1,7 +1,7 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Apr 3 10:56:59 2020 - -@author: cheritie -""" - +# -*- coding: utf-8 -*- +""" +Created on Fri Apr 3 10:56:59 2020 + +@author: cheritie +""" + diff --git a/AO_modules/tools/displayTools.py b/OOPAO/tools/displayTools.py old mode 100755 new mode 100644 similarity index 96% rename from AO_modules/tools/displayTools.py rename to OOPAO/tools/displayTools.py index faeb86e..dd6ee86 --- a/AO_modules/tools/displayTools.py +++ b/OOPAO/tools/displayTools.py @@ -1,459 +1,461 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Mar 16 18:33:20 2020 - -@author: cheritie -""" - -import numpy as np -import matplotlib.pyplot as plt -from matplotlib.offsetbox import OffsetImage, AnnotationBbox -from AO_modules.tools.tools import emptyClass -import matplotlib.gridspec as gridspec -import matplotlib as mpl - -def update_line(hl, new_dataX,new_dataY): - - - hl.set_xdata(new_dataX) - hl.set_ydata(new_dataY) - -def displayMap(A,norma=False,axis=2,mask=0,returnOutput = False): - A = np.squeeze(A) - - if axis==0: - A=np.moveaxis(A,0,-1) - dimA = np.ndim(A) - - if dimA == 2: - n1,n2 = A.shape - if n1 == n2: - plt.figure() - plt.imshow(A) - return -1 - - else: - if np.math.log(n1,np.sqrt(n1)) == 2.0: - nImage = n2 - nPix1 = int(np.sqrt(n1)) - nPix2 = nPix1 - A = np.reshape(A,[nPix1,nPix2,nImage]) - else: - if dimA==3: - n1,n2,n3 = A.shape - nImage = n3 - nPix1 = n1 - nPix2 = n2 - else: - print('Error wrong size for the image cube') - return -1 - -# Create a meta Map - - nSide = int(np.ceil(np.sqrt(nImage))) - - S=np.zeros([nPix1*nSide-1,nPix2*nSide-1]) - - count=0 - for i in range(nSide): - for j in range(nSide): - count+=1 - if count <= nImage: - if np.ndim(mask)==2: - tmp = A[:,:,count-1]*mask - else: - tmp = A[:,:,count-1] - if norma: - tmp = tmp/np.max(np.abs(tmp)) - S[ i*(nPix1-1) : nPix1 +i*(nPix1-1) , j*(nPix2-1) : nPix2 +j*(nPix2-1)] = tmp - - - plt.figure() - plt.imshow(S) - if returnOutput: - return S - -def makeSquareAxes(ax): - """Make an axes square in screen units. - - Should be called after plotting. - """ - ax.set_aspect(1 / ax.get_data_ratio()) - - -def getColorOrder(): - color = (plt.rcParams['axes.prop_cycle'].by_key()['color']) - return color - - -def displayPyramidSignals(wfs,signals,returnOutput=False, norma = False): - - A= np.zeros(wfs.validSignal.shape) - print(A.shape) - A[:]=np.Inf - # one signal only - if np.ndim(signals)==1: - if wfs.validSignal.sum() == signals.shape: - A[np.where(wfs.validSignal==1)]=signals - plt.figure() - plt.imshow(A) - out =A - else: - B= np.zeros([wfs.validSignal.shape[0],wfs.validSignal.shape[1],signals.shape[1]]) - B[:]=np.Inf - if wfs.validSignal.shape[0] == wfs.validSignal.shape[1]: - B[wfs.validSignal,:]=signals - out = displayMap(B,returnOutput=True) - else: - for i in range(signals.shape[1]): - A[np.where(wfs.validSignal==1)]=signals[:,i] - if norma: - A/= np.max(np.abs(signals[:,i])) - B[:,:,i] = A - out = displayMap(B,returnOutput=True) - if returnOutput: - return out - - -def display_wfs_signals(wfs,signals,returnOutput=False, norma = False): - - if wfs.tag == 'pyramid': - A= np.zeros(wfs.validSignal.shape) - print(A.shape) - A[:]=np.Inf - # one signal only - if np.ndim(signals)==1: - if wfs.validSignal.sum() == signals.shape: - A[np.where(wfs.validSignal==1)]=signals - plt.figure() - plt.imshow(A) - out =A - else: - B= np.zeros([wfs.validSignal.shape[0],wfs.validSignal.shape[1],signals.shape[1]]) - B[:]=np.Inf - if wfs.validSignal.shape[0] == wfs.validSignal.shape[1]: - B[wfs.validSignal,:]=signals - out = displayMap(B,returnOutput=True) - else: - for i in range(signals.shape[1]): - A[np.where(wfs.validSignal==1)]=signals[:,i] - if norma: - A/= np.max(np.abs(signals[:,i])) - B[:,:,i] = A - out = displayMap(B,returnOutput=True) - if returnOutput: - return out - if wfs.tag == 'shackHartmann': - A= np.zeros(wfs.valid_slopes_maps.shape) - print(A.shape) - A[:]=np.Inf - # one signal only - if np.ndim(signals)==1: - if wfs.valid_slopes_maps.sum() == signals.shape: - A[np.where(wfs.valid_slopes_maps==1)]=signals - plt.figure() - plt.imshow(A) - out =A - else: - B= np.zeros([wfs.valid_slopes_maps.shape[0],wfs.valid_slopes_maps.shape[1],signals.shape[1]]) - B[:]=np.Inf - if wfs.valid_slopes_maps.shape[0] == wfs.valid_slopes_maps.shape[1]: - B[wfs.valid_slopes_maps,:]=signals - out = displayMap(B,returnOutput=True) - else: - for i in range(signals.shape[1]): - A[np.where(wfs.valid_slopes_maps==1)]=signals[:,i] - if norma: - A/= np.max(np.abs(signals[:,i])) - B[:,:,i] = A - out = displayMap(B,returnOutput=True) - if returnOutput: - return out - -def interactive_plot(x,y,im_array, im_array_ref, event_name ='button_press_event', n_fig = None): - # create figure and plot scatter - if n_fig is None: - fig = plt.figure() - ax = plt.subplot(111) - - else: - fig = plt.figure(n_fig) - ax = plt.subplot(111) - - line, = ax.plot(x,y, ls="",color = 'k', marker="o", markersize = 10) - - # create the annotations box - im = OffsetImage(im_array[0,:,:], zoom=2) - - xybox=(100., 100.) - ab = AnnotationBbox(im, (0,0), xybox=xybox, xycoords='data', - boxcoords="offset points", pad=0.3, arrowprops=dict(arrowstyle="->")) - # add it to the axes and make it invisible - ax.add_artist(ab) - ab.set_visible(False) - - def hover(event): - # if the mouse is over the scatter points - if line.contains(event)[0]: - # find out the index within the array from the event - ind, = line.contains(event)[1]["ind"] - # get the figure size - w,h = fig.get_size_inches()*fig.dpi - ws = (event.x > w/2.)*-1 + (event.x <= w/2.) - hs = (event.y > h/2.)*-1 + (event.y <= h/2.) - # if event occurs in the top or right quadrant of the figure, - # change the annotation box position relative to mouse. - ab.xybox = (xybox[0]*ws, xybox[1]*hs) - # make annotation box visible - ab.set_visible(True) - # place it at the position of the hovered scatter point - ab.xy =(x[ind], y[ind]) - # set the image corresponding to that point - if event.button == 1: - im.set_data(im_array[ind,:,:]) - if event.button == 3: - im.set_data(im_array_ref[ind,:,:]) - else: - #if the mouse is not over a scatter point - ab.set_visible(False) - fig.canvas.draw_idle() - - # add callback for mouse moves - fig.canvas.mpl_connect(event_name, hover) - plt.show() - -def interactive_plot_text(x,y,text_array, event_name ='button_press_event'): - # create figure and plot scatter - fig = plt.figure() - ax = fig.add_subplot(111) - - sc = plt.scatter(x,y) - - # create the annotations box - annot = ax.annotate("", xy=(0,0), xytext=(20,20),textcoords="offset points", - bbox=dict(boxstyle="round", fc="w"), - arrowprops=dict(arrowstyle="->")) - annot.set_visible(False) - - def update_annot(ind): - - pos = sc.get_offsets()[ind["ind"][0]] - annot.xy = pos - text = "{}, {}".format(" ".join(list(map(str,ind["ind"]))), - " ".join([text_array[n] for n in ind["ind"]])) - annot.set_text(text) -# annot.get_bbox_patch().set_facecolor(cmap(norm(c[ind["ind"][0]]))) - annot.get_bbox_patch().set_alpha(0.4) - - - def hover(event): - vis = annot.get_visible() - if event.inaxes == ax: - cont, ind = sc.contains(event) - if cont: - update_annot(ind) - annot.set_visible(True) - fig.canvas.draw_idle() - else: - if vis: - annot.set_visible(False) - fig.canvas.draw_idle() - - # add callback for mouse moves - fig.canvas.mpl_connect(event_name, hover) - plt.show() - - -def compute_gif(cube, name, vect = None, vlim = None, fps = 2): - from matplotlib import animation, rc - rc('animation', html='html5') - data = cube.copy() - - plt.close('all') - fig, ax = plt.subplots(figsize = [5,5]) - line = ax.imshow(np.fliplr(np.flip(data[0,:,:].T))) - # fig.set_facecolor((0.94,0.85,0.05)) - # line.set_clim([-4,1]) - plt.tick_params( - axis='both', # changes apply to the x-axis - which='both', # both major and minor ticks are affected - bottom=False, # ticks along the bottom edge are off - top=False, # ticks along the top edge are off - left = False, - labelbottom=False, - labelleft=False) - plt.tight_layout() - # SR =ax.text(50, 400,'SR: '+str(np.round(ao_res[20],1))+'%',color=(1,1,1),fontsize = 14,weight = 'bold') - - def init(): - tmp = np.copy(data[0,:,:]) - tmp[np.where(tmp==0)] = np.inf - line.set_data(np.fliplr(np.flip(tmp.T))) - # ax.set_title('Time '+str(0) + ' ms -- WFE '+str(wfe[0])+' nm') - if vlim is None: - line.set_clim(vmin = np.min(data[0,:,:]), vmax = np.max(data[0,:,:])) - else: - line.set_clim(vmin = vlim[0], vmax = vlim[1]) - - return (line,) - - - # animation function. This is called sequentially - def animate(i): - tmp = np.copy(data[i,:,:]) - tmp[np.where(tmp==0)] = np.inf - line.set_data(np.fliplr(np.flip(tmp.T))) - # SR.set_text('SR: '+str(np.round(ao_res[i],1))+'%') - if vlim is None: - line.set_clim(vmin = np.min(data[i,:,:]), vmax = np.max(data[i,:,:])) - else: - line.set_clim(vmin = vlim[0], vmax = vlim[1]) - return (line) - # have changed. - anim = animation.FuncAnimation(fig, animate, init_func=init, - frames=data.shape[0], interval=100) - - folder = '//winhome/home/cheritie/My Pictures/gif_from_python/' - anim.save(folder+name+'.gif', writer='imagemagick', fps=fps) - return - -def cl_plot(list_fig,plt_obj= None, type_fig = None,fig_number = 20,n_subplot = None,list_ratio = None, list_title = None, list_lim = None,list_label = None, list_display_axis = None,s=16): - - n_im = len(list_fig) - if n_subplot is None: - n_sp = int(np.ceil(np.sqrt(n_im))) - n_sp_y = n_sp + 1 - else: - n_sp = n_subplot[0] - n_sp_y = n_subplot[1] +1 - if plt_obj is None: - if list_ratio is None: - gs = gridspec.GridSpec(n_sp_y,n_sp, height_ratios=np.ones(n_sp_y), width_ratios=np.ones(n_sp), hspace=0.25, wspace=0.25) - else: - gs = gridspec.GridSpec(n_sp_y,n_sp, height_ratios=list_ratio[0], width_ratios=list_ratio[1], hspace=0.25, wspace=0.25) - - plt_obj = emptyClass() - setattr(plt_obj,'gs',gs) - plt_obj.list_label = list_label - - plt_obj.list_lim = list_lim - - plt_obj.keep_going = True - f = plt.figure(fig_number,figsize = [n_sp*4,n_sp_y*2],facecolor=[0,0.1,0.25], edgecolor = None) - COLOR = 'white' - mpl.rcParams['text.color'] = COLOR - mpl.rcParams['axes.labelcolor'] = COLOR - mpl.rcParams['xtick.color'] = COLOR - mpl.rcParams['ytick.color'] = COLOR - line_comm = ['Stop'] - col_comm = ['r','b','b'] - - for i in range(1): - setattr(plt_obj,'ax_0_'+str(i+1), plt.subplot(gs[n_sp_y-1,:])) - sp_tmp =getattr(plt_obj,'ax_0_'+str(i+1)) - - annot = sp_tmp.annotate(line_comm[i],color ='k', fontsize=15, xy=(0.5,0.5), xytext=(0.5,0.5),bbox=dict(boxstyle="round", fc=col_comm[i])) - setattr(plt_obj,'annot_'+str(i+1), annot) - - plt.axis('off') - - - count = -1 - for i in range(n_sp): - for j in range(n_sp): - if count < n_im-1: - count+=1 - - # print(count) - setattr(plt_obj,'ax_'+str(count), plt.subplot(gs[i,j])) - sp_tmp =getattr(plt_obj,'ax_'+str(count)) - - setattr(plt_obj,'type_fig_'+str(count),type_fig[count]) - # IMSHOW - if type_fig[count] == 'imshow': - data_tmp = list_fig[count] - if len(data_tmp)==3: - setattr(plt_obj,'im_'+str(count),sp_tmp.imshow(data_tmp[2],extent = [data_tmp[0][0],data_tmp[0][1],data_tmp[1][0],data_tmp[1][1]])) - else: - setattr(plt_obj,'im_'+str(count),sp_tmp.imshow(data_tmp)) - im_tmp =getattr(plt_obj,'im_'+str(count)) - plt.colorbar(im_tmp) - - # PLOT - if type_fig[count] == 'plot': - data_tmp = list_fig[count] - if len(data_tmp)==2: - line_tmp, = sp_tmp.plot(data_tmp[0],data_tmp[1],'-x') - else: - line_tmp, = sp_tmp.plot(data_tmp,'-o') - setattr(plt_obj,'im_'+str(count),line_tmp) - - # SCATTER - if type_fig[count] == 'scatter': - data_tmp = list_fig[count] - scatter_tmp = sp_tmp.scatter(data_tmp[0],data_tmp[1],c=data_tmp[2],marker = 'o', s =s) - setattr(plt_obj,'im_'+str(count),scatter_tmp) - sp_tmp.set_facecolor([0,0.1,0.25]) - for spine in sp_tmp.spines.values(): - spine.set_edgecolor([0,0.1,0.25]) - makeSquareAxes(plt.gca()) - plt.colorbar(scatter_tmp) - if list_title is not None: - plt.title(list_title[count]) - if list_display_axis is not None: - if list_display_axis[count] is None: - sp_tmp.set_xticks([]) - sp_tmp.set_yticks([]) - sp_tmp.set_xticklabels([]) - sp_tmp.set_yticklabels([]) - - if plt_obj.list_label is not None: - if plt_obj.list_label[count] is not None: - plt.xlabel(plt_obj.list_label[count][0]) - plt.ylabel(plt_obj.list_label[count][1]) - - def hover(event): - if event.inaxes == plt_obj.ax_0_1: - cont, ind = f.contains(event) - if cont: - plt_obj.keep_going = False - plt_obj.annot_1.set_text('Stopped') - f.canvas.mpl_connect('button_press_event', hover) - return plt_obj - - if plt_obj is not None: - count = 0 - for i in range(n_sp): - for j in range(n_sp): - if count < n_im: - data = list_fig[count] - if getattr(plt_obj,'type_fig_'+str(count)) == 'imshow': - im_tmp =getattr(plt_obj,'im_'+str(count)) - im_tmp.set_data(data) - if plt_obj.list_lim[count] is None: - im_tmp.set_clim(vmin=data.min(),vmax=data.max()) - else: - im_tmp.set_clim(vmin=plt_obj.list_lim[count][0],vmax=plt_obj.list_lim[count][1]) - if getattr(plt_obj,'type_fig_'+str(count)) == 'plot': - if len(data)==2: - im_tmp =getattr(plt_obj,'im_'+str(count)) - im_tmp.set_xdata(data[0]) - im_tmp.set_ydata(data[1]) - im_tmp.axes.set_ylim([np.min(data[1])-0.1*np.abs(np.min(data[1])),np.max(data[1])+0.1*np.abs(np.max(data[1]))]) - im_tmp.axes.set_xlim([np.min(data[0])-0.1*np.abs(np.min(data[0])),np.max(data[0])+0.1*np.abs(np.max(data[0]))]) - else: - im_tmp =getattr(plt_obj,'im_'+str(count)) - im_tmp.set_ydata(data[0]) - im_tmp.axes.set_ylim([np.min(data[0])-0.1*np.abs(np.min(data[0])),np.max(data[0])+0.1*np.abs(np.max(data[0]))]) - - if getattr(plt_obj,'type_fig_'+str(count)) == 'scatter': - n = mpl.colors.Normalize(vmin = min(data), vmax = max(data)) - m = mpl.cm.ScalarMappable(norm=n) - - im_tmp = getattr(plt_obj,'im_'+str(count)) - im_tmp.set_facecolor(m.to_rgba(data)) - im_tmp.colorbar.update_normal(m) - count+=1 - plt.draw() - +# -*- coding: utf-8 -*- +""" +Created on Mon Mar 16 18:33:20 2020 + +@author: cheritie +""" + +import matplotlib as mpl +import matplotlib.gridspec as gridspec +import matplotlib.pyplot as plt +import numpy as np +from matplotlib.offsetbox import AnnotationBbox, OffsetImage + +from .tools import emptyClass + + +def update_line(hl, new_dataX,new_dataY): + + + hl.set_xdata(new_dataX) + hl.set_ydata(new_dataY) + +def displayMap(A,norma=False,axis=2,mask=0,returnOutput = False): + A = np.squeeze(A) + + if axis==0: + A=np.moveaxis(A,0,-1) + dimA = np.ndim(A) + + if dimA == 2: + n1,n2 = A.shape + if n1 == n2: + plt.figure() + plt.imshow(A) + return -1 + + else: + if np.math.log(n1,np.sqrt(n1)) == 2.0: + nImage = n2 + nPix1 = int(np.sqrt(n1)) + nPix2 = nPix1 + A = np.reshape(A,[nPix1,nPix2,nImage]) + else: + if dimA==3: + n1,n2,n3 = A.shape + nImage = n3 + nPix1 = n1 + nPix2 = n2 + else: + print('Error wrong size for the image cube') + return -1 + +# Create a meta Map + + nSide = int(np.ceil(np.sqrt(nImage))) + + S=np.zeros([nPix1*nSide-1,nPix2*nSide-1]) + + count=0 + for i in range(nSide): + for j in range(nSide): + count+=1 + if count <= nImage: + if np.ndim(mask)==2: + tmp = A[:,:,count-1]*mask + else: + tmp = A[:,:,count-1] + if norma: + tmp = tmp/np.max(np.abs(tmp)) + S[ i*(nPix1-1) : nPix1 +i*(nPix1-1) , j*(nPix2-1) : nPix2 +j*(nPix2-1)] = tmp + + + plt.figure() + plt.imshow(S) + if returnOutput: + return S + +def makeSquareAxes(ax): + """Make an axes square in screen units. + + Should be called after plotting. + """ + ax.set_aspect(1 / ax.get_data_ratio()) + + +def getColorOrder(): + color = (plt.rcParams['axes.prop_cycle'].by_key()['color']) + return color + + +def displayPyramidSignals(wfs,signals,returnOutput=False, norma = False): + + A= np.zeros(wfs.validSignal.shape) + print(A.shape) + A[:]=np.Inf + # one signal only + if np.ndim(signals)==1: + if wfs.validSignal.sum() == signals.shape: + A[np.where(wfs.validSignal==1)]=signals + plt.figure() + plt.imshow(A) + out =A + else: + B= np.zeros([wfs.validSignal.shape[0],wfs.validSignal.shape[1],signals.shape[1]]) + B[:]=np.Inf + if wfs.validSignal.shape[0] == wfs.validSignal.shape[1]: + B[wfs.validSignal,:]=signals + out = displayMap(B,returnOutput=True) + else: + for i in range(signals.shape[1]): + A[np.where(wfs.validSignal==1)]=signals[:,i] + if norma: + A/= np.max(np.abs(signals[:,i])) + B[:,:,i] = A + out = displayMap(B,returnOutput=True) + if returnOutput: + return out + + +def display_wfs_signals(wfs,signals,returnOutput=False, norma = False): + + if wfs.tag == 'pyramid': + A= np.zeros(wfs.validSignal.shape) + print(A.shape) + A[:]=np.Inf + # one signal only + if np.ndim(signals)==1: + if wfs.validSignal.sum() == signals.shape: + A[np.where(wfs.validSignal==1)]=signals + plt.figure() + plt.imshow(A) + out =A + else: + B= np.zeros([wfs.validSignal.shape[0],wfs.validSignal.shape[1],signals.shape[1]]) + B[:]=np.Inf + if wfs.validSignal.shape[0] == wfs.validSignal.shape[1]: + B[wfs.validSignal,:]=signals + out = displayMap(B,returnOutput=True) + else: + for i in range(signals.shape[1]): + A[np.where(wfs.validSignal==1)]=signals[:,i] + if norma: + A/= np.max(np.abs(signals[:,i])) + B[:,:,i] = A + out = displayMap(B,returnOutput=True) + if returnOutput: + return out + if wfs.tag == 'shackHartmann': + A= np.zeros(wfs.valid_slopes_maps.shape) + print(A.shape) + A[:]=np.Inf + # one signal only + if np.ndim(signals)==1: + if wfs.valid_slopes_maps.sum() == signals.shape: + A[np.where(wfs.valid_slopes_maps==1)]=signals + plt.figure() + plt.imshow(A) + out =A + else: + B= np.zeros([wfs.valid_slopes_maps.shape[0],wfs.valid_slopes_maps.shape[1],signals.shape[1]]) + B[:]=np.Inf + if wfs.valid_slopes_maps.shape[0] == wfs.valid_slopes_maps.shape[1]: + B[wfs.valid_slopes_maps,:]=signals + out = displayMap(B,returnOutput=True) + else: + for i in range(signals.shape[1]): + A[np.where(wfs.valid_slopes_maps==1)]=signals[:,i] + if norma: + A/= np.max(np.abs(signals[:,i])) + B[:,:,i] = A + out = displayMap(B,returnOutput=True) + if returnOutput: + return out + +def interactive_plot(x,y,im_array, im_array_ref, event_name ='button_press_event', n_fig = None): + # create figure and plot scatter + if n_fig is None: + fig = plt.figure() + ax = plt.subplot(111) + + else: + fig = plt.figure(n_fig) + ax = plt.subplot(111) + + line, = ax.plot(x,y, ls="",color = 'k', marker="o", markersize = 10) + + # create the annotations box + im = OffsetImage(im_array[0,:,:], zoom=2) + + xybox=(100., 100.) + ab = AnnotationBbox(im, (0,0), xybox=xybox, xycoords='data', + boxcoords="offset points", pad=0.3, arrowprops=dict(arrowstyle="->")) + # add it to the axes and make it invisible + ax.add_artist(ab) + ab.set_visible(False) + + def hover(event): + # if the mouse is over the scatter points + if line.contains(event)[0]: + # find out the index within the array from the event + ind, = line.contains(event)[1]["ind"] + # get the figure size + w,h = fig.get_size_inches()*fig.dpi + ws = (event.x > w/2.)*-1 + (event.x <= w/2.) + hs = (event.y > h/2.)*-1 + (event.y <= h/2.) + # if event occurs in the top or right quadrant of the figure, + # change the annotation box position relative to mouse. + ab.xybox = (xybox[0]*ws, xybox[1]*hs) + # make annotation box visible + ab.set_visible(True) + # place it at the position of the hovered scatter point + ab.xy =(x[ind], y[ind]) + # set the image corresponding to that point + if event.button == 1: + im.set_data(im_array[ind,:,:]) + if event.button == 3: + im.set_data(im_array_ref[ind,:,:]) + else: + #if the mouse is not over a scatter point + ab.set_visible(False) + fig.canvas.draw_idle() + + # add callback for mouse moves + fig.canvas.mpl_connect(event_name, hover) + plt.show() + +def interactive_plot_text(x,y,text_array, event_name ='button_press_event'): + # create figure and plot scatter + fig = plt.figure() + ax = fig.add_subplot(111) + + sc = plt.scatter(x,y) + + # create the annotations box + annot = ax.annotate("", xy=(0,0), xytext=(20,20),textcoords="offset points", + bbox=dict(boxstyle="round", fc="w"), + arrowprops=dict(arrowstyle="->")) + annot.set_visible(False) + + def update_annot(ind): + + pos = sc.get_offsets()[ind["ind"][0]] + annot.xy = pos + text = "{}, {}".format(" ".join(list(map(str,ind["ind"]))), + " ".join([text_array[n] for n in ind["ind"]])) + annot.set_text(text) +# annot.get_bbox_patch().set_facecolor(cmap(norm(c[ind["ind"][0]]))) + annot.get_bbox_patch().set_alpha(0.4) + + + def hover(event): + vis = annot.get_visible() + if event.inaxes == ax: + cont, ind = sc.contains(event) + if cont: + update_annot(ind) + annot.set_visible(True) + fig.canvas.draw_idle() + else: + if vis: + annot.set_visible(False) + fig.canvas.draw_idle() + + # add callback for mouse moves + fig.canvas.mpl_connect(event_name, hover) + plt.show() + + +def compute_gif(cube, name, vect = None, vlim = None, fps = 2): + from matplotlib import animation, rc + rc('animation', html='html5') + data = cube.copy() + + plt.close('all') + fig, ax = plt.subplots(figsize = [5,5]) + line = ax.imshow(np.fliplr(np.flip(data[0,:,:].T))) + # fig.set_facecolor((0.94,0.85,0.05)) + # line.set_clim([-4,1]) + plt.tick_params( + axis='both', # changes apply to the x-axis + which='both', # both major and minor ticks are affected + bottom=False, # ticks along the bottom edge are off + top=False, # ticks along the top edge are off + left = False, + labelbottom=False, + labelleft=False) + plt.tight_layout() + # SR =ax.text(50, 400,'SR: '+str(np.round(ao_res[20],1))+'%',color=(1,1,1),fontsize = 14,weight = 'bold') + + def init(): + tmp = np.copy(data[0,:,:]) + tmp[np.where(tmp==0)] = np.inf + line.set_data(np.fliplr(np.flip(tmp.T))) + # ax.set_title('Time '+str(0) + ' ms -- WFE '+str(wfe[0])+' nm') + if vlim is None: + line.set_clim(vmin = np.min(data[0,:,:]), vmax = np.max(data[0,:,:])) + else: + line.set_clim(vmin = vlim[0], vmax = vlim[1]) + + return (line,) + + + # animation function. This is called sequentially + def animate(i): + tmp = np.copy(data[i,:,:]) + tmp[np.where(tmp==0)] = np.inf + line.set_data(np.fliplr(np.flip(tmp.T))) + # SR.set_text('SR: '+str(np.round(ao_res[i],1))+'%') + if vlim is None: + line.set_clim(vmin = np.min(data[i,:,:]), vmax = np.max(data[i,:,:])) + else: + line.set_clim(vmin = vlim[0], vmax = vlim[1]) + return (line) + # have changed. + anim = animation.FuncAnimation(fig, animate, init_func=init, + frames=data.shape[0], interval=100) + + folder = '//winhome/home/cheritie/My Pictures/gif_from_python/' + anim.save(folder+name+'.gif', writer='imagemagick', fps=fps) + return + +def cl_plot(list_fig,plt_obj= None, type_fig = None,fig_number = 20,n_subplot = None,list_ratio = None, list_title = None, list_lim = None,list_label = None, list_display_axis = None,s=16): + + n_im = len(list_fig) + if n_subplot is None: + n_sp = int(np.ceil(np.sqrt(n_im))) + n_sp_y = n_sp + 1 + else: + n_sp = n_subplot[0] + n_sp_y = n_subplot[1] +1 + if plt_obj is None: + if list_ratio is None: + gs = gridspec.GridSpec(n_sp_y,n_sp, height_ratios=np.ones(n_sp_y), width_ratios=np.ones(n_sp), hspace=0.25, wspace=0.25) + else: + gs = gridspec.GridSpec(n_sp_y,n_sp, height_ratios=list_ratio[0], width_ratios=list_ratio[1], hspace=0.25, wspace=0.25) + + plt_obj = emptyClass() + setattr(plt_obj,'gs',gs) + plt_obj.list_label = list_label + + plt_obj.list_lim = list_lim + + plt_obj.keep_going = True + f = plt.figure(fig_number,figsize = [n_sp*4,n_sp_y*2],facecolor=[0,0.1,0.25], edgecolor = None) + COLOR = 'white' + mpl.rcParams['text.color'] = COLOR + mpl.rcParams['axes.labelcolor'] = COLOR + mpl.rcParams['xtick.color'] = COLOR + mpl.rcParams['ytick.color'] = COLOR + line_comm = ['Stop'] + col_comm = ['r','b','b'] + + for i in range(1): + setattr(plt_obj,'ax_0_'+str(i+1), plt.subplot(gs[n_sp_y-1,:])) + sp_tmp =getattr(plt_obj,'ax_0_'+str(i+1)) + + annot = sp_tmp.annotate(line_comm[i],color ='k', fontsize=15, xy=(0.5,0.5), xytext=(0.5,0.5),bbox=dict(boxstyle="round", fc=col_comm[i])) + setattr(plt_obj,'annot_'+str(i+1), annot) + + plt.axis('off') + + + count = -1 + for i in range(n_sp): + for j in range(n_sp): + if count < n_im-1: + count+=1 + + # print(count) + setattr(plt_obj,'ax_'+str(count), plt.subplot(gs[i,j])) + sp_tmp =getattr(plt_obj,'ax_'+str(count)) + + setattr(plt_obj,'type_fig_'+str(count),type_fig[count]) + # IMSHOW + if type_fig[count] == 'imshow': + data_tmp = list_fig[count] + if len(data_tmp)==3: + setattr(plt_obj,'im_'+str(count),sp_tmp.imshow(data_tmp[2],extent = [data_tmp[0][0],data_tmp[0][1],data_tmp[1][0],data_tmp[1][1]])) + else: + setattr(plt_obj,'im_'+str(count),sp_tmp.imshow(data_tmp)) + im_tmp =getattr(plt_obj,'im_'+str(count)) + plt.colorbar(im_tmp) + + # PLOT + if type_fig[count] == 'plot': + data_tmp = list_fig[count] + if len(data_tmp)==2: + line_tmp, = sp_tmp.plot(data_tmp[0],data_tmp[1],'-x') + else: + line_tmp, = sp_tmp.plot(data_tmp,'-o') + setattr(plt_obj,'im_'+str(count),line_tmp) + + # SCATTER + if type_fig[count] == 'scatter': + data_tmp = list_fig[count] + scatter_tmp = sp_tmp.scatter(data_tmp[0],data_tmp[1],c=data_tmp[2],marker = 'o', s =s) + setattr(plt_obj,'im_'+str(count),scatter_tmp) + sp_tmp.set_facecolor([0,0.1,0.25]) + for spine in sp_tmp.spines.values(): + spine.set_edgecolor([0,0.1,0.25]) + makeSquareAxes(plt.gca()) + plt.colorbar(scatter_tmp) + if list_title is not None: + plt.title(list_title[count]) + if list_display_axis is not None: + if list_display_axis[count] is None: + sp_tmp.set_xticks([]) + sp_tmp.set_yticks([]) + sp_tmp.set_xticklabels([]) + sp_tmp.set_yticklabels([]) + + if plt_obj.list_label is not None: + if plt_obj.list_label[count] is not None: + plt.xlabel(plt_obj.list_label[count][0]) + plt.ylabel(plt_obj.list_label[count][1]) + + def hover(event): + if event.inaxes == plt_obj.ax_0_1: + cont, ind = f.contains(event) + if cont: + plt_obj.keep_going = False + plt_obj.annot_1.set_text('Stopped') + f.canvas.mpl_connect('button_press_event', hover) + return plt_obj + + if plt_obj is not None: + count = 0 + for i in range(n_sp): + for j in range(n_sp): + if count < n_im: + data = list_fig[count] + if getattr(plt_obj,'type_fig_'+str(count)) == 'imshow': + im_tmp =getattr(plt_obj,'im_'+str(count)) + im_tmp.set_data(data) + if plt_obj.list_lim[count] is None: + im_tmp.set_clim(vmin=data.min(),vmax=data.max()) + else: + im_tmp.set_clim(vmin=plt_obj.list_lim[count][0],vmax=plt_obj.list_lim[count][1]) + if getattr(plt_obj,'type_fig_'+str(count)) == 'plot': + if len(data)==2: + im_tmp =getattr(plt_obj,'im_'+str(count)) + im_tmp.set_xdata(data[0]) + im_tmp.set_ydata(data[1]) + im_tmp.axes.set_ylim([np.min(data[1])-0.1*np.abs(np.min(data[1])),np.max(data[1])+0.1*np.abs(np.max(data[1]))]) + im_tmp.axes.set_xlim([np.min(data[0])-0.1*np.abs(np.min(data[0])),np.max(data[0])+0.1*np.abs(np.max(data[0]))]) + else: + im_tmp =getattr(plt_obj,'im_'+str(count)) + im_tmp.set_ydata(data[0]) + im_tmp.axes.set_ylim([np.min(data[0])-0.1*np.abs(np.min(data[0])),np.max(data[0])+0.1*np.abs(np.max(data[0]))]) + + if getattr(plt_obj,'type_fig_'+str(count)) == 'scatter': + n = mpl.colors.Normalize(vmin = min(data), vmax = max(data)) + m = mpl.cm.ScalarMappable(norm=n) + + im_tmp = getattr(plt_obj,'im_'+str(count)) + im_tmp.set_facecolor(m.to_rgba(data)) + im_tmp.colorbar.update_normal(m) + count+=1 + plt.draw() + \ No newline at end of file diff --git a/AO_modules/tools/interpolateGeometricalTransformation.py b/OOPAO/tools/interpolateGeometricalTransformation.py old mode 100755 new mode 100644 similarity index 96% rename from AO_modules/tools/interpolateGeometricalTransformation.py rename to OOPAO/tools/interpolateGeometricalTransformation.py index 3f1bb36..135a888 --- a/AO_modules/tools/interpolateGeometricalTransformation.py +++ b/OOPAO/tools/interpolateGeometricalTransformation.py @@ -1,326 +1,328 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Jul 31 14:35:31 2020 - -@author: cheritie -""" - -import numpy as np -import skimage.transform as sk -from joblib import Parallel, delayed -from AO_modules.MisRegistration import MisRegistration -from AO_modules.tools.tools import bin_ndarray - -# Rotation with respect to te center of the image -def rotateImageMatrix(image,angle): - # compute the shift value to center the image around 0 - shift_y, shift_x = np.array(image.shape[:2]) / 2. - # apply the rotation - tf_rotate = sk.SimilarityTransform(rotation=np.deg2rad(angle)) - # center the image around 0 - tf_shift = sk.SimilarityTransform(translation=[-shift_x, -shift_y]) - # re-center the image - tf_shift_inv = sk.SimilarityTransform(translation=[shift_x, shift_y]) - - return ((tf_shift + (tf_rotate + tf_shift_inv))) - - -# Differential scaling in X and Y with respect to the center of the image -def scalingImageMatrix(image,scaling): - # compute the shift value to center the image around 0 - shift_y, shift_x = np.array(image.shape[:2]) / 2. - # apply the scaling in X and Y - tf_scaling = sk.SimilarityTransform(scale=scaling) - # center the image around 0 - tf_shift = sk.SimilarityTransform(translation=[-shift_x, -shift_y]) - # re-center the image - tf_shift_inv = sk.SimilarityTransform(translation=[shift_x, shift_y]) - - return ((tf_shift + (tf_scaling + tf_shift_inv))) - -# Shift in X and Y -def translationImageMatrix(image,shift): - # translate the image with the corresponding shift value - tf_shift = sk.SimilarityTransform(translation=shift) - return tf_shift - -# Anamorphosis = composition of a rotation, a scaling in X and Y and an anti-rotation -def anamorphosisImageMatrix(image,direction,scale): - # Rotate the image - matRot = rotateImageMatrix(image,direction) - # Apply the X and Y scaling - matShearing = scalingImageMatrix(image,scaling=scale) - # De-Rotate the image - matAntiRot = rotateImageMatrix(image,-direction) - - return matRot+matShearing+matAntiRot - -def translation(coord,shift): - x=coord[:,0] - y=coord[:,1] - xOut = x+shift[0] - yOut = y+shift[1] - - coordOut=np.copy(coord) - coordOut[:,0]=xOut - coordOut[:,1]=yOut - - return coordOut - -def rotation(coord,angle): - x=coord[:,0] - y=coord[:,1] - xOut = x*np.cos(angle)-y*np.sin(angle) - yOut = y*np.cos(angle)+x*np.sin(angle) - coordOut=np.copy(coord) - coordOut[:,0] = xOut - coordOut[:,1] = yOut - - return coordOut - -def anamorphosis(coord,angle,mNorm,mRad): - x = coord[:,0] - y = coord[:,1] - mRad += 1 - mNorm += 1 - xOut = x * (mRad*np.cos(angle)**2 + mNorm* np.sin(angle)**2) + y * (mNorm*np.sin(2*angle)/2 -mRad*np.sin(2*angle)/2) - yOut = y * (mNorm*np.cos(angle)**2 + mRad* np.sin(angle)**2) + x * (mNorm*np.sin(2*angle)/2 -mRad*np.sin(2*angle)/2) - - coordOut = np.copy(coord) - coordOut[:,0] = xOut - coordOut[:,1] = yOut - - return coordOut - - - - - -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% START OF THE FUNCTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% - -def interpolateGeometricalTransformation(data,misReg=0, order =3): - - # size of influence functions and number of actuators - nx, ny, nData = data.shape - - # create a MisReg object to store the different mis-registration - if np.isscalar(misReg): - if misReg==0: - misReg=MisRegistration() - else: - print('ERROR: wrong value for the mis-registrations') - - - # 2) transformations for the mis-registration - anamMatrix = anamorphosisImageMatrix(data,misReg.anamorphosisAngle,[1+misReg.radialScaling,1+misReg.tangentialScaling]) - rotMatrix = rotateImageMatrix(data,misReg.rotationAngle) - shiftMatrix = translationImageMatrix(data,[misReg.shiftX,misReg.shiftY]) #units are in pixel of the M1 - - # Global transformation matrix - transformationMatrix = anamMatrix + rotMatrix + shiftMatrix - - data = np.moveaxis(np.asarray(data),-1,0) - - def globalTransformation(image): - output = sk.warp(image,(transformationMatrix).inverse,order=order,mode='constant',cval = 0) - return output - - def joblib_transformation(): - Q=Parallel(n_jobs=8,prefer='threads')(delayed(globalTransformation)(i) for i in data) - return Q - - print('Applying the transformations ... ') - - maps = joblib_transformation() - # change axis order - dataOut = np.moveaxis(np.asarray(np.asarray(maps)),0,-1) - - print('Done! ') - - - return dataOut -# -*- coding: utf-8 -*- -""" -Created on Tue Mar 16 10:04:46 2021 - -@author: cheritie -""" - - -def interpolate_cube(cube_in, pixel_size_in, pixel_size_out, resolution_out, shape_out = None, mis_registration = None, order = 1, joblib_prefer = 'threads', joblib_nJobs = 4): - if mis_registration is None: - mis_registration = MisRegistration() - nAct,nx, ny = cube_in.shape - - # size of the influence functions maps - resolution_in = int(nx) - - # compute the ratio between both pixel scale. - ratio = pixel_size_in/pixel_size_out - # after the interpolation the image will be shifted of a fraction of pixel extra if ratio is not an integer - extra = (ratio)%1 - - # difference in pixels between both resolutions - nPix = resolution_in-resolution_out - - - extra = extra/2 + (np.floor(ratio)-1)*0.5 - nCrop = (nPix/2) - # allocate memory to store the influence functions - influMap = np.zeros([resolution_in,resolution_in]) - - #-------------------- The Following Transformations are applied in the following order ----------------------------------- - - # 1) Down scaling to get the right pixel size according to the resolution of M1 - downScaling = anamorphosisImageMatrix(influMap,0,[ratio,ratio]) - - # 2) transformations for the mis-registration - anamMatrix = anamorphosisImageMatrix(influMap,mis_registration.anamorphosisAngle,[1+mis_registration.radialScaling,1+mis_registration.tangentialScaling]) - rotMatrix = rotateImageMatrix(influMap,mis_registration.rotationAngle) - shiftMatrix = translationImageMatrix(influMap,[mis_registration.shiftY/pixel_size_out,mis_registration.shiftX/pixel_size_out]) #units are in m - - # Shift of half a pixel to center the images on an even number of pixels - alignmentMatrix = translationImageMatrix(influMap,[extra-nCrop,extra-nCrop]) - - # 3) Global transformation matrix - transformationMatrix = downScaling + anamMatrix + rotMatrix + shiftMatrix + alignmentMatrix - - def globalTransformation(image): - output = sk.warp(image,(transformationMatrix).inverse,output_shape = [resolution_out,resolution_out],order=order) - return output - - # definition of the function that is run in parallel for each - def reconstruction(map_2D): - output = globalTransformation(map_2D) - return output - - # print('interpolating... ') - def joblib_reconstruction(): - Q=Parallel(n_jobs = joblib_nJobs,prefer = joblib_prefer)(delayed(reconstruction)(i) for i in cube_in) - return Q - - cube_out = np.asarray(joblib_reconstruction()) - # print('...Done!') - - return cube_out - -def interpolate_image(image_in, pixel_size_in, pixel_size_out,resolution_out, rotation_angle = 0, shift_x = 0,shift_y = 0,anamorphosisAngle=0,tangentialScaling=0,radialScaling=0, shape_out = None, order = 1): - - nx, ny = image_in.shape - - # size of the influence functions maps - resolution_in = int(nx) - - # compute the ratio between both pixel scale. - ratio = pixel_size_in/pixel_size_out - # after the interpolation the image will be shifted of a fraction of pixel extra if ratio is not an integer - extra = (ratio)%1 - - # difference in pixels between both resolutions - nPix = resolution_in-resolution_out - - - extra = extra/2 + (np.floor(ratio)-1)*0.5 - nCrop = (nPix/2) - # allocate memory to store the influence functions - influMap = np.zeros([resolution_in,resolution_in]) - - #-------------------- The Following Transformations are applied in the following order ----------------------------------- - - # 1) Down scaling to get the right pixel size according to the resolution of M1 - downScaling = anamorphosisImageMatrix(influMap,0,[ratio,ratio]) - - # 2) transformations for the mis-registration - anamMatrix = anamorphosisImageMatrix(influMap,anamorphosisAngle,[1+radialScaling,1+tangentialScaling]) - rotMatrix = rotateImageMatrix(influMap,rotation_angle) - shiftMatrix = translationImageMatrix(influMap,[shift_x/pixel_size_out,shift_y/pixel_size_out]) #units are in m - - # Shift of half a pixel to center the images on an even number of pixels - alignmentMatrix = translationImageMatrix(influMap,[extra-nCrop,extra-nCrop]) - - # 3) Global transformation matrix - transformationMatrix = downScaling + anamMatrix + rotMatrix + shiftMatrix + alignmentMatrix - - def globalTransformation(image): - output = sk.warp(image,(transformationMatrix).inverse,output_shape = [resolution_out,resolution_out],order=order) - return output - - # definition of the function that is run in parallel for each - def reconstruction(map_2D): - output = globalTransformation(map_2D) - return output - - image_out = reconstruction(image_in) - # print('...Done!') - - return image_out - - - - - -def binning_optimized(cube_in,binning_factor): - n_im, nx,ny = np.shape(cube_in) - - if nx%binning_factor==0 and binning_factor%1==0: - # in case the binning factor gives an integer number of pixels - cube_out = bin_ndarray(cube_in,[n_im, nx//binning_factor,ny//binning_factor], operation='sum') - else: - # size of the cube maps - resolution_in = int(nx) - resolution_out = int(np.ceil(resolution_in/binning_factor)) - - pixel_size_in = resolution_in - pixel_size_out = resolution_out - - # compute the ratio between both pixel scale. - ratio = pixel_size_in/pixel_size_out - # after the interpolation the image will be shifted of a fraction of pixel extra if ratio is not an integer - extra = (ratio)%1 - - # difference in pixels between both resolutions - nPix = resolution_in-resolution_out - - - extra = extra/2 + (np.floor(ratio)-1)*0.5 - nCrop = (nPix/2) - # allocate memory to store the influence functions - influMap = np.zeros([resolution_in,resolution_in]) - - #-------------------- The Following Transformations are applied in the following order ----------------------------------- - - # 1) Down scaling to get the right pixel size according to the resolution of M1 - downScaling = anamorphosisImageMatrix(influMap,0,[ratio,ratio]) - - # 2) transformations for the mis-registration - anamMatrix = anamorphosisImageMatrix(influMap,1,[1,1]) - - # Shift of half a pixel to center the images on an even number of pixels - alignmentMatrix = translationImageMatrix(influMap,[extra-nCrop,extra-nCrop]) - - # 3) Global transformation matrix - transformationMatrix = downScaling + anamMatrix + alignmentMatrix - - def globalTransformation(image): - output = sk.warp(image,(transformationMatrix).inverse,output_shape = [resolution_out,resolution_out],order=1) - return output - - # definition of the function that is run in parallel for each - def reconstruction(map_2D): - output = globalTransformation(map_2D) - return output - - # print('interpolating... ') - def joblib_reconstruction(): - Q=Parallel(n_jobs = 4,prefer = 'threads')(delayed(reconstruction)(i) for i in cube_in) - return Q - - cube_out = np.asarray(joblib_reconstruction()) - - return cube_out - - - - - +# -*- coding: utf-8 -*- +""" +Created on Fri Jul 31 14:35:31 2020 + +@author: cheritie +""" + +import numpy as np +import skimage.transform as sk +from joblib import Parallel, delayed + +from .tools import bin_ndarray +from ..MisRegistration import MisRegistration + + +# Rotation with respect to te center of the image +def rotateImageMatrix(image,angle): + # compute the shift value to center the image around 0 + shift_y, shift_x = np.array(image.shape[:2]) / 2. + # apply the rotation + tf_rotate = sk.SimilarityTransform(rotation=np.deg2rad(angle)) + # center the image around 0 + tf_shift = sk.SimilarityTransform(translation=[-shift_x, -shift_y]) + # re-center the image + tf_shift_inv = sk.SimilarityTransform(translation=[shift_x, shift_y]) + + return ((tf_shift + (tf_rotate + tf_shift_inv))) + + +# Differential scaling in X and Y with respect to the center of the image +def scalingImageMatrix(image,scaling): + # compute the shift value to center the image around 0 + shift_y, shift_x = np.array(image.shape[:2]) / 2. + # apply the scaling in X and Y + tf_scaling = sk.SimilarityTransform(scale=scaling) + # center the image around 0 + tf_shift = sk.SimilarityTransform(translation=[-shift_x, -shift_y]) + # re-center the image + tf_shift_inv = sk.SimilarityTransform(translation=[shift_x, shift_y]) + + return ((tf_shift + (tf_scaling + tf_shift_inv))) + +# Shift in X and Y +def translationImageMatrix(image,shift): + # translate the image with the corresponding shift value + tf_shift = sk.SimilarityTransform(translation=shift) + return tf_shift + +# Anamorphosis = composition of a rotation, a scaling in X and Y and an anti-rotation +def anamorphosisImageMatrix(image,direction,scale): + # Rotate the image + matRot = rotateImageMatrix(image,direction) + # Apply the X and Y scaling + matShearing = scalingImageMatrix(image,scaling=scale) + # De-Rotate the image + matAntiRot = rotateImageMatrix(image,-direction) + + return matRot+matShearing+matAntiRot + +def translation(coord,shift): + x=coord[:,0] + y=coord[:,1] + xOut = x+shift[0] + yOut = y+shift[1] + + coordOut=np.copy(coord) + coordOut[:,0]=xOut + coordOut[:,1]=yOut + + return coordOut + +def rotation(coord,angle): + x=coord[:,0] + y=coord[:,1] + xOut = x*np.cos(angle)-y*np.sin(angle) + yOut = y*np.cos(angle)+x*np.sin(angle) + coordOut=np.copy(coord) + coordOut[:,0] = xOut + coordOut[:,1] = yOut + + return coordOut + +def anamorphosis(coord,angle,mNorm,mRad): + x = coord[:,0] + y = coord[:,1] + mRad += 1 + mNorm += 1 + xOut = x * (mRad*np.cos(angle)**2 + mNorm* np.sin(angle)**2) + y * (mNorm*np.sin(2*angle)/2 -mRad*np.sin(2*angle)/2) + yOut = y * (mNorm*np.cos(angle)**2 + mRad* np.sin(angle)**2) + x * (mNorm*np.sin(2*angle)/2 -mRad*np.sin(2*angle)/2) + + coordOut = np.copy(coord) + coordOut[:,0] = xOut + coordOut[:,1] = yOut + + return coordOut + + + + + +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% START OF THE FUNCTION %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + +def interpolateGeometricalTransformation(data,misReg=0, order =3): + + # size of influence functions and number of actuators + nx, ny, nData = data.shape + + # create a MisReg object to store the different mis-registration + if np.isscalar(misReg): + if misReg==0: + misReg=MisRegistration() + else: + print('ERROR: wrong value for the mis-registrations') + + + # 2) transformations for the mis-registration + anamMatrix = anamorphosisImageMatrix(data,misReg.anamorphosisAngle,[1+misReg.radialScaling,1+misReg.tangentialScaling]) + rotMatrix = rotateImageMatrix(data,misReg.rotationAngle) + shiftMatrix = translationImageMatrix(data,[misReg.shiftX,misReg.shiftY]) #units are in pixel of the M1 + + # Global transformation matrix + transformationMatrix = anamMatrix + rotMatrix + shiftMatrix + + data = np.moveaxis(np.asarray(data),-1,0) + + def globalTransformation(image): + output = sk.warp(image,(transformationMatrix).inverse,order=order,mode='constant',cval = 0) + return output + + def joblib_transformation(): + Q=Parallel(n_jobs=8,prefer='threads')(delayed(globalTransformation)(i) for i in data) + return Q + + print('Applying the transformations ... ') + + maps = joblib_transformation() + # change axis order + dataOut = np.moveaxis(np.asarray(np.asarray(maps)),0,-1) + + print('Done! ') + + + return dataOut +# -*- coding: utf-8 -*- +""" +Created on Tue Mar 16 10:04:46 2021 + +@author: cheritie +""" + + +def interpolate_cube(cube_in, pixel_size_in, pixel_size_out, resolution_out, shape_out = None, mis_registration = None, order = 1, joblib_prefer = 'threads', joblib_nJobs = 4): + if mis_registration is None: + mis_registration = MisRegistration() + nAct,nx, ny = cube_in.shape + + # size of the influence functions maps + resolution_in = int(nx) + + # compute the ratio between both pixel scale. + ratio = pixel_size_in/pixel_size_out + # after the interpolation the image will be shifted of a fraction of pixel extra if ratio is not an integer + extra = (ratio)%1 + + # difference in pixels between both resolutions + nPix = resolution_in-resolution_out + + + extra = extra/2 + (np.floor(ratio)-1)*0.5 + nCrop = (nPix/2) + # allocate memory to store the influence functions + influMap = np.zeros([resolution_in,resolution_in]) + + #-------------------- The Following Transformations are applied in the following order ----------------------------------- + + # 1) Down scaling to get the right pixel size according to the resolution of M1 + downScaling = anamorphosisImageMatrix(influMap,0,[ratio,ratio]) + + # 2) transformations for the mis-registration + anamMatrix = anamorphosisImageMatrix(influMap,mis_registration.anamorphosisAngle,[1+mis_registration.radialScaling,1+mis_registration.tangentialScaling]) + rotMatrix = rotateImageMatrix(influMap,mis_registration.rotationAngle) + shiftMatrix = translationImageMatrix(influMap,[mis_registration.shiftY/pixel_size_out,mis_registration.shiftX/pixel_size_out]) #units are in m + + # Shift of half a pixel to center the images on an even number of pixels + alignmentMatrix = translationImageMatrix(influMap,[extra-nCrop,extra-nCrop]) + + # 3) Global transformation matrix + transformationMatrix = downScaling + anamMatrix + rotMatrix + shiftMatrix + alignmentMatrix + + def globalTransformation(image): + output = sk.warp(image,(transformationMatrix).inverse,output_shape = [resolution_out,resolution_out],order=order) + return output + + # definition of the function that is run in parallel for each + def reconstruction(map_2D): + output = globalTransformation(map_2D) + return output + + # print('interpolating... ') + def joblib_reconstruction(): + Q=Parallel(n_jobs = joblib_nJobs,prefer = joblib_prefer)(delayed(reconstruction)(i) for i in cube_in) + return Q + + cube_out = np.asarray(joblib_reconstruction()) + # print('...Done!') + + return cube_out + +def interpolate_image(image_in, pixel_size_in, pixel_size_out,resolution_out, rotation_angle = 0, shift_x = 0,shift_y = 0,anamorphosisAngle=0,tangentialScaling=0,radialScaling=0, shape_out = None, order = 1): + + nx, ny = image_in.shape + + # size of the influence functions maps + resolution_in = int(nx) + + # compute the ratio between both pixel scale. + ratio = pixel_size_in/pixel_size_out + # after the interpolation the image will be shifted of a fraction of pixel extra if ratio is not an integer + extra = (ratio)%1 + + # difference in pixels between both resolutions + nPix = resolution_in-resolution_out + + + extra = extra/2 + (np.floor(ratio)-1)*0.5 + nCrop = (nPix/2) + # allocate memory to store the influence functions + influMap = np.zeros([resolution_in,resolution_in]) + + #-------------------- The Following Transformations are applied in the following order ----------------------------------- + + # 1) Down scaling to get the right pixel size according to the resolution of M1 + downScaling = anamorphosisImageMatrix(influMap,0,[ratio,ratio]) + + # 2) transformations for the mis-registration + anamMatrix = anamorphosisImageMatrix(influMap,anamorphosisAngle,[1+radialScaling,1+tangentialScaling]) + rotMatrix = rotateImageMatrix(influMap,rotation_angle) + shiftMatrix = translationImageMatrix(influMap,[shift_x/pixel_size_out,shift_y/pixel_size_out]) #units are in m + + # Shift of half a pixel to center the images on an even number of pixels + alignmentMatrix = translationImageMatrix(influMap,[extra-nCrop,extra-nCrop]) + + # 3) Global transformation matrix + transformationMatrix = downScaling + anamMatrix + rotMatrix + shiftMatrix + alignmentMatrix + + def globalTransformation(image): + output = sk.warp(image,(transformationMatrix).inverse,output_shape = [resolution_out,resolution_out],order=order) + return output + + # definition of the function that is run in parallel for each + def reconstruction(map_2D): + output = globalTransformation(map_2D) + return output + + image_out = reconstruction(image_in) + # print('...Done!') + + return image_out + + + + + +def binning_optimized(cube_in,binning_factor): + n_im, nx,ny = np.shape(cube_in) + + if nx%binning_factor==0 and binning_factor%1==0: + # in case the binning factor gives an integer number of pixels + cube_out = bin_ndarray(cube_in,[n_im, nx//binning_factor,ny//binning_factor], operation='sum') + else: + # size of the cube maps + resolution_in = int(nx) + resolution_out = int(np.ceil(resolution_in/binning_factor)) + + pixel_size_in = resolution_in + pixel_size_out = resolution_out + + # compute the ratio between both pixel scale. + ratio = pixel_size_in/pixel_size_out + # after the interpolation the image will be shifted of a fraction of pixel extra if ratio is not an integer + extra = (ratio)%1 + + # difference in pixels between both resolutions + nPix = resolution_in-resolution_out + + + extra = extra/2 + (np.floor(ratio)-1)*0.5 + nCrop = (nPix/2) + # allocate memory to store the influence functions + influMap = np.zeros([resolution_in,resolution_in]) + + #-------------------- The Following Transformations are applied in the following order ----------------------------------- + + # 1) Down scaling to get the right pixel size according to the resolution of M1 + downScaling = anamorphosisImageMatrix(influMap,0,[ratio,ratio]) + + # 2) transformations for the mis-registration + anamMatrix = anamorphosisImageMatrix(influMap,1,[1,1]) + + # Shift of half a pixel to center the images on an even number of pixels + alignmentMatrix = translationImageMatrix(influMap,[extra-nCrop,extra-nCrop]) + + # 3) Global transformation matrix + transformationMatrix = downScaling + anamMatrix + alignmentMatrix + + def globalTransformation(image): + output = sk.warp(image,(transformationMatrix).inverse,output_shape = [resolution_out,resolution_out],order=1) + return output + + # definition of the function that is run in parallel for each + def reconstruction(map_2D): + output = globalTransformation(map_2D) + return output + + # print('interpolating... ') + def joblib_reconstruction(): + Q=Parallel(n_jobs = 4,prefer = 'threads')(delayed(reconstruction)(i) for i in cube_in) + return Q + + cube_out = np.asarray(joblib_reconstruction()) + + return cube_out + + + + + \ No newline at end of file diff --git a/AO_modules/tools/interpolate_influence_functions.py b/OOPAO/tools/interpolate_influence_functions.py old mode 100755 new mode 100644 similarity index 90% rename from AO_modules/tools/interpolate_influence_functions.py rename to OOPAO/tools/interpolate_influence_functions.py index 8035318..97f7432 --- a/AO_modules/tools/interpolate_influence_functions.py +++ b/OOPAO/tools/interpolate_influence_functions.py @@ -1,93 +1,100 @@ -# -*- coding: utf-8 -*- -""" -Created on Tue Mar 16 10:04:46 2021 - -@author: cheritie -""" - -from AO_modules.tools.interpolateGeometricalTransformation import rotateImageMatrix,rotation,translationImageMatrix,translation,anamorphosis,anamorphosisImageMatrix -import skimage.transform as sk -from joblib import Parallel, delayed -import numpy as np - -def interpolate_influence_functions(influence_functions_in, pixel_size_in, pixel_size_out, resolution_out, mis_registration, coordinates_in = None, order = 1): - - nAct,nx, ny = influence_functions_in.shape - - # size of the influence functions maps - resolution_in = int(nx) - - # compute the ratio between both pixel scale. - ratio = pixel_size_in/pixel_size_out - - # after the interpolation the image will be shifted of a fraction of pixel extra if ratio is not an integer - extra = (ratio)%1 - - # difference in pixels between both resolutions - nPix = resolution_in-resolution_out - - if nPix%2==0: - # case nPix is even - # alignement of the array with respect to the interpolation - # (ratio is not always an integer of pixel) - extra_x = extra/2 -0.5 - extra_y = extra/2 -0.5 - - # crop one extra pixel on one side - nCrop_x = nPix//2 - nCrop_y = nPix//2 - else: - # case nPix is uneven - # alignement of the array with respect to the interpolation - # (ratio is not always an integer of pixel) - extra_x = extra/2 -0.5 -0.5 - extra_y = extra/2 -0.5 -0.5 - # crop one extra pixel on one side - nCrop_x = nPix//2 - nCrop_y = (nPix//2)+1 - - # allocate memory to store the influence functions - influMap = np.zeros([resolution_in,resolution_in]) - - #-------------------- The Following Transformations are applied in the following order ----------------------------------- - - # 1) Down scaling to get the right pixel size according to the resolution of M1 - downScaling = anamorphosisImageMatrix(influMap,0,[ratio,ratio]) - - # 2) transformations for the mis-registration - anamMatrix = anamorphosisImageMatrix(influMap,mis_registration.anamorphosisAngle,[1+mis_registration.radialScaling,1+mis_registration.tangentialScaling]) - rotMatrix = rotateImageMatrix(influMap,mis_registration.rotationAngle) - shiftMatrix = translationImageMatrix(influMap,[mis_registration.shiftY/pixel_size_out,mis_registration.shiftX/pixel_size_out]) #units are in m - - # Shift of half a pixel to center the images on an even number of pixels - alignmentMatrix = translationImageMatrix(influMap,[extra_x-nCrop_x,extra_y-nCrop_y]) - - # 3) Global transformation matrix - transformationMatrix = downScaling + anamMatrix + rotMatrix + shiftMatrix + alignmentMatrix - - def globalTransformation(image): - output = sk.warp(image,(transformationMatrix).inverse,output_shape = [resolution_out,resolution_out],order=order) - return output - - # definition of the function that is run in parallel for each - def reconstruction_IF(influMap): - output = globalTransformation(influMap) - return output - - print('Reconstructing the influence functions ... ') - def joblib_reconstruction(): - Q=Parallel(n_jobs=4,prefer='threads')(delayed(reconstruction_IF)(i) for i in influence_functions_in) - return Q - - influence_functions_out = np.moveaxis(np.asarray(joblib_reconstruction()),0,-1) - if coordinates_in is not None: - # recenter the initial coordinates_ininates around 0 - coordinates_out = ((coordinates_in-resolution_in/2)*ratio) - - # apply the transformations and re-center them for the new resolution resolution_out - coordinates_out = translation(rotation(anamorphosis(coordinates_out,mis_registration.anamorphosisAngle*np.pi/180,mis_registration.radialScaling,mis_registration.tangentialScaling),-mis_registration.rotationAngle*np.pi/180),[mis_registration.shiftX/pixel_size_out,mis_registration.shiftY/pixel_size_out])+resolution_out/2 - - - return influence_functions_out, coordinates_out - else: +# -*- coding: utf-8 -*- +""" +Created on Tue Mar 16 10:04:46 2021 + +@author: cheritie +""" + +import numpy as np +import skimage.transform as sk +from joblib import Parallel, delayed + +from .interpolateGeometricalTransformation import (anamorphosis, + anamorphosisImageMatrix, + rotateImageMatrix, + rotation, + translation, + translationImageMatrix) + + +def interpolate_influence_functions(influence_functions_in, pixel_size_in, pixel_size_out, resolution_out, mis_registration, coordinates_in = None, order = 1): + + nAct,nx, ny = influence_functions_in.shape + + # size of the influence functions maps + resolution_in = int(nx) + + # compute the ratio between both pixel scale. + ratio = pixel_size_in/pixel_size_out + + # after the interpolation the image will be shifted of a fraction of pixel extra if ratio is not an integer + extra = (ratio)%1 + + # difference in pixels between both resolutions + nPix = resolution_in-resolution_out + + if nPix%2==0: + # case nPix is even + # alignement of the array with respect to the interpolation + # (ratio is not always an integer of pixel) + extra_x = extra/2 -0.5 + extra_y = extra/2 -0.5 + + # crop one extra pixel on one side + nCrop_x = nPix//2 + nCrop_y = nPix//2 + else: + # case nPix is uneven + # alignement of the array with respect to the interpolation + # (ratio is not always an integer of pixel) + extra_x = extra/2 -0.5 -0.5 + extra_y = extra/2 -0.5 -0.5 + # crop one extra pixel on one side + nCrop_x = nPix//2 + nCrop_y = (nPix//2)+1 + + # allocate memory to store the influence functions + influMap = np.zeros([resolution_in,resolution_in]) + + #-------------------- The Following Transformations are applied in the following order ----------------------------------- + + # 1) Down scaling to get the right pixel size according to the resolution of M1 + downScaling = anamorphosisImageMatrix(influMap,0,[ratio,ratio]) + + # 2) transformations for the mis-registration + anamMatrix = anamorphosisImageMatrix(influMap,mis_registration.anamorphosisAngle,[1+mis_registration.radialScaling,1+mis_registration.tangentialScaling]) + rotMatrix = rotateImageMatrix(influMap,mis_registration.rotationAngle) + shiftMatrix = translationImageMatrix(influMap,[mis_registration.shiftY/pixel_size_out,mis_registration.shiftX/pixel_size_out]) #units are in m + + # Shift of half a pixel to center the images on an even number of pixels + alignmentMatrix = translationImageMatrix(influMap,[extra_x-nCrop_x,extra_y-nCrop_y]) + + # 3) Global transformation matrix + transformationMatrix = downScaling + anamMatrix + rotMatrix + shiftMatrix + alignmentMatrix + + def globalTransformation(image): + output = sk.warp(image,(transformationMatrix).inverse,output_shape = [resolution_out,resolution_out],order=order) + return output + + # definition of the function that is run in parallel for each + def reconstruction_IF(influMap): + output = globalTransformation(influMap) + return output + + print('Reconstructing the influence functions ... ') + def joblib_reconstruction(): + Q=Parallel(n_jobs=4,prefer='threads')(delayed(reconstruction_IF)(i) for i in influence_functions_in) + return Q + + influence_functions_out = np.moveaxis(np.asarray(joblib_reconstruction()),0,-1) + if coordinates_in is not None: + # recenter the initial coordinates_ininates around 0 + coordinates_out = ((coordinates_in-resolution_in/2)*ratio) + + # apply the transformations and re-center them for the new resolution resolution_out + coordinates_out = translation(rotation(anamorphosis(coordinates_out,mis_registration.anamorphosisAngle*np.pi/180,mis_registration.radialScaling,mis_registration.tangentialScaling),-mis_registration.rotationAngle*np.pi/180),[mis_registration.shiftX/pixel_size_out,mis_registration.shiftY/pixel_size_out])+resolution_out/2 + + + return influence_functions_out, coordinates_out + else: return influence_functions_out \ No newline at end of file diff --git a/AO_modules/tools/set_paralleling_setup.py b/OOPAO/tools/set_paralleling_setup.py old mode 100755 new mode 100644 similarity index 96% rename from AO_modules/tools/set_paralleling_setup.py rename to OOPAO/tools/set_paralleling_setup.py index c9cd885..f31f6d1 --- a/AO_modules/tools/set_paralleling_setup.py +++ b/OOPAO/tools/set_paralleling_setup.py @@ -1,123 +1,124 @@ -# -*- coding: utf-8 -*- -""" -Created on Wed Jan 20 17:02:23 2021 - -@author: cheritie -""" -import ctypes -import socket - -def set_paralleling_setup(wfs,ELT = True, nThread = None, nJob = None): - - try : - mkl_rt = ctypes.CDLL('libmkl_rt.so') - mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads - except: - try: - mkl_rt = ctypes.CDLL('./mkl_rt.dll') - mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads - except: - try: - import mkl - mkl_set_num_threads = mkl.set_num_threads - except: - print('Could not optimize the parallelisation of the code ') - - - if ELT is True: - - name = socket.gethostname() - count = 0 - - if name == 'HQL025432': - mkl_set_num_threads(4) - wfs.nJobs = 4 - count = 1 - - if name == 'HQL025838': - mkl_set_num_threads(4) - wfs.nJobs = 8 - count = 1 - - if name == 'mcao144.hq.eso.org': - mkl_set_num_threads(8) - wfs.nJobs = 12 - count = 1 - - if name == 'mcao146': - mkl_set_num_threads(6) - wfs.nJobs = 12 - count = 1 - - if name == 'mcao145.hq.eso.org': - mkl_set_num_threads(6) - wfs.nJobs = 12 - count = 1 - - if name == 'mcao147.hq.eso.org': - mkl_set_num_threads(6) - wfs.nJobs = 10 - count = 1 - - if name == 'mcao148': - mkl_set_num_threads(6) - wfs.nJobs = 12 - count = 1 - - if name == 'mcao149': - mkl_set_num_threads(8) - wfs.nJobs = 12 - count = 1 - - if name == 'mcao150.hq.eso.org': - mkl_set_num_threads(10) - wfs.nJobs = 16 - count = 1 - - if name == 'mcao151.hq.eso.org': - mkl_set_num_threads(10) - wfs.nJobs = 10 - count = 1 - - if name == 'mcao152.hq.eso.org': - mkl_set_num_threads(8) - wfs.nJobs = 24 - count = 1 - - - if nJob is not None: - wfs.nJobs = nJob - print('setting the number of jobs manually:'+str(nJob)) - if nThread is not None: - mkl_set_num_threads(nThread) - print('setting the number of thread manually:'+str(nThread)) - - # adding a new machine: - # if name == 'nameOfTheMachine': - # mkl_set_num_threads(6) - # wfs.nJobs = 32 - # count = 1 - - if count == 0 : - - print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') - print(' WARNING THE PARALLELIZATION OF THE CODE IS NOT OPTIMIZED FOR THIS MACHINE!') - import multiprocessing - n_cpu = multiprocessing.cpu_count() - if n_cpu > 16: - print(' TAKING THE DEFAULT VALUES FOR JOBLIB : using 10% of the Threads .....') - mkl_set_num_threads(int(n_cpu//10)) - wfs.nJobs = n_cpu//5 - else: - print(' TAKING THE DEFAULT VALUES FOR JOBLIB : using 10% of the Threads .....') - mkl_set_num_threads(int(n_cpu//4)) - wfs.nJobs = n_cpu//2 - - print(' YOU SHOULD CONSIDER ADDING A CASE IN AO_modules.tools.set_paralleling_setup.py! ') - print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') - else: - mkl_set_num_threads(2) - wfs.nJobs = 10 - - - +# -*- coding: utf-8 -*- +""" +Created on Wed Jan 20 17:02:23 2021 + +@author: cheritie +""" +import ctypes +import socket + + +def set_paralleling_setup(wfs,ELT = True, nThread = None, nJob = None): + + try : + mkl_rt = ctypes.CDLL('libmkl_rt.so') + mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads + except: + try: + mkl_rt = ctypes.CDLL('./mkl_rt.dll') + mkl_set_num_threads = mkl_rt.MKL_Set_Num_Threads + except: + try: + import mkl + mkl_set_num_threads = mkl.set_num_threads + except: + print('Could not optimize the parallelisation of the code ') + + + if ELT is True: + + name = socket.gethostname() + count = 0 + + if name == 'HQL025432': + mkl_set_num_threads(4) + wfs.nJobs = 4 + count = 1 + + if name == 'HQL025838': + mkl_set_num_threads(4) + wfs.nJobs = 8 + count = 1 + + if name == 'mcao144.hq.eso.org': + mkl_set_num_threads(8) + wfs.nJobs = 12 + count = 1 + + if name == 'mcao146': + mkl_set_num_threads(6) + wfs.nJobs = 12 + count = 1 + + if name == 'mcao145.hq.eso.org': + mkl_set_num_threads(6) + wfs.nJobs = 12 + count = 1 + + if name == 'mcao147.hq.eso.org': + mkl_set_num_threads(6) + wfs.nJobs = 10 + count = 1 + + if name == 'mcao148': + mkl_set_num_threads(6) + wfs.nJobs = 12 + count = 1 + + if name == 'mcao149': + mkl_set_num_threads(8) + wfs.nJobs = 12 + count = 1 + + if name == 'mcao150.hq.eso.org': + mkl_set_num_threads(10) + wfs.nJobs = 16 + count = 1 + + if name == 'mcao151.hq.eso.org': + mkl_set_num_threads(10) + wfs.nJobs = 10 + count = 1 + + if name == 'mcao152.hq.eso.org': + mkl_set_num_threads(8) + wfs.nJobs = 24 + count = 1 + + + if nJob is not None: + wfs.nJobs = nJob + print('setting the number of jobs manually:'+str(nJob)) + if nThread is not None: + mkl_set_num_threads(nThread) + print('setting the number of thread manually:'+str(nThread)) + + # adding a new machine: + # if name == 'nameOfTheMachine': + # mkl_set_num_threads(6) + # wfs.nJobs = 32 + # count = 1 + + if count == 0 : + + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') + print(' WARNING THE PARALLELIZATION OF THE CODE IS NOT OPTIMIZED FOR THIS MACHINE!') + import multiprocessing + n_cpu = multiprocessing.cpu_count() + if n_cpu > 16: + print(' TAKING THE DEFAULT VALUES FOR JOBLIB : using 10% of the Threads .....') + mkl_set_num_threads(int(n_cpu//10)) + wfs.nJobs = n_cpu//5 + else: + print(' TAKING THE DEFAULT VALUES FOR JOBLIB : using 10% of the Threads .....') + mkl_set_num_threads(int(n_cpu//4)) + wfs.nJobs = n_cpu//2 + + print(' YOU SHOULD CONSIDER ADDING A CASE IN AO_modules.tools.set_paralleling_setup.py! ') + print('!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!') + else: + mkl_set_num_threads(2) + wfs.nJobs = 10 + + + diff --git a/AO_modules/tools/tools.py b/OOPAO/tools/tools.py old mode 100755 new mode 100644 similarity index 95% rename from AO_modules/tools/tools.py rename to OOPAO/tools/tools.py index a838829..f6f5a69 --- a/AO_modules/tools/tools.py +++ b/OOPAO/tools/tools.py @@ -1,226 +1,229 @@ -# -*- coding: utf-8 -*- -""" -Created on Mon Mar 16 18:33:20 2020 - -@author: cheritie -""" - -import numpy as np -import os -import skimage.transform as sk -from astropy.io import fits as pfits -import json -import jsonpickle -import subprocess -#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% USEFUL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% -def print_(input_text,condition): - if condition: - print(input_text) - - -def createFolder(path): - - if path.rfind('.') != -1: - path = path[:path.rfind('/')+1] - - try: - os.makedirs(path) - except OSError: - if path: - path =path - else: - print ("Creation of the directory %s failed:" % path) - print('Maybe you do not have access to this location.') - else: - print ("Successfully created the directory %s !" % path) - -def emptyClass(): - class nameClass: - pass - return nameClass - -def bsxfunMinus(a,b): - A =np.tile(a[...,None],len(b)) - B =np.tile(b[...,None],len(a)) - out = A-B.T - return out - -def cart2pol(x, y): - rho = np.sqrt(x**2 + y**2) - phi = np.arctan2(y, x) - return(rho, phi) - -def pol2cart(rho, phi): - x = rho * np.cos(phi) - y = rho * np.sin(phi) - return(x, y) - -def translationImageMatrix(image,shift): - # translate the image with the corresponding shift value - tf_shift = sk.SimilarityTransform(translation=shift) - return tf_shift - -def globalTransformation(image,shiftMatrix,order=3): - output = sk.warp(image,(shiftMatrix).inverse,order=order) - return output - - -def reshape_2D(A,axis = 2, pupil=False ): - if axis ==2: - out = np.reshape(A,[A.shape[0]*A.shape[1],A.shape[2]]) - else: - out = np.reshape(A,[A.shape[0],A.shape[1]*A.shape[2]]) - if pupil: - out = np.squeeze(out[pupil,:]) - return out - - -def reshape_3D(A,axis = 1 ): - if axis ==1: - dim_rep =np.sqrt(A.shape[0]) - out = np.reshape(A,[dim_rep,dim_rep,A.shape[1]]) - else: - dim_rep =np.sqrt(A.shape[1]) - out = np.reshape(A,[A.shape[0],dim_rep,dim_rep]) - return out - - -def read_json(filename): - with open(filename ) as f: - C = json.load(f) - - data = jsonpickle.decode(C) - - return data - -def read_fits(filename , dim = 0): - hdu = pfits.open(filename) - if dim == 0: - try: - data = np.copy(hdu[1].data) - except: - data = np.copy(hdu[0].data) - else: - - data = hdu[dim].data - hdu.close() - del hdu[0].data - - - return data - - -def write_fits(data, filename , header_name = '',overwrite=True): - - hdr = pfits.Header() - hdr['TITLE'] = header_name - - empty_primary = pfits.PrimaryHDU(header = hdr) - - primary_hdu = pfits.ImageHDU(data) - - hdu = pfits.HDUList([empty_primary, primary_hdu]) - - hdu.writeto(filename,overwrite = overwrite) - hdu.close() - del hdu[0].data - -def findNextPowerOf2(n): - - # decrement `n` (to handle cases when `n` itself - # is a power of 2) - n = n - 1 - - # do till only one bit is left - while n & n - 1: - n = n & n - 1 # unset rightmost bit - - # `n` is now a power of two (less than `n`) - - # return next power of 2 - return n << 1 - -def centroid(image, threshold = 0): - im = np.copy(image) - im[im<threshold]=0 - x = 0 - y = 0 - s = im.sum() - for i in range(im.shape[0]): - for j in range(im.shape[1]): - x+=im[i,j]*j/s - y+=im[j,i]*j/s - - return x,y - - -def bin_ndarray(ndarray, new_shape, operation='sum'): - """ - Bins an ndarray in all axes based on the target shape, by summing or - averaging. - - Number of output dimensions must match number of input dimensions and - new axes must divide old ones. - - Example - ------- - >>> m = np.arange(0,100,1).reshape((10,10)) - >>> n = bin_ndarray(m, new_shape=(5,5), operation='sum') - >>> print(n) - - [[ 22 30 38 46 54] - [102 110 118 126 134] - [182 190 198 206 214] - [262 270 278 286 294] - [342 350 358 366 374]] - - """ - operation = operation.lower() - if not operation in ['sum', 'mean']: - raise ValueError("Operation not supported.") - if ndarray.ndim != len(new_shape): - raise ValueError("Shape mismatch: {} -> {}".format(ndarray.shape, - new_shape)) - compression_pairs = [(d, c//d) for d,c in zip(new_shape, - ndarray.shape)] - flattened = [l for p in compression_pairs for l in p] - ndarray = ndarray.reshape(flattened) - for i in range(len(new_shape)): - op = getattr(ndarray, operation) - ndarray = op(-1*(i+1)) - return ndarray - - - -def get_gpu_memory(): - command = "nvidia-smi --query-gpu=memory.free --format=csv" - memory_free_info = subprocess.check_output(command.split()).decode('ascii').split('\n')[:-1][1:] - memory_free_values = [int(x.split()[0]) for i, x in enumerate(memory_free_info)] - return memory_free_values - - -def compute_fourier_mode(pupil,spatial_frequency,angle_deg,zeropadding = 2): - N = pupil.shape[0] - mode = np.zeros([N,N]) - - t = spatial_frequency*zeropadding/2 - Z = np.zeros([N,N],'complex') - - thet = angle_deg - - Z[N//2+int(t*np.cos(np.deg2rad(thet)+np.pi)),N//2+int(t*np.sin(np.deg2rad(thet)+np.pi))]=1 - Z[N//2-int(t*np.cos(np.deg2rad(thet)+np.pi)),N//2-int(t*np.sin(np.deg2rad(thet)+np.pi))]=-100000 - - support = np.zeros([N*zeropadding,N*zeropadding],dtype='complex') - - - center = zeropadding*N//2 - support [center-N//2:center+N//2,center-N//2:center+N//2]=Z - F = np.fft.ifft2(support) - F= F[center-N//2:center+N//2,center-N//2:center+N//2] - # normalisation - S= np.abs(F)/np.max(np.abs(F)) - S=S - np.mean([S.max(), S.min()]) - mode = S/S.std() - +# -*- coding: utf-8 -*- +""" +Created on Mon Mar 16 18:33:20 2020 + +@author: cheritie +""" + +import json +import os +import subprocess + +import jsonpickle +import numpy as np +import skimage.transform as sk +from astropy.io import fits as pfits + + +#%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% USEFUL FUNCTIONS %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +def print_(input_text,condition): + if condition: + print(input_text) + + +def createFolder(path): + + if path.rfind('.') != -1: + path = path[:path.rfind('/')+1] + + try: + os.makedirs(path) + except OSError: + if path: + path =path + else: + print ("Creation of the directory %s failed:" % path) + print('Maybe you do not have access to this location.') + else: + print ("Successfully created the directory %s !" % path) + +def emptyClass(): + class nameClass: + pass + return nameClass + +def bsxfunMinus(a,b): + A =np.tile(a[...,None],len(b)) + B =np.tile(b[...,None],len(a)) + out = A-B.T + return out + +def cart2pol(x, y): + rho = np.sqrt(x**2 + y**2) + phi = np.arctan2(y, x) + return(rho, phi) + +def pol2cart(rho, phi): + x = rho * np.cos(phi) + y = rho * np.sin(phi) + return(x, y) + +def translationImageMatrix(image,shift): + # translate the image with the corresponding shift value + tf_shift = sk.SimilarityTransform(translation=shift) + return tf_shift + +def globalTransformation(image,shiftMatrix,order=3): + output = sk.warp(image,(shiftMatrix).inverse,order=order) + return output + + +def reshape_2D(A,axis = 2, pupil=False ): + if axis ==2: + out = np.reshape(A,[A.shape[0]*A.shape[1],A.shape[2]]) + else: + out = np.reshape(A,[A.shape[0],A.shape[1]*A.shape[2]]) + if pupil: + out = np.squeeze(out[pupil,:]) + return out + + +def reshape_3D(A,axis = 1 ): + if axis ==1: + dim_rep =np.sqrt(A.shape[0]) + out = np.reshape(A,[dim_rep,dim_rep,A.shape[1]]) + else: + dim_rep =np.sqrt(A.shape[1]) + out = np.reshape(A,[A.shape[0],dim_rep,dim_rep]) + return out + + +def read_json(filename): + with open(filename ) as f: + C = json.load(f) + + data = jsonpickle.decode(C) + + return data + +def read_fits(filename , dim = 0): + hdu = pfits.open(filename) + if dim == 0: + try: + data = np.copy(hdu[1].data) + except: + data = np.copy(hdu[0].data) + else: + + data = hdu[dim].data + hdu.close() + del hdu[0].data + + + return data + + +def write_fits(data, filename , header_name = '',overwrite=True): + + hdr = pfits.Header() + hdr['TITLE'] = header_name + + empty_primary = pfits.PrimaryHDU(header = hdr) + + primary_hdu = pfits.ImageHDU(data) + + hdu = pfits.HDUList([empty_primary, primary_hdu]) + + hdu.writeto(filename,overwrite = overwrite) + hdu.close() + del hdu[0].data + +def findNextPowerOf2(n): + + # decrement `n` (to handle cases when `n` itself + # is a power of 2) + n = n - 1 + + # do till only one bit is left + while n & n - 1: + n = n & n - 1 # unset rightmost bit + + # `n` is now a power of two (less than `n`) + + # return next power of 2 + return n << 1 + +def centroid(image, threshold = 0): + im = np.copy(image) + im[im<threshold]=0 + x = 0 + y = 0 + s = im.sum() + for i in range(im.shape[0]): + for j in range(im.shape[1]): + x+=im[i,j]*j/s + y+=im[j,i]*j/s + + return x,y + + +def bin_ndarray(ndarray, new_shape, operation='sum'): + """ + Bins an ndarray in all axes based on the target shape, by summing or + averaging. + + Number of output dimensions must match number of input dimensions and + new axes must divide old ones. + + Example + ------- + >>> m = np.arange(0,100,1).reshape((10,10)) + >>> n = bin_ndarray(m, new_shape=(5,5), operation='sum') + >>> print(n) + + [[ 22 30 38 46 54] + [102 110 118 126 134] + [182 190 198 206 214] + [262 270 278 286 294] + [342 350 358 366 374]] + + """ + operation = operation.lower() + if not operation in ['sum', 'mean']: + raise ValueError("Operation not supported.") + if ndarray.ndim != len(new_shape): + raise ValueError("Shape mismatch: {} -> {}".format(ndarray.shape, + new_shape)) + compression_pairs = [(d, c//d) for d,c in zip(new_shape, + ndarray.shape)] + flattened = [l for p in compression_pairs for l in p] + ndarray = ndarray.reshape(flattened) + for i in range(len(new_shape)): + op = getattr(ndarray, operation) + ndarray = op(-1*(i+1)) + return ndarray + + + +def get_gpu_memory(): + command = "nvidia-smi --query-gpu=memory.free --format=csv" + memory_free_info = subprocess.check_output(command.split()).decode('ascii').split('\n')[:-1][1:] + memory_free_values = [int(x.split()[0]) for i, x in enumerate(memory_free_info)] + return memory_free_values + + +def compute_fourier_mode(pupil,spatial_frequency,angle_deg,zeropadding = 2): + N = pupil.shape[0] + mode = np.zeros([N,N]) + + t = spatial_frequency*zeropadding/2 + Z = np.zeros([N,N],'complex') + + thet = angle_deg + + Z[N//2+int(t*np.cos(np.deg2rad(thet)+np.pi)),N//2+int(t*np.sin(np.deg2rad(thet)+np.pi))]=1 + Z[N//2-int(t*np.cos(np.deg2rad(thet)+np.pi)),N//2-int(t*np.sin(np.deg2rad(thet)+np.pi))]=-100000 + + support = np.zeros([N*zeropadding,N*zeropadding],dtype='complex') + + + center = zeropadding*N//2 + support [center-N//2:center+N//2,center-N//2:center+N//2]=Z + F = np.fft.ifft2(support) + F= F[center-N//2:center+N//2,center-N//2:center+N//2] + # normalisation + S= np.abs(F)/np.max(np.abs(F)) + S=S - np.mean([S.max(), S.min()]) + mode = S/S.std() + return mode \ No newline at end of file diff --git a/README.md b/README.md index b32fb9a..ecc070b 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ Object Oriented Python Adaptive Optics (OOPAO) is a project under development to This code is inspired from the OOMAO architecture: https://github.com/cmcorreia/LAM-Public developped by C. Correia and R. Conan (https://doi.org/10.1117/12.2054470). The project was initially intended for personal use in the frame of an ESO project. It is now open to any interested user. -# FUNCTIONALITIES +## FUNCTIONALITIES _ Atmosphere: Multi-layers with infinitely and non-stationary phase screens, conditions can be updated on the fly if required _ Telescope: Default circular pupil or user defined, with/without spiders @@ -12,26 +12,75 @@ The project was initially intended for personal use in the frame of an ESO proje _ Source: NGS or LGS _ Control Basis: KL modal basis, Zernike Polynomials -# LICENSE +## LICENSE This project is licensed under the terms of the MIT license. -# MODULES REQUIRED -The code is written for Python 3 (version 3.8.8) and requires the following modules +## INSTALLATION - joblib 1.01 => paralleling computing - scikit-image 0.18.3 => 2D interpolations - numexpr 2.7.3 => memory optimized simple operations - astropy 4.2.1 => handling of fits files - pyFFTW 0.12.0 => optimization of the FFT - mpmath 1.2.1 => arithmetic with arbitrary precision - jsonpickle 1.4.1 => json files encoding - aotools => zernike modes and functionalities for atmosphere computation - numba 0.53.1 => required in aotools +### (Recommended) Creating a virtual environment -If GPU computation is available: - cupy-cuda114 9.5.0 => GPU computation of the PWFS code (Not required) +It is always recommended that you use a virtual environment. First create it: + +``` +python -m venv venv + +# or + +python3 -m venv venv + +``` + +And finally activate it: + +``` +# Unix +source ./venv/bin/activate + +# or + +# Windows PowerShell +.\venv\Scripts\Activate.ps1 +``` + +After the environment is set up and activated, this package can then be easily installed. Anytime you wish to use this +package, you should activate the respective environment. -# CODE OPTIMIZATION +### Using `pip` + +First clone the repository: + +``` +https://github.com/cheritier/OOPAO.git +``` + +And then install the package using `pip`: + +``` +python -m pip install -e OOPAO + +# or + +python3 -m pip install -e OOPAO +``` + + +## MODULES REQUIRED +The code is written for Python 3 (version 3.8.8) and requires the following modules: +``` +joblib => paralleling computing +scikit-image => 2D interpolations +astropy => handling of fits files +pyFFTW => optimization of the FFT +mpmath => arithmetic with arbitrary precision +jsonpickle => json files encoding +aotools => zernike modes and functionalities for atmosphere computation +numba => required in aotools +``` +If GPU computation is available: +``` +cupy => GPU computation of the PWFS code (Not required) +``` +## CODE OPTIMIZATION OOPAO multi-threading is based on the use of the numpy package built with the mkl library, make sure that the proper numpy package is installed to make sure that the operations are multi-threaded. To do this you can use the numpy.show_config() function in your python session: @@ -39,12 +88,10 @@ To do this you can use the numpy.show_config() function in your python session: import numpy numpy.show_config() -If the wrong version is installed, the __load__oopao.py function will raise a warning. - -# CONTRIBUTORS +## CONTRIBUTORS C.T. Heritier, C. Vérinaud -# AKNOWLEDGEMENTS +## ACKNOWLEDGEMENTS This tool has been developped during the Engineering & Research Technology Fellowship of C. Héritier funded by ESO. Some functionalities of the code make use of the aotools package developped by M. J. Townson et al (2019). See https://doi.org/10.1364/OE.27.031316. \ No newline at end of file diff --git a/__init__.py b/__init__.py deleted file mode 100755 index c03131d..0000000 --- a/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Apr 3 10:56:59 2020 - -@author: cheritie -""" - diff --git a/__load__oopao.py b/__load__oopao.py deleted file mode 100644 index 6fa54fd..0000000 --- a/__load__oopao.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Nov 13 09:54:16 2020 - -@author: cheritie -""" -import glob -import sys -import numpy as np - -def load_oopao(): - - # set path properly - name_repository = 'AO_modules' - counter = 0 - path = glob.glob(name_repository,recursive = True) - print('\n') - print('======================================================================================> ') - print(' ✸ * ° * * ') - print(' ° ✸ ▄███▄ ▄███▄ ▄████▄ ▄███▄ * ▄███▄ => ▄▄▄▄ ') - print(' ✸ ° ██* ██ ██ ██ ██ ██ ██ ██ ██ ██ ====> ▄█▀▀ ▀▀█▄ ') - print(' * ° ✸ ██ ██ ██ ° ██ ██ ██ ██ * ██ ██ ██ ==> █▀ ▄█▀▀█▄ ▀█ ') - print('✸ * ° ██ ██ ██ ██ █████▀ ██▄▄▄██ ██ ██ =========> █▀ █▀ ▄▄ ▀█ ▀█ ') - print(' ✸ ° ██ * ██ ██ ██ ██ ██▀▀▀██ ██ ██ ========> █▄ █▄ ▀▀ ▄█ ▄█ ') - print(' * ✸ ° ██ ██ ██ ██ ██ * ██ ██ ██* ██ => █▄ ▀█▄▄█▀ ▄█ ') - print(' ° * ✸ ▀███▀ ▀███▀ ██ ° ██ ██ ▀███▀ ==> ▀█▄▄ ▄▄█▀ ') - print(' ✸ * * * ▀▀▀▀ ') - print('======================================================================================> ') - print('\n') - - n_max = 40 - if path ==[]: - while path == [] and counter!= n_max : - # print('Looking for AO_Modules in the parent repositories...') - name_repository = '../'+name_repository - path = glob.glob(name_repository,recursive = True) - counter += 1 - nameFolder =path[0] - sys.path.append(nameFolder[:-10]) - if counter == n_max: - raise RuntimeError('The AO modules repository could not be found. Make sure that your script is located in a sub-folder of the OOPAO repository!') - # check the version of numpy libraries - try: - config = np.__config__.blas_mkl_info['libraries'][0] - if config != 'mkl_rt': - print('**************************************************************************************************************************************************************') - print('NUMPY WARNING: OOPAO multi-threading requires to use numpy built with mkl library! If you are using AMD or Apple processors the code could be single threaded!') - print('**************************************************************************************************************************************************************') - except: - print('**************************************************************************************************************************************************************') - print('NUMPY WARNING: mkl blas not found! Multi-threading may not work as expected.') - print('**************************************************************************************************************************************************************') - # print('OOPAO has been properly initialized!') - # print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..b508c0f --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,3 @@ +[build-system] +requires = ["setuptools>=42.0"] +build-backend = "setuptools.build_meta" diff --git a/requirements.txt b/requirements.txt index 166b812..8edddb0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,9 +1,23 @@ -numba -joblib==1.01 -scikit-image==0.18.3 -numexpr==2.7.3 -astropy -pyfftw -mpmath==1.2.1 -jsonpickle==1.4.1 -aotools \ No newline at end of file +aotools == 1.0.6 +astropy <= 4.1 ; python_version == "3.6" +astropy <= 4.3.1 ; python_version == "3.7" +astropy == 5.1.1 ; python_version >= "3.8" +joblib == 1.1.1 ; python_version == "3.6" +joblib ; python_version >= "3.8" +jsonpickle < 3.0 ; python_version == "3.6" +jsonpickle == 1.4.1 ; python_version == "3.7" +matplotlib <= 3.3.4 ; python_version == "3.6" +matplotlib <= 3.5.3 ; python_version == "3.7" +matplotlib == 3.6.2 ; python_version >= "3.8" +numba <= 0.53.1 ; python_version <= "3.6" +numba == 0.53.1 +numpy == 1.19.5 ; python_version == "3.6" +numpy == 1.21.6 ; python_version == "3.7" +numpy == 1.23.4 ; python_version >= "3.8" +pyFFTW <= 0.12.0 ; python_version == "3.6" +pyFFTW == 0.12.0 ; python_version >= "3.7" +scikit_image <= 0.17.2 ; python_version == "3.6" +scikit_image == 0.18.3 ; python_version >= "3.7" +scipy <= 1.5.4 ; python_version == "3.6" +scipy <= 1.7.3 ; python_version == "3.7" +scipy == 1.9.3 ; python_version >= "3.8" diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..7e4392b --- /dev/null +++ b/setup.cfg @@ -0,0 +1,49 @@ +[metadata] +name = OOPAO +description = OOPAO +author = cheritier +;version = attr: OOPAO.__version__ +license = GPLv3 +license_files = LICENSE +platforms = unix, linux, osx, cygwin, win32 +url = https://github.com/cheritier/OOPAO +classifiers = + Programming Language :: Python :: 3 + Programming Language :: Python :: 3 :: Only + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + Programming Language :: Python :: 3.9 + Programming Language :: Python :: 3.10 + Operating System :: OS Independent + Intended Audience :: Science/Research + Topic :: Scientific/Engineering + +[options] +packages = + OOPAO +install_requires = + aotools + astropy + joblib + jsonpickle + matplotlib + numba + numpy + pyFFTW + scikit_image + scipy +python_requires = + >=3.6 +package_dir = + =. +zip_safe = + no + +[options.extras_require] +cuda = + cupy + +;[options.entry_points] +;console_scripts = +; OOPAO = OOPAO.main:main diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..7f1a176 --- /dev/null +++ b/setup.py @@ -0,0 +1,4 @@ +from setuptools import setup + +if __name__ == "__main__": + setup() diff --git a/tutorials/__load__oopao.py b/tutorials/__load__oopao.py deleted file mode 100644 index 6fa54fd..0000000 --- a/tutorials/__load__oopao.py +++ /dev/null @@ -1,55 +0,0 @@ -# -*- coding: utf-8 -*- -""" -Created on Fri Nov 13 09:54:16 2020 - -@author: cheritie -""" -import glob -import sys -import numpy as np - -def load_oopao(): - - # set path properly - name_repository = 'AO_modules' - counter = 0 - path = glob.glob(name_repository,recursive = True) - print('\n') - print('======================================================================================> ') - print(' ✸ * ° * * ') - print(' ° ✸ ▄███▄ ▄███▄ ▄████▄ ▄███▄ * ▄███▄ => ▄▄▄▄ ') - print(' ✸ ° ██* ██ ██ ██ ██ ██ ██ ██ ██ ██ ====> ▄█▀▀ ▀▀█▄ ') - print(' * ° ✸ ██ ██ ██ ° ██ ██ ██ ██ * ██ ██ ██ ==> █▀ ▄█▀▀█▄ ▀█ ') - print('✸ * ° ██ ██ ██ ██ █████▀ ██▄▄▄██ ██ ██ =========> █▀ █▀ ▄▄ ▀█ ▀█ ') - print(' ✸ ° ██ * ██ ██ ██ ██ ██▀▀▀██ ██ ██ ========> █▄ █▄ ▀▀ ▄█ ▄█ ') - print(' * ✸ ° ██ ██ ██ ██ ██ * ██ ██ ██* ██ => █▄ ▀█▄▄█▀ ▄█ ') - print(' ° * ✸ ▀███▀ ▀███▀ ██ ° ██ ██ ▀███▀ ==> ▀█▄▄ ▄▄█▀ ') - print(' ✸ * * * ▀▀▀▀ ') - print('======================================================================================> ') - print('\n') - - n_max = 40 - if path ==[]: - while path == [] and counter!= n_max : - # print('Looking for AO_Modules in the parent repositories...') - name_repository = '../'+name_repository - path = glob.glob(name_repository,recursive = True) - counter += 1 - nameFolder =path[0] - sys.path.append(nameFolder[:-10]) - if counter == n_max: - raise RuntimeError('The AO modules repository could not be found. Make sure that your script is located in a sub-folder of the OOPAO repository!') - # check the version of numpy libraries - try: - config = np.__config__.blas_mkl_info['libraries'][0] - if config != 'mkl_rt': - print('**************************************************************************************************************************************************************') - print('NUMPY WARNING: OOPAO multi-threading requires to use numpy built with mkl library! If you are using AMD or Apple processors the code could be single threaded!') - print('**************************************************************************************************************************************************************') - except: - print('**************************************************************************************************************************************************************') - print('NUMPY WARNING: mkl blas not found! Multi-threading may not work as expected.') - print('**************************************************************************************************************************************************************') - # print('OOPAO has been properly initialized!') - # print('%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%') - diff --git a/tutorials/parameter_files/parameterFile_VLT_I_Band_PWFS.py b/tutorials/parameter_files/parameterFile_VLT_I_Band_PWFS.py index 6964cc1..b056d05 100644 --- a/tutorials/parameter_files/parameterFile_VLT_I_Band_PWFS.py +++ b/tutorials/parameter_files/parameterFile_VLT_I_Band_PWFS.py @@ -5,9 +5,7 @@ @author: cheritie """ -import numpy as np -from os import path -from AO_modules.tools.tools import createFolder +from OOPAO.tools.tools import createFolder def initializeParameterFile(): diff --git a/tutorials/run_SPRINT_tutorial_PWFS.py b/tutorials/run_SPRINT_tutorial_PWFS.py index 71e54a1..026f91e 100644 --- a/tutorials/run_SPRINT_tutorial_PWFS.py +++ b/tutorials/run_SPRINT_tutorial_PWFS.py @@ -4,27 +4,22 @@ @author: cheritie """ -# commom modules + import matplotlib.pyplot as plt -import numpy as np - -import __load__oopao -__load__oopao.load_oopao() - -from AO_modules.Atmosphere import Atmosphere -from AO_modules.Pyramid import Pyramid -from AO_modules.DeformableMirror import DeformableMirror -from AO_modules.MisRegistration import MisRegistration -from AO_modules.Telescope import Telescope -from AO_modules.Source import Source -# calibration modules -from AO_modules.calibration.compute_KL_modal_basis import compute_M2C -from AO_modules.calibration.ao_calibration import ao_calibration -# display modules -from AO_modules.tools.displayTools import displayMap - -#%% ----------------------- read parameter file ---------------------------------- +import numpy as np + +from OOPAO.Atmosphere import Atmosphere +from OOPAO.DeformableMirror import DeformableMirror +from OOPAO.MisRegistration import MisRegistration +from OOPAO.Pyramid import Pyramid +from OOPAO.Source import Source +from OOPAO.Telescope import Telescope +from OOPAO.calibration.ao_calibration import ao_calibration +from OOPAO.calibration.compute_KL_modal_basis import compute_M2C +from OOPAO.tools.displayTools import displayMap +# %% ----------------------- read parameter file ---------------------------------- from parameter_files.parameterFile_VLT_I_Band_PWFS import initializeParameterFile + param = initializeParameterFile() #%% ----------------------- TELESCOPE ---------------------------------- @@ -167,7 +162,7 @@ plt.title('WFE: '+str(var_fit) + ' nm') #%% ------------------ SETTING UP SPRINT ALGORITHM ------------------ -from AO_modules.tools.tools import emptyClass +from OOPAO.tools.tools import emptyClass """ Case where the initial mis-registration is quite small (Closed Loop) => USE A MIDDLE ORDER MODE! @@ -194,14 +189,14 @@ obj.param = param -from AO_modules.SPRINT import SPRINT +from OOPAO.SPRINT import SPRINT Sprint = SPRINT(obj,basis, recompute_sensitivity=True) #%% -------------------------- EXAMPLE WITH SMALL MIS-REGISTRATIONS - NO UPDATE OF MODEL-------------------------------------- -from AO_modules.mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration +from OOPAO.mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration # mis-reg considered @@ -270,7 +265,7 @@ misReg_out = np.asarray(misReg_out) #%% -from AO_modules.tools.displayTools import makeSquareAxes +from OOPAO.tools.displayTools import makeSquareAxes plt.figure() plt.subplot(2,2,1) @@ -317,7 +312,7 @@ #%% -------------------------- EXAMPLE WITH LARGE MIS-REGISTRATIONS + UPDATE OF MODEL -------------------------------------- -from AO_modules.tools.tools import emptyClass +from OOPAO.tools.tools import emptyClass """ Case where the initial mis-registration is very large (Bootstrapping) => USE A LOW ORDER MODE! @@ -344,10 +339,10 @@ obj.param = param -from AO_modules.SPRINT import SPRINT +from OOPAO.SPRINT import SPRINT Sprint = SPRINT(obj,basis,recompute_sensitivity=True) -from AO_modules.mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration +from OOPAO.mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration # mis-reg considered diff --git a/tutorials/run_SPRINT_tutorial_SH_WFS.py.py b/tutorials/run_SPRINT_tutorial_SH_WFS.py.py index 2802e2c..9d2338a 100644 --- a/tutorials/run_SPRINT_tutorial_SH_WFS.py.py +++ b/tutorials/run_SPRINT_tutorial_SH_WFS.py.py @@ -4,26 +4,21 @@ @author: cheritie """ -# commom modules + import matplotlib.pyplot as plt -import numpy as np - -import __load__oopao -__load__oopao.load_oopao() - -from AO_modules.Atmosphere import Atmosphere -from AO_modules.DeformableMirror import DeformableMirror -from AO_modules.MisRegistration import MisRegistration -from AO_modules.Telescope import Telescope -from AO_modules.Source import Source -# calibration modules -from AO_modules.calibration.compute_KL_modal_basis import compute_M2C -from AO_modules.calibration.ao_calibration import ao_calibration -# display modules -from AO_modules.tools.displayTools import displayMap - -#%% ----------------------- read parameter file ---------------------------------- +import numpy as np + +from OOPAO.Atmosphere import Atmosphere +from OOPAO.DeformableMirror import DeformableMirror +from OOPAO.MisRegistration import MisRegistration +from OOPAO.Source import Source +from OOPAO.Telescope import Telescope +from OOPAO.calibration.ao_calibration import ao_calibration +from OOPAO.calibration.compute_KL_modal_basis import compute_M2C +from OOPAO.tools.displayTools import displayMap +# %% ----------------------- read parameter file ---------------------------------- from parameter_files.parameterFile_VLT_I_Band_SHWFS import initializeParameterFile + param = initializeParameterFile() #%% ----------------------- TELESCOPE ---------------------------------- @@ -106,7 +101,7 @@ # make sure tel and atm are separated to initialize the PWFS -from AO_modules.ShackHartmann import ShackHartmann +from OOPAO.ShackHartmann import ShackHartmann tel-atm wfs = ShackHartmann(nSubap = param['nSubaperture'],\ telescope = tel,\ @@ -175,7 +170,7 @@ plt.title('WFE: '+str(var_fit) + ' nm') #%% ------------------ SETTING UP SPRINT ALGORITHM ------------------ -from AO_modules.tools.tools import emptyClass +from OOPAO.tools.tools import emptyClass """ Case where the initial mis-registration is quite small (Closed Loop) => USE A MIDDLE ORDER MODE! @@ -202,14 +197,14 @@ obj.param = param -from AO_modules.SPRINT import SPRINT +from OOPAO.SPRINT import SPRINT Sprint = SPRINT(obj,basis, recompute_sensitivity=True) #%% -------------------------- EXAMPLE WITH SMALL MIS-REGISTRATIONS - NO UPDATE OF MODEL-------------------------------------- -from AO_modules.mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration +from OOPAO.mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration # mis-reg considered @@ -278,7 +273,7 @@ misReg_out = np.asarray(misReg_out) #%% -from AO_modules.tools.displayTools import makeSquareAxes +from OOPAO.tools.displayTools import makeSquareAxes plt.figure() plt.subplot(2,2,1) @@ -325,7 +320,7 @@ #%% -------------------------- EXAMPLE WITH LARGE MIS-REGISTRATIONS + UPDATE OF MODEL -------------------------------------- -from AO_modules.tools.tools import emptyClass +from OOPAO.tools.tools import emptyClass """ Case where the initial mis-registration is very large (Bootstrapping) => USE A LOW ORDER MODE! @@ -352,10 +347,10 @@ obj.param = param -from AO_modules.SPRINT import SPRINT +from OOPAO.SPRINT import SPRINT Sprint = SPRINT(obj,basis,recompute_sensitivity=True) -from AO_modules.mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration +from OOPAO.mis_registration_identification_algorithm.applyMisRegistration import applyMisRegistration # mis-reg considered diff --git a/tutorials/run_howTo_CL_VLT_PWFS.py b/tutorials/run_howTo_CL_VLT_PWFS.py index 1476680..65a68b8 100644 --- a/tutorials/run_howTo_CL_VLT_PWFS.py +++ b/tutorials/run_howTo_CL_VLT_PWFS.py @@ -4,30 +4,27 @@ @author: cheritie """ -# commom modules -import matplotlib.pyplot as plt -import numpy as np + import time -plt.ion() -import __load__oopao -__load__oopao.load_oopao() - -from AO_modules.Atmosphere import Atmosphere -from AO_modules.Pyramid import Pyramid -from AO_modules.DeformableMirror import DeformableMirror -from AO_modules.MisRegistration import MisRegistration -from AO_modules.Telescope import Telescope -from AO_modules.Source import Source -# calibration modules -from AO_modules.calibration.compute_KL_modal_basis import compute_M2C -from AO_modules.calibration.ao_calibration import ao_calibration -# display modules -from AO_modules.tools.displayTools import displayMap - -#%% ----------------------- read parameter file ---------------------------------- + +import matplotlib.pyplot as plt +import numpy as np + +from OOPAO.Atmosphere import Atmosphere +from OOPAO.DeformableMirror import DeformableMirror +from OOPAO.MisRegistration import MisRegistration +from OOPAO.Pyramid import Pyramid +from OOPAO.Source import Source +from OOPAO.Telescope import Telescope +from OOPAO.tools.displayTools import displayMap +# %% ----------------------- read parameter file ---------------------------------- from parameter_files.parameterFile_VLT_I_Band_PWFS import initializeParameterFile + param = initializeParameterFile() +# %% +plt.ion() + #%% ----------------------- TELESCOPE ---------------------------------- # create the Telescope object @@ -155,7 +152,7 @@ # nMeasurements = 100) #%% ZERNIKE Polynomials -from AO_modules.Zernike import Zernike +from OOPAO.Zernike import Zernike # create Zernike Object Z = Zernike(tel,300) # compute polynomials for given telescope @@ -174,7 +171,7 @@ # amplitude of the modes in m stroke=1e-9 # Modal Interaction Matrix -from AO_modules.calibration.InteractionMatrix import InteractionMatrix +from OOPAO.calibration.InteractionMatrix import InteractionMatrix #%% @@ -197,7 +194,7 @@ #%% -from AO_modules.calibration.CalibrationVault import CalibrationVault +from OOPAO.calibration.CalibrationVault import CalibrationVault # Modal interaction matrix @@ -210,7 +207,7 @@ #%% -from AO_modules.tools.displayTools import cl_plot +from OOPAO.tools.displayTools import cl_plot tel.resetOPD() # initialize DM commands dm.coefs=0 diff --git a/tutorials/run_howTo_CL_VLT_SH_WFS.py b/tutorials/run_howTo_CL_VLT_SH_WFS.py index 7517a3c..d676ec5 100644 --- a/tutorials/run_howTo_CL_VLT_SH_WFS.py +++ b/tutorials/run_howTo_CL_VLT_SH_WFS.py @@ -4,30 +4,26 @@ @author: cheritie """ -# commom modules -import matplotlib.pyplot as plt -import numpy as np + import time -plt.ion() -import __load__oopao -__load__oopao.load_oopao() - -from AO_modules.Atmosphere import Atmosphere -from AO_modules.ShackHartmann import ShackHartmann -from AO_modules.DeformableMirror import DeformableMirror -from AO_modules.MisRegistration import MisRegistration -from AO_modules.Telescope import Telescope -from AO_modules.Source import Source -# calibration modules -from AO_modules.calibration.compute_KL_modal_basis import compute_M2C -from AO_modules.calibration.ao_calibration import ao_calibration -# display modules -from AO_modules.tools.displayTools import displayMap - -#%% ----------------------- read parameter file ---------------------------------- + +import matplotlib.pyplot as plt +import numpy as np + +from OOPAO.Atmosphere import Atmosphere +from OOPAO.DeformableMirror import DeformableMirror +from OOPAO.MisRegistration import MisRegistration +from OOPAO.ShackHartmann import ShackHartmann +from OOPAO.Source import Source +from OOPAO.Telescope import Telescope +from OOPAO.tools.displayTools import displayMap +# %% ----------------------- read parameter file ---------------------------------- from parameter_files.parameterFile_VLT_I_Band_SHWFS import initializeParameterFile + param = initializeParameterFile() +# %% +plt.ion() #%% ----------------------- TELESCOPE ---------------------------------- @@ -158,7 +154,7 @@ # nMeasurements = 100) #%% ZERNIKE Polynomials -from AO_modules.Zernike import Zernike +from OOPAO.Zernike import Zernike # create Zernike Object Z = Zernike(tel,300) # compute polynomials for given telescope @@ -177,7 +173,7 @@ # amplitude of the modes in m stroke=1e-9 # Modal Interaction Matrix -from AO_modules.calibration.InteractionMatrix import InteractionMatrix +from OOPAO.calibration.InteractionMatrix import InteractionMatrix #%% @@ -202,7 +198,7 @@ #%% # Modal interaction matrix -from AO_modules.calibration.CalibrationVault import CalibrationVault +from OOPAO.calibration.CalibrationVault import CalibrationVault # Modal interaction matrix calib_zernike = CalibrationVault(calib_zonal.D@M2C_zernike) @@ -217,7 +213,7 @@ wfs.is_geometric = False #%% -from AO_modules.tools.displayTools import cl_plot +from OOPAO.tools.displayTools import cl_plot tel.resetOPD() # initialize DM commands dm.coefs=0 diff --git a/tutorials/run_howTo_CL_VLT_SPHERE_SH_WFS.py b/tutorials/run_howTo_CL_VLT_SPHERE_SH_WFS.py index a7710ab..c73ac4c 100644 --- a/tutorials/run_howTo_CL_VLT_SPHERE_SH_WFS.py +++ b/tutorials/run_howTo_CL_VLT_SPHERE_SH_WFS.py @@ -4,29 +4,28 @@ @author: cheritie """ -# commom modules -import matplotlib.pyplot as plt -import numpy as np + import time -plt.ion() -import __load__oopao -__load__oopao.load_oopao() - -from AO_modules.Atmosphere import Atmosphere -from AO_modules.ShackHartmann import ShackHartmann -from AO_modules.DeformableMirror import DeformableMirror -from AO_modules.MisRegistration import MisRegistration -from AO_modules.Telescope import Telescope -from AO_modules.Source import Source -# calibration modules -from AO_modules.calibration.compute_KL_modal_basis import compute_M2C -# display modules -from AO_modules.tools.displayTools import displayMap - -#%% ----------------------- read parameter file ---------------------------------- + +import matplotlib.pyplot as plt +import numpy as np + +from OOPAO.Atmosphere import Atmosphere +from OOPAO.DeformableMirror import DeformableMirror +from OOPAO.MisRegistration import MisRegistration +from OOPAO.ShackHartmann import ShackHartmann +from OOPAO.Source import Source +from OOPAO.Telescope import Telescope +from OOPAO.calibration.compute_KL_modal_basis import compute_M2C +from OOPAO.tools.displayTools import displayMap +# %% ----------------------- read parameter file ---------------------------------- from parameter_files.parameterFile_VLT_SPHERE_SH_WFS import initializeParameterFile + param = initializeParameterFile() +# %% +plt.ion() + #%% ----------------------- TELESCOPE ---------------------------------- # create the Telescope object diff --git a/tutorials/run_tutorial_PSF.py b/tutorials/run_tutorial_PSF.py index 1ea6d6f..1bfd448 100755 --- a/tutorials/run_tutorial_PSF.py +++ b/tutorials/run_tutorial_PSF.py @@ -4,23 +4,16 @@ @author: cheritie """ -# commom modules -import matplotlib.pyplot as plt -import numpy as np -import __load__oopao -__load__oopao.load_oopao() -# local modules -from AO_modules.Telescope import Telescope -from AO_modules.Zernike import Zernike -from AO_modules.Source import Source -from AO_modules.Atmosphere import Atmosphere -from AO_modules.tools.displayTools import displayMap, makeSquareAxes - - - - -#%% ----------------------- read parameter file ---------------------------------- +import matplotlib.pyplot as plt +import numpy as np + +from OOPAO.Atmosphere import Atmosphere +from OOPAO.Source import Source +from OOPAO.Telescope import Telescope +from OOPAO.Zernike import Zernike +from OOPAO.tools.displayTools import displayMap, makeSquareAxes +# %% ----------------------- read parameter file ---------------------------------- from parameter_files.parameterFile_VLT_I_Band import initializeParameterFile param = initializeParameterFile() diff --git a/tutorials/tutorial_howToAdaptiveOptics.ipynb b/tutorials/tutorial_howToAdaptiveOptics.ipynb index dcaa757..12d6996 100755 --- a/tutorials/tutorial_howToAdaptiveOptics.ipynb +++ b/tutorials/tutorial_howToAdaptiveOptics.ipynb @@ -89,29 +89,21 @@ "\n", "@author: cheritie\n", "\"\"\"\n", - "# commom modules\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np \n", - "import time\n", - "\n", - "# adding AO_Module to the path\n", - "import __load__oopao\n", - "__load__oopao.load_oopao()\n", - "\n", - "# loading AO modules\n", - "from AO_modules.Atmosphere import Atmosphere\n", - "from AO_modules.Pyramid import Pyramid\n", - "from AO_modules.DeformableMirror import DeformableMirror\n", - "from AO_modules.MisRegistration import MisRegistration\n", - "from AO_modules.Telescope import Telescope\n", - "from AO_modules.Source import Source\n", "\n", - "# calibration modules \n", - "from AO_modules.calibration.compute_KL_modal_basis import compute_M2C\n", - "from AO_modules.calibration.ao_calibration import ao_calibration\n", + "import time\n", "\n", - "# display modules\n", - "from AO_modules.tools.displayTools import displayMap\n" + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "\n", + "from OOPAO.Atmosphere import Atmosphere\n", + "from OOPAO.DeformableMirror import DeformableMirror\n", + "from OOPAO.MisRegistration import MisRegistration\n", + "from OOPAO.Pyramid import Pyramid\n", + "from OOPAO.Source import Source\n", + "from OOPAO.Telescope import Telescope\n", + "from OOPAO.calibration.ao_calibration import ao_calibration\n", + "from OOPAO.calibration.compute_KL_modal_basis import compute_M2C\n", + "from OOPAO.tools.displayTools import displayMap\n" ] }, { @@ -1213,14 +1205,37 @@ "execution_count": null, "metadata": {}, "outputs": [], + "source": [] + }, + { + "cell_type": "code", + "execution_count": 50, + "metadata": { + "pycharm": { + "name": "#%% to manually measure the interaction matrix\n" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Loading the KL Modal Basis from: data_calibration/M2C_160_res\n", + "Computing the pseudo-inverse of the modal basis...\n", + "Diagonality criteria: 1.9384494009955233e-13 -- using the fast computation\n", + "Loading Interaction matrix data_calibration/VLT_I_band_20x20/zonal_interaction_matrix_160_res_3_mod_slopesMaps_psfCentering_False...\n", + "Done!\n", + "No Modal Gains found. All gains set to 1\n" + ] + } + ], "source": [ - "#%% to manually measure the interaction matrix\n", "#\n", "## amplitude of the modes in m\n", "#stroke=1e-9\n", "## Modal Interaction Matrix \n", "#M2C = M2C[:,:param['nModes']]\n", - "#from AO_modules.calibration.InteractionMatrix import InteractionMatrix\n", + "#from OOPAO.calibration.InteractionMatrix import InteractionMatrix\n", "#\n", "#calib = InteractionMatrix(ngs = ngs,\\\n", "# atm = atm,\\\n", @@ -1242,22 +1257,9 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": null, "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loading the KL Modal Basis from: data_calibration/M2C_160_res\n", - "Computing the pseudo-inverse of the modal basis...\n", - "Diagonality criteria: 1.9384494009955233e-13 -- using the fast computation\n", - "Loading Interaction matrix data_calibration/VLT_I_band_20x20/zonal_interaction_matrix_160_res_3_mod_slopesMaps_psfCentering_False...\n", - "Done!\n", - "No Modal Gains found. All gains set to 1\n" - ] - } - ], + "outputs": [], "source": [ "ao_calib = ao_calibration(param = param,\\\n", " ngs = ngs,\\\n", @@ -1272,15 +1274,6 @@ " nMeasurements = 100)\n" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "## Display Modal Basis\n" - ] - }, { "cell_type": "code", "execution_count": 51, @@ -1323,6 +1316,14 @@ "output_type": "display_data" } ], + "source": [ + "## Display Modal Basis\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], "source": [ "\n", "# project the mode on the DM\n", @@ -1347,419 +1348,25 @@ "plt.plot(np.round(np.std(np.squeeze(KL_dm[tel.pupilLogical,:]),axis = 0),5))\n", "plt.title('KL mode normalization projected on the DM')\n", "plt.show()\n" - ] + ], + "metadata": { + "collapsed": false + } }, { "cell_type": "markdown", - "metadata": {}, "source": [ "## Closed Loop\n", "Here is a code to do a closed-loop simulation using the PSIM code:" - ] + ], + "metadata": { + "collapsed": false + } }, { "cell_type": "code", - "execution_count": 52, - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Telescope and Atmosphere combined!\n" - ] - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "<Figure size 432x288 with 12 Axes>" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Elapsed time: 0.26773595809936523 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop0/20 Turbulence: 606.0402280591612 -- Residual:606.0402280591612\n", - "\n", - "Elapsed time: 0.37099671363830566 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop1/20 Turbulence: 603.8382458678452 -- Residual:603.8382458678452\n", - "\n", - "Elapsed time: 0.3040750026702881 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop2/20 Turbulence: 601.757975924529 -- Residual:506.43713898180295\n", - "\n", - "Elapsed time: 0.27905988693237305 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop3/20 Turbulence: 599.6841650698344 -- Residual:413.1653804600924\n", - "\n", - "Elapsed time: 0.3260042667388916 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop4/20 Turbulence: 597.6594279460551 -- Residual:326.9863089771466\n", - "\n", - "Elapsed time: 0.3509953022003174 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop5/20 Turbulence: 595.5714832571631 -- Residual:251.09263083353306\n", - "\n", - "Elapsed time: 0.34759974479675293 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop6/20 Turbulence: 593.4884660946764 -- Residual:190.6229053402881\n", - "\n", - "Elapsed time: 0.3639976978302002 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop7/20 Turbulence: 591.4361287918499 -- Residual:146.93592292728542\n", - "\n", - "Elapsed time: 0.35454630851745605 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop8/20 Turbulence: 589.4378053091451 -- Residual:122.14753926757396\n", - "\n", - "Elapsed time: 0.29892587661743164 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop9/20 Turbulence: 587.5373806390304 -- Residual:111.70579313024514\n", - "\n", - "Elapsed time: 0.32260870933532715 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop10/20 Turbulence: 585.640091923709 -- Residual:108.24715224875888\n", - "\n", - "Elapsed time: 0.3059971332550049 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop11/20 Turbulence: 583.6574459964706 -- Residual:107.31744526678158\n", - "\n", - "Elapsed time: 0.415050745010376 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop12/20 Turbulence: 581.7561477405845 -- Residual:107.33863192773674\n", - "\n", - "Elapsed time: 0.3309955596923828 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop13/20 Turbulence: 579.9566673886508 -- Residual:107.27097904833207\n", - "\n", - "Elapsed time: 0.31400108337402344 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop14/20 Turbulence: 578.2199784706081 -- Residual:107.57291376576468\n", - "\n", - "Elapsed time: 0.34299731254577637 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop15/20 Turbulence: 576.42379893904 -- Residual:108.03205456991867\n", - "\n", - "Elapsed time: 0.3750009536743164 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop16/20 Turbulence: 574.554871690601 -- Residual:107.7760092386901\n", - "\n", - "Elapsed time: 0.35599589347839355 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop17/20 Turbulence: 572.7795739944361 -- Residual:107.59829310978114\n", - "\n", - "Elapsed time: 0.33803224563598633 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop18/20 Turbulence: 571.0398782850202 -- Residual:107.50869063000403\n", - "\n", - "Elapsed time: 0.33455920219421387 s\n" - ] - }, - { - "data": { - "text/plain": [ - "<Figure size 432x288 with 0 Axes>" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Loop19/20 Turbulence: 569.2670310628939 -- Residual:107.51253606040405\n", - "\n" - ] - }, - { - "data": { - "image/png": "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\n", - "text/plain": [ - "<Figure size 432x288 with 1 Axes>" - ] - }, - "metadata": { - "needs_background": "light" - }, - "output_type": "display_data" - } - ], + "execution_count": null, + "outputs": [], "source": [ "\n", "# These are the calibration data used to close the loop\n", @@ -1882,9 +1489,17 @@ " residual[i]=np.std(tel.OPD[np.where(tel.pupil>0)])*1e9\n", " OPD=tel.OPD[np.where(tel.pupil>0)]\n", "\n", - " print('Loop'+str(i)+'/'+str(param['nLoop'])+' Turbulence: '+str(total[i])+' -- Residual:' +str(residual[i])+ '\\n')\n", - "\n", - "#%%\n", + " print('Loop'+str(i)+'/'+str(param['nLoop'])+' Turbulence: '+str(total[i])+' -- Residual:' +str(residual[i])+ '\\n')\n" + ], + "metadata": { + "collapsed": false + } + }, + { + "cell_type": "code", + "execution_count": null, + "outputs": [], + "source": [ "plt.figure()\n", "plt.plot(total)\n", "plt.plot(residual)\n", @@ -1892,7 +1507,10 @@ "plt.ylabel('WFE [nm]')\n", "\n", "plt.pause(10)\n" - ] + ], + "metadata": { + "collapsed": false + } } ], "metadata": {