def computesys(self, obj, is_zernike=False, is_padding=False, dropout_prob=1): """ This computes the FWD-graph of the Q-PHASE microscope; 1.) Compute the physical dimensions 2.) Compute the sampling for the waves 3.) Create the illumination waves depending on System's properties ##### IMPORTANT! ##### The ordering of the channels is as follows: Nillu, Nz, Nx, Ny """ # define whether we want to pad the experiment self.is_padding = is_padding if (is_padding): print('WARNING: Padding is not yet working correctly!!!!!!!!') # add padding in X/Y to avoid wrap-arounds self.Nx = self.Nx * 2 self.Ny = self.Ny * 2 self.mysize = np.array((self.Nz, self.Nx, self.Ny)) self.obj = obj self.dx = self.dx self.dy = self.dy else: self.mysize = np.array((self.Nz, self.Nx, self.Ny)) self.obj = obj # Decide whether we wan'T to optimize or simply execute the model if (self.is_optimization): if is_padding: # Pad object with zeros along X/Y obj_tmp = np.zeros(self.mysize) # + 1j*np.zeros(muscat.mysize) obj_tmp[:, self.Nx // 2 - self.Nx // 4:self.Nx // 2 + self.Nx // 4, self.Ny // 2 - self.Ny // 4:self.Ny // 2 + self.Ny // 4] = self.obj self.obj = obj_tmp # in case one wants to use this as a fwd-model for an inverse problem self.TF_obj_phase = tf.Variable(self.obj, dtype=tf.float32, name='Object_Variable') self.TF_obj_phase_do = tf.nn.dropout( self.TF_obj_phase, keep_prob=dropout_prob) # eventually apply dropout else: # Variables of the computational graph if is_padding: # Pad object with zeros along X/Y obj_tmp = np.zeros(self.mysize) # + 1j*np.zeros(muscat.mysize) obj_tmp[:, self.Nx // 2 - self.Nx // 4:self.Nx // 2 + self.Nx // 4, self.Ny // 2 - self.Ny // 4:self.Ny // 2 + self.Ny // 4] = self.obj self.obj = obj_tmp self.TF_obj_phase_do = tf.constant(self.obj, dtype=tf.float32, name='Object_const') ## Establish normalized coordinates. #----------------------------------------- vxx = tf_helper.xx( (self.mysize[1], self.mysize[2]), 'freq') * self.lambdaM * self.nEmbb / (self.dx * self.NAo) # normalized optical coordinates in X vyy = tf_helper.yy( (self.mysize[1], self.mysize[2]), 'freq') * self.lambdaM * self.nEmbb / (self.dy * self.NAo) # normalized optical coordinates in Y # AbbeLimit=lambda0/NAo; # Rainer's Method # RelFreq = rr(mysize,'freq')*AbbeLimit/dx; # Is not generally right (dx and dy) self.RelFreq = np.sqrt(tf_helper.abssqr(vxx) + tf_helper.abssqr(vyy)) # spanns the frequency grid of normalized pupil coordinates self.Po = self.RelFreq < 1.0 # Create the pupil of the objective lens # Precomputing the first 9 zernike coefficients self.myzernikes = np.zeros( (self.Po.shape[0], self.Po.shape[1], self.nzernikes)) + 1j * np.zeros( (self.Po.shape[0], self.Po.shape[1], self.nzernikes)) r, theta = zern.cart2pol(vxx, vyy) for i in range(0, self.nzernikes): self.myzernikes[:, :, i] = zern.zernike( r, theta, i + 1, norm=False) # or 8 in X-direction # eventually introduce a phase factor to approximate the experimental data better self.Po = self.Po # Need to shift it before using as a low-pass filter Po=np.ones((np.shape(Po))) if is_zernike: print( '----------> Be aware: We are taking aberrations into account!' ) # Assuming: System has coma along X-direction self.myaberration = np.sum(self.zernikefactors * self.myzernikes, axis=2) self.Po = 1. * self.Po # Prepare the normalized spatial-frequency grid. self.S = self.NAc / self.NAo # Coherence factor self.Ic = self.RelFreq <= self.S myIntensityFactor = 70 self.Ic_map = np.cos((myIntensityFactor * tf_helper.xx( (self.Nx, self.Ny), mode='freq')**2 + myIntensityFactor * tf_helper.yy( (self.Nx, self.Ny), mode='freq')**2))**2 self.Ic = self.Ic * self.Ic_map # weight the intensity in the condenser aperture, unlikely to be uniform print('We are weighing the Intensity int the illu-pupil!') if hasattr(self, 'NAci'): if self.NAci != None and self.NAci > 0: #print('I detected a darkfield illumination aperture!') self.S_o = self.NAc / self.NAo # Coherence factor self.S_i = self.NAci / self.NAo # Coherence factor self.Ic = (1. * (self.RelFreq < self.S_o) * 1. * (self.RelFreq > self.S_i) ) > 0 # Create the pupil of the condenser plane # Shift the pupil in X-direction (optical missalignment) if hasattr(self, 'shiftIcX'): if self.shiftIcX != None: print('Shifting the illumination in X by: ' + str(self.shiftIcX) + ' Pixel') self.Ic = np.roll(self.Ic, self.shiftIcX, axis=1) # Shift the pupil in Y-direction (optical missalignment) if hasattr(self, 'shiftIcY'): if self.shiftIcY != None: print('Shifting the illumination in Y by: ' + str(self.shiftIcY) + ' Pixel') self.Ic = np.roll(self.Ic, self.shiftIcY, axis=0) ## Forward propagator (Ewald sphere based) DO NOT USE NORMALIZED COORDINATES HERE self.kxysqr = (tf_helper.abssqr( tf_helper.xx((self.mysize[1], self.mysize[2]), 'freq') / self.dx) + tf_helper.abssqr( tf_helper.yy( (self.mysize[1], self.mysize[2]), 'freq') / self.dy)) + 0j self.k0 = 1 / self.lambdaM self.kzsqr = tf_helper.abssqr(self.k0) - self.kxysqr self.kz = np.sqrt(self.kzsqr) self.kz[self.kzsqr < 0] = 0 self.dphi = 2 * np.pi * self.kz * self.dz # exp(1i*kz*dz) would be the propagator for one slice ## Get a list of vector coordinates corresponding to the pixels in the mask xfreq = tf_helper.xx((self.mysize[1], self.mysize[2]), 'freq') yfreq = tf_helper.yy((self.mysize[1], self.mysize[2]), 'freq') self.Nc = np.sum(self.Ic > 0) print('Number of Illumination Angles / Plane waves: ' + str(self.Nc)) # Calculate the computatonal grid/sampling self.kxcoord = np.reshape(xfreq[self.Ic > 0], [1, 1, 1, self.Nc]) # NA-positions in condenser aperture plane in x-direction self.kycoord = np.reshape(yfreq[self.Ic > 0], [1, 1, 1, self.Nc]) # NA-positions in condenser aperture plane in y-direction self.RefrCos = np.reshape(self.k0 / self.kz[self.Ic > 0], [1, 1, 1, self.Nc]) # 1/cosine used for the application of the refractive index steps to acount for longer OPD in medium under an oblique illumination angle ## Generate the illumination amplitudes self.intensityweights = self.Ic[self.Ic > 0] self.A_input = self.intensityweights * np.exp( (2 * np.pi * 1j) * (self.kxcoord * tf_helper.repmat4d(tf_helper.xx( (self.mysize[1], self.mysize[2])), self.Nc) + self.kycoord * tf_helper.repmat4d(tf_helper.yy( (self.mysize[1], self.mysize[2])), self.Nc)) ) # Corresponds to a plane wave under many oblique illumination angles - bfxfun ## propagate field to z-stack and sum over all illumination angles self.Alldphi = -np.reshape(np.arange( 0, self.mysize[0], 1), [1, 1, self.mysize[0]]) * np.repeat( self.dphi[:, :, np.newaxis], self.mysize[0], axis=2) # Ordinary backpropagation. This is NOT what we are interested in: self.myAllSlicePropagator = np.transpose( np.exp(1j * self.Alldphi) * (np.repeat( self.dphi[:, :, np.newaxis], self.mysize[0], axis=2) > 0), [2, 0, 1])
tf_fwd = muscat.computemodel(is_forcepos=False) #%% ''' Compute a first guess based on the experimental phase ''' init_guess = np.angle(matlab_val) init_guess = init_guess - np.min(init_guess) init_guess = dn * init_guess / np.max( init_guess) #*dn+1j*.01*np.ones(init_guess.shape) ''' Create a 3D Refractive Index Distributaton as a artificial sample''' #for sphere5 mydiameter = 3 obj = matlab_val squeezefac = muscat.dx / muscat.dz obj = ( ((squeezefac * tf_helper.xx(mysize=obj.shape)**2) + (tf_helper.yy(mysize=obj.shape)**2) + (squeezefac * tf_helper.zz(mysize=obj.shape)**2)) < (mydiameter**2) ) * dn #tf_go.generateObject(mysize=obj.shape, obj_dim=muscat.dx, obj_type ='sphere', diameter = mydiameter, dn = dn) obj_absorption = tf_go.generateObject(mysize=obj.shape, obj_dim=muscat.dx, obj_type='sphere', diameter=mydiameter, dn=.01) obj = obj + 1j * obj_absorption obj = np.roll(obj, 5, 0) # z obj = np.roll(obj, -3, 1) # y obj = np.roll(obj, 1, 2) # x init_guess = obj if (False): init_guess = matlab_val
dropout_prob=my_dropout_prob, mysubsamplingIC=mysubsamplingIC) # Generate Computational Graph (fwd model) tf_fwd = muscat.computemodel(is_forcepos=False) #%% ''' Compute a first guess based on the experimental phase ''' init_guess = np.angle(matlab_val) init_guess = init_guess - np.min(init_guess) init_guess = dn * init_guess / np.max( init_guess) #*dn+1j*.01*np.ones(init_guess.shape) #% try to damp along Z if (nboundaryz > 0): mymask = abs(tf_helper.yy( init_guess.shape)) - init_guess.shape[0] // 2 + nboundaryz mymask[mymask < 0] = 0 mymask = -mymask mymask = np.exp(mymask / nboundaryz * 2) plt.imshow(mymask[:, 16, :]), plt.colorbar(), plt.title( 'Mask to confine the initial object in the center only') init_guess = mymask * init_guess #%% '''# Estimate the Phase difference between Measurement and Simulation''' # This is actually a crucial step and needs to be used with care. We don't want any phase wrappings ! '''Numpy to Tensorflow''' np_meas = matlab_val np_mean = np.mean((np_meas)) print("Mean of the MEasurement is: " + str(np_mean)) '''Define Cost-function'''
def computesys(self, obj, is_padding=False, is_tomo=False, dropout_prob=1): """ This computes the FWD-graph of the Q-PHASE microscope; 1.) Compute the physical dimensions 2.) Compute the sampling for the waves 3.) Create the illumination waves depending on System's properties ##### IMPORTANT! ##### The ordering of the channels is as follows: Nillu, Nz, Nx, Ny """ # define whether we want to pad the experiment self.is_padding = is_padding self.is_tomo = is_tomo self.obj = obj if (is_padding): print( '--------->WARNING: Padding is not yet working correctly!!!!!!!!' ) # add padding in X/Y to avoid wrap-arounds self.mysize_old = np.array((self.Nz, self.Nx, self.Ny)) self.Nx = self.Nx * 2 self.Ny = self.Ny * 2 self.mysize = np.array((self.Nz, self.Nx, self.Ny)) self.obj = obj self.dx = self.dx self.dy = self.dy else: self.mysize = np.array((self.Nz, self.Nx, self.Ny)) self.mysize_old = self.mysize # Decide whether we wan'T to optimize or simply execute the model if (self.is_optimization == 1): #self.TF_obj = tf.Variable(np.real(self.obj), dtype=tf.float32, name='Object_Variable') #self.TF_obj_absorption = tf.Variable(np.imag(self.obj), dtype=tf.float32, name='Object_Variable') with tf.variable_scope("Complex_Object"): self.TF_obj = tf.get_variable('Object_Variable_Real', dtype=tf.float32, initializer=np.float32( np.real(self.obj))) self.TF_obj_absorption = tf.get_variable( 'Object_Variable_Imag', dtype=tf.float32, initializer=np.float32(np.imag(self.obj))) #set reuse flag to True tf.get_variable_scope().reuse_variables() #just an assertion! assert tf.get_variable_scope().reuse == True # assign training variables self.tf_lambda_tv = tf.placeholder(tf.float32, []) self.tf_eps = tf.placeholder(tf.float32, []) self.tf_meas = tf.placeholder(dtype=tf.complex64, shape=self.mysize_old) self.tf_learningrate = tf.placeholder(tf.float32, []) elif (self.is_optimization == 0): # Variables of the computational graph self.TF_obj = tf.constant(np.real(self.obj), dtype=tf.float32, name='Object_const') self.TF_obj_absorption = tf.constant(np.imag(self.obj), dtype=tf.float32, name='Object_const') elif (self.is_optimization == -1): # THis is for the case that we want to train the resnet self.tf_meas = tf.placeholder(dtype=tf.complex64, shape=self.mysize_old) # in case one wants to use this as a fwd-model for an inverse problem #self.TF_obj = tf.Variable(np.real(self.obj), dtype=tf.float32, name='Object_Variable') #self.TF_obj_absorption = tf.Variable(np.imag(self.obj), dtype=tf.float32, name='Object_Variable') self.TF_obj = tf.placeholder(dtype=tf.float32, shape=self.obj.shape, name='Object_Variable_Real') self.TF_obj_absorption = tf.placeholder( dtype=tf.float32, shape=self.obj.shape, name='Object_Variable_Imag') # assign training variables self.tf_lambda_tv = tf.placeholder(tf.float32, []) self.tf_eps = tf.placeholder(tf.float32, []) self.tf_learningrate = tf.placeholder(tf.float32, []) ## Establish normalized coordinates. #----------------------------------------- vxx = tf_helper.xx( (self.mysize[1], self.mysize[2]), 'freq') * self.lambdaM * self.nEmbb / (self.dx * self.NAo) # normalized optical coordinates in X vyy = tf_helper.yy( (self.mysize[1], self.mysize[2]), 'freq') * self.lambdaM * self.nEmbb / (self.dy * self.NAo) # normalized optical coordinates in Y # AbbeLimit=lambda0/NAo; # Rainer's Method # RelFreq = rr(mysize,'freq')*AbbeLimit/dx; # Is not generally right (dx and dy) self.RelFreq = np.sqrt(tf_helper.abssqr(vxx) + tf_helper.abssqr(vyy)) # spanns the frequency grid of normalized pupil coordinates self.Po = self.RelFreq < 1.0 # Create the pupil of the objective lens # Precomputing the first 9 zernike coefficients self.nzernikes = np.squeeze(self.zernikefactors.shape) self.myzernikes = np.zeros( (self.Po.shape[0], self.Po.shape[1], self.nzernikes)) + 1j * np.zeros( (self.Po.shape[0], self.Po.shape[1], self.nzernikes)) r, theta = zern.cart2pol(vxx, vyy) for i in range(0, self.nzernikes): self.myzernikes[:, :, i] = np.fft.fftshift( zern.zernike(r, theta, i + 1, norm=False)) # or 8 in X-direction # eventually introduce a phase factor to approximate the experimental data better self.Po = np.fft.fftshift( self.Po ) # Need to shift it before using as a low-pass filter Po=np.ones((np.shape(Po))) print('----------> Be aware: We are taking aberrations into account!') # Assuming: System has coma along X-direction self.myaberration = np.sum(self.zernikefactors * self.myzernikes, axis=2) self.Po = 1. * self.Po #*np.exp(1j*self.myaberration) # Prepare the normalized spatial-frequency grid. self.S = self.NAc / self.NAo # Coherence factor self.Ic = self.RelFreq <= self.S # Take Darkfield into account if hasattr(self, 'NAci'): if self.NAci != None and self.NAci > 0: #print('I detected a darkfield illumination aperture!') self.S_o = self.NAc / self.NAo # Coherence factor self.S_i = self.NAci / self.NAo # Coherence factor self.Ic = (1. * (self.RelFreq < self.S_o) * 1. * (self.RelFreq > self.S_i) ) > 0 # Create the pupil of the condenser plane # weigh the illumination source with some cos^2 intensity weight?! myIntensityFactor = 70 self.Ic_map = np.cos((myIntensityFactor * tf_helper.xx( (self.Nx, self.Ny), mode='freq')**2 + myIntensityFactor * tf_helper.yy( (self.Nx, self.Ny), mode='freq')**2))**2 self.Ic = self.Ic * np.sqrt( self.Ic_map ) # weight the intensity in the condenser aperture, unlikely to be uniform # print('--------> ATTENTION! - We are not weighing the Intensity int the illu-pupil!') # Shift the pupil in X-direction (optical missalignment) if hasattr(self, 'shiftIcX'): if self.shiftIcX != None: if (is_padding): self.shiftIcX = self.shiftIcX * 2 print('Shifting the illumination in X by: ' + str(self.shiftIcX) + ' Pixel') if (0): self.Ic = np.roll(self.Ic, self.shiftIcX, axis=1) elif (1): tform = AffineTransform(scale=(1, 1), rotation=0, shear=0, translation=(self.shiftIcX, 0)) self.Ic = warp(self.Ic, tform.inverse, output_shape=self.Ic.shape) elif (0): # We apply a phase-factor to shift the source in realspace - so make it trainable self.shift_xx = tf_helper.xx( (self.mysize[1], self.mysize[2]), 'freq') self.Ic = np.abs( np.fft.ifft2( np.fft.fft2(self.Ic) * np.exp(1j * 2 * np.pi * self.shift_xx * self.shiftIcX))) # Shift the pupil in Y-direction (optical missalignment) if hasattr(self, 'shiftIcY'): if self.shiftIcY != None: if (is_padding): self.shiftIcY = self.shiftIcY * 2 print('Shifting the illumination in Y by: ' + str(self.shiftIcY) + ' Pixel') if (0): self.Ic = np.roll(self.Ic, self.shiftIcY, axis=0) elif (1): tform = AffineTransform(scale=(1, 1), rotation=0, shear=0, translation=(0, self.shiftIcY)) self.Ic = warp(self.Ic, tform.inverse, output_shape=self.Ic.shape) elif (0): # We apply a phase-factor to shift the source in realspace - so make it trainable self.shift_yy = tf_helper.yy( (self.mysize[1], self.mysize[2]), 'freq') self.Ic = np.abs( np.fft.ifft2( np.fft.fft2(self.Ic) * np.exp(1j * self.shift_yy * self.shiftIcY))) ## Forward propagator (Ewald sphere based) DO NOT USE NORMALIZED COORDINATES HERE self.kxysqr = (tf_helper.abssqr( tf_helper.xx((self.mysize[1], self.mysize[2]), 'freq') / self.dx) + tf_helper.abssqr( tf_helper.yy( (self.mysize[1], self.mysize[2]), 'freq') / self.dy)) + 0j self.k0 = 1 / self.lambdaM self.kzsqr = tf_helper.abssqr(self.k0) - self.kxysqr self.kz = np.sqrt(self.kzsqr) self.kz[self.kzsqr < 0] = 0 self.dphi = 2 * np.pi * self.kz * self.dz # exp(1i*kz*dz) would be the propagator for one slice ## Get a list of vector coordinates corresponding to the pixels in the mask xfreq = tf_helper.xx((self.mysize[1], self.mysize[2]), 'freq') yfreq = tf_helper.yy((self.mysize[1], self.mysize[2]), 'freq') self.Nc = np.sum(self.Ic > 0) print('Number of Illumination Angles / Plane waves: ' + str(self.Nc)) # Calculate the computatonal grid/sampling self.kxcoord = np.reshape(xfreq[self.Ic > 0], [1, 1, 1, self.Nc]) # NA-positions in condenser aperture plane in x-direction self.kycoord = np.reshape(yfreq[self.Ic > 0], [1, 1, 1, self.Nc]) # NA-positions in condenser aperture plane in y-direction self.RefrCos = np.reshape(self.k0 / self.kz[self.Ic > 0], [1, 1, 1, self.Nc]) # 1/cosine used for the application of the refractive index steps to acount for longer OPD in medium under an oblique illumination angle ## Generate the illumination amplitudes self.intensityweights = self.Ic[self.Ic > 0] self.A_input = self.intensityweights * np.exp( (2 * np.pi * 1j) * (self.kxcoord * tf_helper.repmat4d(tf_helper.xx( (self.mysize[1], self.mysize[2])), self.Nc) + self.kycoord * tf_helper.repmat4d(tf_helper.yy( (self.mysize[1], self.mysize[2])), self.Nc)) ) # Corresponds to a plane wave under many oblique illumination angles - bfxfun ## propagate field to z-stack and sum over all illumination angles self.Alldphi = -np.reshape(np.arange( 0, self.mysize[0], 1), [1, 1, self.mysize[0]]) * np.repeat( np.fft.fftshift(self.dphi)[:, :, np.newaxis], self.mysize[0], axis=2) # Ordinary backpropagation. This is NOT what we are interested in: self.myAllSlicePropagator = np.transpose( np.exp(1j * self.Alldphi) * (np.repeat(np.fft.fftshift(self.dphi)[:, :, np.newaxis], self.mysize[0], axis=2) > 0), [2, 0, 1])