def __init__(self, shape, pixel_size, sample_detector, energy, delta_energy): """ Creates instance of monochromatic (area) XRD detector. Creates XRD detector with correct geometry and experimental setup (i.e. sample to detector distance, energy and energy resolution). Inherits from Rings/Peaks classes, allowing for the calculation/estimation and visualisation of intensity profiles and Debye-Scherrer rings for materials or combinations of materials/phases. Args: shape (tuple): Detector shape (x, y) in pixels pixel_size (float): Pixel size (mm) sample_detector (float): Sample to detector distance (mm). energy (float): X-ray energy (keV) energy_sigma (float): Energy resolution (keV) """ self.method = 'mono' self.energy = energy # Instantiate position array (r, phi, 2theta, q) for detector y, x = np.ogrid[:float(shape[0]), :float(shape[1])] x, y = x - (shape[0] - 1) / 2, y - (shape[1] - 1) / 2 r = (x**2 + y**2)**.5 self.phi = np.arctan(y / x) self.phi[np.logical_and(x < 0, y > 0)] += np.pi self.phi[np.logical_and(x < 0, y < 0)] -= np.pi self.two_theta = np.arctan(r * pixel_size / sample_detector) self.q = tth_to_q(self.two_theta, energy) # Beam energy variation (used to estimate FWHM) tth = np.linspace(0, self.two_theta.max(), 100) fwhm_q = e_to_q(delta_energy, tth) fwhm_tth = q_to_tth(fwhm_q, energy) # FWHM should vary approx. with Caglioti polynomial self._fwhm = [0.01, 0.01] #np.polyfit(np.tan(tth / 2), fwhm_tth **2, 3) # Flux wrt. energy/q - i.e. no variation for mono. self._flux = np.array([[0, 1], [self.q.max(), 1]]) # assert np.array_equal(self._flux[:, 0], [0, self.q.max()]) self.flux_q = interp1d(self._flux[:, 0], self._flux[:, 1]) # Parameter store for save/reload self._det_param = { 'shape': shape, 'pixel_size': pixel_size, 'sample_detector': sample_detector, 'energy': energy, 'delta_energy': delta_energy } # Empty dicts for storing peaks / materials self.a, self.fwhm, self.q0 = {}, {}, {} self.materials, self.hkl = {}, {} self._back = np.ones(1)
def fwhm_q(self, q, param=None): if param is None: param = self._fwhm if self.method == 'mono': theta = q_to_tth(q, self.energy) / 2 fwhm_tth = np.polyval(param, np.tan(theta))**0.5 return tth_to_q(fwhm_tth, self.energy) else: # print(np.polyval(param, q)) return np.polyval(param, q)**0.5
def intensity_factors(self, material, b=1, q=None, plot=True, x_axis='q'): """ Calculates normalised intensity factors (with option for plotting). Finds intensity factors wrt. q based on material and diffraction setup. Either returns values or plots for visualisation. Args: material (str): Element symbol (compound formula) b (float): B factor q (np.ndarray): Values of q to calculate intensty factors at. plot (bool): Plot selector x_axis (str): Plot relative to 'q' or 'energy' / '2theta' Returns: tuple: Intensity factor components(i_lp, i_sf, i_tf, flux) """ # Make decorator from this? valid = ['q'] + ['2theta' if self.method == 'mono' else 'energy'] error = "Can't plot wrt. {} in {} mode".format(x_axis, self.method) assert x_axis in valid, error method = self.method if q is None and method == 'mono': x, y = self.q.shape[0] / 2, self.q.shape[1] / 2 bins = (x**2 + y**2)**.5 q = np.linspace(0, self.q.max(), bins) else: q = self.q if q is None else q # Intensity factors if method == 'mono': i_lp = lp_factor(q_to_tth(q, self.energy)) else: i_lp = np.ones_like(q) i_sf = scattering_factor(material, q) # consider adding complex i_tf = temp_factor(q, b) flux = self.flux_q(q) if plot: ind = np.argsort(q)[q > 2] q = q if x_axis == 'q' else self._convert(q) labels = ['flux', 'lorentz', 'scatter', 'temp'] for i_f, label in zip([flux, i_lp, i_sf, i_tf], labels): plt.plot(q[ind], i_f[ind] / i_f[ind].max(), '-', label=label) total = (i_sf[ind]**2) * i_lp[ind] * i_tf[ind] * flux[ind] plt.plot(q[ind], total / np.max(total), 'k-.', label='total') plt.ylim([0, 1.05]) legend = plt.legend() legend.get_frame().set_color('white') plt.ylabel('Relative Intensity Factor') plt.xlabel(self.label_dict[x_axis]) plt.show() else: return i_lp, i_sf, i_tf, flux
def __init__(self, shape, pixel_size, sample_detector, energy, delta_energy): """ Creates instance of monochromatic (area) XRD detector. Creates XRD detector with correct geometry and experimental setup (i.e. sample to detector distance, energy and energy resolution). Inherits from Rings/Peaks classes, allowing for the calculation/estimation and visualisation of intensity profiles and Debye-Scherrer rings for materials or combinations of materials/phases. Args: shape (tuple): Detector shape (x, y) in pixels pixel_size (float): Pixel size (mm) sample_detector (float): Sample to detector distance (mm). energy (float): X-ray energy (keV) energy_sigma (float): Energy resolution (keV) """ self.method = 'mono' self.energy = energy # Instantiate position array (r, phi, 2theta, q) for detector y, x = np.ogrid[:float(shape[0]), :float(shape[1])] x, y = x - (shape[0] - 1) / 2, y - (shape[1] - 1) / 2 r = (x ** 2 + y ** 2) ** .5 self.phi = np.arctan(y / x) self.phi[np.logical_and(x < 0, y > 0)] += np.pi self.phi[np.logical_and(x < 0, y < 0)] -= np.pi self.two_theta = np.arctan(r * pixel_size / sample_detector) self.q = tth_to_q(self.two_theta, energy) # Beam energy variation (used to estimate FWHM) tth = np.linspace(0, self.two_theta.max(), 100) fwhm_q = e_to_q(delta_energy, tth) fwhm_tth = q_to_tth(fwhm_q, energy) # FWHM should vary approx. with Caglioti polynomial self._fwhm = [0.01, 0.01]#np.polyfit(np.tan(tth / 2), fwhm_tth **2, 3) # Flux wrt. energy/q - i.e. no variation for mono. self._flux = np.array([[0, 1], [self.q.max(), 1]]) # assert np.array_equal(self._flux[:, 0], [0, self.q.max()]) self.flux_q = interp1d(self._flux[:, 0], self._flux[:, 1]) # Parameter store for save/reload self._det_param = {'shape': shape, 'pixel_size': pixel_size, 'sample_detector': sample_detector, 'energy': energy, 'delta_energy': delta_energy} # Empty dicts for storing peaks / materials self.a, self.fwhm, self.q0 = {}, {}, {} self.materials, self.hkl = {}, {} self._back = np.ones(1)
def _convert(self, q): """ Helper function to convert q to 2theta (mono) or energy (edxd). Args: q (float, ndarray): q in A^-1 Returns: float, ndarray: Energy (keV) or 2theta (rad) """ if self.method == 'mono': return q_to_tth(q, self.energy) * 180 / np.pi else: return q_to_e(q, self.two_theta)