def test_topotomo_tilts(self): # tests cases from ma2285 experiment on id11, omega offset = -90 T = np.array([[0, -1, 0], [1, 0, 0], [0, 0, 1]]) al = Lattice.from_symbol('Al') p = HklPlane(0, 0, 2, lattice=al) rod = [0.1449, -0.0281, 0.0616] o = Orientation.from_rodrigues(rod) (ut, lt) = o.topotomo_tilts(p, T) self.assertAlmostEqual(180 / np.pi * ut, 2.236, 3) self.assertAlmostEqual(180 / np.pi * lt, 16.615, 3) # use test case from AlLi_sam8_dct_cen_ p = HklPlane(2, 0, 2, lattice=al) rod = [0.0499, -0.3048, 0.1040] o = Orientation.from_rodrigues(rod) (ut, lt) = o.topotomo_tilts(p, T) self.assertAlmostEqual(180 / np.pi * ut, -11.04, 2) self.assertAlmostEqual(180 / np.pi * lt, -0.53, 2) # test case from ma3921 T = Orientation.compute_instrument_transformation_matrix(-1.2, 0.7, 90) Ti7Al = Lattice.hexagonal(0.2931, 0.4694) # nm (h, k, l) = HklPlane.four_to_three_indices(-1, 2, -1, 0) p = HklPlane(h, k, l, Ti7Al) o = Orientation.from_rodrigues([0.7531, 0.3537, 0.0621]) (ut, lt) = o.topotomo_tilts(p, T) self.assertAlmostEqual(180 / np.pi * ut, 11.275, 2) self.assertAlmostEqual(180 / np.pi * lt, -4.437, 2)
def test_HklPlane_normal(self): ZrO2 = Lattice.tetragonal(3.64, 5.27) p = HklPlane(1, 1, 1, ZrO2) n = p.normal() self.assertAlmostEqual(n[0], 0.635, 3) self.assertAlmostEqual(n[1], 0.635, 3) self.assertAlmostEqual(n[2], 0.439, 3)
def build_list(lattice=None, max_miller=3, extinction=None, Laue_extinction=False, max_keV=120.): hklplanes = [] indices = range(-max_miller, max_miller + 1) for h in indices: for k in indices: for l in indices: if h == k == l == 0: # skip (0, 0, 0) continue if not extinction : hklplanes.append(HklPlane(h, k, l, lattice)) if extinction == 'FCC': # take plane if only all odd or all even hkl indices if (h % 2 == 0 and k % 2 == 0 and l % 2 == 0) or (h % 2 == 1 and k % 2 == 1 and l % 2 == 1): hklplanes.append(HklPlane(h, k, l, lattice)) if extinction == 'BCC': # take plane only if the sum indices is even if ((h**2 + k**2 + l**2) % 2 == 0): hklplanes.append(HklPlane(h, k, l, lattice)) if Laue_extinction is True: lam_min = lambda_keV_to_nm(max_keV) val = 2. * lattice._lengths[0] / lam_min # lattice have to be cubic ! print 'Limit value is %d' % val for hkl in hklplanes: (h, k, l) = hkl.miller_indices() test = h ** 2 + k ** 2 + l ** 2 if val < test: # TODO check the test hklplanes.remove(HklPlane(h, k, l, lattice)) return hklplanes
def test_equality(self): p1 = HklPlane(1, 1, 1) p2 = HklPlane(1, 1, 1) p3 = HklPlane(-1, 1, 1) self.assertEqual(p1, p2) self.assertTrue(p1 == p2) self.assertTrue(p1 != p3)
def test_Bragg_condition(self): al = Lattice.from_symbol('Al') p = HklPlane(0, 0, 2, lattice=al) lambda_keV = 42 lambda_nm = lambda_keV_to_nm(lambda_keV) rod = [0.1449, -0.0281, 0.0616] o = Orientation.from_rodrigues(rod) (w1, w2) = o.dct_omega_angles(p, lambda_keV, verbose=False) # test the two solution of the rotating crystal for omega in (w1, w2): alpha = o.compute_XG_angle(p, omega, verbose=True) theta_bragg = p.bragg_angle(lambda_keV) self.assertAlmostEqual(alpha, 180 / np.pi * (np.pi / 2 - theta_bragg))
def test_topotomo_tilts(self): al = Lattice.from_symbol('Al') p = HklPlane(0, 0, 2, lattice=al) rod = [0.1449, -0.0281, 0.0616] o = Orientation.from_rodrigues(rod) (ut, lt) = o.topotomo_tilts(p) self.assertAlmostEqual(180 / np.pi * ut, 2.236, 3) self.assertAlmostEqual(180 / np.pi * lt, -16.615, 3) # use test case from AlLi_sam8_dct_cen_ p = HklPlane(2, 0, 2, lattice=al) rod = [0.0499, -0.3048, 0.1040] o = Orientation.from_rodrigues(rod) (ut, lt) = o.topotomo_tilts(p) self.assertAlmostEqual(180 / np.pi * ut, -11.04, 2) self.assertAlmostEqual(180 / np.pi * lt, 0.53, 2)
def dct_projection(orientations, data, dif_grains, omega, lambda_keV, detector, lattice, include_direct_beam=True, att=5, verbose=True): '''Work in progress, will replace function in the microstructure module.''' full_proj = np.zeros(detector.size, dtype=np.float) lambda_nm = lambda_keV_to_nm(lambda_keV) omegar = omega * np.pi / 180 R = np.array([[np.cos(omegar), -np.sin(omegar), 0], [np.sin(omegar), np.cos(omegar), 0], [0, 0, 1]]) if include_direct_beam: # add the direct beam part by computing the radiograph of the sample without the diffracting grains data_abs = np.where(data > 0, 1, 0) for (gid, (h, k, l)) in dif_grains: mask_dif = (data == gid) data_abs[mask_dif] = 0 # remove this grain from the absorption proj = radiograph(data_abs, omega) add_to_image(full_proj, proj[::-1, ::-1] / att, np.array(full_proj.shape) // 2) # add diffraction spots X = np.array([1., 0., 0.]) / lambda_nm for (gid, (h, k, l)) in dif_grains: grain_data = np.where(data == gid, 1, 0) if np.sum(grain_data) < 1: print('skipping grain %d' % gid) continue local_com = np.array(ndimage.measurements.center_of_mass(grain_data, data)) print('local center of mass (voxel): {0}'.format(local_com)) g_center_mm = detector.pixel_size * (local_com - 0.5 * np.array(data.shape)) print('center of mass (voxel): {0}'.format(local_com - 0.5 * np.array(data.shape))) print('center of mass (mm): {0}'.format(g_center_mm)) # compute scattering vector gt = orientations[gid].orientation_matrix().transpose() # gt = micro.get_grain(gid).orientation_matrix().transpose() p = HklPlane(h, k, l, lattice) G = np.dot(R, np.dot(gt, p.scattering_vector())) K = X + G # position of the grain at this rotation g_pos_rot = np.dot(R, g_center_mm) pg = detector.project_along_direction(K, g_pos_rot) (up, vp) = detector.lab_to_pixel(pg) if verbose: print('\n* gid=%d, (%d,%d,%d) plane, angle=%.1f' % (gid, h, k, l, omega)) print('diffraction vector:', K) print('postion of the grain at omega=%.1f is ' % omega, g_pos_rot) print('up=%d, vp=%d for plane (%d,%d,%d)' % (up, vp, h, k, l)) data_dif = grain_data[ndimage.find_objects(data == gid)[0]] proj_dif = radiograph(data_dif, omega) # (Y, Z) coordinate system add_to_image(full_proj, proj_dif[::-1, ::-1], (up, vp), verbose) # (u, v) axes correspond to (-Y, -Z) return full_proj
def test_indexation(self): """Verify indexing solution from a known Laue pattern.""" euler_angles = (191.9, 69.9, 138.9) # degrees, /!\ not in fz orientation = Orientation.from_euler(euler_angles) # list of plane normals, obtained from the detector image hkl_normals = np.array( [[0.11066932863248755, 0.8110118739480003, 0.5744667440465002], [0.10259261224575777, 0.36808036454584847, -0.9241166599236196], [0.12497400210731163, 0.38160000643453934, 0.9158400154428944], [0.21941448008210823, 0.5527234994434788, -0.8039614537359691], [0.10188581412204267, -0.17110594738052967, -0.9799704259066699], [0.10832511255237177, -0.19018912890874434, 0.975752922227471], [0.13621754927492466, -0.8942526135605741, 0.4263297343719016], [0.04704092862601945, -0.45245473334950004, -0.8905458243704446]]) miller_indices = [(3, -5, 0), (5, 4, -2), (2, -5, -1), (3, -4, -5), (2, -2, 3), (-3, 4, -3), (3, -4, 3), (3, -2, 3), (-5, 5, -1), (5, -5, 1)] hkl_planes = [] for indices in miller_indices: (h, k, l) = indices hkl_planes.append(HklPlane(h, k, l, self.ni)) solutions = index(hkl_normals, hkl_planes, tol_angle=0.5, tol_disorientation=3.0) final_orientation = Orientation(solutions[0]) angle, ax1, ax2 = final_orientation.disorientation( orientation, crystal_structure=Symmetry.cubic) self.assertLess(angle * 180 / np.pi, 1.0)
def test_select_lambda(self): """Verify the wavelength diffracted by a given hkl plane.""" orientation = Orientation.cube() hkl = HklPlane(-1, -1, -1, self.ni) (the_lambda, theta) = select_lambda(hkl, orientation) self.assertAlmostEqual(the_lambda, 5.277, 3) self.assertAlmostEqual(theta * 180 / np.pi, 35.264, 3)
def test_scattering_vector(self): Fe_fcc = Lattice.face_centered_cubic(0.287) # FCC iron hkl = HklPlane(2, 0, 0, Fe_fcc) Gc = hkl.scattering_vector() self.assertAlmostEqual(np.linalg.norm(Gc), 1 / hkl.interplanar_spacing()) Al_fcc = Lattice.face_centered_cubic(0.405) hkl = HklPlane(0, 0, 2, lattice=Al_fcc) Gc = hkl.scattering_vector() self.assertAlmostEqual(np.linalg.norm(Gc), 1 / hkl.interplanar_spacing())
def setup(self, omega_step, grain_ids=None): """Setup the forward simulation. :param float omega_step: the angular integration step (in degrees) use to compute the diffraction comditions. :param list grain_ids: a list of grain ids to restrict the forward simulation (use all grains by default). """ assert self.exp.source.min_energy == self.exp.source.max_energy # monochromatic case lambda_keV = self.exp.source.max_energy self.omegas = np.linspace(0.0, 360.0, num=int(360.0 / omega_step), endpoint=False) self.reflections = [] for omega in self.omegas: self.reflections.append([]) if grain_ids: # make a list of the grains selected for the forward simulation grains = [ self.exp.sample.microstructure.get_grain(gid) for gid in grain_ids ] else: grains = self.exp.sample.microstructure.grains for g in grains: for plane in self.hkl_planes: (h, k, i, l) = HklPlane.three_to_four_indices(*plane.miller_indices()) try: (w1, w2) = g.dct_omega_angles(plane, lambda_keV, verbose=False) except ValueError: if self.verbose: print( 'plane {} does not fulfil the Bragg condition for grain {:d}' .format((h, k, i, l), g.id)) continue # add angles for Friedel pairs w3 = (w1 + 180.) % 360 w4 = (w2 + 180.) % 360 if self.verbose and g.id == self.check: print( 'grain %d, angles for plane %d%d%d: w1=%.3f and w2=%.3f | delta=%.1f' % (g.id, h, k, l, w1, w2, w1 - w2)) print('(%3d, %3d, %3d, %3d) -- %6.2f & %6.2f' % (h, k, i, l, w1, w2)) self.reflections[int(w1 / omega_step)].append( [g.id, (h, k, l)]) self.reflections[int(w2 / omega_step)].append( [g.id, (h, k, l)]) self.reflections[int(w3 / omega_step)].append( [g.id, (-h, -k, -l)]) self.reflections[int(w4 / omega_step)].append( [g.id, (-h, -k, -l)])
def test_dct_omega_angles(self): # test with a BCC Titanium lattice lambda_keV = 30 lambda_nm = 1.2398 / lambda_keV a = 0.3306 # lattice parameter in nm Ti_bcc = Lattice.cubic(a) (h, k, l) = (0, 1, 1) hkl = HklPlane(h, k, l, lattice=Ti_bcc) o = Orientation.from_euler((103.517, 42.911, 266.452)) theta = hkl.bragg_angle(lambda_keV, verbose=False) gt = o.orientation_matrix( ) # our B (here called gt) corresponds to g^{-1} in Poulsen 2004 A = h * gt[0, 0] + k * gt[1, 0] + l * gt[2, 0] B = -h * gt[0, 1] - k * gt[1, 1] - l * gt[2, 1] C = -2 * a * np.sin( theta )**2 / lambda_nm # the minus sign comes from the main equation Delta = 4 * (A**2 + B**2 - C**2) self.assertEqual(Delta > 0, True) t1 = (B - 0.5 * np.sqrt(Delta)) / (A + C) t2 = (B + 0.5 * np.sqrt(Delta)) / (A + C) # verifying A cos(w) + B sin(w) = C:' for t in (t1, t2): x = A * (1 - t**2) / (1 + t**2) + B * 2 * t / (1 + t**2) self.assertAlmostEqual(x, C, 2) # verifying (A + C) * t**2 - 2 * B * t + (C - A) = 0' for t in (t1, t2): self.assertAlmostEqual((A + C) * t**2 - 2 * B * t + (C - A), 0.0, 2) (w1, w2) = o.dct_omega_angles(hkl, lambda_keV, verbose=False) self.assertAlmostEqual(w1, 196.709, 2) self.assertAlmostEqual(w2, 28.334, 2) # test with an FCC Aluminium-Lithium lattice a = 0.40495 # lattice parameter in nm Al_fcc = Lattice.face_centered_cubic(a) hkl = HklPlane(-1, 1, 1, Al_fcc) o = Orientation.from_rodrigues([0.0499, -0.3048, 0.1040]) w1, w2 = o.dct_omega_angles(hkl, 40, verbose=False) self.assertAlmostEqual(w1, 109.2, 1) self.assertAlmostEqual(w2, 296.9, 1)
def test_select_lambda(self): """Verify that the rotating crystal conditions correspond to the selected wave length diffracted after rotating the crystal in both positions.""" hkl_dif = HklPlane(2, 0, 2, self.al) lambda_keV = 40.0 w1, w2 = self.g4.dct_omega_angles(hkl_dif, lambda_keV, verbose=False) for omega in [w1, w2]: omegar = omega * np.pi / 180 R = np.array([[np.cos(omegar), -np.sin(omegar), 0], [np.sin(omegar), np.cos(omegar), 0], [0, 0, 1]]) o_rot = Orientation(np.dot(self.g4.orientation_matrix(), R.T)) self.assertAlmostEqual( select_lambda(hkl_dif, o_rot, verbose=False)[0], lambda_keV, 6)
def set_diffracting_famillies(self, hkl_list): """Set the list of diffracting hk planes using a set of families.""" symmetry = self.exp.get_sample().get_material().get_symmetry() hkl_planes = [] for hkl in hkl_list: # here we set include_friedel_pairs to False as we take it into account in the calculation planes = HklPlane.get_family(hkl, include_friedel_pairs=True, crystal_structure=symmetry) for plane in planes: # fix the lattice plane.set_lattice(self.exp.get_sample().get_material()) hkl_planes.extend(planes) self.set_hkl_planes(hkl_planes)
def test_110_normal_monoclinic(self): """Testing (110) plane normal in monoclinic crystal structure. This test comes from http://www.mse.mtu.edu/~drjohn/my3200/stereo/sg5.html corrected for a few errors in the html page. In this test, the lattice is defined with the c-axis aligned with the Z direction of the Cartesian frame. """ Mg2Si = Lattice.from_parameters(1.534, 0.405, 0.683, 90., 106., 90., x_aligned_with_a=False) a = Mg2Si.matrix[0] b = Mg2Si.matrix[1] c = Mg2Si.matrix[2] self.assertAlmostEqual(a[0], 1.475, 3) self.assertAlmostEqual(a[1], 0., 3) self.assertAlmostEqual(a[2], -0.423, 3) self.assertAlmostEqual(b[0], 0., 3) self.assertAlmostEqual(b[1], 0.405, 3) self.assertAlmostEqual(b[2], 0., 3) self.assertAlmostEqual(c[0], 0., 3) self.assertAlmostEqual(c[1], 0., 3) self.assertAlmostEqual(c[2], 0.683, 3) p = HklPlane(1, 1, 1, Mg2Si) Gc = p.scattering_vector() self.assertAlmostEqual(Gc[0], 1.098, 3) self.assertAlmostEqual(Gc[1], 2.469, 3) self.assertAlmostEqual(Gc[2], 1.464, 3) self.assertAlmostEqual(p.interplanar_spacing(), 0.325, 3) Ghkl = np.dot(Mg2Si.matrix, Gc) self.assertEqual(Ghkl[0], 1.) # h self.assertEqual(Ghkl[1], 1.) # k self.assertEqual(Ghkl[2], 1.) # l
def test_gnomonic_projection_point(self): """Verify that the gnomonic projection of two diffracted points on a detector give access to the angle between the lattice plane normals.""" olivine = Lattice.orthorhombic( 1.022, 0.596, 0.481) # nm Barret & Massalski convention orientation = Orientation.cube() p1 = HklPlane(2, 0, -3, olivine) p2 = HklPlane(3, -1, -3, olivine) detector = RegArrayDetector2d(size=(512, 512), u_dir=[0, -1, 0], v_dir=[0, 0, -1]) detector.pixel_size = 0.200 # mm, 0.1 mm with factor 2 binning detector.ucen = 235 detector.vcen = 297 detector.ref_pos = np.array([131., 0., 0.]) + \ (detector.size[0] / 2 - detector.ucen) * detector.u_dir * detector.pixel_size + \ (detector.size[1] / 2 - detector.vcen) * detector.v_dir * detector.pixel_size # mm angle = 180 / np.pi * np.arccos(np.dot(p1.normal(), p2.normal())) # test the gnomonic projection for normal and not normal X-ray incidence for ksi in [0.0, 1.0]: # deg Xu = np.array( [np.cos(ksi * np.pi / 180), 0., np.sin(ksi * np.pi / 180)]) OC = detector.project_along_direction( Xu ) # C is the intersection of the direct beam with the detector K1 = diffracted_vector(p1, orientation, Xu=Xu) K2 = diffracted_vector(p2, orientation, Xu=Xu) R1 = detector.project_along_direction(K1, origin=[0., 0., 0.]) R2 = detector.project_along_direction(K2, origin=[0., 0., 0.]) OP1 = gnomonic_projection_point(R1, OC=OC)[0] OP2 = gnomonic_projection_point(R2, OC=OC)[0] hkl_normal1 = OP1 / np.linalg.norm(OP1) hkl_normal2 = (OP2 / np.linalg.norm(OP2)) # the projection must give the normal to the diffracting plane for i in range(3): self.assertAlmostEqual(hkl_normal1[i], p1.normal()[i], 6) self.assertAlmostEqual(hkl_normal2[i], p2.normal()[i], 6) angle_gp = 180 / np.pi * np.arccos(np.dot(hkl_normal1, hkl_normal2)) self.assertAlmostEqual(angle, angle_gp, 6)
def plot_all_dif_spots(gid, detector, hkl_miller=None, uv=None, lattice=None, lambda_keV=None, spots=True, dif_spots_image=None, max_value=None, positions=True, debye_rings=True, suffix=''): plt.figure(figsize=(13, 8)) # plt.figure() if spots and dif_spots_image is not None: if not max_value: max_value = dif_spots_image.max() plt.imshow(dif_spots_image.T, cmap=cm.gray, vmin=0, vmax=max_value) families = [] indices = [] colors = 'crbgmy' # crbgmycrbgmycrbgmycrbgmy' # use a cycler here t = np.linspace(0.0, 2 * np.pi, num=37) if positions or debye_rings: if hkl_miller is None: raise ValueError( 'The list of miller indices of each reflection must be provided using variable g_hkl_miller') for i, (h, k, l) in enumerate(hkl_miller): l = [abs(h), abs(k), abs(l)] # l.sort() # miller indices are now sorted, should use the lattice symmetry here family_name = '%d%d%d' % (l[0], l[1], l[2]) if families.count(family_name) == 0: families.append(family_name) indices.append(i) indices.append(len(hkl_miller)) print(families, indices) # now plot each family for i in range(len(families)): family = families[i] c = colors[i % len(colors)] if positions and uv is not None: plt.plot(uv[indices[i]:indices[i + 1], 0], uv[indices[i]:indices[i + 1], 1], 's', color=c, label=family) if debye_rings and lattice is not None and lambda_keV: theta = HklPlane(int(family[0]), int(family[1]), int(family[2]), lattice).bragg_angle(lambda_keV) L = detector.ref_pos[0] / detector.pixel_size * np.tan(2 * theta) # 2 theta distance on the detector print('2theta = %g, L = %g' % (2 * theta * 180 / np.pi, L)) plt.plot(0.5 * detector.size[0] + L * np.cos(t), 0.5 * detector.size[1] + L * np.sin(t), '--', color=c) plt.title('grain %d diffraction spot locations on the detector' % gid) # plt.legend(numpoints=1, loc='center') plt.legend(numpoints=1, ncol=2, bbox_to_anchor=(1.02, 1), loc=2) # plt.axis('equal') plt.axis([0, 2047, 2047, 0]) plt.savefig('g%d%s_difspot_positions.pdf' % (gid, suffix))
def setup(self, omega_step): """Setup the forward simulation.""" assert self.exp.source.min_energy == self.exp.source.max_energy # monochromatic case lambda_keV = self.exp.source.max_energy self.omegas = np.linspace(0.0, 360.0, num=int(360.0 / omega_step), endpoint=False) self.reflections = [] for omega in self.omegas: self.reflections.append([]) for g in self.exp.sample.microstructure.grains: for plane in self.hkl_planes: (h, k, i, l) = HklPlane.three_to_four_indices(*plane.miller_indices()) try: (w1, w2) = g.dct_omega_angles(plane, lambda_keV, verbose=False) except ValueError: if self.verbose: print( 'plane {} does not fulfil the Bragg condition for grain {:d}' .format((h, k, i, l), g.id)) continue # add angles for Friedel pairs w3 = (w1 + 180.) % 360 w4 = (w2 + 180.) % 360 if self.verbose and g.id == self.check: print( 'grain %d, angles for plane %d%d%d: w1=%.3f and w2=%.3f | delta=%.1f' % (g.id, h, k, l, w1, w2, w1 - w2)) print('(%3d, %3d, %3d, %3d) -- %6.2f & %6.2f' % (h, k, i, l, w1, w2)) self.reflections[int(w1 / omega_step)].append( [g.id, (h, k, l)]) self.reflections[int(w2 / omega_step)].append( [g.id, (h, k, l)]) self.reflections[int(w3 / omega_step)].append( [g.id, (-h, -k, -l)]) self.reflections[int(w4 / omega_step)].append( [g.id, (-h, -k, -l)])
def test_scattering_vector_th(self): """ compute the scattering vector using the formal definition and compare it with the components obtained using the reciprocal lattice. The formulae are available in the Laue Atlas p61, one typo in Eq. 6.1 was corrected. """ (a, b, c) = self.hex._lengths (alpha, beta, gamma) = np.radians(self.hex._angles) delta = pi / 2 - gamma chi = gamma - atan( (cos(alpha) - cos(gamma) * cos(beta)) / (cos(beta) * cos(delta))) epsilon = pi / 2 - acos( (cos(alpha) + cos(beta)) / (cos(chi) + cos(gamma - chi))) psi = acos(sin(epsilon) * cos(delta + chi)) for (hp, kp, lp) in [(1, 1, 1), [1, 2, 0]]: # compute the h, k, l in the Cartesian coordinate system h = hp / a k = (a / hp - b / kp * cos(gamma)) / (a / hp * b / kp * cos(delta)) l = (lp / c - hp / a * cos(beta) - kp / b * cos(psi)) / cos(epsilon) Gc = HklPlane(hp, kp, lp, self.hex).scattering_vector() self.assertAlmostEqual(Gc[0], h, 7) self.assertAlmostEqual(Gc[1], k, 7) self.assertAlmostEqual(Gc[2], l, 7)
def fsim_grain(self, gid=1): self.grain = self.exp.get_sample().get_microstructure().get_grain(gid) sample = self.exp.get_sample() lattice = sample.get_microstructure().get_lattice() source = self.exp.get_source() detector = self.exp.get_active_detector() data = np.zeros_like(detector.data) if self.verbose: print('Forward Simulation for grain %d' % self.grain.id) sample.geo.discretize_geometry(grain_id=self.grain.id) # we use either the hkl planes for this grain or the ones defined for the whole simulation if hasattr(self.grain, 'hkl_planes') and len(self.grain.hkl_planes) > 0: print('using hkl from the grain') hkl_planes = [ HklPlane(h, k, l, lattice) for (h, k, l) in self.grain.hkl_planes ] else: if len(self.hkl_planes) == 0: print( 'warning: no reflection defined for this simulation, using all planes with max miller=%d' % self.max_miller) self.set_hkl_planes( build_list(lattice=lattice, max_miller=self.max_miller)) hkl_planes = self.hkl_planes n_hkl = len(hkl_planes) positions = sample.geo.get_positions( ) # size n_vox, with 3 elements items n_vox = len(positions) Xu_vectors, thetas, the_energies, X_vectors, K_vectors = LaueForwardSimulation.fsim_laue( self.grain.orientation, hkl_planes, positions, source.position) OR_vectors = [ detector.project_along_direction( origin=positions[i_vox], direction=K_vectors[i_vox * n_hkl + i_hkl]) for i_vox in range(n_vox) for i_hkl in range(n_hkl) ] # size nb_vox * n_hkl uv = [detector.lab_to_pixel(OR)[0].astype(np.int) for OR in OR_vectors] # now construct a boolean list to select the diffraction spots if source.min_energy is None and source.max_energy is None: # TODO use the use_energy_limits attribute energy_in = [True for k in range(len(the_energies))] else: energy_in = [ source.min_energy < the_energies[k] < source.max_energy for k in range(len(the_energies)) ] uv_in = [ 0 < uv[k][0] < detector.get_size_px()[0] and 0 < uv[k][1] < detector.get_size_px()[1] for k in range(len(uv)) ] # size n, diffraction located on the detector spot_in = [uv_in[k] and energy_in[k] for k in range(len(uv))] if self.verbose: print('%d diffraction events on the detector among %d' % (sum(spot_in), len(uv))) # now sum the counts on the detector individual pixels for k in range(len(uv)): if spot_in[k]: data[uv[k][0], uv[k][1]] += 1 return data
def test_bragg_angle(self): l = Lattice.cubic(0.287) # FCC iron hkl = HklPlane(2, 0, 0, l) # 200 reflection at 8 keV is at 32.7 deg self.assertAlmostEqual(hkl.bragg_angle(8), 0.5704164)
base_name = os.path.splitext(__file__)[0] s3d = Scene3D(display=False, ren_size=(800, 800), name=base_name, background=black) # create a python Grain object from the image data orientation = Orientation.from_rodrigues(np.array([0.3889, -0.0885, 0.3268])) grain = Grain(1, orientation) grain_data = HST_read(im_file, header_size=0, autoparse_filename=True, verbose=True) grain.position = ndimage.measurements.center_of_mass(grain_data, grain_data) print 'grain position:', grain.position grain.volume = ndimage.measurements.sum(grain_data) # label is 1.0 here grain.add_vtk_mesh(grain_data, contour=False) print 'adding bounding box' grain_bbox = box_3d(size=np.shape(grain_data), line_color=white) print 'adding grain with slip planes' hklplanes = [HklPlane(1, 1, 1)] grain_with_planes = grain_3d(grain, hklplanes, show_normal=False, \ plane_opacity=1.0, show_orientation=True) tr = vtk.vtkTransform() tr.Translate(grain.position) grain_with_planes.SetUserTransform(tr) print 'adding a lattice to picture the grain orientation' lat_size = 20 l = Lattice.face_centered_cubic(lat_size) cubic = lattice_3d_with_planes(l, hklplanes, crystal_orientation=grain.orientation, \ show_normal=True, plane_opacity=1.0, origin='mid', sphereColor=grey, sphereRadius=0.1) apply_translation_to_actor(cubic, (lat_size, lat_size, lat_size)) print 'adding axes'
def test_SchimdFactor(self): o = Orientation.from_euler([0., 0., 0.]) ss = SlipSystem(HklPlane(1, 1, 1), HklDirection(0, 1, -1)) self.assertAlmostEqual(o.schmid_factor(ss), 0.4082, 4)
im_file = os.path.join(data_dir, scan) print('create a python Grain object') orientation = Orientation.from_rodrigues(np.array([0.3889, -0.0885, 0.3268])) grain = Grain(1, orientation) grain_data = HST_read(im_file, autoparse_filename=True, verbose=True) grain.position = ndimage.measurements.center_of_mass(grain_data, grain_data) grain.volume = ndimage.measurements.sum(grain_data) # label is 1.0 here grain.add_vtk_mesh(grain_data, contour=False) # grain.save_vtk_repr() # save the grain mesh in vtk format print('adding bounding box') grain_bbox = box_3d(size=np.shape(grain_data), line_color=white) print('adding grain with slip planes') p1 = HklPlane(1, 1, 1) p2 = HklPlane(1, 1, -1) hklplanes = [p1] grain_with_planes = add_grain_to_3d_scene(grain, hklplanes, show_orientation=True) tr = vtk.vtkTransform() tr.Translate(grain.position) grain_with_planes.SetUserTransform(tr) print('creating 3d renderer') ren = vtk.vtkRenderer() ren.SetBackground(0.0, 0.0, 0.0) ren.AddActor(grain_bbox) ren.AddActor(grain_with_planes) cam = setup_camera(size=np.shape(grain_data)) cam.Dolly(0.9) ren.SetActiveCamera(cam)
def fsim_grain(self, gid=1): self.grain = self.exp.get_sample().get_microstructure().get_grain(gid) sample = self.exp.get_sample() lattice = sample.get_microstructure().get_lattice() source = self.exp.get_source() detector = self.exp.get_active_detector() data = np.zeros_like(detector.data) if self.verbose: print('Forward Simulation for grain %d' % self.grain.id) sample.geo.discretize_geometry(grain_id=self.grain.id) # we use either the hkl planes for this grain or the ones defined for the whole simulation if hasattr(self.grain, 'hkl_planes') and len(self.grain.hkl_planes) > 0: print('using hkl from the grain') hkl_planes = [ HklPlane(h, k, l, lattice) for (h, k, l) in self.grain.hkl_planes ] else: if len(self.hkl_planes) == 0: print( 'warning: no reflection defined for this simulation, using all planes with max miller=%d' % self.max_miller) self.set_hkl_planes( build_list(lattice=lattice, max_miller=self.max_miller)) hkl_planes = self.hkl_planes n_hkl = len(hkl_planes) gt = self.grain.orientation_matrix().transpose() # here we use list comprehension to avoid for loops d_spacings = [hkl.interplanar_spacing() for hkl in hkl_planes] # size n_hkl G_vectors = [hkl.scattering_vector() for hkl in hkl_planes ] # size n_hkl, with 3 elements items Gs_vectors = [gt.dot(Gc) for Gc in G_vectors] # size n_hkl, with 3 elements items positions = sample.geo.get_positions( ) # size n_vox, with 3 elements items n_vox = len(positions) # total number of discrete positions Xu_vectors = [ (pos - source.position) / np.linalg.norm(pos - source.position) for pos in positions ] # size n_vox thetas = [ np.arccos(np.dot(Xu, Gs / np.linalg.norm(Gs))) - np.pi / 2 for Xu in Xu_vectors for Gs in Gs_vectors ] # size n_vox * n_hkl the_energies = [ lambda_nm_to_keV(2 * d_spacings[i_hkl] * np.sin(thetas[i_Xu * n_hkl + i_hkl])) for i_Xu in range(n_vox) for i_hkl in range(n_hkl) ] # size n_vox * n_hkl X_vectors = [ np.array(Xu_vectors[i_Xu]) / 1.2398 * the_energies[i_Xu * n_hkl + i_hkl] for i_Xu in range(n_vox) for i_hkl in range(n_hkl) ] # size n_vox * n_hkl K_vectors = [ X_vectors[i_Xu * n_hkl + i_hkl] + Gs_vectors[i_hkl] for i_Xu in range(n_vox) for i_hkl in range(n_hkl) ] # size n_vox * n_hkl OR_vectors = [ detector.project_along_direction( origin=positions[i_vox], direction=K_vectors[i_vox * n_hkl + i_hkl]) for i_vox in range(n_vox) for i_hkl in range(n_hkl) ] # size nb_vox * n_hkl uv = [detector.lab_to_pixel(OR)[0].astype(np.int) for OR in OR_vectors] # now construct a boolean list to select the diffraction spots if source.min_energy is None and source.max_energy is None: # TODO use the use_energy_limits attribute energy_in = [True for k in range(len(the_energies))] else: energy_in = [ source.min_energy < the_energies[k] < source.max_energy for k in range(len(the_energies)) ] uv_in = [ 0 < uv[k][0] < detector.get_size_px()[0] and 0 < uv[k][1] < detector.get_size_px()[1] for k in range(len(uv)) ] # size n, diffraction located on the detector spot_in = [uv_in[k] and energy_in[k] for k in range(len(uv))] if self.verbose: print('%d diffraction events on the detector among %d' % (sum(spot_in), len(uv))) # now sum the counts on the detector individual pixels for k in range(len(uv)): if spot_in[k]: data[uv[k][0], uv[k][1]] += 1 return data
Hkl planes are added to the lattice and displayed. ''' # Create the Renderer and RenderWindow ren = vtk.vtkRenderer() ren.SetBackground(white) # crystal orientation o = Orientation.from_euler((15.0, -45.0, 0.0)) o = None # hexagonal lattice a = 1.0 # 0.321 # nm c = 1.5 # 0.521 # nm l = Lattice.hexagonal(a, c) grid = hexagonal_lattice_grid(l) print(np.array(HklPlane.four_to_three_indices(1, 0, -1, 0)) / 0.6) # prismatic 1 print(np.array(HklPlane.four_to_three_indices(0, 1, -1, 0)) / 0.6) # prismatic 2 print(np.array(HklPlane.four_to_three_indices(0, 1, -1, 1)) / 0.6 * 3) # pyramidal 1 print(np.array(HklPlane.four_to_three_indices(1, 1, -2, 2)) / 0.6 * 3) # pyramidal 2 print(np.array(HklPlane.four_to_three_indices(0, 0, 0, 1))) # basal p1 = HklPlane(2., 1, 0, lattice=l) # attach the plane to the hexagonal lattice p2 = HklPlane(-1, 2, 0, lattice=l) p3 = HklPlane(-3., 6., 5., lattice=l) p4 = HklPlane(3, 9, 10, lattice=l) p5 = HklPlane(0, 0, 1, lattice=l) # basal hklplanes = [p3, p5] hexagon = vtk.vtkAssembly()
def plot_sst(self, ax=None, mk='s', ann=False): """ Create the inverse pole figure in the unit standard triangle. :param ax: a reference to a pyplot ax to draw the poles. :param mk: marker used to plot the poles (square by default). :param bool ann: Annotate the pole with the coordinates of the vector if True (False by default). """ # first draw the boundary of the symmetry domain limited by 3 hkl plane normals, called here A, B and C symmetry = self.lattice._symmetry if symmetry is Symmetry.cubic: sst_poles = [(0, 0, 1), (1, 0, 1), (1, 1, 1)] ax.axis([-0.05, 0.45, -0.05, 0.40]) elif symmetry is Symmetry.hexagonal: sst_poles = [(0, 0, 1), (2, -1, 0), (1, 0, 0)] ax.axis([-0.05, 1.05, -0.05, 0.6]) else: print('unssuported symmetry: %s' % symmetry) A = HklPlane(*sst_poles[0], lattice=self.lattice) B = HklPlane(*sst_poles[1], lattice=self.lattice) C = HklPlane(*sst_poles[2], lattice=self.lattice) self.plot_line_between_crystal_dir(A.normal(), B.normal(), ax=ax, col='k') self.plot_line_between_crystal_dir(B.normal(), C.normal(), ax=ax, col='k') self.plot_line_between_crystal_dir(C.normal(), A.normal(), ax=ax, col='k') # display the 3 crystal axes poles = [A, B, C] v_align = ['top', 'top', 'bottom'] for i in range(3): hkl = poles[i] c_dir = hkl.normal() c = c_dir + self.z c /= c[2] # SP'/SP = r/z with r=1 pole_str = '%d%d%d' % hkl.miller_indices() if symmetry is Symmetry.hexagonal: pole_str = '%d%d%d%d' % HklPlane.three_to_four_indices( *hkl.miller_indices()) ax.annotate(pole_str, (c[0], c[1] - (2 * (i < 2) - 1) * 0.01), xycoords='data', fontsize=12, horizontalalignment='center', verticalalignment=v_align[i]) # now plot the sample axis for grain in self.microstructure.grains: # move to the fundamental zone g = grain.orientation_matrix() # compute axis and apply SST symmetry if self.axis == 'Z': axis = self.z elif self.axis == 'Y': axis = self.y else: axis = self.x axis_rot = self.sst_symmetry(g.dot(axis)) label = '' if self.map_field == 'grain_id': label = 'grain ' + str(grain.id) self.plot_crystal_dir(axis_rot, mk=mk, col=self.get_color_from_field(grain), ax=ax, ann=ann, lab=label) if self.verbose: print('plotting %s in crystal CS: %s' % (self.axis, axis_rot)) ax.axis('off') ax.set_title('%s-axis SST inverse %s projection' % (self.axis, self.proj))
verbose=True) grain.position = ndimage.measurements.center_of_mass(grain_data, grain_data) print('grain position: %s' % str(grain.position)) grain.volume = ndimage.measurements.sum(grain_data) # label is 1.0 here grain.add_vtk_mesh(grain_data, contour=False) print('adding bounding box') grain_bbox = box_3d(size=np.shape(grain_data), line_color=white) print('adding grain with slip planes') z_offsets = np.linspace(-50, 50, 6, endpoint=True) print(z_offsets) plane_origins = np.zeros((len(z_offsets), 3), dtype=float) plane_origins[:, 2] = z_offsets hkl_planes = [HklPlane(1, 1, 1)] * len(z_offsets) grain_with_planes = grain_3d(grain, hkl_planes, plane_origins=plane_origins, show_normal=False, \ plane_opacity=1.0, show_orientation=False) tr = vtk.vtkTransform() tr.Translate(grain.position) grain_with_planes.SetUserTransform(tr) print('adding axes') axes = axes_actor(length=100, fontSize=60) axes.GetXAxisCaptionActor2D().GetCaptionTextProperty().SetColor(grey) print('setting up camera') cam = setup_camera(size=np.shape(grain_data)) cam.Dolly(0.9) # add all actors to the 3d scene and render
def dct_projection(self, omega, include_direct_beam=True, att=5): """Function to compute a full DCT projection at a given omega angle. :param float omega: rotation angle in degrees. :param bool include_direct_beam: flag to compute the transmission through the sample. :param float att: an attenuation factor used to limit the gray levels in the direct beam. :return: the dct projection as a 2D numpy array """ if len(self.reflections) == 0: print( 'empty list of reflections, you should run the setup function first' ) return None grain_ids = self.exp.get_sample().get_grain_ids() detector = self.exp.get_active_detector() lambda_keV = self.exp.source.max_energy lattice = self.exp.get_sample().get_material() index = np.argmax(self.omegas > omega) dif_grains = self.reflections[ index - 1] # grains diffracting between omegas[index - 1] and omegas[index] # intialize image result full_proj = np.zeros(detector.get_size_px(), dtype=np.float) lambda_nm = lambda_keV_to_nm(lambda_keV) omegar = omega * np.pi / 180 R = np.array([[np.cos(omegar), -np.sin(omegar), 0], [np.sin(omegar), np.cos(omegar), 0], [0, 0, 1]]) if include_direct_beam: # add the direct beam part by computing the radiograph of the sample without the diffracting grains data_abs = np.where(grain_ids > 0, 1, 0) for (gid, (h, k, l)) in dif_grains: mask_dif = (grain_ids == gid) data_abs[mask_dif] = 0 # remove this grain from the absorption proj = radiograph( data_abs, omega )[:, ::-1] # (u, v) axes correspond to (Y, -Z) for DCT detector add_to_image(full_proj, proj / att, np.array(full_proj.shape) // 2) # add diffraction spots X = np.array([1., 0., 0.]) / lambda_nm for (gid, (h, k, l)) in dif_grains: grain_data = np.where(grain_ids == gid, 1, 0) if np.sum(grain_data) < 1: print('skipping grain %d' % gid) continue local_com = np.array( ndimage.measurements.center_of_mass(grain_data, grain_ids)) print('local center of mass (voxel): {0}'.format(local_com)) g_center_mm = detector.get_pixel_size() * ( local_com - 0.5 * np.array(grain_ids.shape)) print('center of mass (voxel): {0}'.format( local_com - 0.5 * np.array(grain_ids.shape))) print('center of mass (mm): {0}'.format(g_center_mm)) # compute scattering vector gt = self.exp.get_sample().get_microstructure().get_grain( gid).orientation_matrix().transpose() p = HklPlane(h, k, l, lattice) G = np.dot(R, np.dot(gt, p.scattering_vector())) K = X + G # position of the grain at this rotation angle g_pos_rot = np.dot(R, g_center_mm) pg = detector.project_along_direction(K, g_pos_rot) up, vp = detector.lab_to_pixel(pg)[0] if self.verbose: print('\n* gid=%d, (%d,%d,%d) plane, angle=%.1f' % (gid, h, k, l, omega)) print('diffraction vector:', K) print('postion of the grain at omega=%.1f is ' % omega, g_pos_rot) print('up=%d, vp=%d for plane (%d,%d,%d)' % (up, vp, h, k, l)) data_dif = grain_data[ndimage.find_objects(grain_ids == gid)[0]] proj_dif = radiograph(data_dif, omega) # (Y, Z) coordinate system add_to_image(full_proj, proj_dif[:, ::-1], (up, vp), self.verbose) # (u, v) axes correspond to (Y, -Z) return full_proj
def test_multiplicity(self): """Int Tables of Crystallography Vol. 1 p 32.""" self.assertEqual( HklPlane(1, 0, 0).multiplicity(symmetry=Symmetry.cubic), 6) for h in range(1, 4): self.assertEqual( HklPlane(h, 0, 0).multiplicity(symmetry=Symmetry.tetragonal), 4) self.assertEqual( HklPlane(0, h, 0).multiplicity(symmetry=Symmetry.tetragonal), 4) self.assertEqual( HklPlane(h, h, 0).multiplicity(symmetry=Symmetry.tetragonal), 4) self.assertEqual( HklPlane(-h, h, 0).multiplicity(symmetry=Symmetry.tetragonal), 4) self.assertEqual( HklPlane(h, h, 1).multiplicity(symmetry=Symmetry.tetragonal), 8) self.assertEqual( HklPlane(-h, h, 1).multiplicity(symmetry=Symmetry.tetragonal), 8) self.assertEqual( HklPlane(0, 0, 1).multiplicity(symmetry=Symmetry.tetragonal), 2) self.assertEqual( HklPlane(1, 0, 2).multiplicity(symmetry=Symmetry.tetragonal), 8) self.assertEqual( HklPlane(-1, 0, 2).multiplicity(symmetry=Symmetry.tetragonal), 8) self.assertEqual( HklPlane(0, 1, 2).multiplicity(symmetry=Symmetry.tetragonal), 8) self.assertEqual( HklPlane(0, -1, 2).multiplicity(symmetry=Symmetry.tetragonal), 8) self.assertEqual( HklPlane(1, 2, 0).multiplicity(symmetry=Symmetry.tetragonal), 8) self.assertEqual( HklPlane(-1, 2, 0).multiplicity(symmetry=Symmetry.tetragonal), 8) self.assertEqual( HklPlane(1, 2, 3).multiplicity(symmetry=Symmetry.tetragonal), 16)