def onclick(click_event): """ Process mouse click events in the 2D cross-correlation map :param click_event: mouse click event """ global angles, q_range, ccf, current_q, ax0, ax1, my_cmap, ymin, ymax if click_event.inaxes == ax0: # click in the 2D cross-correlation map current_q = util.find_nearest( reference_array=q_range, test_values=click_event.ydata ) ymin = ccf[current_q, indices].min() ymax = 1.2 * ccf[current_q, indices].max() ax1.cla() ax1.plot( angles, ccf[current_q, :], linestyle="None", marker=".", markerfacecolor="blue", ) ax1.set_xlim(0, 180) ax1.set_ylim(ymin, ymax) ax1.set_xlabel("Angle (deg)") ax1.set_ylabel("Cross-correlation (A.U.)") ax1.set_xticks(np.arange(0, 181, 30)) ax1.set_title("Cross-correlation at q={:.3f}".format(q_range[current_q])) plt.draw()
def onclick(click_event): """ Process mouse click events in the interactive line plot :param click_event: mouse click event """ global sum_roi, vline, ax1, ax2, index_peak, motor_positions, data, my_cmap, sum_int global figure, scale, motor_text, max_text if click_event.inaxes == ax1: # click in the line plot index_peak = util.find_nearest(motor_positions, click_event.xdata) vline.remove() if scale == "linear": vline = ax1.vlines( x=motor_positions[index_peak], ymin=sum_int.min(), ymax=sum_int[index_peak], colors="r", linestyle="dotted", ) else: # 'log' vline = ax1.vlines( x=motor_positions[index_peak], ymin=np.log10(sum_int.min()), ymax=np.log10(sum_int[index_peak]), colors="r", linestyle="dotted", ) ax2.cla() ax2.imshow( np.log10( data[index_peak, sum_roi[0] : sum_roi[1], sum_roi[2] : sum_roi[3]] ), cmap=my_cmap, vmin=0, ) ax2.axis("scaled") ax2.set_title("ROI at line") motor_text.remove() motor_text = figure.text( 0.70, 0.75, motor_name + " = {:.2f}".format(motor_positions[index_peak]), size=10, ) max_text.remove() max_text = figure.text( 0.70, 0.70, "ROI max at line = " + "{:.0f}".format( data[index_peak, sum_roi[0] : sum_roi[1], sum_roi[2] : sum_roi[3]].max() ), size=10, ) plt.draw()
def calc_ccf_polar(point, q1_name, q2_name, bin_values, polar_azi_int): """ Calculate for the cross-correlation of point with all other points at the second q value and sort the result. :param point: the reference point :param q1_name: key for the first q value in the dictionnary polar_azi_int :param q2_name: key for the second q value in the dictionnary polar_azi_int :param bin_values: in radians, angular bin values where to calculate the cross-correlation :param polar_azi_int: a dictionnary with fields 'q1', 'q2', ... Each field contains three 1D arrays: polar angle, azimuthal angle and intensity values for each point :return: the sorted cross-correlation values, angular bins indices and number of points contributing to the angular bins """ # calculate the angle between the current point and all points from the second q value (delta in [0 pi]) delta_val = np.arccos( np.sin(polar_azi_int[q1_name][point, 0]) * np.sin(polar_azi_int[q2_name][:, 0]) * np.cos(polar_azi_int[q2_name][:, 1] - polar_azi_int[q1_name][point, 1]) + np.cos(polar_azi_int[q1_name][point, 0]) * np.cos(polar_azi_int[q2_name][:, 0])) # It can happen that the value in the arccos is outside [-1, 1] because of the limited floating precision of Python, # which result in delta_val = nan. These points would contribute to the 0 and 180 degrees CCF, and can be neglected. # find the nearest angular bin value for each value of the array delta nearest_indices = util.find_nearest(test_values=delta_val, reference_array=bin_values, width=bin_values[1] - bin_values[0]) # update the counter of bin indices counter_indices, counter_val = np.unique( nearest_indices, return_counts=True) # counter_indices are sorted # filter out -1 indices which correspond to no neighbour in the range defined by width in find_nearest() counter_val = np.delete(counter_val, np.argwhere(counter_indices == -1)) counter_indices = np.delete(counter_indices, np.argwhere(counter_indices == -1)) # calculate the contribution to the cross-correlation for bins in counter_indices ccf_uniq_val = np.zeros(len(counter_indices)) for idx in range(len(counter_indices)): ccf_uniq_val[idx] = ( polar_azi_int[q1_name][point, 2] * polar_azi_int[q2_name][nearest_indices == counter_indices[idx], 2]).sum() return ccf_uniq_val, counter_val, counter_indices
hist, bin_edges = np.histogram(amp[amp > cutoff_amp].flatten(), bins=50) bin_step = (bin_edges[1] - bin_edges[0]) / 2 bin_axis = bin_edges + bin_step bin_axis = bin_axis[0:len(hist)] # interpolate the histogram newbin_axis = np.linspace(bin_axis.min(), bin_axis.max(), 500) interp_hist = interp1d(bin_axis, hist, kind='cubic') newhist = interp_hist(newbin_axis) ############################################## # fit the peak with a pseudovoigt line shape # ############################################## if fit: # find indices of the histogram points belonging to the range of interest ind_min, ind_max = util.find_nearest( newbin_axis, [min(fit_range), max(fit_range)]) fit_axis = newbin_axis[np.arange(ind_min, ind_max + 1, 1)] fit_hist = newhist[np.arange(ind_min, ind_max + 1, 1)] # offset_hist = min(fit_hist) # define the initial parameters fit_params = Parameters() if lineshape == 'pseudovoigt': cen = newbin_axis[np.unravel_index(newhist.argmax(), newhist.shape)] fit_params.add('amp_1', value=50000, min=100, max=1000000) fit_params.add('cen_1', value=cen, min=cen - 0.2, max=cen + 0.2) fit_params.add('sig_1', value=0.1, min=0.01, max=0.5) fit_params.add('ratio_1', value=0.5, min=0, max=1) # run the fit result = minimize(util.objective_lmfit,
np.savez_compressed(root_folder + 'q+angular_avg.npz', q=q_axis, avg=y_mean_masked, median=y_median_masked) if save_txt: file = open(root_folder + 'q+angular_avg.txt', "w") file.write('{:8s}'.format('q') + '\t' + '{:10s}'.format('avg') + '\n') for idx in range(len(q_axis)): file.write('{:8.6f}'.format(q_axis[idx]) + '\t' + '{:10.1f}'.format(y_mean_masked[idx]) + '\n') file.close() ############# # plot data # ############# q_axvline = util.find_nearest(q_axis, vertical_lines) fig, ax0 = plt.subplots(1, 1) plt0 = ax0.plot(q_axis, np.log10(y_mean_masked), 'r') plt.xlabel('q (1/nm)') plt.ylabel('Angular average (A.U.)') if xlim is not None: plt.xlim(xlim[0], xlim[1]) if ylim is not None: plt.ylim(ylim[0], ylim[1]) ymax = np.log10(y_mean_masked.max()) for counter, value in enumerate(vertical_lines): ax0.axvline(x=value, ymax=np.log10(y_mean_masked[q_axvline[counter]]) / ymax, linestyle='--') plt.savefig(root_folder + 'angular_avg_labels.png')
else: ylim = [0, np.log10(average[~np.isnan(average)].max()) + 1] ax.set_xlim(xlim[0], xlim[1]) ax.set_ylim(ylim[0], ylim[1]) ################################################## # combine ranges of interest in a single dataset # ################################################## nb_ranges = len(fit_range) nb_points = np.zeros(nb_ranges, dtype=int) fit_range = np.asarray(fit_range) for idx in range(nb_ranges): # find indices of distances belonging to ranges of interest myrange = fit_range[idx] ind_min, ind_max = util.find_nearest( distances, [myrange.min(), myrange.max()]) nb_points[idx] = ind_max - ind_min + 1 # check if the number of points in ranges in the same, interpolate otherwise max_points = nb_points.max() combined_xaxis = [] combined_data = [] for idx in range(nb_ranges): # find indices of distances belonging to ranges of interest myrange = fit_range[idx] ind_min, ind_max = util.find_nearest( distances, [myrange.min(), myrange.max()]) indices = np.arange(ind_min, ind_max + 1, 1) if (ind_max - ind_min + 1) != max_points: interp = interp1d(distances[indices], average[indices],
def bcc_lattice(q_values, unitcell_param, pivot, euler_angles=(0, 0, 0), offset_indices=False, verbose=False): """ Calculate Bragg peaks positions using experimental parameters for a BCC unit cell. :param q_values: tuple of 1D arrays (qx, qz, qy), q_values range where to look for Bragg peaks :param unitcell_param: the unit cell parameter of the FCC lattice :param pivot: tuple, the pivot point position in pixels for the rotation :param euler_angles: tuple of angles for rotating the unit cell around (qx, qz, qy) :param offset_indices: if True, return the non rotated lattice with the origin of indices corresponding to the length of padded q values :param verbose: True to have printed comments :return: offsets after padding, the list of Bragg peaks positions in pixels, and the corresponding list of hlk. """ lattice_list = [ ] # position of the pixels corresponding to hkl reflections peaks_list = [] # list of hkl fitting the data range recipr_param = 2 * np.pi / unitcell_param # reciprocal lattice is simple cubic of parameter 2*pi/unitcell_param if verbose: print('fcc unit cell of parameter a =', unitcell_param, 'nm') print('reciprocal unit cell of parameter 2*pi/a =', str('{:.4f}'.format(recipr_param)), '1/nm') qx = q_values[0] # along z downstream in CXI convention qz = q_values[1] # along y vertical up in CXI convention qy = q_values[2] # along x outboard in CXI convention q_max = np.sqrt(abs(qx).max()**2 + abs(qz).max()**2 + abs(qy).max()**2) numz, numy, numx = len(qx), len(qz), len(qy) # calculate the maximum Miller indices which fit into q_max h_max = int(np.floor(q_max * unitcell_param / (2 * np.pi))) hkl = np.arange(start=-h_max, stop=h_max + 1, step=1) # pad q arrays in order to find the position in pixels of each hkl within the array # otherwise it finds the first or last index but this can be far from the real peak position leftpad_z, leftpad_y, leftpad_x = numz, numy, numx # offset of indices to the left pad_qx = qx[0] - leftpad_z * (qx[1] - qx[0]) + np.arange( 3 * numz) * (qx[1] - qx[0]) pad_qz = qz[0] - leftpad_y * (qz[1] - qz[0]) + np.arange( 3 * numy) * (qz[1] - qz[0]) pad_qy = qy[0] - leftpad_x * (qy[1] - qy[0]) + np.arange( 3 * numx) * (qy[1] - qy[0]) # calculate peaks position for the non rotated lattice for h in hkl: # h downstream along qx for k in hkl: # k outboard along qy for l in hkl: # l vertical up along qz # simple cubic unit cell with two point basis (0,0,0), (0.5,0.5,0.5) struct_factor = np.real(1 + np.exp(1j * np.pi * (h + k + l))) if struct_factor != 0: # find the position of the pixel nearest to q_bragg pix_h = util.find_nearest(original_array=pad_qx, array_values=h * recipr_param) pix_k = util.find_nearest(original_array=pad_qy, array_values=k * recipr_param) pix_l = util.find_nearest(original_array=pad_qz, array_values=l * recipr_param) lattice_list.append([pix_h, pix_l, pix_k]) peaks_list.append([h, l, k]) if offset_indices: # non rotated lattice, the origin of indices will correspond to the length of padded q values return (leftpad_z, leftpad_y, leftpad_x), lattice_list, peaks_list else: # rotate previously calculated peaks, the origin of indices will correspond to the length of original q values lattice_pos, peaks = rotate_lattice(lattice_list=lattice_list, peaks_list=peaks_list, original_shape=(numz, numy, numx), pad_offset=(leftpad_z, leftpad_y, leftpad_x), pivot=pivot, euler_angles=euler_angles) return (leftpad_z, leftpad_y, leftpad_x), lattice_pos, peaks
for key, value in result.items(): # loop over linecuts # value is a dictionary {'distance': 1D array, 'cut': 1D array} tmp_str = f"{key}" print(f'\n{"#" * len(tmp_str)}\n' + tmp_str + "\n" + f'{"#" * len(tmp_str)}') for idx_roi, roi in enumerate( fit_roi[idx_point] ): # loop over the ROIs, roi is a tuple of two number # define the fit initial center tmp_str = f"{roi}" indent = 2 print(f'\n{" " * indent}{"-" * len(tmp_str)}\n' + f'{" " * indent}' + tmp_str + "\n" + f'{" " * indent}{"-" * len(tmp_str)}') # find linecut indices falling into the roi ind_start, ind_stop = util.find_nearest(value["distance"], roi) # fit a RectangleModel from lmfit to the peaks midpoint = (roi[0] + roi[1]) / 2 offset = (roi[1] - roi[0]) / 8 # initialize fit parameters (guess does not perform well) rect_mod = RectangleModel(form="erf") rect_params = rect_mod.make_params() rect_params["amplitude"].set(0.75, min=0.5, max=1) rect_params["center1"].set(midpoint - offset, min=roi[0], max=roi[1]) rect_params["sigma1"].set(1, min=0.001, max=10) rect_params["center2"].set(midpoint + offset, min=roi[0], max=roi[1])
plt.ylabel('Angular average (A.U.)') plt.title("Click to select background points\nx to pause/resume for pan/zoom\n" "a restart ; p plot background ; q quit") if xlim is not None: plt.xlim(xlim[0], xlim[1]) if ylim is not None: plt.ylim(ylim[0], ylim[1]) plt.connect('key_press_event', press_key) plt.connect('button_press_event', on_click) plt.show() ######################################################### # fit background and interpolate it to mach data points # ######################################################### xy_array = np.asarray(xy) indices = util.find_nearest(distances, xy_array[:, 0]) if scale == 'linear': interpolation = interp1d(distances[indices], data[indices], kind='linear', bounds_error=False, fill_value=np.nan) background = interpolation(distances) background[np.isnan(background)] = 0 data_back = data - background data_back[data_back <= 0] = 0 else: # fit direcly log values, less artefactsdistances.max interpolation = interp1d(distances[indices], np.log10(data[indices]), kind='linear', bounds_error=False, fill_value=np.nan) background = interpolation(distances) background = 10**background background[np.isnan(background)] = 0 data_back = data - background data_back[data_back <= 1] = 1 # will appear as 0 in log plot
for key, value in result.items(): # loop over linecuts # value is a dictionary {'distance': 1D array, 'cut': 1D array} tmp_str = f'{key}' print(f'\n{"#" * len(tmp_str)}\n' + tmp_str + '\n' + f'{"#" * len(tmp_str)}') for idx_roi, roi in enumerate( fit_roi[idx_point] ): # loop over the ROIs, roi is a tuple of two number # define the fit initial center tmp_str = f'{roi}' indent = 2 print(f'\n{" " * indent}{"-" * len(tmp_str)}\n' + f'{" " * indent}' + tmp_str + '\n' + f'{" " * indent}{"-" * len(tmp_str)}') # find linecut indices falling into the roi ind_start, ind_stop = util.find_nearest(value['distance'], roi) # fit a RectangleModel from lmfit to the peaks midpoint = (roi[0] + roi[1]) / 2 offset = (roi[1] - roi[0]) / 8 # initialize fit parameters (guess does not perform well) rect_mod = RectangleModel(form='erf') rect_params = rect_mod.make_params() rect_params['amplitude'].set(0.75, min=0.5, max=1) rect_params['center1'].set(midpoint - offset, min=roi[0], max=roi[1]) rect_params['sigma1'].set(1, min=0.001, max=10) rect_params['center2'].set(midpoint + offset, min=roi[0], max=roi[1])
except MemoryError: # switch to the for loop, not enough memory to calculate the CCF using vectorization print('Not enough memory, switching to the iterative calculation') start = time.time() for idx in range( nb_points[0]): # loop over the points of the first q value # calculate the angle between the current point and all points from the second q value (delta in [0 pi]) delta = np.arccos( np.sin(theta_phi_int['q1'][idx, 0]) * np.sin(theta_phi_int[key_q2][:, 0]) * np.cos(theta_phi_int[key_q2][:, 1] - theta_phi_int['q1'][idx, 1]) + np.cos(theta_phi_int['q1'][idx, 0]) * np.cos(theta_phi_int[key_q2][:, 0])) # find the nearest angular bin value for each value of the array delta indices = util.find_nearest(test_values=delta, reference_array=ang_corr_count[:, 0]) # update the cross-correlation function with correlations for the current point. Nan values are already removed. ang_corr_count[indices, 1] = theta_phi_int['q1'][ idx, 2] * theta_phi_int[key_q2][indices, 2] # update the counter of bin indices index, counts = np.unique(indices, return_counts=True) ang_corr_count[index, 2] = ang_corr_count[index, 2] + counts del index, counts, indices, delta gc.collect() end = time.time() print('Time ellapsed for the calculation of the CCF:', int(end - start), 's')