def psf_rl(measured_intensity, coherent_intensity, iterations=20, debugging=False): """ Partial coherence deconvolution using Richardson-Lucy algorithm. See J.N. Clark et al., Nat. Comm. 3, 993 (2012). :param measured_intensity: measured object with partial coherent illumination :param coherent_intensity: estimate of the object measured by a fully coherent illumination :param iterations: number of iterations for the Richardson-Lucy algorithm :param debugging: True to see plots :return: """ psf = np.abs( richardson_lucy(image=measured_intensity, psf=coherent_intensity, iterations=iterations, clip=False)) psf = (psf / psf.sum()).astype(np.float32) if debugging: gu.multislices_plot(fftshift(psf), scale='log', sum_frames=False, title='log(psf) in detector frame', reciprocal_space=True, vmin=-5, is_orthogonal=False, plot_colorbar=True) return psf
def deconvolution_rl(image, psf=None, psf_shape=(10, 10, 10), iterations=20, debugging=False): """ Image deconvolution using Richardson-Lucy algorithm. The algorithm is based on a PSF (Point Spread Function), where PSF is described as the impulse response of the optical system. :param image: image to be deconvoluted :param psf: psf to be used as a first guess :param psf_shape: shape of the kernel used for deconvolution :param iterations: number of iterations for the Richardson-Lucy algorithm :param debugging: True to see plots :return: """ image = image.astype(np.float) ndim = image.ndim if psf is None: print('Initializing the psf using a', ndim, 'D multivariate normal window\n') print('sigma =', 0.3, ' mu =', 0.0) psf = pu.gaussian_window(window_shape=psf_shape, sigma=0.3, mu=0.0, debugging=False) psf = psf.astype(float) if debugging: gu.multislices_plot(array=psf, sum_frames=False, plot_colorbar=True, scale='linear', title='Gaussian window', reciprocal_space=False, is_orthogonal=True) im_deconv = np.abs( richardson_lucy(image=image, psf=psf, iterations=iterations, clip=False)) if debugging: image = abs(image) / abs(image).max() im_deconv = abs(im_deconv) / abs(im_deconv).max() gu.combined_plots(tuple_array=(image, im_deconv), tuple_sum_frames=False, tuple_colorbar=True, tuple_scale='linear', tuple_width_v=None, tuple_width_h=None, tuple_vmin=0, tuple_vmax=1, tuple_title=('Before RL', 'After ' + str(iterations) + ' iterations of RL (normalized)')) return im_deconv
def upsample(array, upsampling_factor, voxelsizes, debugging=False): """ Upsample array using a factor of upsampling. :param array: the real array to be upsampled :param upsampling_factor: int, the upsampling factor :param voxelsizes: list, the voxel sizes of array :param debugging: True to see plots :return: the upsampled array """ from scipy.interpolate import RegularGridInterpolator if array.ndim != 3: raise ValueError('Expecting a 3D array as input') if not isinstance(upsampling_factor, int): raise ValueError('upsampling_factor should be an integer') if debugging: gu.multislices_plot(array, sum_frames=False, invert_yaxis=True, title='Array before upsampling') nbz, nby, nbx = array.shape numz, numy, numx = nbz * upsampling_factor, nby * upsampling_factor, nbx * upsampling_factor newvoxelsizes = [voxsize / upsampling_factor for voxsize in voxelsizes] newz, newy, newx = np.meshgrid( np.arange(-numz // 2, numz // 2, 1) * newvoxelsizes[0], np.arange(-numy // 2, numy // 2, 1) * newvoxelsizes[1], np.arange(-numx // 2, numx // 2, 1) * newvoxelsizes[2], indexing='ij') rgi = RegularGridInterpolator( (np.arange(-nbz // 2, nbz // 2) * voxelsizes[0], np.arange(-nby // 2, nby // 2) * voxelsizes[1], np.arange(-nbx // 2, nbx // 2) * voxelsizes[2]), array, method='linear', bounds_error=False, fill_value=0) obj = rgi( np.concatenate((newz.reshape( (1, newz.size)), newy.reshape( (1, newz.size)), newx.reshape((1, newz.size)))).transpose()) obj = obj.reshape((numz, numy, numx)).astype(array.dtype) if debugging: gu.multislices_plot(obj, sum_frames=False, invert_yaxis=True, title='Array after upsampling') return obj, newvoxelsizes
data = pu.flip_reconstruction(data, debugging=True) data = abs(data) # take the real part ################################### # clean interactively the support # ################################### if flag_interact: data = data / data.max(initial=None) # normalize data[data < support_threshold] = 0 fig, _, _ = gu.multislices_plot(data, sum_frames=False, scale='linear', plot_colorbar=True, vmin=0, vmax=1, title='Support before masking', is_orthogonal=True, reciprocal_space=False) fig.canvas.mpl_disconnect(fig.canvas.manager.key_press_handler_id) cid = plt.connect('close_event', close_event) fig.waitforbuttonpress() plt.disconnect(cid) plt.close(fig) ############################################# # mask the projected data in each dimension # ############################################# plt.ioff() width = 0
pivot=pivot) ############### # plot result # ############### qx, qz, qy = q_values # mark the direct beam position struct_array[pivot[0] - 2:pivot[0] + 3, pivot[1] - 2:pivot[1] + 3, pivot[2] - 2:pivot[2] + 3] = maxpeak if debug: fig, _, _ = gu.multislices_plot( struct_array, sum_frames=False, title='Simulated diffraction pattern', vmin=0, vmax=maxpeak, slice_position=[pivot[0], pivot[1], pivot[2]], plot_colorbar=True, cmap=my_cmap, is_orthogonal=True, reciprocal_space=True) fig.text(0.60, 0.30, "Origin of reciprocal space (Qx,Qz,Qy) = " + str(pivot[0]) + "," + str(pivot[1]) + "," + str(pivot[2]), size=12) fig.text(0.60, 0.25, "Energy = " + str(energy / 1000) + " keV", size=12) fig.text(0.60, 0.20, "SDD = " + str(sdd) + " m", size=12) fig.text(0.60, 0.15, unitcell + " unit cell of parameter = " + str(unitcell_param) +
comment = f'_direction{direction[0]}_{direction[1]}_{comment}' ######################### # normalize the modulus # ######################### obj = abs(obj) / abs(obj).max() # normalize the modulus to 1 obj[np.isnan(obj)] = 0 # remove nans if ndim == 2: gu.imshow_plot(array=obj, plot_colorbar=True, reciprocal_space=False, is_orthogonal=True) else: gu.multislices_plot(array=obj, sum_frames=False, plot_colorbar=True, reciprocal_space=False, is_orthogonal=True, slice_position=(25, 37, 25)) ##################################### # create the linecut for each point # ##################################### result = dict() for point in points: # get the distances and the modulus values along the linecut distance, cut = util.linecut(array=obj, point=point, direction=direction, voxel_size=voxel_size) # store the result in a dictionary (cuts can have different lengths depending on the direction) result[f'voxel {point}'] = {'distance': distance, 'cut': cut}
def detector_frame(self, obj, voxelsize, width_z=None, width_y=None, width_x=None, debugging=False, **kwargs): """ Interpolate the orthogonal object back into the non-orthogonal detector frame :param obj: real space object, in the orthogonal laboratory frame :param voxelsize: voxel size of the original object :param width_z: size of the area to plot in z (axis 0), centered on the middle of the initial array :param width_y: size of the area to plot in y (axis 1), centered on the middle of the initial array :param width_x: size of the area to plot in x (axis 2), centered on the middle of the initial array :param debugging: True to show plots before and after interpolation :param kwargs: - 'title': title for the debugging plots :return: object interpolated on an orthogonal grid """ for k in kwargs.keys(): if k in ['title']: title = kwargs['title'] else: raise Exception( "unknown keyword argument given: allowed is 'title'") try: title except NameError: # title not declared title = 'Object' nbz, nby, nbx = obj.shape if debugging: gu.multislices_plot(abs(obj), sum_frames=True, width_z=width_z, width_y=width_y, width_x=width_x, title=title + ' before interpolation\n') ortho_matrix = self.update_coords(array_shape=(nbz, nby, nbx), tilt_angle=self.tilt_angle, pixel_x=self.pixel_x, pixel_y=self.pixel_y) ################################################ # interpolate the data into the detector frame # ################################################ myz, myy, myx = np.meshgrid(np.arange(-nbz // 2, nbz // 2, 1), np.arange(-nby // 2, nby // 2, 1), np.arange(-nbx // 2, nbx // 2, 1), indexing='ij') new_x = ortho_matrix[0, 0] * myx + ortho_matrix[ 0, 1] * myy + ortho_matrix[0, 2] * myz new_y = ortho_matrix[1, 0] * myx + ortho_matrix[ 1, 1] * myy + ortho_matrix[1, 2] * myz new_z = ortho_matrix[2, 0] * myx + ortho_matrix[ 2, 1] * myy + ortho_matrix[2, 2] * myz del myx, myy, myz # la partie rgi est sure: c'est la taille de l'objet orthogonal de depart rgi = RegularGridInterpolator( (np.arange(-nbz // 2, nbz // 2) * voxelsize, np.arange(-nby // 2, nby // 2) * voxelsize, np.arange(-nbx // 2, nbx // 2) * voxelsize), obj, method='linear', bounds_error=False, fill_value=0) detector_obj = rgi( np.concatenate((new_z.reshape( (1, new_z.size)), new_y.reshape( (1, new_z.size)), new_x.reshape( (1, new_z.size)))).transpose()) detector_obj = detector_obj.reshape((nbz, nby, nbx)).astype(obj.dtype) if debugging: gu.multislices_plot(abs(detector_obj), sum_frames=True, width_z=width_z, width_y=width_y, width_x=width_x, title=title + ' interpolated in detector frame\n') return detector_obj
title="Select the reconstruction file", filetypes=[("NPZ", "*.npz")], ) npzfile = np.load(file_path) phase = npzfile["displacement"] if flip_phase: phase = -1 * phase amp = npzfile["amp"] if amp.ndim != 3: raise ValueError("3D arrays are expected") gu.multislices_plot( array=amp, sum_frames=False, scale="linear", plot_colorbar=True, reciprocal_space=False, is_orthogonal=True, title="Modulus", ) ################################ # optionally load the q values # ################################ if load_qvalues: file_path = filedialog.askopenfilename( initialdir=datadir, title="Select the q values", filetypes=[("NPZ", "*.npz")] ) q_values = np.load(file_path) qx = q_values["qx"] qz = q_values["qz"]
ycom, nby - ycom, xcom, nbx - xcom, ) # asymmetric half ranges for idx, val in enumerate(half_range): plot_range.append(min(val or max_range[2 * idx], max_range[2 * idx])) plot_range.append(min(val or max_range[2 * idx + 1], max_range[2 * idx + 1])) print("\nPlotting symmetrical ranges:", plot_symmetrical) print("Plotting range from the center of mass:", plot_range) gu.multislices_plot( array=data, sum_frames=True, scale="log", cmap=my_cmap, reciprocal_space=True, is_orthogonal=is_orthogonal, ) ################################ # optionally load the q values # ################################ if load_qvalues: file_path = filedialog.askopenfilename( initialdir=datadir, title="Select the q values", filetypes=[("NPZ", "*.npz")] ) q_values = np.load(file_path) qx = q_values["qx"] qz = q_values["qz"] qy = q_values["qy"]
############# # load mask # ############# file_path = filedialog.askopenfilename(initialdir=datadir, title="Select the mask", filetypes=[("NPZ", "*.npz")]) npzfile = np.load(file_path) mask = npzfile[list(npzfile.files)[0]] if debug: gu.multislices_plot( diff_pattern, sum_frames=False, plot_colorbar=True, cmap=my_cmap, title="measured amplitude", scale="log", vmin=np.nan, vmax=np.nan, reciprocal_space=True, is_orthogonal=True, ) gu.multislices_plot( mask, sum_frames=False, plot_colorbar=True, cmap=my_cmap, title="mask", scale="linear", vmin=0, vmax=1,
print( 'The experimental data and calculated q values have different shape, check "roi_detector" parameter!' ) sys.exit() print('Origin of the reciprocal space at pixel', pivot) ########################## # plot experimental data # ########################## if debug: gu.multislices_plot(data, sum_frames=True, title='data', vmin=0, vmax=np.log10(data).max(), scale='log', plot_colorbar=True, cmap=my_cmap, is_orthogonal=True, reciprocal_space=True) if qvalues_flag: gu.contour_slices(data, q_coordinates=(exp_qvalues['qx'], exp_qvalues['qz'], exp_qvalues['qy']), sum_frames=True, title='Experimental data', levels=np.linspace(0, np.log10(data.max()) + 1, 20, endpoint=False),
if background_roi is not None: background = obj[background_roi[0]:background_roi[1] + 1, background_roi[2:background_roi[3] + 1], ].mean() print(f"removing background = {background:.2f} from the data") obj = obj - background if ndim == 2: gu.imshow_plot(array=obj, plot_colorbar=True, reciprocal_space=False, is_orthogonal=True) else: gu.multislices_plot( array=obj, sum_frames=False, plot_colorbar=True, reciprocal_space=False, is_orthogonal=True, ) ##################################### # create the linecut for each point # ##################################### result = {} for point in points: # get the distances and the modulus values along the linecut distance, cut = util.linecut(array=obj, point=point, direction=direction, voxel_size=voxel_size) # store the result in a dictionary
support = np.zeros(amp.shape) support[abs(amp) > support_threshold * abs(amp).max()] = 1 coordination_matrix = pu.calc_coordination(support, kernel=np.ones((3, 3, 3)), debugging=False) surface = np.copy(support) surface[coordination_matrix > 22] = 0 # remove the bulk 22 bulk = support - surface del coordination_matrix gc.collect() ######################################################## # define edges using the coordination number of voxels # ######################################################## edges = pu.calc_coordination(support, kernel=np.ones((9, 9, 9)), debugging=False) edges[support == 0] = 0 if debug: gu.multislices_plot(edges, invert_yaxis=True, vmin=0, title='Coordination matrix') edges[edges > edges_coord] = 0 # remove facets and bulk edges[np.nonzero(edges)] = 1 # edge support gu.scatter_plot(array=np.asarray(np.nonzero(edges)).T, markersize=2, markercolor='b', labels=('x', 'y', 'z'), title='edges') ######################################################## # define corners using the coordination number of voxels # ######################################################## corners = pu.calc_coordination(support, kernel=np.ones((9, 9, 9)), debugging=False) corners[support == 0] = 0 if debug: gu.multislices_plot(corners, invert_yaxis=True, vmin=0, title='Coordination matrix') corners[corners > corners_coord] = 0 # remove edges, facets and bulk corners[np.nonzero(corners)] = 1 # corner support gu.scatter_plot(array=np.asarray(np.nonzero(corners)).T, markersize=2, markercolor='b', labels=('x', 'y', 'z'),
def grid_cdi( data, mask, setup, frames_logical, correct_curvature=False, debugging=False, **kwargs, ): """ Interpolate reciprocal space forward CDI data. The interpolation is done from the measurement cylindrical frame to the laboratory frame (cartesian coordinates). Note that it is based on PetraIII P10 beamline ( counterclockwise rotation, detector seen from the front). :param data: the 3D data, already binned in the detector frame :param mask: the corresponding 3D mask :param setup: an instance of the class Setup :param frames_logical: array of initial length the number of measured frames. In case of padding the length changes. A frame whose index is set to 1 means that it is used, 0 means not used, -1 means padded (added) frame. :param correct_curvature: if True, will correct for the curvature of the Ewald sphere :param debugging: set to True to see plots :param kwargs: - 'fill_value': tuple of two real numbers, fill values to use for pixels outside of the interpolation range. The first value is for the data, the second for the mask. Default is (0, 0) :return: the data and mask interpolated in the laboratory frame, q values (downstream, vertical up, outboard) """ fill_value = kwargs.get("fill_value", (0, 0)) valid.valid_ndarray(arrays=(data, mask), ndim=3) if setup.name == "P10_SAXS": if setup.rocking_angle == "inplane": if setup.custom_scan: cdi_angle = setup.custom_motors["hprz"] else: cdi_angle, _, _ = setup.loader.motor_positions(setup=setup) # second return value is the X-ray energy, third the detector distance else: raise ValueError( "out-of-plane rotation not yet implemented for forward CDI data" ) else: raise NotImplementedError( "Not yet implemented for beamlines other than P10") data, mask, cdi_angle, frames_logical = check_cdi_angle( data=data, mask=mask, cdi_angle=cdi_angle, frames_logical=frames_logical, debugging=debugging, ) if debugging: print("\ncdi_angle", cdi_angle) nbz, nby, nbx = data.shape print("\nData shape after check_cdi_angle and before regridding:", nbz, nby, nbx) print("\nAngle range:", cdi_angle.min(), cdi_angle.max()) (interp_data, interp_mask), q_values, corrected_dirbeam = setup.ortho_cdi( arrays=(data, mask), cdi_angle=cdi_angle, fill_value=fill_value, correct_curvature=correct_curvature, debugging=debugging, ) qx, qz, qy = q_values # check for Nan interp_mask[np.isnan(interp_data)] = 1 interp_data[np.isnan(interp_data)] = 0 interp_mask[np.isnan(interp_mask)] = 1 # set the mask as an array of integers, 0 or 1 interp_mask[np.nonzero(interp_mask)] = 1 interp_mask = interp_mask.astype(int) # apply the mask to the data interp_data[np.nonzero(interp_mask)] = 0 # calculate the position in pixels of the origin of the reciprocal space pivot_z = int((setup.direct_beam[1] - setup.detector.roi[2]) / setup.detector.binning[2]) # 90 degrees conter-clockwise rotation of detector X around qz, downstream _, numy, numx = interp_data.shape pivot_y = int(numy - corrected_dirbeam[0]) # detector Y vertical down, opposite to qz vertical up pivot_x = int(numx - corrected_dirbeam[1]) # detector X inboard at P10, opposite to qy outboard print("\nOrigin of the reciprocal space (Qx,Qz,Qy): " f"({pivot_z}, {pivot_y}, {pivot_x})\n") # plot the gridded data final_binning = ( setup.detector.preprocessing_binning[2] * setup.detector.binning[2], setup.detector.preprocessing_binning[1] * setup.detector.binning[1], setup.detector.preprocessing_binning[2] * setup.detector.binning[2], ) plot_comment = ( f"_{numx}_{numy}_{numx}" f"_{final_binning[0]}_{final_binning[1]}_{final_binning[2]}.png") # sample rotation around the vertical direction at P10: the effective # binning in axis 0 is binning[2] max_z = interp_data.sum(axis=0).max() fig, _, _ = gu.contour_slices( interp_data, (qx, qz, qy), sum_frames=True, title="Regridded data", levels=np.linspace(0, np.ceil(np.log10(max_z)), 150, endpoint=True), plot_colorbar=True, scale="log", is_orthogonal=True, reciprocal_space=True, ) fig.text( 0.55, 0.30, "Origin of the reciprocal space (Qx,Qz,Qy):\n\n" + " ({:d}, {:d}, {:d})".format(pivot_z, pivot_y, pivot_x), size=14, ) fig.savefig(setup.detector.savedir + "reciprocal_space_sum" + plot_comment) plt.close(fig) fig, _, _ = gu.contour_slices( interp_data, (qx, qz, qy), sum_frames=False, title="Regridded data", levels=np.linspace(0, np.ceil(np.log10(interp_data.max(initial=None))), 150, endpoint=True), slice_position=(pivot_z, pivot_y, pivot_x), plot_colorbar=True, scale="log", is_orthogonal=True, reciprocal_space=True, ) fig.text( 0.55, 0.30, "Origin of the reciprocal space (Qx,Qz,Qy):\n\n" + " ({:d}, {:d}, {:d})".format(pivot_z, pivot_y, pivot_x), size=14, ) fig.savefig(setup.detector.savedir + "reciprocal_space_central" + plot_comment) plt.close(fig) fig, _, _ = gu.multislices_plot( interp_data, sum_frames=False, scale="log", plot_colorbar=True, vmin=0, slice_position=(pivot_z, pivot_y, pivot_x), title="Regridded data", is_orthogonal=True, reciprocal_space=True, ) fig.text( 0.55, 0.30, "Origin of the reciprocal space (Qx,Qz,Qy):\n\n" + " ({:d}, {:d}, {:d})".format(pivot_z, pivot_y, pivot_x), size=14, ) fig.savefig(setup.detector.savedir + "reciprocal_space_central_pix" + plot_comment) plt.close(fig) if debugging: gu.multislices_plot( interp_mask, sum_frames=False, scale="linear", plot_colorbar=True, vmin=0, title="Regridded mask", is_orthogonal=True, reciprocal_space=True, ) return interp_data, interp_mask, [qx, qz, qy], frames_logical
def run(prm): """ Run the postprocessing. :param prm: the parsed parameters """ pretty = pprint.PrettyPrinter(indent=4) ################################ # assign often used parameters # ################################ bragg_peak = prm.get("bragg_peak") debug = prm.get("debug", False) comment = prm.get("comment", "") centering_method = prm.get("centering_method", "max_com") original_size = prm.get("original_size") phasing_binning = prm.get("phasing_binning", [1, 1, 1]) preprocessing_binning = prm.get("preprocessing_binning", [1, 1, 1]) ref_axis_q = prm.get("ref_axis_q", "y") fix_voxel = prm.get("fix_voxel") save = prm.get("save", True) tick_spacing = prm.get("tick_spacing", 50) tick_direction = prm.get("tick_direction", "inout") tick_length = prm.get("tick_length", 10) tick_width = prm.get("tick_width", 2) invert_phase = prm.get("invert_phase", True) correct_refraction = prm.get("correct_refraction", False) threshold_unwrap_refraction = prm.get("threshold_unwrap_refraction", 0.05) threshold_gradient = prm.get("threshold_gradient", 1.0) offset_method = prm.get("offset_method", "mean") phase_offset = prm.get("phase_offset", 0) offset_origin = prm.get("phase_offset_origin") sort_method = prm.get("sort_method", "variance/mean") correlation_threshold = prm.get("correlation_threshold", 0.90) roi_detector = create_roi(dic=prm) # parameters below must be provided try: detector_name = prm["detector"] beamline_name = prm["beamline"] rocking_angle = prm["rocking_angle"] isosurface_strain = prm["isosurface_strain"] output_size = prm["output_size"] save_frame = prm["save_frame"] data_frame = prm["data_frame"] scan = prm["scan"] sample_name = prm["sample_name"] root_folder = prm["root_folder"] except KeyError as ex: print("Required parameter not defined") raise ex prm["sample"] = (f"{sample_name}+{scan}",) ######################### # Check some parameters # ######################### if not prm.get("backend"): prm["backend"] = "Qt5Agg" matplotlib.use(prm["backend"]) if prm["simulation"]: invert_phase = False correct_refraction = 0 if invert_phase: phase_fieldname = "disp" else: phase_fieldname = "phase" if data_frame == "detector": is_orthogonal = False else: is_orthogonal = True if data_frame == "crystal" and save_frame != "crystal": print( "data already in the crystal frame before phase retrieval," " it is impossible to come back to the laboratory " "frame, parameter 'save_frame' defaulted to 'crystal'" ) save_frame = "crystal" axis_to_array_xyz = { "x": np.array([1, 0, 0]), "y": np.array([0, 1, 0]), "z": np.array([0, 0, 1]), } # in xyz order ############### # Set backend # ############### if prm.get("backend") is not None: try: plt.switch_backend(prm["backend"]) except ModuleNotFoundError: print(f"{prm['backend']} backend is not supported.") ################### # define colormap # ################### if prm.get("grey_background"): bad_color = "0.7" else: bad_color = "1.0" # white background colormap = gu.Colormap(bad_color=bad_color) my_cmap = colormap.cmap ####################### # Initialize detector # ####################### detector = create_detector( name=detector_name, template_imagefile=prm.get("template_imagefile"), roi=roi_detector, binning=phasing_binning, preprocessing_binning=preprocessing_binning, pixel_size=prm.get("pixel_size"), ) #################################### # define the experimental geometry # #################################### setup = Setup( beamline=beamline_name, detector=detector, energy=prm.get("energy"), outofplane_angle=prm.get("outofplane_angle"), inplane_angle=prm.get("inplane_angle"), tilt_angle=prm.get("tilt_angle"), rocking_angle=rocking_angle, distance=prm.get("sdd"), sample_offsets=prm.get("sample_offsets"), actuators=prm.get("actuators"), custom_scan=prm.get("custom_scan", False), custom_motors=prm.get("custom_motors"), dirbeam_detector_angles=prm.get("dirbeam_detector_angles"), direct_beam=prm.get("direct_beam"), is_series=prm.get("is_series", False), ) ######################################## # Initialize the paths and the logfile # ######################################## setup.init_paths( sample_name=sample_name, scan_number=scan, root_folder=root_folder, data_dir=prm.get("data_dir"), save_dir=prm.get("save_dir"), specfile_name=prm.get("specfile_name"), template_imagefile=prm.get("template_imagefile"), ) setup.create_logfile( scan_number=scan, root_folder=root_folder, filename=detector.specfile ) # load the goniometer positions needed in the calculation # of the transformation matrix setup.read_logfile(scan_number=scan) ################### # print instances # ################### print(f'{"#"*(5+len(str(scan)))}\nScan {scan}\n{"#"*(5+len(str(scan)))}') print("\n##############\nSetup instance\n##############") pretty.pprint(setup.params) print("\n#################\nDetector instance\n#################") pretty.pprint(detector.params) ################ # preload data # ################ if prm.get("reconstruction_file") is not None: file_path = (prm["reconstruction_file"],) else: root = tk.Tk() root.withdraw() file_path = filedialog.askopenfilenames( initialdir=detector.scandir if prm.get("data_dir") is None else detector.datadir, filetypes=[ ("NPZ", "*.npz"), ("NPY", "*.npy"), ("CXI", "*.cxi"), ("HDF5", "*.h5"), ], ) nbfiles = len(file_path) plt.ion() obj, extension = util.load_file(file_path[0]) if extension == ".h5": comment = comment + "_mode" print("\n###############\nProcessing data\n###############") nz, ny, nx = obj.shape print("Initial data size: (", nz, ",", ny, ",", nx, ")") if not original_size: original_size = obj.shape print("FFT size before accounting for phasing_binning", original_size) original_size = tuple( [ original_size[index] // phasing_binning[index] for index in range(len(phasing_binning)) ] ) print("Binning used during phasing:", detector.binning) print("Padding back to original FFT size", original_size) obj = util.crop_pad(array=obj, output_shape=original_size) ########################################################################### # define range for orthogonalization and plotting - speed up calculations # ########################################################################### zrange, yrange, xrange = pu.find_datarange( array=obj, amplitude_threshold=0.05, keep_size=prm.get("keep_size", False) ) numz = zrange * 2 numy = yrange * 2 numx = xrange * 2 print( f"Data shape used for orthogonalization and plotting: ({numz}, {numy}, {numx})" ) #################################################################################### # find the best reconstruction from the list, based on mean amplitude and variance # #################################################################################### if nbfiles > 1: print("\nTrying to find the best reconstruction\nSorting by ", sort_method) sorted_obj = pu.sort_reconstruction( file_path=file_path, amplitude_threshold=isosurface_strain, data_range=(zrange, yrange, xrange), sort_method=sort_method, ) else: sorted_obj = [0] ####################################### # load reconstructions and average it # ####################################### avg_obj = np.zeros((numz, numy, numx)) ref_obj = np.zeros((numz, numy, numx)) avg_counter = 1 print("\nAveraging using", nbfiles, "candidate reconstructions") for counter, value in enumerate(sorted_obj): obj, extension = util.load_file(file_path[value]) print("\nOpening ", file_path[value]) prm[f"from_file_{counter}"] = file_path[value] if prm.get("flip_reconstruction", False): obj = pu.flip_reconstruction(obj, debugging=True) if extension == ".h5": centering_method = "do_nothing" # do not center, data is already cropped # just on support for mode decomposition # correct a roll after the decomposition into modes in PyNX obj = np.roll(obj, prm.get("roll_modes", [0, 0, 0]), axis=(0, 1, 2)) fig, _, _ = gu.multislices_plot( abs(obj), sum_frames=True, plot_colorbar=True, title="1st mode after centering", ) # use the range of interest defined above obj = util.crop_pad(obj, [2 * zrange, 2 * yrange, 2 * xrange], debugging=False) # align with average reconstruction if counter == 0: # the fist array loaded will serve as reference object print("This reconstruction will be used as reference.") ref_obj = obj avg_obj, flag_avg = reg.average_arrays( avg_obj=avg_obj, ref_obj=ref_obj, obj=obj, support_threshold=0.25, correlation_threshold=correlation_threshold, aligning_option="dft", space=prm.get("averaging_space", "reciprocal_space"), reciprocal_space=False, is_orthogonal=is_orthogonal, debugging=debug, ) avg_counter = avg_counter + flag_avg avg_obj = avg_obj / avg_counter if avg_counter > 1: print("\nAverage performed over ", avg_counter, "reconstructions\n") del obj, ref_obj gc.collect() ################ # unwrap phase # ################ phase, extent_phase = pu.unwrap( avg_obj, support_threshold=threshold_unwrap_refraction, debugging=debug, reciprocal_space=False, is_orthogonal=is_orthogonal, ) print( "Extent of the phase over an extended support (ceil(phase range)) ~ ", int(extent_phase), "(rad)", ) phase = util.wrap(phase, start_angle=-extent_phase / 2, range_angle=extent_phase) if debug: gu.multislices_plot( phase, width_z=2 * zrange, width_y=2 * yrange, width_x=2 * xrange, plot_colorbar=True, title="Phase after unwrap + wrap", reciprocal_space=False, is_orthogonal=is_orthogonal, ) ############################################# # phase ramp removal before phase filtering # ############################################# amp, phase, rampz, rampy, rampx = pu.remove_ramp( amp=abs(avg_obj), phase=phase, initial_shape=original_size, method="gradient", amplitude_threshold=isosurface_strain, threshold_gradient=threshold_gradient, ) del avg_obj gc.collect() if debug: gu.multislices_plot( phase, width_z=2 * zrange, width_y=2 * yrange, width_x=2 * xrange, plot_colorbar=True, title="Phase after ramp removal", reciprocal_space=False, is_orthogonal=is_orthogonal, ) ######################## # phase offset removal # ######################## support = np.zeros(amp.shape) support[amp > isosurface_strain * amp.max()] = 1 phase = pu.remove_offset( array=phase, support=support, offset_method=offset_method, phase_offset=phase_offset, offset_origin=offset_origin, title="Phase", debugging=debug, ) del support gc.collect() phase = util.wrap( obj=phase, start_angle=-extent_phase / 2, range_angle=extent_phase ) ############################################################################## # average the phase over a window or apodize to reduce noise in strain plots # ############################################################################## half_width_avg_phase = prm.get("half_width_avg_phase", 0) if half_width_avg_phase != 0: bulk = pu.find_bulk( amp=amp, support_threshold=isosurface_strain, method="threshold" ) # the phase should be averaged only in the support defined by the isosurface phase = pu.mean_filter( array=phase, support=bulk, half_width=half_width_avg_phase ) del bulk gc.collect() if half_width_avg_phase != 0: comment = comment + "_avg" + str(2 * half_width_avg_phase + 1) gridz, gridy, gridx = np.meshgrid( np.arange(0, numz, 1), np.arange(0, numy, 1), np.arange(0, numx, 1), indexing="ij", ) phase = ( phase + gridz * rampz + gridy * rampy + gridx * rampx ) # put back the phase ramp otherwise the diffraction # pattern will be shifted and the prtf messed up if prm.get("apodize", False): amp, phase = pu.apodize( amp=amp, phase=phase, initial_shape=original_size, window_type=prm.get("apodization_window", "blackman"), sigma=prm.get("apodization_sigma", [0.30, 0.30, 0.30]), mu=prm.get("apodization_mu", [0.0, 0.0, 0.0]), alpha=prm.get("apodization_alpha", [1.0, 1.0, 1.0]), is_orthogonal=is_orthogonal, debugging=True, ) comment = comment + "_apodize_" + prm.get("apodization_window", "blackman") ################################################################ # save the phase with the ramp for PRTF calculations, # # otherwise the object will be misaligned with the measurement # ################################################################ np.savez_compressed( detector.savedir + "S" + str(scan) + "_avg_obj_prtf" + comment, obj=amp * np.exp(1j * phase), ) #################################################### # remove again phase ramp before orthogonalization # #################################################### phase = phase - gridz * rampz - gridy * rampy - gridx * rampx avg_obj = amp * np.exp(1j * phase) # here the phase is again wrapped in [-pi pi[ del amp, phase, gridz, gridy, gridx, rampz, rampy, rampx gc.collect() ###################### # centering of array # ###################### if centering_method == "max": avg_obj = pu.center_max(avg_obj) # shift based on max value, # required if it spans across the edge of the array before COM elif centering_method == "com": avg_obj = pu.center_com(avg_obj) elif centering_method == "max_com": avg_obj = pu.center_max(avg_obj) avg_obj = pu.center_com(avg_obj) ####################### # save support & vti # ####################### if prm.get("save_support", False): # to be used as starting support in phasing, hence still in the detector frame support = np.zeros((numz, numy, numx)) support[abs(avg_obj) / abs(avg_obj).max() > 0.01] = 1 # low threshold because support will be cropped by shrinkwrap during phasing np.savez_compressed( detector.savedir + "S" + str(scan) + "_support" + comment, obj=support ) del support gc.collect() if prm.get("save_rawdata", False): np.savez_compressed( detector.savedir + "S" + str(scan) + "_raw_amp-phase" + comment, amp=abs(avg_obj), phase=np.angle(avg_obj), ) # voxel sizes in the detector frame voxel_z, voxel_y, voxel_x = setup.voxel_sizes_detector( array_shape=original_size, tilt_angle=( prm.get("tilt_angle") * detector.preprocessing_binning[0] * detector.binning[0] ), pixel_x=detector.pixelsize_x, pixel_y=detector.pixelsize_y, verbose=True, ) # save raw amp & phase to VTK # in VTK, x is downstream, y vertical, z inboard, # thus need to flip the last axis gu.save_to_vti( filename=os.path.join( detector.savedir, "S" + str(scan) + "_raw_amp-phase" + comment + ".vti" ), voxel_size=(voxel_z, voxel_y, voxel_x), tuple_array=(abs(avg_obj), np.angle(avg_obj)), tuple_fieldnames=("amp", "phase"), amplitude_threshold=0.01, ) ######################################################### # calculate q of the Bragg peak in the laboratory frame # ######################################################### q_lab = ( setup.q_laboratory ) # (1/A), in the laboratory frame z downstream, y vertical, x outboard qnorm = np.linalg.norm(q_lab) q_lab = q_lab / qnorm angle = simu.angle_vectors( ref_vector=[q_lab[2], q_lab[1], q_lab[0]], test_vector=axis_to_array_xyz[ref_axis_q], ) print( f"\nNormalized diffusion vector in the laboratory frame (z*, y*, x*): " f"({q_lab[0]:.4f} 1/A, {q_lab[1]:.4f} 1/A, {q_lab[2]:.4f} 1/A)" ) planar_dist = 2 * np.pi / qnorm # qnorm should be in angstroms print(f"Wavevector transfer: {qnorm:.4f} 1/A") print(f"Atomic planar distance: {planar_dist:.4f} A") print(f"\nAngle between q_lab and {ref_axis_q} = {angle:.2f} deg") if debug: print( "Angle with y in zy plane = " f"{np.arctan(q_lab[0]/q_lab[1])*180/np.pi:.2f} deg" ) print( "Angle with y in xy plane = " f"{np.arctan(-q_lab[2]/q_lab[1])*180/np.pi:.2f} deg" ) print( "Angle with z in xz plane = " f"{180+np.arctan(q_lab[2]/q_lab[0])*180/np.pi:.2f} deg\n" ) planar_dist = planar_dist / 10 # switch to nm ####################### # orthogonalize data # ####################### print("\nShape before orthogonalization", avg_obj.shape, "\n") if data_frame == "detector": if debug: phase, _ = pu.unwrap( avg_obj, support_threshold=threshold_unwrap_refraction, debugging=True, reciprocal_space=False, is_orthogonal=False, ) gu.multislices_plot( phase, width_z=2 * zrange, width_y=2 * yrange, width_x=2 * xrange, sum_frames=False, plot_colorbar=True, reciprocal_space=False, is_orthogonal=False, title="unwrapped phase before orthogonalization", ) del phase gc.collect() if not prm.get("outofplane_angle") and not prm.get("inplane_angle"): print("Trying to correct detector angles using the direct beam") # corrected detector angles not provided if bragg_peak is None and detector.template_imagefile is not None: # Bragg peak position not provided, find it from the data data, _, _, _ = setup.diffractometer.load_check_dataset( scan_number=scan, detector=detector, setup=setup, frames_pattern=prm.get("frames_pattern"), bin_during_loading=False, flatfield=prm.get("flatfield"), hotpixels=prm.get("hotpix_array"), background=prm.get("background"), normalize=prm.get("normalize_flux", "skip"), ) bragg_peak = bu.find_bragg( data=data, peak_method="maxcom", roi=detector.roi, binning=None, ) roi_center = ( bragg_peak[0], bragg_peak[1] - detector.roi[0], # no binning as in bu.find_bragg bragg_peak[2] - detector.roi[2], # no binning as in bu.find_bragg ) bu.show_rocking_curve( data, roi_center=roi_center, tilt_values=setup.incident_angles, savedir=detector.savedir, ) setup.correct_detector_angles(bragg_peak_position=bragg_peak) prm["outofplane_angle"] = setup.outofplane_angle prm["inplane_angle"] = setup.inplane_angle obj_ortho, voxel_size, transfer_matrix = setup.ortho_directspace( arrays=avg_obj, q_com=np.array([q_lab[2], q_lab[1], q_lab[0]]), initial_shape=original_size, voxel_size=fix_voxel, reference_axis=axis_to_array_xyz[ref_axis_q], fill_value=0, debugging=True, title="amplitude", ) prm["transformation_matrix"] = transfer_matrix else: # data already orthogonalized using xrayutilities # or the linearized transformation matrix obj_ortho = avg_obj try: print("Select the file containing QxQzQy") file_path = filedialog.askopenfilename( title="Select the file containing QxQzQy", initialdir=detector.savedir, filetypes=[("NPZ", "*.npz")], ) npzfile = np.load(file_path) qx = npzfile["qx"] qy = npzfile["qy"] qz = npzfile["qz"] except FileNotFoundError: raise FileNotFoundError( "q values not provided, the voxel size cannot be calculated" ) dy_real = ( 2 * np.pi / abs(qz.max() - qz.min()) / 10 ) # in nm qz=y in nexus convention dx_real = ( 2 * np.pi / abs(qy.max() - qy.min()) / 10 ) # in nm qy=x in nexus convention dz_real = ( 2 * np.pi / abs(qx.max() - qx.min()) / 10 ) # in nm qx=z in nexus convention print( f"direct space voxel size from q values: ({dz_real:.2f} nm," f" {dy_real:.2f} nm, {dx_real:.2f} nm)" ) if fix_voxel: voxel_size = fix_voxel print(f"Direct space pixel size for the interpolation: {voxel_size} (nm)") print("Interpolating...\n") obj_ortho = pu.regrid( array=obj_ortho, old_voxelsize=(dz_real, dy_real, dx_real), new_voxelsize=voxel_size, ) else: # no need to interpolate voxel_size = dz_real, dy_real, dx_real # in nm if ( data_frame == "laboratory" ): # the object must be rotated into the crystal frame # before the strain calculation print("Rotating the object in the crystal frame for the strain calculation") amp, phase = util.rotate_crystal( arrays=(abs(obj_ortho), np.angle(obj_ortho)), is_orthogonal=True, reciprocal_space=False, voxel_size=voxel_size, debugging=(True, False), axis_to_align=q_lab[::-1], reference_axis=axis_to_array_xyz[ref_axis_q], title=("amp", "phase"), ) obj_ortho = amp * np.exp( 1j * phase ) # here the phase is again wrapped in [-pi pi[ del amp, phase del avg_obj gc.collect() ###################################################### # center the object (centering based on the modulus) # ###################################################### print("\nCentering the crystal") obj_ortho = pu.center_com(obj_ortho) #################### # Phase unwrapping # #################### print("\nPhase unwrapping") phase, extent_phase = pu.unwrap( obj_ortho, support_threshold=threshold_unwrap_refraction, debugging=True, reciprocal_space=False, is_orthogonal=True, ) amp = abs(obj_ortho) del obj_ortho gc.collect() ############################################# # invert phase: -1*phase = displacement * q # ############################################# if invert_phase: phase = -1 * phase ######################################## # refraction and absorption correction # ######################################## if correct_refraction: # or correct_absorption: bulk = pu.find_bulk( amp=amp, support_threshold=threshold_unwrap_refraction, method=prm.get("optical_path_method", "threshold"), debugging=debug, ) kin = setup.incident_wavevector kout = setup.exit_wavevector # kin and kout were calculated in the laboratory frame, # but after the geometric transformation of the crystal, this # latter is always in the crystal frame (for simpler strain calculation). # We need to transform kin and kout back # into the crystal frame (also, xrayutilities output is in crystal frame) kin = util.rotate_vector( vectors=[kin[2], kin[1], kin[0]], axis_to_align=axis_to_array_xyz[ref_axis_q], reference_axis=[q_lab[2], q_lab[1], q_lab[0]], ) kout = util.rotate_vector( vectors=[kout[2], kout[1], kout[0]], axis_to_align=axis_to_array_xyz[ref_axis_q], reference_axis=[q_lab[2], q_lab[1], q_lab[0]], ) # calculate the optical path of the incoming wavevector path_in = pu.get_opticalpath( support=bulk, direction="in", k=kin, debugging=debug ) # path_in already in nm # calculate the optical path of the outgoing wavevector path_out = pu.get_opticalpath( support=bulk, direction="out", k=kout, debugging=debug ) # path_our already in nm optical_path = path_in + path_out del path_in, path_out gc.collect() if correct_refraction: phase_correction = ( 2 * np.pi / (1e9 * setup.wavelength) * prm["dispersion"] * optical_path ) phase = phase + phase_correction gu.multislices_plot( np.multiply(phase_correction, bulk), width_z=2 * zrange, width_y=2 * yrange, width_x=2 * xrange, sum_frames=False, plot_colorbar=True, vmin=0, vmax=np.nan, title="Refraction correction on the support", is_orthogonal=True, reciprocal_space=False, ) correct_absorption = False if correct_absorption: amp_correction = np.exp( 2 * np.pi / (1e9 * setup.wavelength) * prm["absorption"] * optical_path ) amp = amp * amp_correction gu.multislices_plot( np.multiply(amp_correction, bulk), width_z=2 * zrange, width_y=2 * yrange, width_x=2 * xrange, sum_frames=False, plot_colorbar=True, vmin=1, vmax=1.1, title="Absorption correction on the support", is_orthogonal=True, reciprocal_space=False, ) del bulk, optical_path gc.collect() ############################################## # phase ramp and offset removal (mean value) # ############################################## print("\nPhase ramp removal") amp, phase, _, _, _ = pu.remove_ramp( amp=amp, phase=phase, initial_shape=original_size, method=prm.get("phase_ramp_removal", "gradient"), amplitude_threshold=isosurface_strain, threshold_gradient=threshold_gradient, debugging=debug, ) ######################## # phase offset removal # ######################## print("\nPhase offset removal") support = np.zeros(amp.shape) support[amp > isosurface_strain * amp.max()] = 1 phase = pu.remove_offset( array=phase, support=support, offset_method=offset_method, phase_offset=phase_offset, offset_origin=offset_origin, title="Orthogonal phase", debugging=debug, reciprocal_space=False, is_orthogonal=True, ) del support gc.collect() # Wrap the phase around 0 (no more offset) phase = util.wrap( obj=phase, start_angle=-extent_phase / 2, range_angle=extent_phase ) ################################################################ # calculate the strain depending on which axis q is aligned on # ################################################################ print(f"\nCalculation of the strain along {ref_axis_q}") strain = pu.get_strain( phase=phase, planar_distance=planar_dist, voxel_size=voxel_size, reference_axis=ref_axis_q, extent_phase=extent_phase, method=prm.get("strain_method", "default"), debugging=debug, ) ################################################ # optionally rotates back the crystal into the # # laboratory frame (for debugging purpose) # ################################################ q_final = None if save_frame in {"laboratory", "lab_flat_sample"}: comment = comment + "_labframe" print("\nRotating back the crystal in laboratory frame") amp, phase, strain = util.rotate_crystal( arrays=(amp, phase, strain), axis_to_align=axis_to_array_xyz[ref_axis_q], voxel_size=voxel_size, is_orthogonal=True, reciprocal_space=False, reference_axis=[q_lab[2], q_lab[1], q_lab[0]], debugging=(True, False, False), title=("amp", "phase", "strain"), ) # q_lab is already in the laboratory frame q_final = q_lab if save_frame == "lab_flat_sample": comment = comment + "_flat" print("\nSending sample stage circles to 0") (amp, phase, strain), q_final = setup.diffractometer.flatten_sample( arrays=(amp, phase, strain), voxel_size=voxel_size, q_com=q_lab[::-1], # q_com needs to be in xyz order is_orthogonal=True, reciprocal_space=False, rocking_angle=setup.rocking_angle, debugging=(True, False, False), title=("amp", "phase", "strain"), ) if save_frame == "crystal": # rotate also q_lab to have it along ref_axis_q, # as a cross-checkm, vectors needs to be in xyz order comment = comment + "_crystalframe" q_final = util.rotate_vector( vectors=q_lab[::-1], axis_to_align=axis_to_array_xyz[ref_axis_q], reference_axis=q_lab[::-1], ) ############################################### # rotates the crystal e.g. for easier slicing # # of the result along a particular direction # ############################################### # typically this is an inplane rotation, q should stay aligned with the axis # along which the strain was calculated if prm.get("align_axis", False): print("\nRotating arrays for visualization") amp, phase, strain = util.rotate_crystal( arrays=(amp, phase, strain), reference_axis=axis_to_array_xyz[prm["ref_axis"]], axis_to_align=prm["axis_to_align"], voxel_size=voxel_size, debugging=(True, False, False), is_orthogonal=True, reciprocal_space=False, title=("amp", "phase", "strain"), ) # rotate q accordingly, vectors needs to be in xyz order q_final = util.rotate_vector( vectors=q_final[::-1], axis_to_align=axis_to_array_xyz[prm["ref_axis"]], reference_axis=prm["axis_to_align"], ) q_final = q_final * qnorm print( f"\nq_final = ({q_final[0]:.4f} 1/A," f" {q_final[1]:.4f} 1/A, {q_final[2]:.4f} 1/A)" ) ############################################## # pad array to fit the output_size parameter # ############################################## if output_size is not None: amp = util.crop_pad(array=amp, output_shape=output_size) phase = util.crop_pad(array=phase, output_shape=output_size) strain = util.crop_pad(array=strain, output_shape=output_size) print(f"\nFinal data shape: {amp.shape}") ###################### # save result to vtk # ###################### print( f"\nVoxel size: ({voxel_size[0]:.2f} nm, {voxel_size[1]:.2f} nm," f" {voxel_size[2]:.2f} nm)" ) bulk = pu.find_bulk( amp=amp, support_threshold=isosurface_strain, method="threshold" ) if save: prm["comment"] = comment np.savez_compressed( f"{detector.savedir}S{scan}_amp{phase_fieldname}strain{comment}", amp=amp, phase=phase, bulk=bulk, strain=strain, q_com=q_final, voxel_sizes=voxel_size, detector=detector.params, setup=setup.params, params=prm, ) # save results in hdf5 file with h5py.File( f"{detector.savedir}S{scan}_amp{phase_fieldname}strain{comment}.h5", "w" ) as hf: out = hf.create_group("output") par = hf.create_group("params") out.create_dataset("amp", data=amp) out.create_dataset("bulk", data=bulk) out.create_dataset("phase", data=phase) out.create_dataset("strain", data=strain) out.create_dataset("q_com", data=q_final) out.create_dataset("voxel_sizes", data=voxel_size) par.create_dataset("detector", data=str(detector.params)) par.create_dataset("setup", data=str(setup.params)) par.create_dataset("parameters", data=str(prm)) # save amp & phase to VTK # in VTK, x is downstream, y vertical, z inboard, # thus need to flip the last axis gu.save_to_vti( filename=os.path.join( detector.savedir, "S" + str(scan) + "_amp-" + phase_fieldname + "-strain" + comment + ".vti", ), voxel_size=voxel_size, tuple_array=(amp, bulk, phase, strain), tuple_fieldnames=("amp", "bulk", phase_fieldname, "strain"), amplitude_threshold=0.01, ) ###################################### # estimate the volume of the crystal # ###################################### amp = amp / amp.max() temp_amp = np.copy(amp) temp_amp[amp < isosurface_strain] = 0 temp_amp[np.nonzero(temp_amp)] = 1 volume = temp_amp.sum() * reduce(lambda x, y: x * y, voxel_size) # in nm3 del temp_amp gc.collect() ############################## # plot slices of the results # ############################## pixel_spacing = [tick_spacing / vox for vox in voxel_size] print( "\nPhase extent without / with thresholding the modulus " f"(threshold={isosurface_strain}): {phase.max()-phase.min():.2f} rad, " f"{phase[np.nonzero(bulk)].max()-phase[np.nonzero(bulk)].min():.2f} rad" ) piz, piy, pix = np.unravel_index(phase.argmax(), phase.shape) print( f"phase.max() = {phase[np.nonzero(bulk)].max():.2f} " f"at voxel ({piz}, {piy}, {pix})" ) strain[bulk == 0] = np.nan phase[bulk == 0] = np.nan # plot the slice at the maximum phase gu.combined_plots( (phase[piz, :, :], phase[:, piy, :], phase[:, :, pix]), tuple_sum_frames=False, tuple_sum_axis=0, tuple_width_v=None, tuple_width_h=None, tuple_colorbar=True, tuple_vmin=np.nan, tuple_vmax=np.nan, tuple_title=("phase at max in xy", "phase at max in xz", "phase at max in yz"), tuple_scale="linear", cmap=my_cmap, is_orthogonal=True, reciprocal_space=False, ) # bulk support fig, _, _ = gu.multislices_plot( bulk, sum_frames=False, title="Orthogonal bulk", vmin=0, vmax=1, is_orthogonal=True, reciprocal_space=False, ) fig.text(0.60, 0.45, "Scan " + str(scan), size=20) fig.text( 0.60, 0.40, "Bulk - isosurface=" + str("{:.2f}".format(isosurface_strain)), size=20, ) plt.pause(0.1) if save: plt.savefig(detector.savedir + "S" + str(scan) + "_bulk" + comment + ".png") # amplitude fig, _, _ = gu.multislices_plot( amp, sum_frames=False, title="Normalized orthogonal amp", vmin=0, vmax=1, tick_direction=tick_direction, tick_width=tick_width, tick_length=tick_length, pixel_spacing=pixel_spacing, plot_colorbar=True, is_orthogonal=True, reciprocal_space=False, ) fig.text(0.60, 0.45, f"Scan {scan}", size=20) fig.text( 0.60, 0.40, f"Voxel size=({voxel_size[0]:.1f}, {voxel_size[1]:.1f}, " f"{voxel_size[2]:.1f}) (nm)", size=20, ) fig.text(0.60, 0.35, f"Ticks spacing={tick_spacing} nm", size=20) fig.text(0.60, 0.30, f"Volume={int(volume)} nm3", size=20) fig.text(0.60, 0.25, "Sorted by " + sort_method, size=20) fig.text(0.60, 0.20, f"correlation threshold={correlation_threshold}", size=20) fig.text(0.60, 0.15, f"average over {avg_counter} reconstruction(s)", size=20) fig.text(0.60, 0.10, f"Planar distance={planar_dist:.5f} nm", size=20) if prm.get("get_temperature", False): temperature = pu.bragg_temperature( spacing=planar_dist * 10, reflection=prm["reflection"], spacing_ref=prm.get("reference_spacing"), temperature_ref=prm.get("reference_temperature"), use_q=False, material="Pt", ) fig.text(0.60, 0.05, f"Estimated T={temperature} C", size=20) if save: plt.savefig(detector.savedir + f"S{scan}_amp" + comment + ".png") # amplitude histogram fig, ax = plt.subplots(1, 1) ax.hist(amp[amp > 0.05 * amp.max()].flatten(), bins=250) ax.set_ylim(bottom=1) ax.tick_params( labelbottom=True, labelleft=True, direction="out", length=tick_length, width=tick_width, ) ax.spines["right"].set_linewidth(1.5) ax.spines["left"].set_linewidth(1.5) ax.spines["top"].set_linewidth(1.5) ax.spines["bottom"].set_linewidth(1.5) fig.savefig(detector.savedir + f"S{scan}_histo_amp" + comment + ".png") # phase fig, _, _ = gu.multislices_plot( phase, sum_frames=False, title="Orthogonal displacement", vmin=-prm.get("phase_range", np.pi / 2), vmax=prm.get("phase_range", np.pi / 2), tick_direction=tick_direction, cmap=my_cmap, tick_width=tick_width, tick_length=tick_length, pixel_spacing=pixel_spacing, plot_colorbar=True, is_orthogonal=True, reciprocal_space=False, ) fig.text(0.60, 0.30, f"Scan {scan}", size=20) fig.text( 0.60, 0.25, f"Voxel size=({voxel_size[0]:.1f}, {voxel_size[1]:.1f}, " f"{voxel_size[2]:.1f}) (nm)", size=20, ) fig.text(0.60, 0.20, f"Ticks spacing={tick_spacing} nm", size=20) fig.text(0.60, 0.15, f"average over {avg_counter} reconstruction(s)", size=20) if half_width_avg_phase > 0: fig.text( 0.60, 0.10, f"Averaging over {2*half_width_avg_phase+1} pixels", size=20 ) else: fig.text(0.60, 0.10, "No phase averaging", size=20) if save: plt.savefig(detector.savedir + f"S{scan}_displacement" + comment + ".png") # strain fig, _, _ = gu.multislices_plot( strain, sum_frames=False, title="Orthogonal strain", vmin=-prm.get("strain_range", 0.002), vmax=prm.get("strain_range", 0.002), tick_direction=tick_direction, tick_width=tick_width, tick_length=tick_length, plot_colorbar=True, cmap=my_cmap, pixel_spacing=pixel_spacing, is_orthogonal=True, reciprocal_space=False, ) fig.text(0.60, 0.30, f"Scan {scan}", size=20) fig.text( 0.60, 0.25, f"Voxel size=({voxel_size[0]:.1f}, " f"{voxel_size[1]:.1f}, {voxel_size[2]:.1f}) (nm)", size=20, ) fig.text(0.60, 0.20, f"Ticks spacing={tick_spacing} nm", size=20) fig.text(0.60, 0.15, f"average over {avg_counter} reconstruction(s)", size=20) if half_width_avg_phase > 0: fig.text( 0.60, 0.10, f"Averaging over {2*half_width_avg_phase+1} pixels", size=20 ) else: fig.text(0.60, 0.10, "No phase averaging", size=20) if save: plt.savefig(detector.savedir + f"S{scan}_strain" + comment + ".png")
max_z, max_y, max_x = np.unravel_index(data.argmax(), shape=data.shape) com_z, com_y, com_x = center_of_mass( data[:, int(max_y) - debug_pix:int(max_y) + debug_pix, int(max_x) - debug_pix:int(max_x) + debug_pix, ]) # correct the pixel offset due to the ROI defined by debug_pix around the max piz = com_z # the data was not cropped along the first axis piy = com_y + max_y - debug_pix pix = com_x + max_x - debug_pix if debug: fig, _, _ = gu.multislices_plot( data, sum_frames=True, plot_colorbar=True, cmap=my_cmap, title="scan" + str(scan_nb), scale="log", is_orthogonal=False, reciprocal_space=True, ) fig.text(0.60, 0.30, f"(piz, piy, pix) = ({piz:.1f}, {piy:.1f}, {pix:.1f})", size=12) plt.draw() if peak_method == "max_com": fig, _, _ = gu.multislices_plot( data[:, int(max_y) - debug_pix:int(max_y) + debug_pix, int(max_x) - debug_pix:int(max_x) + debug_pix, ],
final_binning = np.multiply(pre_binning, phasing_binning) detector.binning = final_binning print('Pixel sizes after phasing_binning (vertical, horizontal): ', detector.pixelsize_y, detector.pixelsize_x, '(m)') diff_pattern = pu.bin_data(array=diff_pattern, binning=phasing_binning, debugging=False) mask = pu.bin_data(array=mask, binning=phasing_binning, debugging=False) numz, numy, numx = diff_pattern.shape # this shape will be used for the calculation of q values print('\nMeasured data shape =', numz, numy, numx, ' Max(measured amplitude)=', np.sqrt(diff_pattern).max()) diff_pattern[np.nonzero(mask)] = 0 z0, y0, x0 = center_of_mass(diff_pattern) z0, y0, x0 = [int(z0), int(y0), int(x0)] print("COM of measured pattern after masking: ", z0, y0, x0, ' Number of unmasked photons =', diff_pattern.sum()) fig, _, _ = gu.multislices_plot(np.sqrt(diff_pattern), sum_frames=False, title='3D diffraction amplitude', vmin=0, vmax=3.5, is_orthogonal=False, reciprocal_space=True, slice_position=[z0, y0, x0], scale='log', plot_colorbar=True) plt.figure() plt.imshow(np.log10(np.sqrt(diff_pattern).sum(axis=0)), cmap=my_cmap, vmin=0, vmax=3.5) plt.title('abs(diffraction amplitude).sum(axis=0)') plt.colorbar() plt.pause(0.1) ################################################ # calculate the q matrix respective to the COM # ################################################ hxrd.Ang2Q.init_area('z-', 'y+', cch1=int(y0), cch2=int(x0), Nch1=numy, Nch2=numx, pwidth1=detector.pixelsize_y, pwidth2=detector.pixelsize_x, distance=setup.distance) # first two arguments in init_area are the direction of the detector if simulation:
int(y0) - 20:int(y0) + 21, int(x0) - 20:int(x0) + 21, ]) z0, y0, x0 = [ int(np.rint(z0 - 20 + fine_com[0])), int(np.rint(y0 - 20 + fine_com[1])), int(np.rint(x0 - 20 + fine_com[2])), ] print(f"refined COM: {z0}, {y0}, {x0}, " f"Number of unmasked photons = {diff_pattern.sum():.0f}\n") fig, _, _ = gu.multislices_plot( np.sqrt(diff_pattern), sum_frames=False, title="3D diffraction amplitude", vmin=0, vmax=3.5, is_orthogonal=False, reciprocal_space=True, slice_position=[z0, y0, x0], scale="log", plot_colorbar=True, ) plt.figure() plt.imshow(np.log10(np.sqrt(diff_pattern).sum(axis=0)), cmap=my_cmap, vmin=0, vmax=3.5) plt.title("abs(diffraction amplitude).sum(axis=0)") plt.colorbar() plt.pause(0.1)
if use_bulk: try: bulk = npzfile["bulk"] except KeyError: print("Bulk is not of key of the npz file") print("Using the modulus and support_threshold to define the bulk") amp = npzfile["amp"] bulk = pu.find_bulk(amp=amp, support_threshold=support_threshold, method="threshold") if debug: gu.multislices_plot( amp, sum_frames=False, title="Amplitude", plot_colorbar=True, cmap=my_cmap, is_orthogonal=True, reciprocal_space=False, ) del amp nz, ny, nx = bulk.shape support = bulk else: # use amplitude print("Using the modulus and support_threshold to define the support") amp = npzfile["amp"] nz, ny, nx = amp.shape support = np.ones((nz, ny, nx)) support[abs(amp) < support_threshold * abs(amp).max()] = 0 if debug: gu.multislices_plot(
###################### # define the support # ###################### obj = abs(obj) min_obj = obj[np.nonzero(obj)].min() obj = obj / min_obj # normalize to the non-zero min to avoid dividing by small numbers obj[obj == 0] = min_offset # avoid dividing by 0 support = np.zeros(obj.shape) support[obj >= isosurface_threshold * obj.max()] = 1 support[support == 0] = min_offset if debug: gu.multislices_plot(obj, sum_frames=False, reciprocal_space=False, is_orthogonal=True, plot_colorbar=True, title='normalized modulus') gu.multislices_plot(support, sum_frames=False, reciprocal_space=False, is_orthogonal=True, vmin=0, vmax=1, plot_colorbar=True, title=f'support at threshold {isosurface_threshold}') ################################### # calculate the blurring function # ################################### psf_guess = pu.gaussian_window(window_shape=obj.shape,
root.withdraw() file_path = filedialog.askopenfilename(initialdir=root_folder, title="Select the diffraction pattern", filetypes=[("NPZ", "*.npz")]) npzfile = np.load(file_path) diff_pattern = pu.bin_data(npzfile[list(npzfile.files)[0]].astype(int), (bin_factor, bin_factor, bin_factor), debugging=False) diff_pattern[diff_pattern < threshold] = 0 nz, ny, nx = diff_pattern.shape print('Data shape after binning:', nz, ny, nx) gu.multislices_plot(diff_pattern, sum_frames=True, plot_colorbar=True, cmap=my_cmap, title='diffraction pattern', scale='log', vmin=np.nan, vmax=np.nan, reciprocal_space=True, is_orthogonal=True) ############# # load mask # ############# if load_mask: file_path = filedialog.askopenfilename(initialdir=root_folder, title="Select the mask", filetypes=[("NPZ", "*.npz")]) npzfile = np.load(file_path) mask = pu.bin_data(npzfile[list(npzfile.files)[0]], (bin_factor, bin_factor, bin_factor), debugging=False)
new_shape = [ max(numz, 2 * pixel_FOV), max(numy, 2 * pixel_FOV), max(numx, 2 * pixel_FOV) ] amp = pu.crop_pad(array=amp, output_shape=new_shape, debugging=False) phase = pu.crop_pad(array=phase, output_shape=new_shape, debugging=False) numz, numy, numx = amp.shape if debug: gu.multislices_plot(amp, sum_frames=False, title='Input modulus', vmin=0, tick_direction=tick_direction, tick_width=tick_width, tick_length=tick_length, pixel_spacing=pixel_spacing, plot_colorbar=True, is_orthogonal=True, reciprocal_space=False) gu.multislices_plot(phase, sum_frames=False, title='Input phase', vmin=-phase_range, vmax=phase_range, tick_direction=tick_direction, cmap=my_cmap, tick_width=tick_width, tick_length=tick_length, pixel_spacing=pixel_spacing,
######################### # load a reconstruction # ######################### plt.ion() root = tk.Tk() root.withdraw() file_path = filedialog.askopenfilename(initialdir=datadir, filetypes=[("NPZ", "*.npz")]) npzfile = np.load(file_path) amp = npzfile["amp"] gu.multislices_plot( amp, sum_frames=False, plot_colorbar=False, vmin=0, vmax=1, cmap=my_cmap, title="Input amplitude", ) ################################# # pad data to the original size # ################################# print("Initial data size:", amp.shape) if len(original_size) == 0: original_size = amp.shape print("FFT size before accounting for binning", original_size) original_size = tuple( [original_size[index] // binning[index] for index in range(len(binning))]) print("Binning used during phasing:", binning)
avg_obj = np.zeros((numz, numy, numx)) ref_obj = np.zeros((numz, numy, numx)) avg_counter = 1 print('\nAveraging using', nbfiles, 'candidate reconstructions') for counter, value in enumerate(sorted_obj): obj, extension = util.load_file(file_path[value]) print('\nOpening ', file_path[value]) if flip_reconstruction: obj = pu.flip_reconstruction(obj, debugging=debug) if extension == '.h5': centering_method = 'do_nothing' # do not center, data is already cropped just on support for mode decomposition # correct a roll after the decomposition into modes in PyNX obj = np.roll(obj, roll_modes, axis=(0, 1, 2)) fig, _, _ = gu.multislices_plot(abs(obj), sum_frames=True, plot_colorbar=True, title='1st mode after centering') # fig.waitforbuttonpress() plt.close(fig) # use the range of interest defined above obj = pu.crop_pad(obj, [2 * zrange, 2 * yrange, 2 * xrange], debugging=debug) # align with average reconstruction if counter == 0: # the fist array loaded will serve as reference object print('This reconstruction will serve as reference object.') ref_obj = obj avg_obj, flag_avg = pu.average_obj(avg_obj=avg_obj, ref_obj=ref_obj, obj=obj, support_threshold=0.25, correlation_threshold=avg_threshold, aligning_option='dft', method=avg_method, debugging=debug) avg_counter = avg_counter + flag_avg
def orthogonalize(self, obj, initial_shape=(), voxel_size=np.nan, width_z=None, width_y=None, width_x=None, debugging=False, **kwargs): """ Interpolate obj on the orthogonal reference frame defined by the setup. :param obj: real space object, in a non-orthogonal frame (output of phasing program) :param initial_shape: shape of the FFT used for phasing :param voxel_size: user-defined voxel size, in nm :param width_z: size of the area to plot in z (axis 0), centered on the middle of the initial array :param width_y: size of the area to plot in y (axis 1), centered on the middle of the initial array :param width_x: size of the area to plot in x (axis 2), centered on the middle of the initial array :param debugging: True to show plots before and after interpolation :param kwargs: - 'title': title for the debugging plots :return: object interpolated on an orthogonal grid """ for k in kwargs.keys(): if k in ['title']: title = kwargs['title'] else: raise Exception( "unknown keyword argument given: allowed is 'title'") try: title except NameError: # title not declared title = 'Object' if len(initial_shape) == 0: initial_shape = obj.shape if debugging: gu.multislices_plot(abs(obj), sum_frames=True, width_z=width_z, width_y=width_y, width_x=width_x, title=title + ' in detector frame') tilt_sign = np.sign(self.tilt_angle) wavelength = 12.398 * 1e-7 / self.energy # in m dz_realspace = wavelength / (initial_shape[0] * abs(self.tilt_angle) * np.pi / 180) * 1e9 # in nm dy_realspace = wavelength * self.distance / ( initial_shape[1] * self.pixel_y) * 1e9 # in nm dx_realspace = wavelength * self.distance / ( initial_shape[2] * self.pixel_x) * 1e9 # in nm print('Real space pixel size (z, y, x) based on initial FFT shape: (', str('{:.2f}'.format(dz_realspace)), 'nm,', str('{:.2f}'.format(dy_realspace)), 'nm,', str('{:.2f}'.format(dx_realspace)), 'nm )') nbz, nby, nbx = obj.shape # could be smaller if the object was cropped around the support if nbz != initial_shape[0] or nby != initial_shape[ 1] or nbx != initial_shape[2]: tilt = tilt_sign * wavelength / (nbz * dz_realspace * np.pi / 180) * 1e9 # in m pixel_y = wavelength * self.distance / (nby * dy_realspace) * 1e9 # in m pixel_x = wavelength * self.distance / (nbx * dx_realspace) * 1e9 # in m print('Tilt, pixel_y, pixel_x based on actual array shape: (', str('{:.4f}'.format(tilt)), 'deg,', str('{:.2f}'.format(pixel_y * 1e6)), 'um,', str('{:.2f}'.format(pixel_x * 1e6)), 'um)') dz_realspace = wavelength / (nbz * abs(tilt) * np.pi / 180) * 1e9 # in nm dy_realspace = wavelength * self.distance / ( nby * pixel_y) * 1e9 # in nm dx_realspace = wavelength * self.distance / ( nbx * pixel_x) * 1e9 # in nm print( 'New real space pixel size (z, y, x) based on actual array shape: (', str('{:.2f}'.format(dz_realspace)), ' nm,', str('{:.2f}'.format(dy_realspace)), 'nm,', str('{:.2f}'.format(dx_realspace)), 'nm )') else: tilt = self.tilt_angle pixel_y = self.pixel_y pixel_x = self.pixel_x if np.isnan(voxel_size): voxel = np.mean([dz_realspace, dy_realspace, dx_realspace]) # in nm else: voxel = voxel_size ortho_matrix = self.update_coords(array_shape=(nbz, nby, nbx), tilt_angle=tilt, pixel_x=pixel_x, pixel_y=pixel_y) ############################################################### # Vincent Favre-Nicolin's method using inverse transformation # ############################################################### myz, myy, myx = np.meshgrid(np.arange(-nbz // 2, nbz // 2, 1) * voxel, np.arange(-nby // 2, nby // 2, 1) * voxel, np.arange(-nbx // 2, nbx // 2, 1) * voxel, indexing='ij') ortho_imatrix = np.linalg.inv(ortho_matrix) new_x = ortho_imatrix[0, 0] * myx + ortho_imatrix[ 0, 1] * myy + ortho_imatrix[0, 2] * myz new_y = ortho_imatrix[1, 0] * myx + ortho_imatrix[ 1, 1] * myy + ortho_imatrix[1, 2] * myz new_z = ortho_imatrix[2, 0] * myx + ortho_imatrix[ 2, 1] * myy + ortho_imatrix[2, 2] * myz del myx, myy, myz gc.collect() rgi = RegularGridInterpolator( (np.arange(-nbz // 2, nbz // 2), np.arange( -nby // 2, nby // 2), np.arange(-nbx // 2, nbx // 2)), obj, method='linear', bounds_error=False, fill_value=0) ortho_obj = rgi( np.concatenate((new_z.reshape( (1, new_z.size)), new_y.reshape( (1, new_z.size)), new_x.reshape( (1, new_z.size)))).transpose()) ortho_obj = ortho_obj.reshape((nbz, nby, nbx)).astype(obj.dtype) if debugging: gu.multislices_plot(abs(ortho_obj), sum_frames=True, width_z=width_z, width_y=width_y, width_x=width_x, title=title + ' in the orthogonal laboratory frame') return ortho_obj, voxel
if reload_mask: print('Reload previous mask') file_path = filedialog.askopenfilename(initialdir=homedir, title="Select mask file", filetypes=[("NPZ", "*.npz")]) mask = np.load(file_path) npz_key = mask.files mask = mask[npz_key[0]] ############################## # save the raw data and mask # ############################## fig, _, _ = gu.multislices_plot(data, sum_frames=True, scale='log', plot_colorbar=True, vmin=0, title='Data before aliens removal\n', invert_yaxis=False, reciprocal_space=True) plt.savefig(savedir + 'rawdata_S' + str(scans[scan_nb]) + '.png') if flag_interact: fig.waitforbuttonpress() plt.close(fig) fig, _, _ = gu.multislices_plot(mask, sum_frames=True, scale='linear', plot_colorbar=True, vmin=0, vmax=(nz, ny, nx),
obj, _ = util.load_file(file_path) if obj.ndim != 3: print('a 3D reconstruction array is expected') sys.exit() nbz, nby, nbx = obj.shape print("Initial data size:", nbz, nby, nbx) amp = abs(obj) amp = amp / amp.max() gu.multislices_plot(amp, sum_frames=False, title='Normalized modulus', vmin=0, vmax=1, plot_colorbar=True, is_orthogonal=True, reciprocal_space=False) mean_amp = amp[amp > cutoff_amp].mean() std_amp = amp[amp > cutoff_amp].std() print("Mean amp=", mean_amp) print("Std amp=", std_amp) 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)
and origin[2] + crop_shape[2] // 2 <= nbx, 'origin incompatible with crop_shape' data = pu.crop_pad(array=data, output_shape=crop_shape, crop_center=origin) gc.collect() nbz, nby, nbx = data.shape print('data shape after cropping:', data.shape) # calculate the new position of origin new_origin = (crop_shape[0] // 2, crop_shape[1] // 2, crop_shape[2] // 2) else: new_origin = origin if plots: gu.multislices_plot(data, sum_frames=True, scale='log', plot_colorbar=True, title='S' + str(scan) + '\n Data before rotation', vmin=0, reciprocal_space=True, is_orthogonal=True) ################### # rotate the data # ################### # define the rotation matrix in the order (x, y, z) rotation_matrix = np.array( [[ np.cos(tilt) + (1 - np.cos(tilt)) * rotation_axis[0]**2, rotation_axis[0] * rotation_axis[1] * (1 - np.cos(tilt)) - rotation_axis[2] * np.sin(tilt), rotation_axis[0] * rotation_axis[2] * (1 - np.cos(tilt)) + rotation_axis[1] * np.sin(tilt) ],
print('Data size after cropping / padding:', nz, ny, nx) if mask_zero_event: # mask points when there is no intensity along the whole rocking curve - probably dead pixels for idx in range(nz): temp_mask = mask[idx, :, :] temp_mask[np.sum(data, axis=0) == 0] = 1 # enough, numpy array is mutable hence mask will be modified del temp_mask plt.ioff() ############################## # save the raw data and mask # ############################## fig, _, _ = gu.multislices_plot(data, sum_frames=True, scale='log', plot_colorbar=True, vmin=0, title='Data before aliens removal\n', is_orthogonal=not use_rawdata, reciprocal_space=True) plt.savefig(savedir + 'data_before_masking_sum_S' + str(scans[scan_nb]) + '_' + str(nz) + '_' + str(ny) + '_' + str(nx) + '_' + str(binning[0]) + '_' + str(binning[1]) + '_' + str(binning[2]) + '.png') if flag_interact: cid = plt.connect('close_event', close_event) fig.waitforbuttonpress() plt.disconnect(cid) plt.close(fig) piz, piy, pix = np.unravel_index(data.argmax(), data.shape) fig = gu.combined_plots((data[piz, :, :], data[:, piy, :], data[:, :, pix]), tuple_sum_frames=False, tuple_sum_axis=0, tuple_width_v=None, tuple_width_h=None, tuple_colorbar=True, tuple_vmin=0, tuple_vmax=np.nan, tuple_scale='log', tuple_title=('data at max in xy', 'data at max in xz', 'data at max in yz'), is_orthogonal=not use_rawdata, reciprocal_space=False)
else: # the data will be gridded, binning[0] is already set to 1 # sample rotation around the vertical direction at P10: # the effective binning in axis 0 is preprocessing_binning[2]*binning[2] binning_comment = ( f"_{detector.preprocessing_binning[2]*detector.binning[2]}" f"_{detector.preprocessing_binning[1]*detector.binning[1]}" f"_{detector.preprocessing_binning[2]*detector.binning[2]}") tmp_data = np.copy( data) # do not modify the raw data before the interpolation tmp_data[mask == 1] = 0 fig, _, _ = gu.multislices_plot( tmp_data, sum_frames=True, scale="log", plot_colorbar=True, vmin=0, title="Data before gridding\n", is_orthogonal=False, reciprocal_space=True, ) plt.savefig(detector.savedir + f"data_before_gridding_S{scan_nb}_{nz}_{ny}_{nx}" + binning_comment + ".png") plt.close(fig) del tmp_data gc.collect() print("\nGridding the data in the orthonormal laboratory frame") data, mask, q_values, frames_logical = cdi.grid_cdi( data=data, mask=mask,