def get_plot_data(self): """ Produces suitable Rotations for the construction of a wireframe for self """ from orix.vector import Vector3d # gets a grid of vector directions theta = np.linspace(0, 2 * np.pi - EPSILON, 361) rho = np.linspace(0, np.pi - EPSILON, 181) theta, rho = np.meshgrid(theta, rho) g = Vector3d.from_polar(rho, theta) # get the cell vector normal norms n = Rodrigues.from_rotation(self).norm.data[:, np.newaxis, np.newaxis] if n.size == 0: return Rotation.from_neo_euler(AxAngle.from_axes_angles(g, np.pi)) d = (-self.axis).dot_outer(g.unit).data x = n * d omega = 2 * np.arctan(np.where(x != 0, x**-1, np.pi)) # keeps the smallest allowed angle omega[omega < 0] = np.pi omega = np.min(omega, axis=0) r = Rotation.from_neo_euler(AxAngle.from_axes_angles(g.unit, omega)) return r
def fundamental_sector(self): from orix.vector.neo_euler import AxAngle from orix.vector.spherical_region import SphericalRegion symmetry = self.antipodal symmetry = symmetry[symmetry.angle > 0] axes, order = symmetry.get_highest_order_axis() if order > 6: return Vector3d.empty() axis = Vector3d.zvector().get_nearest(axes, inclusive=True) r = Rotation.from_neo_euler( AxAngle.from_axes_angles(axis, 2 * np.pi / order)) diads = symmetry.diads nearest_diad = axis.get_nearest(diads) if nearest_diad.size == 0: nearest_diad = axis.perpendicular n1 = axis.cross(nearest_diad).unit n2 = -(r * n1) next_diad = r * nearest_diad n = Vector3d.stack((n1, n2)).flatten() sr = SphericalRegion(n.unique()) inside = symmetry[symmetry.axis < sr] if inside.size == 0: return sr axes, order = inside.get_highest_order_axis() axis = axis.get_nearest(axes) r = Rotation.from_neo_euler( AxAngle.from_axes_angles(axis, 2 * np.pi / order)) nearest_diad = next_diad n1 = axis.cross(nearest_diad).unit n2 = -(r * n1) n = Vector3d(np.concatenate((n.data, n1.data, n2.data))) sr = SphericalRegion(n.unique()) return sr
def detector2sample( sample_tilt: float, detector_tilt: float, convention: Optional[str] = None, ) -> Rotation: """Rotation U_S to align detector frame D with sample frame S. Parameters ---------- sample_tilt Sample tilt in degrees. detector_tilt Detector tilt in degrees. convention Which sample reference frame to use, either the one used by EDAX TSL (default), "tsl", or the one used by Bruker, "bruker". Returns ------- Rotation """ # Rotation about sample (microscope) X axis tilt = -np.deg2rad((sample_tilt - 90) - detector_tilt) ax_angle = neo_euler.AxAngle.from_axes_angles(Vector3d.xvector(), tilt) r = Rotation.from_neo_euler(ax_angle) if convention != "bruker": # Followed by a 90 degree rotation about the sample Z axis, # if the TSL sample reference frame is used ax_angle_bruker2tsl = neo_euler.AxAngle.from_axes_angles( Vector3d.zvector(), np.pi / 2) r = Rotation.from_neo_euler(ax_angle_bruker2tsl) * r return r.to_matrix()[0]
def from_symmetry(cls, s1, s2=C1): """The set of unique (mis)orientations of a symmetrical object. Parameters ---------- s1, s2 : Symmetry """ s1, s2 = get_proper_groups(s1, s2) large_cell_normals = _get_large_cell_normals(s1, s2) disjoint = s1 & s2 # if s1._tuples == s2._tuples: # disjoint = disjoint.laue fundamental_sector = disjoint.fundamental_sector() fundamental_sector_normals = Rotation.from_neo_euler( AxAngle.from_axes_angles(fundamental_sector, np.pi) ) normals = Rotation( np.concatenate([large_cell_normals.data, fundamental_sector_normals.data]) ) orientation_region = cls(normals) vertices = orientation_region.vertices() if vertices.size: orientation_region = orientation_region[ np.any( np.isclose(orientation_region.dot_outer(vertices).data, 0), axis=1 ) ] return orientation_region
def test_antipodal(rotation, improper): rotation = Rotation(rotation) rotation.improper = improper a = rotation.antipodal assert np.allclose(a[0].data, rotation.data) assert np.allclose(a[1].data, -rotation.data) assert np.allclose(a[0].improper, rotation.improper) assert np.allclose(a[1].improper, rotation.improper)
def test_to_matrix(self): r = Rotation([[1, 0, 0, 0], [3, 0, 0, 0], [0, 1, 0, 0], [0, 2, 0, 0]]).reshape(2, 2) om = np.array( [np.eye(3), np.eye(3), np.diag([1, -1, -1]), np.diag([1, -1, -1])]) assert np.allclose(r.to_matrix(), om.reshape((2, 2, 3, 3)))
def test_from_matrix(self): r = Rotation([[1, 0, 0, 0], [3, 0, 0, 0], [0, 1, 0, 0], [0, 2, 0, 0]]) om = np.array( [np.eye(3), np.eye(3), np.diag([1, -1, -1]), np.diag([1, -1, -1])] ) assert np.allclose(Rotation.from_matrix(om).data, r.data) assert np.allclose( Rotation.from_matrix(om.reshape((2, 2, 3, 3))).data, r.reshape(2, 2).data )
def get_local_grid(resolution=2, center=None, grid_width=10): """ Generates a grid of rotations about a given rotation Parameters ---------- resolution : float, optional The characteristic distance between a rotation and its neighbour (degrees) center : euler angle tuple or orix.quaternion.rotation.Rotation, optional The rotation at which the grid is centered. If None (default) uses the identity grid_width : float, optional The largest angle of rotation away from center that is acceptable (degrees) Returns ------- rotation_list : list of tuples """ if isinstance(center, tuple): z = np.deg2rad(np.asarray(center)) center = Rotation.from_euler(z, convention="bunge", direction="crystal2lab") orix_grid = get_sample_local(resolution=resolution, center=center, grid_width=grid_width) rotation_list = get_list_from_orix(orix_grid, rounding=2) return rotation_list
def get_plot_data(self): from orix.vector import Vector3d theta = np.linspace(0, 2 * np.pi + 1e-9, 361) rho = np.linspace(0, np.pi, 181) theta, rho = np.meshgrid(theta, rho) g = Vector3d.from_polar(rho, theta) n = Rodrigues.from_rotation(self).norm.data[:, np.newaxis, np.newaxis] if n.size == 0: return Rotation.from_neo_euler(AxAngle.from_axes_angles(g, np.pi)) d = (-self.axis).dot_outer(g.unit).data x = n * d x = 2 * np.arctan(x**-1) x[x < 0] = np.pi x = np.min(x, axis=0) r = Rotation.from_neo_euler(AxAngle.from_axes_angles(g.unit, x)) return r
def rotate(self, axis=None, angle=0): """Convenience function for rotating this vector. Shapes of 'axis' and 'angle' must be compatible with shape of this vector for broadcasting. Parameters ---------- axis : Vector3d or array_like, optional The axis of rotation. Defaults to the z-vector. angle : array_like, optional The angle of rotation, in radians. Returns ------- Vector3d A new vector with entries rotated. Examples -------- >>> from math import pi >>> v = Vector3d((0, 1, 0)) >>> axis = Vector3d((0, 0, 1)) >>> angles = [0, pi/4, pi/2, 3*pi/4, pi] >>> v.rotate(axis=axis, angle=angles) """ from orix.quaternion.rotation import Rotation from orix.vector.neo_euler import AxAngle axis = Vector3d.zvector() if axis is None else axis angle = 0 if angle is None else angle q = Rotation.from_neo_euler(AxAngle.from_axes_angles(axis, angle)) return q * self
def detector2reciprocal_lattice( sample_tilt: float, detector_tilt: float, lattice: Lattice, rotation: Rotation, ) -> np.ndarray: """Rotation U_Kstar from detector to reciprocal crystal lattice frame Kstar. Parameters ---------- sample_tilt Sample tilt in degrees. detector_tilt Detector tilt in degrees. lattice Crystal lattice. rotation Unit cell rotation from the sample frame S. Returns ------- np.ndarray """ # Rotation U_S to align the detector frame D with the sample frame S _detector2sample = detector2sample(sample_tilt, detector_tilt) # Rotation U_O from S to the Cartesian crystal frame C sample2cartesian = rotation.to_matrix() # Rotation U_A from C to the reciprocal crystal lattice frame Kstar structure_matrix = get_direct_structure_matrix(lattice) cartesian2reciprocal = np.linalg.inv(structure_matrix).T return cartesian2reciprocal.dot(sample2cartesian).dot(_detector2sample)
def uniform_SO3_sample(resolution): """ Returns rotations that are evenly spaced according to the Haar measure on SO3 Parameters ---------- resolution : float The characteristic distance between a rotation and its neighbour (degrees) Returns ------- q : orix.quaternion.rotation.Rotation grid containing appropriate rotations """ num_steps = int(np.ceil(360 / resolution)) if num_steps % 2 == 1: num_steps = int(num_steps + 1) half_steps = int(num_steps / 2) alpha = np.linspace(0, 2 * np.pi, num=num_steps, endpoint=False) beta = np.arccos(np.linspace(1, -1, num=half_steps, endpoint=False)) gamma = np.linspace(0, 2 * np.pi, num=num_steps, endpoint=False) q = np.asarray(list(product(alpha, beta, gamma))) # convert to quaternions q = Rotation.from_euler(q, convention="bunge", direction="crystal2lab") # remove duplicates q = q.unique() return q
def test_from_to_matrix(self): om = np.array( [np.eye(3), np.eye(3), np.diag([1, -1, -1]), np.diag([1, -1, -1])]) assert np.allclose(Rotation.from_matrix(om).to_matrix(), om)
def faces(self): normals = Rotation(self) vertices = self.vertices() faces = [] for n in normals: faces.append(vertices[np.isclose(vertices.dot(n).data, 0)]) faces = [f for f in faces if f.size > 2] return faces
def get_grid_around_beam_direction(beam_rotation, resolution, angular_range=(0, 360)): """ Creates a rotation list of rotations for which the rotation is about given beam direction Parameters ---------- beam_rotation : tuple A desired beam direction as a rotation (rzxz eulers), usually found via get_rotation_from_z_to_direction resolution : float The resolution of the grid (degrees) angular_range : tuple The minimum (included) and maximum (excluded) rotation around the beam direction to be included Returns ------- rotation_list : list of tuples Example ------- >>> from diffsims.generators.zap_map_generator import get_rotation_from_z_to_direction >>> beam_rotation = get_rotation_from_z_to_direction(structure,[1,1,1]) >>> grid = get_grid_around_beam_direction(beam_rotation,1) """ z = np.deg2rad(np.asarray(beam_rotation)) beam_rotation = Rotation.from_euler(z, convention="bunge", direction="crystal2lab") angles = np.deg2rad( np.arange(start=angular_range[0], stop=angular_range[1], step=resolution)) axes = np.repeat([[0, 0, 1]], angles.shape[0], axis=0) in_plane_rotation = Rotation.from_neo_euler( AxAngle.from_axes_angles(axes, angles)) orix_grid = beam_rotation * in_plane_rotation rotation_list = get_list_from_orix(orix_grid, rounding=2) return rotation_list
def vertices(self): """The vertices of the asymmetric domain. Returns ------- Rotation """ normal_combinations = list(itertools.combinations(self, 3)) if len(normal_combinations) < 1: return Rotation.empty() c1, c2, c3 = zip(*normal_combinations) c1, c2, c3 = Rotation.stack(c1).flatten(), Rotation.stack( c2).flatten(), Rotation.stack(c3).flatten() v = Rotation.triple_cross(c1, c2, c3) v = v[~np.any(np.isnan(v.data), axis=-1)] v = v[v < self].unique() surface = np.any(np.isclose(v.dot_outer(self).data, 0), axis=1) return v[surface]
def test_orientations_symmetry(self, point_group, rotation, expected_orientation): r = Rotation(rotation) cm = CrystalMap(rotations=r, phase_id=np.array([0])) cm.phases = PhaseList(Phase("a", point_group=point_group)) o = cm.orientations assert np.allclose( o.data, Orientation(r).set_symmetry(point_group).data, atol=1e-3 ) assert np.allclose(o.data, expected_orientation, atol=1e-3)
def test_get_sample_local_width(big, small): """ Checks that width follows the expected trend (X - Sin(X)) """ resolution = np.pi z = get_sample_local(resolution=resolution, grid_width=small) assert np.all(z.angle_with(Rotation([1, 0, 0, 0])) < np.deg2rad(small)) assert np.any( z.angle_with(Rotation([1, 0, 0, 0])) > np.deg2rad(small - 1.5 * resolution) ) x_size = z.size assert x_size > 0 y_size = get_sample_local(resolution=np.pi, grid_width=big).size x_v = np.deg2rad(small) - np.sin(np.deg2rad(small)) y_v = np.deg2rad(big) - np.sin(np.deg2rad(big)) exp = y_size / x_size theory = y_v / x_v # resolution/width is high, so we must be generous on tolerance assert np.isclose(exp, theory, rtol=0.2)
def _get_large_cell_normals(s1, s2): dp = get_distinguished_points(s1, s2) normals = Rodrigues.zero(dp.shape + (2, )) planes1 = dp.axis * np.tan(dp.angle.data / 4) planes2 = -dp.axis * np.tan(dp.angle.data / 4)**-1 planes2.data[np.isnan(planes2.data)] = 0 normals[:, 0] = planes1 normals[:, 1] = planes2 normals: Rotation = Rotation.from_neo_euler(normals).flatten().unique( antipodal=False) if not normals.size: return normals _, inv = normals.axis.unique(return_inverse=True) axes_unique = [] angles_unique = [] for i in np.unique(inv): n = normals[inv == i] axes_unique.append(n.axis.data[0]) angles_unique.append(n.angle.data.max()) normals = Rotation.from_neo_euler( AxAngle.from_axes_angles(np.array(axes_unique), angles_unique)) return normals
def test_mul_rotation(r1, i1, r2, i2, expected, expected_i): r1 = Rotation(r1) r1.improper = i1 r2 = Rotation(r2) r2.improper = i2 r = r1 * r2 assert isinstance(r, Rotation) assert np.allclose(r.data, expected) assert np.all(r.improper == expected_i)
def detector2sample(sample_tilt: float, detector_tilt: float) -> Rotation: """Rotation U_S to align detector frame D with sample frame S. Parameters ---------- sample_tilt Sample tilt in degrees. detector_tilt Detector tilt in degrees. Returns ------- Rotation """ x_axis = Vector3d.xvector() tilt = -np.deg2rad((sample_tilt - 90) - detector_tilt) ax_angle = neo_euler.AxAngle.from_axes_angles(x_axis, tilt) return Rotation.from_neo_euler(ax_angle).to_matrix()[0]
def dict2crystalmap(dictionary): """Get a crystal map from necessary items in a dictionary. Parameters ---------- dictionary : dict Dictionary with crystal map information. Returns ------- CrystalMap """ dictionary = copy.deepcopy(dictionary) data = dictionary["data"] header = dictionary["header"] # New dictionary with CrystalMap initialization arguments as keys crystal_map_dict = { # Use dstack and squeeze to allow more rotations per data point "rotations": Rotation.from_euler( np.dstack((data.pop("phi1"), data.pop("Phi"), data.pop("phi2"))).squeeze(), ), "scan_unit": header["scan_unit"], "phase_list": dict2phaselist(header["phases"]), "phase_id": data.pop("phase_id"), "is_in_data": data.pop("is_in_data"), } # Add standard items by updating the new dictionary for direction in ["z", "y", "x"]: this_direction = data.pop(direction) if hasattr(this_direction, "__iter__") is False: this_direction = None crystal_map_dict[direction] = this_direction _ = [data.pop(i) for i in ["id"]] # What's left should be properties like quality metrics etc. crystal_map_dict.update({"prop": data}) return CrystalMap(**crystal_map_dict)
def loadang(file_string: str): """Load ``.ang`` files. Parameters ---------- file_string : str Path to the ``.ang`` file. This file is assumed to list the Euler angles in the Bunge convention in the first three columns. Returns ------- Rotation """ from orix.quaternion.rotation import Rotation data = np.loadtxt(file_string) euler = data[:, :3] rotation = Rotation.from_euler(euler) return rotation
def loadctf(file_string: str): """Load ``.ang`` files. Parameters ---------- file_string : str Path to the ``.ctf`` file. This file is assumed to list the Euler angles in the Bunge convention in the columns 5, 6, and 7. Returns ------- Rotation """ from orix.quaternion.rotation import Rotation data = np.loadtxt(file_string, skiprows=17)[:, 5:8] euler = np.radians(data) rotation = Rotation.from_euler(euler) return rotation
def nickel_rotations(): """A set of 25 rotations in a TSL crystal reference frame (RD-TD-ND). The rotations are from an EMsoft indexing of patterns in the region of interest (row0:row1, col0:col1) = (79:84, 134:139) of the first Nickel data set in this set of scans: https://zenodo.org/record/3265037. """ return Rotation( np.array( [ [0.8662, 0.2033, -0.3483, -0.2951], [0.8888, 0.3188, -0.2961, -0.1439], [0.8883, 0.3188, -0.2973, -0.1444], [0.8884, 0.3187, -0.2975, -0.1437], [0.9525, 0.1163, -0.218, -0.1782], [0.8658, 0.2031, -0.3486, -0.296], [0.8661, 0.203, -0.3486, -0.2954], [0.8888, 0.3179, -0.297, -0.1439], [0.9728, -0.1634, 0.0677, 0.1494], [0.9526, 0.1143, -0.2165, -0.1804], [0.8659, 0.2033, -0.3483, -0.2958], [0.8663, 0.2029, -0.348, -0.2955], [0.8675, 0.1979, -0.3455, -0.298], [0.9728, -0.1633, 0.0685, 0.1494], [0.9726, -0.1634, 0.0684, 0.1506], [0.8657, 0.2031, -0.3481, -0.297], [0.8666, 0.2033, -0.3475, -0.2949], [0.9111, 0.3315, -0.1267, -0.2095], [0.9727, -0.1635, 0.0681, 0.1497], [0.9727, -0.1641, 0.0682, 0.1495], [0.8657, 0.2024, -0.3471, -0.2986], [0.9109, 0.3318, -0.1257, -0.2105], [0.9113, 0.3305, -0.1257, -0.2112], [0.9725, -0.1643, 0.0691, 0.1497], [0.9727, -0.1633, 0.0685, 0.1499], ] ) )
def test_to_matrix(self): r = Rotation([[1, 0, 0, 0], [3, 0, 0, 0], [0, 1, 0, 0], [0, 2, 0, 0]]) om = np.array( [np.eye(3), np.eye(3), np.diag([1, -1, -1]), np.diag([1, -1, -1])] ) # Shapes are handled correctly assert np.allclose(r.reshape(2, 2).to_matrix(), om.reshape(2, 2, 3, 3)) r2 = Rotation( [ [0.1, 0.2, 0.3, 0.4], [0.5, 0.6, 0.7, 0.8], [0.9, 0.91, 0.92, 0.93], [1, 2, 3, 4], ] ) om_from_r2 = r2.to_matrix() # Inverse equal to transpose assert all(np.allclose(np.linalg.inv(i), i.T) for i in om_from_r2) # Cross product of any two rows gives the third assert all(np.allclose(np.cross(i[:, 0], i[:, 1]), i[:, 2]) for i in om_from_r2) # Sum of squares of any column or row equals unity assert np.allclose(np.sum(np.square(om_from_r2), axis=1), 1) # Rows assert np.allclose(np.sum(np.square(om_from_r2), axis=2), 1) # Columns
def _tuples(self): """set of tuple : the differentiators of this group.""" s = Rotation(self.flatten()) tuples = set([tuple(d) for d in s._differentiators()]) return tuples
def rotations(): return Rotation([(2, 4, 6, 8), (-1, -3, -5, -7)])
def r_tsl2bruker(): """A rotation from the TSL to Bruker crystal reference frame.""" return Rotation.from_neo_euler( neo_euler.AxAngle.from_axes_angles(Vector3d.zvector(), np.pi / 2))
def test_get_rotation_matrix_from_diffpy(self): """Checking that getting rotation matrices from diffpy.structure works without issue. """ r = Rotation.from_matrix([i.R for i in sg225.symop_list]) assert not np.isnan(r.data).any()