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 grain_projection_simulation(self, gid=1): """Function to compute all the grain projection in DCT geometry and create a composite image. :param int gid: the id of the grain to project (1 default). """ print('forward simulation of grain %d' % gid) detector = self.exp.get_active_detector() lambda_keV = self.exp.source.max_energy lambda_nm = lambda_keV_to_nm(lambda_keV) X = np.array([1., 0., 0.]) / lambda_nm lattice = self.exp.get_sample().get_material() if not hasattr(self, 'grain'): # load the corresponding grain self.load_grain(gid=gid) # compute all the omega values print('simulating diffraction spot positions on the detector') omegas = np.zeros(2 * len(self.hkl_planes)) g_uv = np.zeros((2, 2 * len(self.hkl_planes))) for i, plane in enumerate(self.hkl_planes): #print(plane.miller_indices()) try: w1, w2 = self.grain.dct_omega_angles(plane, lambda_keV, verbose=False) except ValueError: # plane does not fulfil the Bragg condition continue omegas[2 * i] = w1 omegas[2 * i + 1] = w2 for j in range(2): omega = omegas[2 * i + j] 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]]) gt = self.grain.orientation_matrix().transpose() G = np.dot(R, np.dot(gt, plane.scattering_vector())) K = X + G # position of the grain at this rotation angle g_pos_rot = np.dot(R, self.grain.center) pg = detector.project_along_direction(K, g_pos_rot) (up, vp) = detector.lab_to_pixel(pg)[0] g_uv[:, 2 * i + j] = up, vp # check detector flips hor_flip = np.dot(detector.u_dir, [0, -1, 0]) < 0 ver_flip = np.dot(detector.v_dir, [0, 0, -1]) < 0 if self.verbose: print(detector.u_dir) print(detector.v_dir) print('detector horizontal flip: %s' % hor_flip) print('detector vertical flip: %s' % ver_flip) # compute the projections stack_sim = self.grain_projections(omegas, gid, hor_flip=hor_flip, ver_flip=ver_flip) return self.grain_projection_image(g_uv, stack_sim)
def grain_projection_simulation(self, gid=1, data=None): """Function to compute all the grain projection in DCT geometry and create a composite image.""" print('forward simulation of grain %d' % gid) detector = self.exp.get_active_detector() lambda_keV = self.exp.source.max_energy lambda_nm = lambda_keV_to_nm(lambda_keV) X = np.array([1., 0., 0.]) / lambda_nm lattice = self.exp.get_sample().get_material() if not hasattr(self, 'grain'): # load the corresponding grain self.load_grain(gid=gid) from scipy import ndimage if data is None: grain_ids = self.exp.get_sample().get_grain_ids() print('binarizing grain %d' % gid) data = np.where( grain_ids[ndimage.find_objects(grain_ids == gid)[0]] == gid, 1, 0) print('shape of binary grain is {}'.format(data.shape)) # compute all the omega values print('simulating diffraction spot positions on the detector') omegas = np.zeros(2 * len(self.hkl_planes)) g_uv = np.zeros((2, 2 * len(self.hkl_planes))) for i, plane in enumerate(self.hkl_planes): #print(plane.miller_indices()) try: w1, w2 = self.grain.dct_omega_angles(plane, lambda_keV, verbose=False) except ValueError: # plane does not fulfil the Bragg condition continue omegas[2 * i] = w1 omegas[2 * i + 1] = w2 for j in range(2): omega = omegas[2 * i + j] 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]]) gt = self.grain.orientation_matrix().transpose() G = np.dot(R, np.dot(gt, plane.scattering_vector())) K = X + G # position of the grain at this rotation angle g_pos_rot = np.dot(R, self.grain.center) pg = detector.project_along_direction(K, g_pos_rot) (up, vp) = detector.lab_to_pixel(pg)[0] g_uv[:, 2 * i + j] = up, vp # build the 3d projection stack at once print('building grain projections stack') stack_sim = radiographs(data, omegas) stack_sim = stack_sim.transpose( 2, 0, 1)[:, :, :: -1] # (u, v) axes correspond to (Y, -Z) for DCT detector return self.grain_projection_image(g_uv, stack_sim)
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 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 compute_Laue_pattern(orientation, detector, hkl_planes=None, Xu=np.array([1., 0., 0.]), use_friedel_pair=False, spectrum=None, spectrum_thr=0., r_spot=5, color_field='constant', inverted=False, show_direct_beam=False, verbose=False): """ Compute a transmission Laue pattern. The data array of the given `Detector2d` instance is initialized with the result. The incident beam is assumed to be along the X axis: (1, 0, 0) but can be changed to any direction. The crystal can have any orientation using an instance of the `Orientation` class. The `Detector2d` instance holds all the geometry (detector size and position). A parameter controls the meaning of the values in the diffraction spots in the image. It can be just a constant value, the diffracted beam energy (in keV) or the intensity as computed by the :py:meth:`diffracted_intensity` method. :param orientation: The crystal orientation. :param detector: An instance of the Detector2d class. :param list hkl_planes: A list of the lattice planes to include in the pattern. :param Xu: The unit vector of the incident X-ray beam (default along the X-axis). :param bool use_friedel_pair: also consider the Friedel pair of each lattice plane in the list as candidate for diffraction. :param spectrum: A two columns array of the spectrum to use for the calculation. :param float spectrum_thr: The threshold to use to determine if a wave length is contributing or not. :param int r_spot: Size of the spots on the detector in pixel (5 by default) :param str color_field: a traing describing, must be 'constant', 'energy' or 'intensity' :param bool inverted: A flag to control if the pattern needs to be inverted. :param bool show_direct_beam: A flag to control if the direct beam is shown. :param bool verbose: activate verbose mode (False by default). :return: the computed pattern as a numpy array. """ detector.data = np.zeros(detector.size, dtype=np.float32) # create a small square image for one spot spot = np.ones((2 * r_spot + 1, 2 * r_spot + 1), dtype=np.uint8) max_val = np.iinfo(np.uint8).max # 255 here direct_beam_lab = detector.project_along_direction(Xu) direct_beam_pix = detector.lab_to_pixel(direct_beam_lab)[0] if show_direct_beam: add_to_image(detector.data, max_val * 3 * spot, (direct_beam_pix[0], direct_beam_pix[1])) if spectrum is not None: print('using spectrum') #indices = np.argwhere(spectrum[:, 1] > spectrum_thr) E_min = min(spectrum) #float(spectrum[indices[0], 0]) E_max = max(spectrum) #float(spectrum[indices[-1], 0]) lambda_min = lambda_keV_to_nm(E_max) lambda_max = lambda_keV_to_nm(E_min) #if verbose: print('energy bounds: [{0:.1f}, {1:.1f}] keV'.format(E_min, E_max)) for hkl in hkl_planes: (the_energy, theta) = select_lambda(hkl, orientation, Xu=Xu, verbose=False) if the_energy < 0: if use_friedel_pair: if verbose: print('switching to Friedel pair') hkl = hkl.friedel_pair() (the_energy, theta) = select_lambda(hkl, orientation, Xu=Xu, verbose=False) else: continue assert(the_energy >= 0) if spectrum is not None: if the_energy < E_min or the_energy > E_max: #print('skipping reflection {0:s} which would diffract at {1:.1f}'.format(hkl.miller_indices(), abs(the_energy))) continue #print('including reflection {0:s} which will diffract at {1:.1f}'.format(hkl.miller_indices(), abs(the_energy))) K = diffracted_vector(hkl, orientation, Xu=Xu, use_friedel_pair=False, verbose=verbose) if K is None or np.dot(Xu, K) == 0: continue # skip diffraction // to the detector d = np.dot((detector.ref_pos - np.array([0., 0., 0.])), detector.w_dir) / np.dot(K, detector.w_dir) if d < 0: if verbose: print('skipping diffraction not towards the detector') continue R = detector.project_along_direction(K, origin=[0., 0., 0.]) (u, v) = detector.lab_to_pixel(R)[0] if verbose and u >= 0 and u < detector.size[0] and v >= 0 and v < detector.size[1]: print('* %d%d%d reflexion' % hkl.miller_indices()) print('diffracted beam will hit the detector at (%.3f, %.3f) mm or (%d, %d) pixels' % (R[1], R[2], u, v)) print('diffracted beam energy is {0:.1f} keV'.format(abs(the_energy))) print('Bragg angle is {0:.2f} deg'.format(abs(theta * 180 / pi))) # mark corresponding pixels on the image detector if color_field == 'constant': add_to_image(detector.data, max_val * spot, (u, v)) elif color_field == 'energy': add_to_image(detector.data, abs(the_energy) * spot.astype(float), (u, v)) elif color_field == 'intensity': I = diffracted_intensity(hkl, I0=max_val, verbose=verbose) add_to_image(detector.data, I * spot, (u, v)) else: raise ValueError('unsupported color_field: %s' % color_field) if inverted: # np.invert works only with integer types print('casting to uint8 and inverting image') # limit maximum to max_val (255) and convert to uint8 over = detector.data > 255 detector.data[over] = 255 detector.data = detector.data.astype(np.uint8) detector.data = np.invert(detector.data) return detector.data
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