def diffraction_pattern_for_radial(self): """ Two diffraction patterns with easy to see radial profiles, wrapped in ElectronDiffraction <2|8,8> """ dp = ElectronDiffraction(np.zeros((2, 8, 8))) dp.data[0] = np.array([[0., 0., 2., 2., 2., 2., 0., 0.], [0., 2., 3., 3., 3., 3., 2., 0.], [2., 3., 3., 4., 4., 3., 3., 2.], [2., 3., 4., 5., 5., 4., 3., 2.], [2., 3., 4., 5., 5., 4., 3., 2.], [2., 3., 3., 4., 4., 3., 3., 2.], [0., 2., 3., 3., 3., 3., 2., 0.], [0., 0., 2., 2., 2., 2., 0., 0.]]) dp.data[1] = np.array([[0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0.], [1., 1., 1., 1., 1., 1., 1., 1.], [1., 1., 1., 1., 1., 1., 1., 1.], [0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0.]]) return dp
def test_reproject_as_polar(diffraction_pattern: ElectronDiffraction): shape_cartesian = diffraction_pattern.axes_manager.signal_shape diffraction_pattern.reproject_as_polar() assert isinstance(diffraction_pattern, Signal2D) shape_polar = diffraction_pattern.axes_manager.signal_shape assert shape_polar[0] == max(shape_cartesian) assert shape_polar[1] > np.sqrt(2) * shape_cartesian[0] / 2
def plot_calibrated_data(self, data_to_plot, *args, **kwargs): """ Plot calibrated data for visual inspection. Parameters ---------- data_to_plot : string Specify the calibrated data to be plotted. Valid options are: {'au_x_grating_dp', 'au_x_grating_im', 'moo3_dp', 'moo3_im'} """ # Construct object containing user defined data to plot and set the # calibration checking that it is defined. if data_to_plot == 'au_x_grating_dp': dpeg = self.calibration_data.au_x_grating_dp size = dpeg.data.shape[0] dpegs = stack_method([dpeg, dpeg, dpeg, dpeg]) dpegs = ElectronDiffraction(dpegs.data.reshape((2, 2, size, size))) dpegs.apply_affine_transformation(self.affine_matrix, preserve_range=True, inplace=True) data = dpegs.mean((0, 1)) data.set_diffraction_calibration(self.diffraction_calibration) elif data_to_plot == 'au_x_grating_im': data = self.calibration_data.au_x_grating_im #Plot the data data.plot(*args, **kwargs)
def pattern_for_fit_ring(self, input_parameters): dp = ElectronDiffraction(np.zeros((256, 256))) x0 = input_parameters dp.data = dp.generate_ring_pattern(mask=True, mask_radius=10, scale=x0[0], amplitude=x0[1], spread=x0[2], direct_beam_amplitude=x0[3], asymmetry=x0[4], rotation=x0[5]) return dp
def ragged_peak(self): """ A small selection of peaks in an ElectronDiffraction, to allow flexibilty of test building here. """ pattern = np.zeros((2, 2, 128, 128)) pattern[:, :, 40:42, 45] = 1 pattern[:, :, 110, 30:32] = 1 pattern[1, 0, 71:73, 21:23] = 1 dp = ElectronDiffraction(pattern) dp.set_diffraction_calibration(1) return dp
def test_plot_best_matching_results_on_signal_vector(structure, rot_list, edc): # Just test that the code runs library, match_results = get_vector_match_results(structure, rot_list, edc) # Hyperspy can only add markers to square signals match_results.data = np.vstack((match_results.data, match_results.data)) dp = ElectronDiffraction(2 * [2 * [np.zeros((144, 144))]]) match_results.plot_best_matching_results_on_signal(dp, library=library)
def test_bad_vectors_numpy(): """ tests that putting bad vectors in causes an error to be thrown when you initiate the geneartor """ v = np.array([[1, -100]]) dp = ElectronDiffraction(np.ones((20, 20))) sprg = SubpixelrefinementGenerator(dp, v)
def as_signal(self, size, sigma, max_r): """Returns the diffraction data as an ElectronDiffraction signal with two-dimensional Gaussians representing each diffracted peak. Should only be used for qualitative work. Parameters ---------- size : int Side length (in pixels) for the signal to be simulated. sigma : float Standard deviation of the Gaussian function to be plotted. max_r : float Half the side length in reciprocal Angstroms. Defines the signal's calibration Returns ------- dp : ElectronDiffraction Simulated electron diffraction pattern. """ from skimage.filters import gaussian as point_spread l, delta_l = np.linspace(-max_r, max_r, size, retstep=True) mask_for_max_r = np.logical_and( np.abs(self.coordinates[:, 0]) < max_r, np.abs(self.coordinates[:, 1]) < max_r) coords = self.coordinates[mask_for_max_r] inten = self.intensities[mask_for_max_r] dp_dat = np.zeros([size, size]) x, y = (coords)[:, 0], (coords)[:, 1] if len(x) > 0: # avoiding problems in the peakless case num = np.digitize(x, l, right=True), np.digitize(y, l, right=True) dp_dat[num] = inten # sigma in terms of pixels. transpose for Hyperspy dp_dat = point_spread(dp_dat, sigma=sigma / delta_l).T dp_dat = dp_dat / np.max(dp_dat) dp = ElectronDiffraction(dp_dat) dp.set_diffraction_calibration(2 * max_r / size) return dp
def test_apply_affine_transformation_with_casting(self, diffraction_pattern): diffraction_pattern.change_dtype('uint8') transformed_dp = ElectronDiffraction( diffraction_pattern).apply_affine_transformation(D=np.array( [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.2]]), order=2, keep_dtype=True, inplace=False) assert transformed_dp.data.dtype == 'uint8'
def get_distortion_residuals(self, mask_radius, spread): """Obtain residuals for experimental data and distortion corrected data with respect to a simulated symmetric ring pattern. Parameters ---------- mask_radius : int Radius, in pixels, for a mask over the direct beam disc. spread : float Gaussian spread of each ring in the simulated pattern. Returns ------- diff_init : ElectronDiffraction Difference between experimental data and simulated symmetric ring pattern. diff_end : ElectronDiffraction Difference between distortion corrected data and simulated symmetric ring pattern. """ # Check all required parameters are defined as attributes if self.calibration_data.au_x_grating_dp is None: raise ValueError( "This method requires an Au X-grating diffraction " "pattern to be provided. Please update the " "CalibrationDataLibrary.") if self.affine_matrix is None: raise ValueError( "This method requires a distortion matrix to have " "been determined. Use get_elliptical_distortion " "to determine this matrix.") # Set name for experimental data pattern dpeg = self.calibration_data.au_x_grating_dp ringP = self.ring_params size = dpeg.data.shape[0] dpref = generate_ring_pattern(image_size=size, mask=True, mask_radius=mask_radius, scale=ringP[0], amplitude=ringP[1], spread=spread, direct_beam_amplitude=ringP[3], asymmetry=1, rotation=ringP[5]) # Apply distortion corrections to experimental data dpegs = stack_method([dpeg, dpeg, dpeg, dpeg]) dpegs = ElectronDiffraction(dpegs.data.reshape((2, 2, size, size))) dpegs.apply_affine_transformation(self.affine_matrix, preserve_range=True, inplace=True) # Calculate residuals to be returned diff_init = ElectronDiffraction(dpeg.data - dpref.data) diff_end = ElectronDiffraction(dpegs.inav[0, 0].data - dpref.data) residuals = stack_method([diff_init, diff_end]) return ElectronDiffraction(residuals)
def diffraction_pattern(self): dp = ElectronDiffraction(np.zeros((2, 8, 8))) dp.data[0] = np.array([[0., 0., 2., 2., 2., 2., 0., 0.], [0., 2., 3., 3., 3., 3., 2., 0.], [2., 3., 3., 4., 4., 3., 3., 2.], [2., 3., 4., 5., 5., 4., 3., 2.], [2., 3., 4., 5., 5., 4., 3., 2.], [2., 3., 3., 4., 4., 3., 3., 2.], [0., 2., 3., 3., 3., 3., 2., 0.], [0., 0., 2., 2., 2., 2., 0., 0.]]) dp.data[1] = np.array([[0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 1., 1., 0., 0., 0.], [0., 0., 0., 1., 1., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0.], [0., 0., 0., 0., 0., 0., 0., 0.]]) return dp
def ring_pattern(input_parameters): x0 = input_parameters ring_data = generate_ring_pattern(image_size=256, mask=True, mask_radius=10, scale=x0[0], amplitude=x0[1], spread=x0[2], direct_beam_amplitude=x0[3], asymmetry=x0[4], rotation=x0[5]) return ElectronDiffraction(ring_data)
def plot_corrected_diffraction_pattern(self, reference_circle=True): """Plot the distortion corrected diffraction pattern with an optional reference circle. Parameters ---------- reference_circle : bool If True a CircleROI widget is added to the plot for reference. """ # Check all required parameters are defined as attributes if self.calibration_data.au_x_grating_dp is None: raise ValueError( "This method requires an Au X-grating diffraction " "pattern to be provided. Please update the " "CalibrationDataLibrary.") if self.affine_matrix is None: raise ValueError( "This method requires a distortion matrix to have " "been determined. Use get_elliptical_distortion " "to determine this matrix.") # Set name for experimental data pattern dpeg = self.calibration_data.au_x_grating_dp # Apply distortion corrections to experimental data size = dpeg.data.shape[0] dpegs = stack_method([dpeg, dpeg, dpeg, dpeg]) dpegs = ElectronDiffraction(dpegs.data.reshape((2, 2, size, size))) dpegs.apply_affine_transformation(self.affine_matrix, preserve_range=True, inplace=True) dpegm = dpegs.mean((0, 1)) # Plot distortion corrected data dpegm.plot(cmap='magma', vmax=0.1) # add reference circle if specified if reference_circle is True: circ = CircleROI(cx=128, cy=128, r=53.5, r_inner=0) circ.add_widget(dpegm)
def test_strain_mapping_affine_transform(): latt = diffpy.structure.lattice.Lattice(3, 3, 3, 90, 90, 90) atom = diffpy.structure.atom.Atom(atype='Zn', xyz=[0, 0, 0], lattice=latt) structure = diffpy.structure.Structure(atoms=[atom], lattice=latt) ediff = DiffractionGenerator(300., 0.025) affines = [[[1, 0, 0], [0, 1, 0], [0, 0, 1]], [[1.04, 0, 0], [0, 1, 0], [0, 0, 1]], [[1.08, 0, 0], [0, 1, 0], [0, 0, 1]], [[1.12, 0, 0], [0, 1, 0], [0, 0, 1]]] data = [] for affine in affines: # same coords as used for latt above latt_rot = diffpy.structure.lattice.Lattice(3, 3, 3, 90, 90, 90, baserot=affine) structure.placeInLattice(latt_rot) diff_dat = ediff.calculate_ed_data(structure, 2.5) dpi = diff_dat.as_signal(64, 0.02, 2.5) data.append(dpi.data) data = np.array(data) dp = ElectronDiffraction(data.reshape((2, 2, 64, 64))) m = dp.create_model() ref = ScalableReferencePattern(dp.inav[0, 0]) m.append(ref) m.multifit() disp_grad = ref.construct_displacement_gradient() assert disp_grad.data.shape == np.asarray(affines).reshape(2, 2, 3, 3).shape
def create_spot(): z1 = np.zeros((128, 128)) z2 = np.zeros((128, 128)) for r in [4, 3, 2]: c = 1 / r rr, cc = draw.circle(30, 90, radius=r, shape=z1.shape) #30 is y! z1[rr, cc] = c z2[rr, cc] = c rr2, cc2 = draw.circle(100, 60, radius=r, shape=z2.shape) z2[rr2, cc2] = c dp = ElectronDiffraction(np.asarray([[z1, z1], [z2, z2]])) # this needs to be in 2x2 print(dp.axes_manager) return dp
def single_peak(self): pattern = np.zeros((2, 2, 128, 128)) pattern[:, :, 40, 45] = 1 #single point peak return ElectronDiffraction(pattern)
def test_remove_background(self, diffraction_pattern: ElectronDiffraction, method, kwargs): bgr = diffraction_pattern.remove_background(method=method, **kwargs) assert bgr.data.shape == diffraction_pattern.data.shape assert bgr.max() <= diffraction_pattern.max()
def test_get_background_model(self, diffraction_pattern: ElectronDiffraction, saturation_radius): bgm = diffraction_pattern.get_background_model(saturation_radius) assert bgm.axes_manager.signal_shape == diffraction_pattern.axes_manager.signal_shape
def test_get_diffraction_variance(diffraction_pattern: ElectronDiffraction): dv = diffraction_pattern.get_diffraction_variance() assert dv.axes_manager.navigation_shape == (3, ) assert dv.axes_manager.signal_shape == diffraction_pattern.axes_manager.signal_shape
def test_init(): z = np.zeros((2, 2, 2, 2)) dp = ElectronDiffraction( z, metadata={'Acquisition_instrument': { 'SEM': 'Expensive-SEM' }})
def test_apply_gain_normalisation(diffraction_pattern: ElectronDiffraction, dark_reference, bright_reference): diffraction_pattern.apply_gain_normalisation( dark_reference=dark_reference, bright_reference=bright_reference) assert diffraction_pattern.max() == bright_reference assert diffraction_pattern.min() == dark_reference
def get_diffraction_calibration(self, mask_length, linewidth): """Determine the diffraction pattern pixel size calibration in units of reciprocal Angsstroms per pixel. Parameters ---------- mask_length : float Halfwidth of the region excluded from peak finding around the diffraction pattern center. linewidth : float Width of Line2DROI used to obtain line trace from distortion corrected diffraction pattern. Returns ------- diff_cal : float Diffraction calibration in reciprocal Angstroms per pixel. """ # Check that necessary calibration data is provided if self.calibration_data.au_x_grating_dp is None: raise ValueError( "This method requires an Au X-grating diffraction " "pattern to be provided. Please update the " "CalibrationDataLibrary.") if self.affine_matrix is None: raise ValueError( "This method requires a distortion matrix to have " "been determined. Use get_elliptical_distortion " "to determine this matrix.") dpeg = self.calibration_data.au_x_grating_dp size = dpeg.data.shape[0] dpegs = stack_method([dpeg, dpeg, dpeg, dpeg]) dpegs = ElectronDiffraction(dpegs.data.reshape((2, 2, size, size))) dpegs.apply_affine_transformation(self.affine_matrix, preserve_range=True, inplace=True) dpegm = dpegs.mean((0, 1)) # Define line roi along which to take trace for calibration line = Line2DROI(x1=5, y1=5, x2=250, y2=250, linewidth=linewidth) # Obtain line trace trace = line(dpegm) trace = trace.as_signal1D(0) # Find peaks in line trace either side of direct beam db = (np.sqrt(2) * 128) - (5 * np.sqrt(2)) pka = trace.isig[db + mask_length:].find_peaks1D_ohaver()[0]['position'] pkb = trace.isig[:db - mask_length].find_peaks1D_ohaver()[0]['position'] # Determine predicted position of 022 peak of Au pattern d022=1.437 au_pre = db - (self.ring_params[0] / 1.437) au_post = db + (self.ring_params[0] / 1.437) # Calculate differences between predicted and measured positions prediff = np.abs(pkb - au_pre) postdiff = np.abs(pka - au_post) # Calculate new calibration value based on most accurate peak positions dc = (2 / 1.437) / (pka[postdiff == min(postdiff)] - pkb[prediff == min(prediff)]) # Store diffraction calibration value as attribute self.diffraction_calibration = dc[0] return dc[0]
def simulate_kinematic_scattering(atomic_coordinates, element, accelerating_voltage, simulation_size=256, max_k=1.5, illumination='plane_wave', sigma=20, scattering_params='lobato'): """Simulate electron scattering from arrangement of atoms comprising one elemental species. Parameters ---------- atomic_coordinates : array Array specifying atomic coordinates in structure. element : string Element symbol, e.g. "C". accelerating_voltage : float Accelerating voltage in keV. simulation_size : int Simulation size, n, specifies the n x n array size for the simulation calculation. max_k : float Maximum scattering vector magnitude in reciprocal angstroms. illumination : string Either 'plane_wave' or 'gaussian_probe' illumination sigma : float Gaussian probe standard deviation, used when illumination == 'gaussian_probe' scattering_params : string Type of scattering factor calculation to use. One of 'lobato', 'xtables'. Returns ------- simulation : ElectronDiffraction ElectronDiffraction simulation. """ # Delayed loading to prevent circular dependencies. from pyxem.signals.electron_diffraction import ElectronDiffraction # Get atomic scattering parameters for specified element. coeffs = np.array(get_scattering_params_dict(scattering_params)[element]) # Calculate electron wavelength for given keV. wavelength = get_electron_wavelength(accelerating_voltage) # Define a 2D array of k-vectors at which to evaluate scattering. l = np.linspace(-max_k, max_k, simulation_size) kx, ky = np.meshgrid(l, l) # Convert 2D k-vectors into 3D k-vectors accounting for Ewald sphere. k = np.array((kx, ky, (wavelength / 2) * (kx**2 + ky**2))) # Calculate scattering vector squared for each k-vector. gs_sq = np.linalg.norm(k, axis=0)**2 # Get the scattering factors for this element. fs = get_atomic_scattering_factors(gs_sq, coeffs[np.newaxis, :], scattering_params) # Evaluate scattering from all atoms scattering = np.zeros_like(gs_sq) if illumination == 'plane_wave': for r in atomic_coordinates: scattering = scattering + (fs * np.exp(np.dot(k.T, r) * np.pi * 2j)) elif illumination == 'gaussian_probe': for r in atomic_coordinates: probe = (1 / (np.sqrt(2 * np.pi) * sigma)) * \ np.exp((-np.abs(((r[0]**2) - (r[1]**2)))) / (4 * sigma**2)) scattering = scattering + (probe * fs * np.exp(np.dot(k.T, r) * np.pi * 2j)) else: raise ValueError( "User specified illumination '{}' not defined.".format( illumination)) # Calculate intensity intensity = (scattering * scattering.conjugate()).real return ElectronDiffraction(intensity)
def ragged_peak(self): pattern = np.zeros((2, 2, 128, 128)) pattern[:, :, 40, 45] = 1 pattern[1, 0, 71, 21] = 1 return ElectronDiffraction(pattern)
def diffraction_pattern(request): return ElectronDiffraction(request.param)
def axes_test_dp(self): dp_data = np.random.randint(0, 10, (2, 2, 10, 10)) dp = ElectronDiffraction(dp_data) return dp
def diffraction_pattern(request): """ A boring, multiuse dp, with signature: ElectronDiffraction <2,2|8,8> """ return ElectronDiffraction(request.param)
def diffraction_pattern_one_dimension(request): """ 1D (in navigation space) diffraction pattern <1|8,8> """ return ElectronDiffraction(request.param)
def simulate_kinematic_scattering(atomic_coordinates, element, accelerating_voltage, simulation_size=256, max_k=1.5, illumination='plane_wave', sigma=20, scattering_params='lobato'): """Simulate electron scattering from arrangement of atoms comprising one elemental species. Parameters ---------- atomic_coordinates : array Array specifying atomic coordinates in structure. element : string Element symbol, e.g. "C". accelerating_voltage : float Accelerating voltage in keV. simulation_size : int Simulation size, n, specifies the n x n array size for the simulation calculation. max_k : float Maximum scattering vector magnitude in reciprocal angstroms. illumination = string Either 'plane_wave' or 'gaussian_probe' illumination Returns ------- simulation : ElectronDiffraction ElectronDiffraction simulation. """ # Delayed loading to prevent circular dependencies. from pyxem.signals.electron_diffraction import ElectronDiffraction # Get atomic scattering parameters for specified element. if scattering_params == 'lobato': c = np.array(ATOMIC_SCATTERING_PARAMS_LOBATO[element]) elif scattering_params == 'xtables': c = np.array(ATOMIC_SCATTERING_PARAMS[element]) else: raise NotImplementedError( "The scattering parameters `{}` are not implemented. " "See documentation for available " "implementations.".format(scattering_params)) # Calculate electron wavelength for given keV. wavelength = get_electron_wavelength(accelerating_voltage) # Define a 2D array of k-vectors at which to evaluate scattering. l = np.linspace(-max_k, max_k, simulation_size) kx, ky = np.meshgrid(l, l) # Convert 2D k-vectors into 3D k-vectors accounting for Ewald sphere. k = np.array((kx, ky, (wavelength / 2) * (kx**2 + ky**2))) # Calculate scatering angle squared for each k-vector. s2s = (np.linalg.norm(k, axis=0) / 2)**2 gs = (np.linalg.norm(k, axis=0)) # Evaluate atomic scattering factor. if scattering_params == 'lobato': fs = np.zeros_like(s2s) for i in np.arange(5): fs = (fs + c[i, 0] * (2 + c[i, 1] * gs**2) * np.divide(1, np.square(1 + c[i, 1] * gs**2))) elif scattering_params == 'xtables': fs = np.zeros_like(s2s) for i in np.arange(5): fs = fs + (c[i][0] * np.exp(-c[i][1] * s2s)) # Evaluate scattering from all atoms scattering = np.zeros_like(s2s) if illumination == 'plane_wave': for r in atomic_coordinates: scattering = scattering + (fs * np.exp(np.dot(k.T, r) * np.pi * 2j)) elif illumination == 'gaussian_probe': for r in atomic_coordinates: probe = (1 / (np.sqrt(2 * np.pi) * sigma)) * \ np.exp((-np.abs(((r[0]**2) - (r[1]**2)))) / (4 * sigma**2)) scattering = scattering + (probe * fs * np.exp(np.dot(k.T, r) * np.pi * 2j)) else: raise ValueError("User specified illumination not defined.") # Calculate intensity intensity = (scattering * scattering.conjugate()).real return ElectronDiffraction(intensity)
def diffraction_pattern(request): """A simple, multiuse dp, with dimensions: ElectronDiffraction <2,2|8,8> """ return ElectronDiffraction(request.param)