def test_hesse_lines(self, nickel_phase): """Desired Hesse lines.""" full_shape = (2, 2, 2) bands = KikuchiBand( phase=nickel_phase, hkl=np.array([[-1, 1, 1], [0, -2, 0]]), hkl_detector=np.tile( np.array([[0.26, 0.32, 0.26], [-0.21, 0.45, -0.27]]), full_shape + (1, 1), ), in_pattern=np.ones(full_shape, dtype=bool), ) assert np.allclose( bands.hesse_line_x, np.array([ [[-0.39764706, -0.22992701], [-0.39764706, -0.22992701]], [[-0.39764706, -0.22992701], [-0.39764706, -0.22992701]], ]), ) assert np.allclose( bands.hesse_line_y, np.array([ [[-0.48941176, 0.49270073], [-0.48941176, 0.49270073]], [[-0.48941176, 0.49270073], [-0.48941176, 0.49270073]], ]), )
def test_getitem( self, nickel_phase, nav_shape, n_bands, get_key, desired_data_shape_before, desired_data_shape_after, ): """Slicing works as expected.""" hkl = np.repeat(np.arange(1, n_bands + 1), 3).reshape((n_bands, 3)) hkl_detector = np.tile( np.repeat(np.arange(1, n_bands + 1), 3).reshape((n_bands, 3)), nav_shape + (1, 1), ) # shape nav_shape + (size, 3) bands = KikuchiBand( phase=nickel_phase, hkl=hkl, hkl_detector=hkl_detector, in_pattern=np.ones(nav_shape + (n_bands, ), dtype=bool), ) assert bands._data_shape == desired_data_shape_before new_bands = bands[get_key] assert new_bands._data_shape == desired_data_shape_after
def test_plane_trace_coordinates( self, nickel_phase, hkl, hkl_detector, gnomonic_radius, plane_trace_coordinates, ): """Desired gnomonic (P1, P2) coordinates.""" bands = KikuchiBand( phase=nickel_phase, hkl=hkl, hkl_detector=hkl_detector, in_pattern=np.ones(hkl_detector.shape[:-1], dtype=bool), gnomonic_radius=gnomonic_radius, ) assert (bands.plane_trace_coordinates.shape == plane_trace_coordinates.shape) assert np.allclose( bands.plane_trace_coordinates, plane_trace_coordinates, atol=1e-4, equal_nan=True, )
def test_repr(self, nickel_phase, hkl, hkl_detector, dim_str, band_str): """Desired representation.""" bands = KikuchiBand( phase=nickel_phase, hkl=hkl, hkl_detector=hkl_detector, in_pattern=np.ones(hkl_detector.shape[:-1], dtype=bool), ) assert repr(bands) == ( f"KikuchiBand {dim_str}\n" f"Phase: {nickel_phase.name} ({nickel_phase.point_group.name})\n" f"{band_str}")
def nickel_kikuchi_band(nickel_rlp, nickel_rotations, pc1): rlp = nickel_rlp.symmetrise() phase = rlp.phase hkl = rlp.hkl.data nav_shape = (5, 5) detector = EBSDDetector( shape=(60, 60), binning=8, px_size=70, pc=np.ones(nav_shape + (3, )) * pc1, sample_tilt=70, tilt=0, convention="tsl", ) nav_dim = detector.navigation_dimension navigation_axes = (1, 2)[:nav_dim] # Output shape is (3, n, 3) or (3, ny, nx, 3) det2recip = detector2reciprocal_lattice( sample_tilt=detector.sample_tilt, detector_tilt=detector.tilt, lattice=phase.structure.lattice, rotation=nickel_rotations.reshape(*nav_shape), ) # Output shape is (nhkl, n, 3) or (nhkl, ny, nx, 3) hkl_detector = np.tensordot(hkl, det2recip, axes=(1, 0)) # Determine whether a band is visible in a pattern upper_hemisphere = hkl_detector[..., 2] > 0 is_in_some_pattern = np.sum(upper_hemisphere, axis=navigation_axes) != 0 # Get bands that are in some pattern and their coordinates in the # proper shape hkl = hkl[is_in_some_pattern, ...] hkl_in_pattern = upper_hemisphere[is_in_some_pattern, ...].T hkl_detector = np.moveaxis(hkl_detector[is_in_some_pattern], source=0, destination=nav_dim) return KikuchiBand( phase=phase, hkl=hkl, hkl_detector=hkl_detector, in_pattern=hkl_in_pattern, gnomonic_radius=detector.r_max, )
def geometrical_simulation( self, reciprocal_lattice_point: Optional[ReciprocalLatticePoint] = None ) -> GeometricalEBSDSimulation: """Project a set of Kikuchi bands and zone axes onto the detector, one set for each rotation of the unit cell. The zone axes are calculated from the Kikuchi bands. Parameters ---------- reciprocal_lattice_point Crystal planes to project onto the detector. If None, and the generator has a phase with a unit cell with a point group, a set of planes with minimum distance of 1 Å and their symmetrically equivalent planes are used. Returns ------- GeometricalEBSDSimulation Examples -------- >>> from diffsims.crystallography import ReciprocalLatticePoint >>> simgen EBSDSimulationGenerator (1,) EBSDDetector (60, 60), px_size 1 um, binning 1, tilt 0, pc (0.5, 0.5, 0.5) <name: . space group: None. point group: None. proper point group: None. color: tab:blue> Rotation (1,) >>> sim1 = simgen.geometrical_simulation() >>> sim1.bands.size 94 >>> rlp = ReciprocalLatticePoint( ... phase=simgen.phase, hkl=[[1, 1, 1], [2, 0, 0]] ... ) >>> sim2 = simgen.geometrical_simulation() >>> sim2.bands.size 13 """ rlp = reciprocal_lattice_point if rlp is None and (hasattr(self.phase.point_group, "name") and hasattr(self.phase.structure.lattice, "abcABG")): rlp = ReciprocalLatticePoint.from_min_dspacing(self.phase, min_dspacing=1) rlp = rlp[rlp.allowed].symmetrise() elif rlp is None: raise ValueError("A ReciprocalLatticePoint object must be passed") self._rlp_phase_is_compatible(rlp) # Unit cell parameters (called more than once) phase = rlp.phase hkl = rlp._hkldata # Get number of navigation dimensions and navigation axes # indices n_nav_dims = self.navigation_dimension navigation_axes = (1, 2)[:n_nav_dims] # Get Kikuchi band coordinates for all bands in all patterns # U_Kstar, transformation from detector frame D to reciprocal # crystal lattice frame Kstar. # Output shape is (3, n, 3) or (3, ny, nx, 3) det2recip = detector2reciprocal_lattice( sample_tilt=self.detector.sample_tilt, detector_tilt=self.detector.tilt, lattice=phase.structure.lattice, rotation=self.rotations, ) # Output shape is (nhkl, n, 3) or (nhkl, ny, nx, 3) hkl_detector = np.tensordot(hkl, det2recip, axes=(1, 0)) if n_nav_dims == 0: hkl_detector = hkl_detector.squeeze() # Get bands that are in some pattern hkl_is_upper, hkl_in_a_pattern = _get_coordinates_in_upper_hemisphere( z_coordinates=hkl_detector[..., 2], navigation_axes=navigation_axes) hkl = hkl[hkl_in_a_pattern, ...] hkl_in_pattern = hkl_is_upper[hkl_in_a_pattern, ...].T # Get coordinates in the proper shape hkl_detector = np.moveaxis(hkl_detector[hkl_in_a_pattern], source=0, destination=n_nav_dims) # And store it all bands = KikuchiBand( phase=phase, hkl=hkl, hkl_detector=hkl_detector, in_pattern=hkl_in_pattern, gnomonic_radius=self.detector.r_max, ) # Get zone axes coordinates from Kikuchi bands uvw = _get_uvw_from_hkl(hkl=hkl) det2direct = detector2direct_lattice( sample_tilt=self.detector.sample_tilt, detector_tilt=self.detector.tilt, lattice=self.phase.structure.lattice, rotation=self.rotations, ) uvw_detector = np.tensordot(uvw, det2direct, axes=(1, 0)) if n_nav_dims == 0: uvw_detector = uvw_detector.squeeze() uvw_is_upper, uvw_in_a_pattern = _get_coordinates_in_upper_hemisphere( z_coordinates=uvw_detector[..., 2], navigation_axes=navigation_axes) uvw = uvw[uvw_in_a_pattern, ...] uvw_in_pattern = uvw_is_upper[uvw_in_a_pattern, ...].T uvw_detector = np.moveaxis(uvw_detector[uvw_in_a_pattern], source=0, destination=n_nav_dims) zone_axes = ZoneAxis( phase=phase, uvw=uvw, uvw_detector=uvw_detector, in_pattern=uvw_in_pattern, gnomonic_radius=self.detector.r_max, ) return GeometricalEBSDSimulation( detector=self.detector, rotations=self.rotations, bands=bands, zone_axes=zone_axes, )
def test_init( self, nickel_phase, hkl, hkl_detector, within_gnomonic_radius, hesse_distance, hesse_alpha, nav_shape, nav_dim, size, ): """Desired initialization behaviour.""" in_pattern = np.ones(hkl_detector.shape[:-1], dtype=bool) gnomonic_radius = 10 bands = KikuchiBand( phase=nickel_phase, hkl=hkl, hkl_detector=hkl_detector, in_pattern=in_pattern, gnomonic_radius=gnomonic_radius, ) # Correct number of bands and navigation points and their shape assert bands.navigation_shape == nav_shape assert bands.navigation_dimension == nav_dim assert bands.size == size # No rounding or anything assert np.allclose(bands.hkl.data, hkl) assert np.allclose(bands.hkl_detector.data, hkl_detector) # Correct detector and gnomonic coordinates assert np.allclose(bands.x_detector, hkl_detector[..., 0]) assert np.allclose(bands.y_detector, hkl_detector[..., 1]) assert np.allclose(bands.z_detector, hkl_detector[..., 2]) assert np.allclose(bands.x_gnomonic, hkl_detector[..., 0] / hkl_detector[..., 2]) assert np.allclose(bands.y_gnomonic, hkl_detector[..., 1] / hkl_detector[..., 2]) # Whether bands are in points are treated correctly assert bands.in_pattern.shape == in_pattern.shape assert np.allclose(bands.in_pattern, in_pattern) # Broadcasting of gnomonic radius is correct gnomonic_radius2 = np.atleast_1d(gnomonic_radius * np.ones(nav_shape)) assert bands.gnomonic_radius.shape == gnomonic_radius2.shape assert np.allclose(bands.gnomonic_radius, gnomonic_radius2) # Whether a band should be plotted or not assert ( bands.within_gnomonic_radius.shape == within_gnomonic_radius.shape) assert np.allclose(bands.within_gnomonic_radius, within_gnomonic_radius) # Hesse distance and alpha assert bands.hesse_distance.shape == hesse_distance.shape assert np.allclose(bands.hesse_distance, hesse_distance) assert bands.hesse_alpha.shape == hesse_alpha.shape assert np.allclose(bands.hesse_alpha, hesse_alpha, equal_nan=True)
def geometrical_simulation( self, reciprocal_lattice_point: Optional[ReciprocalLatticePoint] = None, ) -> GeometricalEBSDSimulation: """Project a set of center positions of Kikuchi bands on the detector, one set for each rotation of the unit cell. Parameters ---------- reciprocal_lattice_point : Crystal planes to project onto the detector. If None, and the generator has a phase with a unit cell with a point group, a set of planes with minimum distance of 1 Å and their symmetrically equivalent planes are used. Returns ------- GeometricalEBSDSimulation Examples -------- >>> from diffsims.crystallography import ReciprocalLatticePoint >>> simgen EBSDSimulationGenerator (1,) EBSDDetector (60, 60), px_size 1 um, binning 1, tilt 0, pc (0.5, 0.5, 0.5) <name: . space group: None. point group: None. proper point group: None. color: tab:blue> Rotation (1,) >>> sim1 = simgen.geometrical_simulation() >>> sim1.bands.size 94 >>> rlp = ReciprocalLatticePoint( ... phase=simgen.phase, hkl=[[1, 1, 1], [2, 0, 0]] ... ) >>> sim2 = simgen.geometrical_simulation() >>> sim2.bands.size 13 """ rlp = reciprocal_lattice_point if rlp is None and (hasattr(self.phase.point_group, "name") and hasattr(self.phase.structure.lattice, "abcABG")): rlp = ReciprocalLatticePoint.from_min_dspacing(self.phase, min_dspacing=1) rlp = rlp[rlp.allowed].symmetrise() elif rlp is None: raise ValueError("A ReciprocalLatticePoint object must be passed") self._rlp_phase_is_compatible(rlp) # Unit cell parameters (called more than once) phase = rlp.phase hkl = rlp._hkldata # Get Kikuchi band coordinates for all bands in all patterns # U_Kstar, transformation from detector frame D to reciprocal crystal # lattice frame Kstar # TODO: Possible bottleneck due to large dot products! Room for # lots of improvements with dask. # Output shape is (3, n, 3) or (3, ny, nx, 3) det2recip = detector2reciprocal_lattice( sample_tilt=self.detector.sample_tilt, detector_tilt=self.detector.tilt, lattice=phase.structure.lattice, rotation=self.rotations, ) # Output shape is (nhkl, n, 3) or (nhkl, ny, nx, 3) band_coordinates = np.tensordot(hkl, det2recip, axes=(1, 0)) # Determine whether a band is visible in a pattern upper_hemisphere = band_coordinates[..., 2] > 0 nav_dim = self.navigation_dimension navigation_axes = (1, 2)[:nav_dim] is_in_some_pattern = np.sum(upper_hemisphere, axis=navigation_axes) != 0 # Get bands that were in some pattern and their coordinates in the # proper shape hkl = hkl[is_in_some_pattern, ...] hkl_in_pattern = upper_hemisphere[is_in_some_pattern, ...].T band_coordinates = np.moveaxis(band_coordinates[is_in_some_pattern], source=0, destination=nav_dim) # And store it all bands = KikuchiBand( phase=phase, hkl=hkl, hkl_detector=band_coordinates, in_pattern=hkl_in_pattern, gnomonic_radius=self.detector.r_max, ) # Get zone axes coordinates # U_K, transformation from detector frame D to direct crystal lattice # frame K # det2direct = detector2direct_lattice( # sample_tilt=self.detector.sample_tilt, # detector_tilt=self.detector.tilt, # lattice=phase.structure.lattice, # orientation=self.orientations, # ) # hkl_transposed_upper = hkl_transposed[..., upper_hemisphere] # axis_coordinates = det2direct.T.dot(hkl_transposed_upper).T # zone_axes = ZoneAxis( # phase=phase, hkl=upper_hkl, coordinates=axis_coordinates # ) return GeometricalEBSDSimulation( detector=self.detector, rotations=self.rotations, bands=bands, # zone_axes=zone_axes, )