def absorptive_helper(self, array, axes, window_function="none", window_length=0): """ helper function for absorptive This is a function to Fourier Transform experimental 2DIR spectra, ie, spectra with a time and frequency axis. It basically repeats the Fourier function for all pixels. INPUT: - array (numpy.ndarray): a 2d array of time * pixels - window_function (name, "none"): "none", "guassian" etc - window_length (int, 0): length of the window. 0 means the whole range - flag_plot (BOOL, False): will plot the windowed time domain CHANGELOG: 20101204/RB: started as fourier_helper 20110909/RB: continued 20130131/RB: re-implemented as absorptive-helper. The FFT is now done in this function instead of in the Mathematics module. That function had a load of overhead which was not needed. """ x, y = numpy.shape(array) if axes == 2 or axes == [1, 1]: array[:, 0] /= 2 array[0, :] /= 2 # prepare output # it needs to have the correct size and it should be complex if self.zeropad_to != None: x = self.zeropad_to ft_array = numpy.reshape(numpy.zeros(x * y, dtype=numpy.cfloat), (x, y)) # do fft if window_function != "none": self.printWarning("No 2D window functions have been implemented", inspect.stack()) ft_array[:, :] = numpy.fft.fft2(array, n=self.zeropad_to) else: if axes == 1 or axes == [0, 1]: array = array.T # prepare output # it needs to have the correct size and it should be complex if self.zeropad_to != None: x = self.zeropad_to ft_array = numpy.reshape(numpy.zeros(x * y, dtype=numpy.cfloat), (x, y)) # do fft for i in range(y): array[:, i] /= 2 # correct first element if window_function != "none": array[:, i] = MATH.window_functions(array[:, i], window_function, window_length) ft_array[:, i] = numpy.fft.fft(array[:, i], n=self.zeropad_to) if axes == 1 or axes == [0, 1]: ft_array = ft_array.T return ft_array
def calculate_gvd(range_L, n_steps, SC): """ Function to calculate the Group Velocity (VG) and Group Velocity Dispersion (GVD) using the Sellmeier Equation. The index of refraction as a function of wavelength is described by the Sellmeier equation. The VG is the first derivative of this, the GVD the second derivative. This is calculated using a fairly simple (y2-y1)/(x2-x1) calculation. To accomodate this, these are all calculated for a range of values. CHANGELOG: 20110909/RB: started 20150805/RB: copied to Crocodile. Added documentation. INPUT: range_L (array, 2 elements): minimum and maximum in micron n_steps (int): number of steps for the calculation. It is best to use a large number of steps. SC (array): Sellmeier Coefficients OUPUT: n_x: x-axis for Sellmeier (wavelengths in micron) n_y: index of refraction vg_x: x-axis (wavelengths in nm) for group velocity vg_y: group velocity gvd_x: x-axis (wavelengths in nm) gvd_y: group velocity dispersion """ # calculate the index of refraction over the defined range n_x = numpy.linspace(range_L[0], range_L[1], num = n_steps) n_y = E.Sellmeier(SC, n_x) # calculate the derivatives [d1x, d1y] = numpy.array(M.derivative(n_x, n_y)) [d2x, d2y] = numpy.array(M.derivative(d1x, d1y)) # define the x axes vg_x = d1x gvd_x = d2x vg_y = (C.c_ms / (vg_x)) / (1 - (vg_x / n_y[1:-1]) * d1y) gvd_y = (gvd_x**3/(2*numpy.pi*C.c_ms*C.c_ms)) * d2y gvd_y *= 1e21 return n_x, n_y, vg_x, vg_y, gvd_x, gvd_y
def find_w1_peaks(self, flag_verbose = False): data = self.mess.s[self.w1_peaks_y_i[0]:self.w1_peaks_y_i[1], self.w1_peaks_x_i[0]:self.w1_peaks_x_i[1]] y_axis = self.mess.s_axis[0][self.w1_peaks_y_i[0]:self.w1_peaks_y_i[1]] y = numpy.sum(data,1) A_out = MATH.fit(y_axis, y, EQ.rb_lorentzian, self.w1_peaks_A_in) self.w1_peaks[0] = A_out[1]
def fit_double_lorentzian(self, flag_plot = False, flag_verbose = False): """ For a selection of points on the w1-axis, take a cut (giving w3 vs z (intensity) plot) and fit it with a double Lorentzian. self.dl_x_i[0] etc are the min/max indices to be fitted """ if flag_verbose: self.verbose("Fit double Lorentzian for " + self.objectname, flag_verbose = flag_verbose) self.verbose(" x_min: " + str(self.dl_x_i[0]) + " " + str(self.mess.s_axis[2][self.dl_x_i[0]]), flag_verbose = flag_verbose) self.verbose(" x_max: " + str(self.dl_x_i[1]) + " " + str(self.mess.s_axis[2][self.dl_x_i[1]]), flag_verbose = flag_verbose) self.verbose(" y_min: " + str(self.dl_y_i[0]) + " " + str(self.mess.s_axis[0][self.dl_y_i[0]]), flag_verbose = flag_verbose) self.verbose(" y_max: " + str(self.dl_y_i[1]) + " " + str(self.mess.s_axis[0][self.dl_y_i[1]]), flag_verbose = flag_verbose) # select the part of the data to be fitted data = self.mess.s[self.dl_y_i[0]:self.dl_y_i[1], self.dl_x_i[0]:self.dl_x_i[1]] x_axis = self.mess.s_axis[2][self.dl_x_i[0]:self.dl_x_i[1]] y_axis = self.mess.s_axis[0][self.dl_y_i[0]:self.dl_y_i[1]] # arrays for the results n_y, n_x = numpy.shape(data) y_max = numpy.zeros(n_y) # index of the maximum y_min = numpy.zeros(n_y) # index of the minimum y_out_array = numpy.zeros((n_y, 8)) # fitting parameters if flag_plot: plt.figure() color_array = ["b", "g", "r", "c", "m", "y", "k"] # calculate the fit for the cut of w1 for i in range(n_y): y = data[i,:] A_out = MATH.fit(x_axis, y, EQ.rb_two_lorentzians, self.dl_A_in) y_out_array[i,:] = A_out x_fit = numpy.arange(x_axis[0], x_axis[-1], 0.1) y_fit = EQ.rb_two_lorentzians(A_out, x_fit) if flag_plot: plt.plot(x_fit, y_fit, c = color_array[i%len(color_array)]) plt.plot(x_axis, y, ":", c = color_array[i%len(color_array)]) y_max[i] = x_fit[numpy.argmax(y_fit)] y_min[i] = x_fit[numpy.argmin(y_fit)] self.dl_ble = y_min self.dl_esa = y_max self.dl_A = y_out_array if flag_plot: plt.show()
def fit_tilt(self, flag_verbose = False): if flag_verbose: self.verbose("Fit tilt for " + self.objectname, flag_verbose = flag_verbose) self.verbose(" x_min: " + str(self.dl_x_i[0]) + " " + str(self.mess.s_axis[2][self.dl_x_i[0]]), flag_verbose = flag_verbose) self.verbose(" x_max: " + str(self.dl_x_i[1]) + " " + str(self.mess.s_axis[2][self.dl_x_i[1]]), flag_verbose = flag_verbose) self.verbose(" y_min: " + str(self.dl_y_i[0] + self.l_i[0]) + " " + str(self.mess.s_axis[0][self.dl_y_i[0] + self.l_i[0]]), flag_verbose = flag_verbose) self.verbose(" y_max: " + str(self.dl_y_i[0] + self.l_i[1]) + " " + str(self.mess.s_axis[0][self.dl_y_i[0] + self.l_i[1]]), flag_verbose = flag_verbose) y = self.mess.s_axis[0][self.dl_y_i[0] + self.l_i[0]:self.dl_y_i[0] + self.l_i[1]] x = self.dl_ble[self.l_i[0]:self.l_i[1]] self.l_A_ble = MATH.fit(x, y, EQ.linear, self.l_A_in) x = self.dl_esa[self.l_i[0]:self.l_i[1]] self.l_A_esa = MATH.fit(x, y, EQ.linear, self.l_A_in) self.l_angle_ble = 90 - numpy.arctan(self.l_A_ble[1]) * 180 / numpy.pi self.l_angle_esa = 90 - numpy.arctan(self.l_A_esa[1]) * 180 / numpy.pi self.l_slope_ble = 1 / self.l_A_ble[1] self.l_slope_esa = 1 / self.l_A_esa[1]
def make_plot(self, ax = False, normalize = False, fit = False): """ Make a plot of scan spectrum data. INPUT: - ax (plt axis instance, or False): If False, a new figure and axis instance will be made. - normalize (bool, False): If True, the minimum is subtract from the data, then it is divided by the maximum. - fit (Bool, False): If True, a fit will be made and will also be plotted. The fitting parameters are written to the terminal. CHANGELOG: 201604-RB: started function """ if ax == False: fig = plt.figure() ax = fig.add_subplot(111) if normalize: for ds in range(self.r_n[2]): self.r[:,0,ds,0,0,0,0,0] -= numpy.nanmin(self.r[:,0,ds,0,0,0,0,0]) self.r[:,0,ds,0,0,0,0,0] /= numpy.nanmax(self.r[:,0,ds,0,0,0,0,0]) ax.plot(self.r_axes[0], self.r[:,0,0,0,0,0,0,0], color = "g") ax.plot(self.r_axes[0], self.r[:,0,1,0,0,0,0,0], color = "r") if fit: colors = ["lightgreen", "orange"] labels = ["probe", "reference"] sigma = (self.r_axes[0][0] - self.r_axes[0][-1]) / 4 print(" mu sigma offset scale") for ds in range(self.r_n[2]): A = [sigma, self.r_axes[0][numpy.argmax(self.r[:,0,ds,0,0,0,0,0])], 0, 1] # initial guess A_final = M.fit(self.r_axes[0], self.r[:,0,ds,0,0,0,0,0], EQ.rb_gaussian, A) ax.plot(self.r_axes[0], EQ.rb_gaussian(A_final, self.r_axes[0]), color = colors[ds]) print("{label:10} {mu:.5} {sigma:.3} {offset:.3} {scale:.3}".format(label = labels[ds], mu = A_final[1], sigma = A_final[0], offset = A_final[2], scale = A_final[3]))
def calculate_pulse_length(data, A = [], dt = CONST.hene_fringe_fs, plot_results = True, print_results = True, fwhm_limit = 200, mu_shift = 10, flag_recursive_limit = False): """ Calculate the pulse length INPUT: data (array): the result of a measure phase measurement for one pulse pair. Usually measure phase is saved as two measurements of two pulse pairs. A (list, default = []): [sigma, mu, offset, scale, frequency, phase] if A is empty, some reasonable default values are used. dt (float, HeNe-fringe in fs): to convert from indices to time plot (BOOL, True): plot the results print_res (BOOL, True): print the results fwhm_limit (float, 200): if FWHM is above this limit, it will try again by shifting the mean a bit (by mu_shift) mu_shift (float, 10): shift the mean a bit flag_recursive_limit (BOOL, False): function will try again if set to False. To prevent endless recursion. OUTPUT: - the output can be plotted to confirm the fit - the fitting results can be printed - the FWHM is returned CHANGELOG: 20130408/RB: started function """ # input for a single IR pulse if A == []: A = default_A() data /= numpy.amax(data) # make the time axis t = numpy.arange(len(data)) - len(data)/2 t *= dt # fit the single pulse by convoluting it with itself and fitting it to the data A_out = MA.fit(t, data, fit_convolve, A) # calculate the fitted pulse and convoluted pulse y_single = pulse(A_out, t) y_conv = fit_convolve(A_out, t) # calculate the envelope sigma = A_out[0] mu = A_out[1] offset = A_out[2] scale = A_out[3] A = [sigma, mu, offset, scale] y_env = envelope(A,t) # for the FWHM we need a higher time resolution t2 = numpy.arange(len(data)*10) - 5*len(data) t2 *= dt / 10 y_fwhm = envelope(A,t2) # use the envelope to calculate the FWHM # make it positive if y_fwhm[int(len(t2)/2)] < 0: y_fwhm = -y_fwhm max_fwhm = numpy.amax(y_fwhm) # select the part of the list l = numpy.where(y_fwhm > max_fwhm/2)[0] # the length of the list in indices, multiply with time distance FWHM = len(l) * dt / 10 if FWHM > fwhm_limit and flag_recursive_limit == False: # FWHM is above the limit, try again by shifting the mean a bit print("Trying again...") A = default_A() A[1] = mu_shift FWHM = calculate_pulse_length(data, A = A, dt = dt, plot_results = plot_results, print_results = print_results, flag_recursive_limit = True) else: # yay! a reasonable result. plot and print the results. if plot_results: plot_res(t, data, y_conv, y_single, y_env) if print_results: print_res(A_out, FWHM, dt) return FWHM
def test_fit(self): x = numpy.arange(10) y = numpy.sin(x) A = [0,1,2,3] A = MATH.fit(x, y, EQ.rb_cos, A) print(A)
def super_absorptive(self, axes, window_function="none", window_length=0, flag_verbose=False): """ Calculate the absorptive spectrum. This function does the Fourier transform. It phases the spectrum. It makes the axes. INPUT: - axes (number or list): 0 or [1,0] for first axis, 1 or [0,1] for second axis, 2 or [1,1] for both axes - window_function (name, "none"): "none", "guassian" etc - window_length (int, 0): length of the window. 0 means the whole range - flag_plot (BOOL, False): will plot the windowed time domain CHANGELOG: 20101204/RB: started 20110909/RB: continued 20130131/RB: copied from croc to Crocodile. The function should now work for time-freq, freq-time and time-time domain. Only the FFT will give an error, the other problems give a warning. 20130208/RB: now checks used variables before the calculation instead of try/except during the calculation WARNING: Realistically, only the time-freq FFT is performed. The others may have problems. """ self.verbose("Super absorptive", flag_verbose) # VARIABLE CHECKING # r should be a list with numpy.ndarrays # if not the case, return False if type(self.r) != list: self.printError("r should be a list.", inspect.stack()) return False elif type(self.r[0]) != numpy.ndarray or type(self.r[1]) != numpy.ndarray: self.printError("r[0] or r[1] should be a numpy.ndarray. Did you assign r?", inspect.stack()) return False # r_axis should be a list # index 0 and 2 should be a numpy.ndarray # if not the case, skip making the axes flag_axis = True if type(self.r_axis) != list: flag_axis = False self.printWarning("r_axis should be a list. s_axis will not be calculated.", inspect.stack()) elif type(self.r_axis[0]) != numpy.ndarray or type(self.r_axis[2]) != numpy.ndarray: flag_axis = False self.printWarning( "r_axis[0] or r_axis[2] should be a numpy.ndarray. s_axis will not be calculated.", inspect.stack() ) # undersampling has to be an integer # if it is a BOOL, it can be converted to 0 or 1 if type(self.undersampling) == bool: if self.undersampling: self.undersampling = 1 else: self.undersampling = 0 self.printWarning("undersampling is a BOOL, will use " + str(self.undersampling), inspect.stack()) if type(self.undersampling) != int: self.printError("undersampling is NaN, not a valid value. ", inspect.stack()) return False # the phase should not be a numpy.nan if numpy.isnan(self.phase_degrees): self.printError("phase_degrees is NaN, not a valid value. ", inspect.stack()) return False # ACTUAL FUNCTION # do the FFT try: for i in range(len(self.r)): self.f[i] = self.absorptive_helper( array=numpy.copy(self.r[i]), axes=axes, window_function=window_function, window_length=window_length ) except ValueError: self.printError("Problem with the Fourier Transforms. Are r[0] and r[1] assigned?", inspect.stack()) return False self.verbose(" done with FFT", flag_verbose) # phase the spectrum self.s = numpy.real(numpy.exp(1j * self.phase_rad) * self.f[0] + numpy.exp(-1j * self.phase_rad) * self.f[1]) # select part of the data # calculate the axes if axes == 0 or axes == [1, 0] or axes == 2 or axes == [1, 1]: if self.undersampling % 2 == 0: self.f[0] = self.f[0][: (len(self.f[0]) / 2)][:] self.f[1] = self.f[1][: (len(self.f[1]) / 2)][:] self.s = self.s[: (len(self.s) / 2)][:] else: self.f[0] = self.f[0][(len(self.f[0]) / 2) :][:] self.f[1] = self.f[1][(len(self.f[1]) / 2) :][:] self.s = self.s[(len(self.s) / 2) :][:] if flag_axis: self.s_axis[0] = MATH.make_ft_axis( length=2 * numpy.shape(self.s)[0], dt=self.r_axis[0][1] - self.r_axis[0][0], undersampling=self.undersampling, ) self.s_axis[0] = self.s_axis[0][0 : len(self.s_axis[0]) / 2] if axes == 1 or axes == [0, 1] or axes == 2 or axes == [1, 1]: if self.undersampling % 2 == 0: self.f[0] = self.f[0][:][: (len(self.f[0]) / 2)] self.f[1] = self.f[1][:][: (len(self.f[1]) / 2)] self.s = self.s[:][: (len(self.s) / 2)] else: self.f[0] = self.f[0][:][(len(self.f[0]) / 2) :] self.f[1] = self.f[1][:][(len(self.f[1]) / 2) :] self.s = self.s[:][(len(self.s) / 2) :] if flag_axis: self.s_axis[2] = MATH.make_ft_axis( length=2 * numpy.shape(self.s)[0], dt=self.r_axis[2][1] - self.r_axis[2][0], undersampling=self.undersampling, ) self.s_axis[2] = self.s_axis[2][0 : len(self.s_axis[0]) / 2] if flag_axis: if axes == 0 or axes == [1, 0]: self.s_axis[2] = self.r_axis[2] + self.r_correction[2] if axes == 1 or axes == [0, 1]: self.s_axis[0] = self.r_axis[0] + self.r_correction[0] # add some stuff to self self.s_units = ["cm-1", "fs", "cm-1"] if flag_axis: self.s_resolution = [(self.s_axis[0][1] - self.s_axis[0][0]), 0, (self.s_axis[2][1] - self.s_axis[2][0])] return True