def get_DIC_image(dic_map, scaling_factor): # Construct an array of Euler angles grain_quats = np.empty((len(dic_map), 4)) # Transformation orientations from EBSD orientation reference frame # to EBSD spatial reference frame frame_transform = Quat.fromAxisAngle(np.array((1, 0, 0)), np.pi) if dic_map.ebsdMap.crystalSym == 'hexagonal': # Convert hex convention from y // a2 of EBSD map to x // a1 for DAMASK hex_transform = Quat.fromAxisAngle(np.array([0, 0, 1]), -np.pi / 6) for i, grain in enumerate(dic_map): grain_quats[i] = (hex_transform * grain.ebsdGrain.refOri * frame_transform).quatCoef else: for i, grain in enumerate(dic_map): grain_quats[i] = (grain.ebsdGrain.refOri * frame_transform).quatCoef # Filter out -1 (grain boundary points) and -2 (too small grains) # values in the grain image grain_image = dic_map.grains remove_boundary_points(grain_image) remove_small_grain_points(grain_image) remove_boundary_points(grain_image) remove_boundary_points(grain_image, force_remove=True) # scale down image if needed if scaling_factor != 1: grain_image = zoom(grain_image, scaling_factor, order=0, prefilter=False, mode='nearest') # downstream expects grain numbering to start at 0 not 1 grain_image -= 1 DIC_image = { 'orientations': { 'type': 'quat', 'unit_cell_alignment': { 'x': 'a' }, 'quaternions': grain_quats, 'P': 1, # DefDAP uses P=+1 (e.g see `defdap.quat.Quat.__mul__`) 'quat_component_ordering': 'scalar-vector', }, 'grains': grain_image, } try: DIC_image['scale'] = dic_map.scale except ValueError: pass return DIC_image
def construct_beta_quat_array( ebsd_map: ebsd.Map, alpha_phase_id: int = 0, variant_map: np.ndarray = None, ) -> np.ndarray: """Construct Parameters ---------- ebsd_map: EBSD map to assign the beta variants for. alpha_phase_id Index of the alpha phase in the EBSD map. """ if variant_map is None: variant_map = construct_variant_map(ebsd_map, alpha_phase_id) transformations = [] for sym in unq_hex_syms: transformations.append(burg_trans * sym.conjugate) trans_comps = Quat.extract_quat_comps(transformations) trans_comps = trans_comps[:, variant_map[variant_map >= 0]] quat_comps = Quat.extract_quat_comps(ebsd_map.quatArray[variant_map >= 0]) quat_comps_beta = np.empty_like(quat_comps) # transformations[variant] * quat quat_comps_beta[0, :] = (trans_comps[0, :] * quat_comps[0, :] - trans_comps[1, :] * quat_comps[1, :] - trans_comps[2, :] * quat_comps[2, :] - trans_comps[3, :] * quat_comps[3, :]) quat_comps_beta[1, :] = (trans_comps[1, :] * quat_comps[0, :] + trans_comps[0, :] * quat_comps[1, :] - trans_comps[3, :] * quat_comps[2, :] + trans_comps[2, :] * quat_comps[3, :]) quat_comps_beta[2, :] = (trans_comps[2, :] * quat_comps[0, :] + trans_comps[0, :] * quat_comps[2, :] - trans_comps[1, :] * quat_comps[3, :] + trans_comps[3, :] * quat_comps[1, :]) quat_comps_beta[3, :] = (trans_comps[3, :] * quat_comps[0, :] + trans_comps[0, :] * quat_comps[3, :] - trans_comps[2, :] * quat_comps[1, :] + trans_comps[1, :] * quat_comps[2, :]) # swap into positive hemisphere if required quat_comps_beta[:, quat_comps_beta[0, :] < 0] *= -1 beta_quat_array = np.empty_like(ebsd_map.quatArray) beta_quat_array[variant_map < 0] = Quat(1, 0, 0, 0) for i, idx in enumerate(zip(*np.where(variant_map >= 0))): beta_quat_array[idx] = Quat(quat_comps_beta[:, i]) return beta_quat_array
def get_EBSD_image(ebsd_map, scaling_factor): # Construct an array of Euler angles grain_quats = np.empty((len(ebsd_map), 4)) # Transformation orientations from EBSD orientation reference frame # to EBSD spatial reference frame frame_transform = Quat.fromAxisAngle(np.array((1, 0, 0)), np.pi) if ebsd_map.crystalSym == 'hexagonal': # Convert hex convention from y // a2 of EBSD map to x // a1 for DAMASK hex_transform = Quat.fromAxisAngle(np.array([0, 0, 1]), -np.pi / 6) for i, grain in enumerate(ebsd_map): grain_quats[i] = (hex_transform * grain.refOri * frame_transform).quatCoef else: for i, grain in enumerate(ebsd_map): grain_quats[i] = (grain.refOri * frame_transform).quatCoef # Filter out -2 (too small grains) values in the grain image grain_image = ebsd_map.grains remove_small_grain_points(grain_image) # scale down image if needed if scaling_factor != 1: grain_image = zoom(grain_image, scaling_factor, order=0, prefilter=False, mode='nearest') # downstream expects grain numbering to start at 0 not 1 grain_image -= 1 EBSD_image = { 'orientations': { 'type': 'quat', 'unit_cell_alignment': { 'x': 'a' }, 'quaternions': grain_quats, 'P': 1, # DefDAP uses P=+1 (e.g see `defdap.quat.Quat.__mul__`) 'quat_component_ordering': 'scalar-vector', }, 'grains': grain_image, 'scale': ebsd_map.scale, 'phase_labels': [phase.name for phase in ebsd_map.phases], 'grain_phases': [grain.phaseID for grain in ebsd_map], } return EBSD_image
def plotGrainDataIPF(self, direction, mapData=None, grainData=None, grainIds=-1, **kwargs): """ Plot IPF of grain reference (average) orientations with points coloured by grain average values from map data. Parameters ---------- mapData : numpy.ndarray Array of map data to grain average. This must be cropped! direction : numpy.ndarray Vector of reference direction for the IPF. plotColourBar : bool, optional Set to False to exclude the colour bar from the plot. vmin : float, optional Minimum value of colour scale. vmax : float, optional Maximum value for colour scale. cLabel : str, optional Colour bar label text. """ # Set default plot parameters then update with any input plotParams = {} plotParams.update(kwargs) if grainData is None: if mapData is None: raise ValueError("Either 'mapData' or 'grainData' must " "be supplied.") else: grainData = self.calcGrainAv(mapData, grainIds=grainIds) # Check that grains have been detected in the map self.checkGrainsDetected() if type(grainIds) is int and grainIds == -1: grainIds = range(len(self)) if len(grainData) != len(grainIds): raise Exception("Must be 1 value for each grain in grainData.") grainOri = np.empty(len(grainIds), dtype=Quat) for i, grainId in enumerate(grainIds): grain = self[grainId] grainOri[i] = grain.refOri plot = Quat.plotIPF(grainOri, direction, self.crystalSym, c=grainData, **plotParams) return plot
def calc_beta_oris_from_misori( alpha_ori: Quat, neighbour_oris: List[Quat], burg_tol: float = 5) -> Tuple[List[List[Quat]], List[float]]: """Calculate the possible beta orientations for a given alpha orientation using the misorientation relation to neighbour orientations. Parameters ---------- alpha_ori A quaternion representing the alpha orientation neighbour_oris Quaternions representing neighbour grain orientations burg_tol The threshold misorientation angle to determine neighbour relations Returns ------- list of lists of defdap.Quat.quat Possible beta orientations, grouped by each neighbour. Any neighbour with deviation greater than the tolerance is excluded. list of float Deviations from perfect Burgers transformation """ burg_tol *= np.pi / 180. # This needed to move further up calculation process unq_cub_sym_comps = Quat.extract_quat_comps(unq_cub_syms) alpha_ori_inv = alpha_ori.conjugate beta_oris = [] beta_devs = [] for neighbour_ori in neighbour_oris: min_misoris, min_cub_sym_idxs = calc_misori_of_variants( alpha_ori_inv, neighbour_ori, unq_cub_sym_comps) # find the hex symmetries (i, j) from give the minimum # deviation from the burgers relation for the minimum store: # the deviation, the hex symmetries (i, j) and the cubic # symmetry if the deviation is over a threshold then set # cubic symmetry to -1 min_misori_idx = np.unravel_index(np.argmin(min_misoris), min_misoris.shape) burg_dev = min_misoris[min_misori_idx] if burg_dev < burg_tol: beta_oris.append( beta_oris_from_cub_sym(alpha_ori, min_cub_sym_idxs[min_misori_idx], int(min_misori_idx[0]))) beta_devs.append(burg_dev) return beta_oris, beta_devs
def buildMisOriList(self, calcAxis=False): quatCompsSym = Quat.calcSymEqvs(self.quatList, self.crystalSym) if self.refOri is None: self.refOri = Quat.calcAverageOri(quatCompsSym) misOriArray, minQuatComps = Quat.calcMisOri(quatCompsSym, self.refOri) self.averageMisOri = misOriArray.mean() self.misOriList = list(misOriArray) if calcAxis: # Now for axis calulation refOriInv = self.refOri.conjugate misOriAxis = np.empty((3, minQuatComps.shape[1])) Dq = np.empty((4, minQuatComps.shape[1])) # refOriInv * minQuat for all points (* is quaternion product) # change to minQuat * refOriInv Dq[0, :] = (refOriInv[0] * minQuatComps[0, :] - refOriInv[1] * minQuatComps[1, :] - refOriInv[2] * minQuatComps[2, :] - refOriInv[3] * minQuatComps[3, :]) Dq[1, :] = (refOriInv[1] * minQuatComps[0, :] + refOriInv[0] * minQuatComps[1, :] + refOriInv[3] * minQuatComps[2, :] - refOriInv[2] * minQuatComps[3, :]) Dq[2, :] = (refOriInv[2] * minQuatComps[0, :] + refOriInv[0] * minQuatComps[2, :] + refOriInv[1] * minQuatComps[3, :] - refOriInv[3] * minQuatComps[1, :]) Dq[3, :] = (refOriInv[3] * minQuatComps[0, :] + refOriInv[0] * minQuatComps[3, :] + refOriInv[2] * minQuatComps[1, :] - refOriInv[1] * minQuatComps[2, :]) Dq[:, Dq[0] < 0] = -Dq[:, Dq[0] < 0] # numpy broadcasting taking care of different array sizes misOriAxis[:, :] = (2 * Dq[1:4, :] * np.arccos(Dq[0, :])) / np.sqrt(1 - np.power(Dq[0, :], 2)) # hack it back into a list. Need to change self.*List to be arrays, it was a bad decision to # make them lists in the beginning self.misOriAxisList = [] for row in misOriAxis.transpose(): self.misOriAxisList.append(row)
def transformData(self): """ Rotate map by 180 degrees and transform quats """ print("\rTransforming EBSD data...", end="") self.eulerAngleArray = self.eulerAngleArray[:, ::-1, ::-1] self.bandContrastArray = self.bandContrastArray[::-1, ::-1] self.phaseArray = self.phaseArray[::-1, ::-1] self.buildQuatArray() transformQuat = Quat.fromAxisAngle(np.array([0, 0, 1]), np.pi) for i in range(self.xDim): for j in range(self.yDim): self.quatArray[j, i] = self.quatArray[j, i] * transformQuat print("\rDone ", end="")
def buildQuatArray(self): """ Build quaternion array """ print("\rBuilding quaternion array...", end="") self.checkDataLoaded() if self.quatArray is None: # create the array of quat objects self.quatArray = Quat.createManyQuats(self.eulerAngleArray) print("\rDone ", end="") return
def beta_oris() -> List[Quat]: """The 6 possible beta orientations for the `ori_single_valid` fixture.""" return [ Quat(0.11460994, 0.97057328, 0.10987647, -0.18105038), Quat(0.71894262, 0.52597293, -0.12611599, 0.43654181), Quat(0.4812518, 0.86403135, -0.00937589, 0.14750805), Quat(0.23608204, -0.12857975, 0.96217918, 0.04408791), Quat(0.39243229, -0.10727847, -0.59866151, -0.68999466), Quat(0.62851433, -0.23585823, 0.36351768, -0.64590675) ]
def test_ori_tol(beta_oris: List[Quat]): ori_tol = 5. possible_beta_oris = [ [beta_oris[0] * Quat.fromAxisAngle(np.array([1, 0, 0]), 1.01 * ori_tol * np.pi / 180)], [beta_oris[1]], [beta_oris[1]], [beta_oris[1], beta_oris[3], beta_oris[4]] ] variant_count = recon.count_beta_variants( beta_oris, possible_beta_oris, ori_tol ) expected_variant_count = [0, 3, 0, 1, 1, 0] assert all(np.equal(variant_count, expected_variant_count))
def plotIPFMap(self, direction, **kwargs): # Set default plot parameters then update with any input plotParams = {} plotParams.update(kwargs) # calculate IPF colours IPFcolours = Quat.calcIPFcolours( self.quatArray.flatten(), direction, self.crystalSym ) # reshape back to map shape array IPFcolours = np.reshape(IPFcolours, (self.yDim, self.xDim, 3)) plot = MapPlot.create(self, IPFcolours, **plotParams) return plot
def calc_misori_of_variants( alpha_ori_inv: Quat, neighbour_ori: Quat, unq_cub_sym_comps: np.ndarray) -> Tuple[np.ndarray, np.ndarray]: """Calculate possible symmetry variants between two orientations. Calculate all possible sym variants for misorientation between two orientations undergoing a Burgers type transformation. Then calculate the misorientation to the nearest cubic symmetry, this is the deviation to a perfect Burgers transformation. Parameters ---------- alpha_ori_inv Inverse of first orientation neighbour_ori Second orientation unq_cub_sym_comps Components of the unique cubic symmetries Returns ------- min_misoris : np.ndarray The minimum misorientation for each of the possible beta variants - shape (12, 12) min_cub_sym_idx : np.ndarray The minimum cubic symmetry index for each of the possible variants - shape (12, 12) """ # calculate all possible S^B_m (eqn 11. from [1]) from the # measured misorientation from 2 neighbour alpha grains # for each S^B_m calculate the 'closest' cubic symmetry # (from reduced subset) and the deviation from this symmetry # Vectorised calculation of: # hex_sym[j].inv * ((neighbour_ori * alpha_ori_inv) * hex_sym[i]) # labelled: d = h2.inv * (c * h1) hex_sym_comps = Quat.extract_quat_comps(hex_syms) c = (neighbour_ori * alpha_ori_inv).quatCoef h1 = np.repeat(hex_sym_comps, 12, axis=1) # outer loop h2 = np.tile(hex_sym_comps, (1, 12)) # inner loop d = np.zeros_like(h1) c_dot_h1 = c[1] * h1[1] + c[2] * h1[2] + c[3] * h1[3] c_dot_h2 = c[1] * h2[1] + c[2] * h2[2] + c[3] * h2[3] h1_dot_h2 = h1[1] * h2[1] + h1[2] * h2[2] + h1[3] * h2[3] d[0] = (c[0] * h1[0] * h2[0] - h2[0] * c_dot_h1 + c[0] * h1_dot_h2 + h1[0] * c_dot_h2 + h2[1] * (c[2] * h1[3] - c[3] * h1[2]) + h2[2] * (c[3] * h1[1] - c[1] * h1[3]) + h2[3] * (c[1] * h1[2] - c[2] * h1[1])) d[1] = (c[0] * h2[0] * h1[1] + h1[0] * h2[0] * c[1] - c[0] * h1[0] * h2[1] + c_dot_h1 * h2[1] + c_dot_h2 * h1[1] - h1_dot_h2 * c[1] + h2[0] * (c[2] * h1[3] - c[3] * h1[2]) + c[0] * (h1[2] * h2[3] - h1[3] * h2[2]) + h1[0] * (c[2] * h2[3] - c[3] * h2[2])) d[2] = (c[0] * h2[0] * h1[2] + h1[0] * h2[0] * c[2] - c[0] * h1[0] * h2[2] + c_dot_h1 * h2[2] + c_dot_h2 * h1[2] - h1_dot_h2 * c[2] + h2[0] * (c[3] * h1[1] - c[1] * h1[3]) + c[0] * (h1[3] * h2[1] - h1[1] * h2[3]) + h1[0] * (c[3] * h2[1] - c[1] * h2[3])) d[3] = (c[0] * h2[0] * h1[3] + h1[0] * h2[0] * c[3] - c[0] * h1[0] * h2[3] + c_dot_h1 * h2[3] + c_dot_h2 * h1[3] - h1_dot_h2 * c[3] + h2[0] * (c[1] * h1[2] - c[2] * h1[1]) + c[0] * (h1[1] * h2[2] - h1[2] * h2[1]) + h1[0] * (c[1] * h2[2] - c[2] * h2[1])) # Vectorised calculation of: # burg_trans * (d * burg_trans.inv) # labelled: beta_vars = b * (c * b.inv) b = burg_trans.quatCoef beta_vars = np.zeros_like(h1) b_dot_b = b[1] * b[1] + b[2] * b[2] + b[3] * b[3] b_dot_d = b[1] * d[1] + b[2] * d[2] + b[3] * d[3] beta_vars[0] = d[0] * (b[0] * b[0] + b_dot_b) beta_vars[1] = (d[1] * (b[0] * b[0] - b_dot_b) + 2 * b_dot_d * b[1] + 2 * b[0] * (b[2] * d[3] - b[3] * d[2])) beta_vars[2] = (d[2] * (b[0] * b[0] - b_dot_b) + 2 * b_dot_d * b[2] + 2 * b[0] * (b[3] * d[1] - b[1] * d[3])) beta_vars[3] = (d[3] * (b[0] * b[0] - b_dot_b) + 2 * b_dot_d * b[3] + 2 * b[0] * (b[1] * d[2] - b[2] * d[1])) # calculate misorientation to each of the cubic symmetries misoris = np.einsum("ij,ik->jk", beta_vars, unq_cub_sym_comps) misoris = np.abs(misoris) misoris[misoris > 1] = 1. misoris = 2 * np.arccos(misoris) # find the cubic symmetry with minimum misorientation for each of # the beta misorientation variants min_cub_sym_idx = np.argmin(misoris, axis=1) min_misoris = misoris[np.arange(144), min_cub_sym_idx] # reshape to 12 x 12 for each of the hex sym multiplications min_cub_sym_idx = min_cub_sym_idx.reshape((12, 12)) min_misoris = min_misoris.reshape((12, 12)) return min_misoris, min_cub_sym_idx
def test_calc(self): mock_map = Mock(spec=ebsd.Map) mock_map.quatArray = np.array([ Quat([0.31666724, -0.91588522, 0.13405465, -0.20713635]), Quat([0.17612928, -0.20214505, -0.21599713, -0.93886159]), Quat([0.24967011, -0.224283, -0.26045348, -0.90527673]), Quat([0.68070418, -0.53435491, 0.23619103, -0.44195072]), Quat([0.7293098, -0.34952765, -0.07857021, -0.58289309]), Quat([0.00169551, 0.14777021, 0.12010977, 0.98169992]), Quat([0.47365417, -0.20536756, -0.20468846, -0.83161201]), Quat([0.78776179, -0.27972705, 0.09209514, -0.54101999]), Quat([0.33727506, -0.13311405, -0.13924545, -0.92148624]), Quat([0.41137644, -0.85164404, 0.21420115, -0.24411007]), Quat([0.26092708, -0.27640969, -0.29049792, -0.87813763]), Quat([0.51237664, -0.62480323, -0.16679851, -0.56503925]) ]).reshape((3, 4)) mock_map.shape = mock_map.quatArray.shape variant_map = np.array([[0, 3, -1, 1], [2, 2, 4, 4], [5, -1, 1, 2]]) beta_quat_array = recon.construct_beta_quat_array( mock_map, variant_map=variant_map ) expected_comps = [ np.array([0.3586687, -0.42249045, -0.28570384, 0.78181321]), np.array([0.04396812, -0.20834367, -0.45309011, 0.86566105]), np.array([1., 0., 0., 0.]), np.array([0.13489744, -0.93982694, 0.30093181, -0.08926393]), np.array([0.66997125, -0.66745906, 0.24828395, 0.2097427]), np.array([0.46111146, -0.72030688, -0.25269131, -0.4524172]), np.array([0.31807292, 0.2294237, -0.791435, 0.46885503]), np.array([0.1535714, -0.26761921, -0.83850223, 0.44912114]), np.array([0.51219503, 0.6609771, -0.31826009, -0.44662741]), np.array([1., 0., 0., 0.]), np.array([0.26763438, -0.77098237, -0.48040495, -0.32119948]), np.array([0.56407948, -0.75208451, -0.06296879, 0.33498979]) ] assert all([np.allclose(quat.quatCoef, row) for quat, row in zip(beta_quat_array.flat, expected_comps)])
def ori_quat_list_valid() -> List[Quat]: """A list of sample quaternion representing the orientations of grains.""" return [ Quat(0.22484510, 0.45464871, -0.70807342, 0.49129550), Quat(0.36520321, 0.25903472, -0.40342268, 0.79798357) ]
def ori_single_valid_3() -> Quat: """A single sample quaternion representing the orientation of a grain.""" return Quat(0.8730071, -0.41360125, -0.02295757, -0.25742097)
def ori_single_valid_2() -> Quat: """A single sample quaternion representing the orientation of a grain.""" return Quat(0.11939881, -0.36445855, -0.67237386, -0.63310922)
def ori_single_valid() -> Quat: """A single sample quaternion representing the orientation of a grain.""" return Quat(0.22484510, 0.45464871, -0.70807342, 0.49129550)
def plotOriSpread(self, direction=np.array([0, 0, 1]), **kwargs): plotParams = {'marker': '.'} plotParams.update(kwargs) return Quat.plotIPF(self.quatList, direction, self.crystalSym, **plotParams)
import numpy as np from defdap.quat import Quat hex_syms = Quat.symEqv("hexagonal") # subset of hexagonal symmetries that give unique orientations when the # Burgers transformation is applied unq_hex_syms = [ hex_syms[0], hex_syms[5], hex_syms[4], hex_syms[2], hex_syms[10], hex_syms[11] ] cubic_syms = Quat.symEqv("cubic") # subset of cubic symmetries that give unique orientations when the # Burgers transformation is applied unq_cub_syms = [ cubic_syms[0], cubic_syms[7], cubic_syms[9], cubic_syms[1], cubic_syms[22], cubic_syms[16], cubic_syms[12], cubic_syms[15], cubic_syms[4], cubic_syms[8], cubic_syms[21], cubic_syms[20] ] # HCP -> BCC burg_eulers = np.array([135, 90, 354.74]) * np.pi / 180 burg_trans = Quat.fromEulerAngles(*burg_eulers).conjugate
def calc_beta_oris_from_boundary_misori( grain: ebsd.Grain, neighbour_network: nx.Graph, quat_array: np.ndarray, alpha_phase_id: int, burg_tol: float = 5 ) -> Tuple[List[List[Quat]], List[float], List[Quat]]: """Calculate the possible beta orientations for pairs of alpha and neighbour orientations using the misorientation relation to neighbour orientations. Parameters ---------- grain The grain currently being reconstructed neighbour_network A neighbour network mapping grain boundary connectivity quat_array Array of quaternions, representing the orientations of the pixels of the EBSD map burg_tol : The threshold misorientation angle to determine neighbour relations Returns ------- list of lists of defdap.Quat.quat Possible beta orientations, grouped by each neighbour. Any neighbour with deviation greater than the tolerance is excluded. list of float Deviations from perfect Burgers transformation list of Quat Alpha orientations """ # This needed to move further up calculation process unq_cub_sym_comps = Quat.extract_quat_comps(unq_cub_syms) beta_oris = [] beta_devs = [] alpha_oris = [] neighbour_grains = neighbour_network.neighbors(grain) neighbour_grains = [ grain for grain in neighbour_grains if grain.phaseID == alpha_phase_id ] for neighbour_grain in neighbour_grains: bseg = neighbour_network[grain][neighbour_grain]['boundary'] # check sense of bseg if grain is bseg.grain1: ipoint = 0 else: ipoint = 1 for boundary_point_pair in bseg.boundaryPointPairsX: point = boundary_point_pair[ipoint] alpha_ori = quat_array[point[1], point[0]] point = boundary_point_pair[ipoint - 1] neighbour_ori = quat_array[point[1], point[0]] min_misoris, min_cub_sym_idxs = calc_misori_of_variants( alpha_ori.conjugate, neighbour_ori, unq_cub_sym_comps) # find the hex symmetries (i, j) from give the minimum # deviation from the burgers relation for the minimum store: # the deviation, the hex symmetries (i, j) and the cubic # symmetry if the deviation is over a threshold then set # cubic symmetry to -1 min_misori_idx = np.unravel_index(np.argmin(min_misoris), min_misoris.shape) burg_dev = min_misoris[min_misori_idx] if burg_dev < burg_tol / 180 * np.pi: beta_oris.append( beta_oris_from_cub_sym(alpha_ori, min_cub_sym_idxs[min_misori_idx], int(min_misori_idx[0]))) beta_devs.append(burg_dev) alpha_oris.append(alpha_ori) return beta_oris, beta_devs, alpha_oris
def findBoundaries(self, boundDef=10): """ Find grain boundaries :param boundDef: critical misorientation :type boundDef: float """ self.buildQuatArray() print("\rFinding boundaries...", end="") syms = Quat.symEqv(self.crystalSym) numSyms = len(syms) # array to store quat components of initial and symmetric equivalents quatComps = np.empty((numSyms, 4, self.yDim, self.xDim)) # populate with initial quat components for i, row in enumerate(self.quatArray): for j, quat in enumerate(row): quatComps[0, :, i, j] = quat.quatCoef # loop of over symmetries and apply to initial quat components # (excluding first symmetry as this is the identity transformation) for i, sym in enumerate(syms[1:], start=1): # sym[i] * quat for all points (* is quaternion product) quatComps[i, 0] = (quatComps[0, 0] * sym[0] - quatComps[0, 1] * sym[1] - quatComps[0, 2] * sym[2] - quatComps[0, 3] * sym[3]) quatComps[i, 1] = (quatComps[0, 0] * sym[1] + quatComps[0, 1] * sym[0] - quatComps[0, 2] * sym[3] + quatComps[0, 3] * sym[2]) quatComps[i, 2] = (quatComps[0, 0] * sym[2] + quatComps[0, 2] * sym[0] - quatComps[0, 3] * sym[1] + quatComps[0, 1] * sym[3]) quatComps[i, 3] = (quatComps[0, 0] * sym[3] + quatComps[0, 3] * sym[0] - quatComps[0, 1] * sym[2] + quatComps[0, 2] * sym[1]) # swap into positve hemisphere if required quatComps[i, :, quatComps[i, 0] < 0] *= -1 # Arrays to store neigbour misorientation in positive x and y direction misOrix = np.zeros((numSyms, self.yDim, self.xDim)) misOriy = np.zeros((numSyms, self.yDim, self.xDim)) # loop over symmetries calculating misorientation to initial for i in range(numSyms): for j in range(self.xDim - 1): misOrix[i, :, j] = abs(np.einsum("ij,ij->j", quatComps[0, :, :, j], quatComps[i, :, :, j + 1])) for j in range(self.yDim - 1): misOriy[i, j, :] = abs(np.einsum("ij,ij->j", quatComps[0, :, j, :], quatComps[i, :, j + 1, :])) misOrix[misOrix > 1] = 1 misOriy[misOriy > 1] = 1 # find min misorientation (max here as misorientaion is cos of this) misOrix = np.max(misOrix, axis=0) misOriy = np.max(misOriy, axis=0) # convert to misorientation in degrees misOrix = 360 * np.arccos(misOrix) / np.pi misOriy = 360 * np.arccos(misOriy) / np.pi # set boundary locations where misOrix or misOriy are greater than set value self.boundaries = np.zeros((self.yDim, self.xDim), dtype=int) for i in range(self.xDim): for j in range(self.yDim): if (misOrix[j, i] > boundDef) or (misOriy[j, i] > boundDef): self.boundaries[j, i] = -1 print("\rDone ", end="") return
def calcNye(self): """ Calculates Nye tensor and related GND density for the EBSD map. Stores result in self.Nye and self.GND. """ self.buildQuatArray() print("\rFinding boundaries...", end="") syms = Quat.symEqv(self.crystalSym) numSyms = len(syms) # array to store quat components of initial and symmetric equivalents quatComps = np.empty((numSyms, 4, self.yDim, self.xDim)) # populate with initial quat components for i, row in enumerate(self.quatArray): for j, quat in enumerate(row): quatComps[0, :, i, j] = quat.quatCoef # loop of over symmetries and apply to initial quat components # (excluding first symmetry as this is the identity transformation) for i, sym in enumerate(syms[1:], start=1): # sym[i] * quat for all points (* is quaternion product) quatComps[i, 0] = (quatComps[0, 0] * sym[0] - quatComps[0, 1] * sym[1] - quatComps[0, 2] * sym[2] - quatComps[0, 3] * sym[3]) quatComps[i, 1] = (quatComps[0, 0] * sym[1] + quatComps[0, 1] * sym[0] - quatComps[0, 2] * sym[3] + quatComps[0, 3] * sym[2]) quatComps[i, 2] = (quatComps[0, 0] * sym[2] + quatComps[0, 2] * sym[0] - quatComps[0, 3] * sym[1] + quatComps[0, 1] * sym[3]) quatComps[i, 3] = (quatComps[0, 0] * sym[3] + quatComps[0, 3] * sym[0] - quatComps[0, 1] * sym[2] + quatComps[0, 2] * sym[1]) # swap into positve hemisphere if required quatComps[i, :, quatComps[i, 0] < 0] *= -1 # Arrays to store neigbour misorientation in positive x and y direction misOrix = np.zeros((numSyms, self.yDim, self.xDim)) misOriy = np.zeros((numSyms, self.yDim, self.xDim)) # loop over symmetries calculating misorientation to initial for i in range(numSyms): for j in range(self.xDim - 1): misOrix[i, :, j] = abs(np.einsum("ij,ij->j", quatComps[0, :, :, j], quatComps[i, :, :, j + 1])) for j in range(self.yDim - 1): misOriy[i, j, :] = abs(np.einsum("ij,ij->j", quatComps[0, :, j, :], quatComps[i, :, j + 1, :])) misOrix[misOrix > 1] = 1 misOriy[misOriy > 1] = 1 # find min misorientation (max here as misorientaion is cos of this) argmisOrix = np.argmax(misOrix, axis=0) argmisOriy = np.argmax(misOriy, axis=0) misOrix = np.max(misOrix, axis=0) misOriy = np.max(misOriy, axis=0) # convert to misorientation in degrees misOrix = 360 * np.arccos(misOrix) / np.pi misOriy = 360 * np.arccos(misOriy) / np.pi # calculate relative elastic distortion tensors at each point in the two directions betaderx = np.zeros((3, 3, self.yDim, self.xDim)) betadery = betaderx for i in range(self.xDim - 1): for j in range(self.yDim - 1): q0x = Quat(quatComps[0, 0, j, i], quatComps[0, 1, j, i], quatComps[0, 2, j, i], quatComps[0, 3, j, i]) qix = Quat(quatComps[argmisOrix[j, i], 0, j, i + 1], quatComps[argmisOrix[j, i], 1, j, i + 1], quatComps[argmisOrix[j, i], 2, j, i + 1], quatComps[argmisOrix[j, i], 3, j, i + 1]) misoquatx = qix.conjugate * q0x # change stepsize to meters betaderx[:, :, j, i] = (Quat.rotMatrix(misoquatx) - np.eye(3)) / self.stepSize / 1e-6 q0y = Quat(quatComps[0, 0, j, i], quatComps[0, 1, j, i], quatComps[0, 2, j, i], quatComps[0, 3, j, i]) qiy = Quat(quatComps[argmisOriy[j, i], 0, j + 1, i], quatComps[argmisOriy[j, i], 1, j + 1, i], quatComps[argmisOriy[j, i], 2, j + 1, i], quatComps[argmisOriy[j, i], 3, j + 1, i]) misoquaty = qiy.conjugate * q0y # change stepsize to meters betadery[:, :, j, i] = (Quat.rotMatrix(misoquaty) - np.eye(3)) / self.stepSize / 1e-6 # Calculate the Nye Tensor alpha = np.empty((3, 3, self.yDim, self.xDim)) bavg = 1.4e-10 # Burgers vector alpha[0, 2] = (betadery[0, 0] - betaderx[0, 1]) / bavg alpha[1, 2] = (betadery[1, 0] - betaderx[1, 1]) / bavg alpha[2, 2] = (betadery[2, 0] - betaderx[2, 1]) / bavg alpha[:, 1] = betaderx[:, 2] / bavg alpha[:, 0] = -1 * betadery[:, 2] / bavg # Calculate 3 possible L1 norms of Nye tensor for total # disloction density alpha_total3 = np.empty((self.yDim, self.xDim)) alpha_total5 = np.empty((self.yDim, self.xDim)) alpha_total9 = np.empty((self.yDim, self.xDim)) alpha_total3 = 30 / 10. *( abs(alpha[0, 2]) + abs(alpha[1, 2]) + abs(alpha[2, 2]) ) alpha_total5 = 30 / 14. * ( abs(alpha[0, 2]) + abs(alpha[1, 2]) + abs(alpha[2, 2]) + abs(alpha[1, 0]) + abs(alpha[0, 1]) ) alpha_total9 = 30 / 20. * ( abs(alpha[0, 2]) + abs(alpha[1, 2]) + abs(alpha[2, 2]) + abs(alpha[0, 0]) + abs(alpha[1, 0]) + abs(alpha[2, 0]) + abs(alpha[0, 1]) + abs(alpha[1, 1]) + abs(alpha[2, 1]) ) alpha_total3[abs(alpha_total3) < 1] = 1e12 alpha_total5[abs(alpha_total3) < 1] = 1e12 alpha_total9[abs(alpha_total3) < 1] = 1e12 # choose from the different alpha_totals according to preference; # see Ruggles GND density paper self.GND = alpha_total9 self.Nye = alpha
def calcAverageOri(self): quatCompsSym = Quat.calcSymEqvs(self.quatList, self.crystalSym) self.refOri = Quat.calcAverageOri(quatCompsSym)
def plotRefOri(self, direction=np.array([0, 0, 1]), **kwargs): plotParams = {'marker': '+'} plotParams.update(kwargs) return Quat.plotIPF([self.refOri], direction, self.crystalSym, **plotParams)