def fs_gap(data: DataType, shape=None, energy_range=None): data = normalize_to_spectrum(data) if energy_range is None: energy_range = slice(-0.1, None) data.sel(eV=energy_range) reduction = None if shape is None: # Just rebin the data along 'phi' reduction = {'phi': 16} data = rebin(data, reduction=reduction, shape=shape) return broadcast_model(GStepBModel, data, ['phi', 'beta'])
def set_data(self, data: DataType, **kwargs): original_data = normalize_to_spectrum(data) self.original_data = original_data if len(data.dims) > 2: assert 'eV' in original_data.dims data = data.sel(eV=slice(-0.05, 0.05)).sum('eV', keep_attrs=True) data.coords['eV'] = 0 else: data = original_data if 'eV' in data.dims: data = data.S.transpose_to_back('eV') self.data = data.copy(deep=True) if not kwargs: rng_mul = 1 if data.coords['hv'] < 12: rng_mul = 0.5 if data.coords['hv'] < 7: rng_mul = 0.25 if 'eV' in self.data.dims: kwargs = { 'kp': np.linspace(-2, 2, 400) * rng_mul, } else: kwargs = { 'kx': np.linspace(-3, 3, 300) * rng_mul, 'ky': np.linspace(-3, 3, 300) * rng_mul, } self.conversion_kwargs = kwargs
def calculate_shirley_background(xps: DataType, energy_range: slice = None, eps=1e-7, max_iters=50, n_samples=5): """ Calculates a shirley background iteratively over the full energy range `energy_range`. :param xps: :param energy_range: :param eps: :param max_iters: :return: """ if energy_range is None: energy_range = slice(None, None) xps = normalize_to_spectrum(xps) xps_for_calc = xps.sel(eV=energy_range) bkg = calculate_shirley_background_full_range(xps_for_calc, eps, max_iters) full_bkg = xps * 0 left_idx = np.searchsorted(full_bkg.eV.values, bkg.eV.values[0], side='left') right_idx = left_idx + len(bkg) full_bkg.values[:left_idx] = bkg.values[0] full_bkg.values[left_idx:right_idx] = bkg.values full_bkg.values[right_idx:] = bkg.values[-1] return full_bkg
def symmetrize(data: DataType, subpixel=False, full_spectrum=False): """ Symmetrizes data across the chemical potential. This provides a crude tool by which gap analysis can be performed. In this implementation, subpixel accuracy is achieved by interpolating data. :param data: Input array. :param subpixel: Enable subpixel correction :param full_spectrum: Returns data above and below the chemical potential. By default, only the bound part of the spectrum (below the chemical potential) is returned, because the other half is identical. :return: """ data = normalize_to_spectrum(data).S.transpose_to_front('eV') if subpixel or full_spectrum: data = _shift_energy_interpolate(data) above = data.sel(eV=slice(0, None)) below = data.sel(eV=slice(None, 0)).copy(deep=True) l = len(above.coords['eV']) zeros = below.values * 0 zeros[-l:] = above.values[::-1] below.values = below.values + zeros if full_spectrum: if not subpixel: warnings.warn("full spectrum symmetrization uses subpixel correction") full_data = below.copy(deep=True) new_above = full_data.copy(deep=True)[::-1] new_above.coords['eV'] = (new_above.coords['eV'] * -1) full_data = xr.concat([full_data, new_above[1:]], dim='eV') result = full_data else: result = below return result
def remove_incoherent_background(data: DataType, set_zero=True): """ Sometimes spectra are contaminated by data above the Fermi level for various reasons (such as broad core levels from 2nd harmonic light, or slow enough electrons in ToF experiments to be counted in subsequent pulses). :param data: :param set_zero: :return: """ data = normalize_to_spectrum(data) approximate_fermi_energy_level = data.S.find_spectrum_energy_edges().max() background = data.sel(eV=slice(approximate_fermi_energy_level + 0.1, None)) density = background.sum('eV') / (np.logical_not(np.isnan(background)) * 1).sum('eV') new = data - density if set_zero: new.values[new.values < 0] = 0 return new
def normalize_by_fermi_dirac(data: DataType, reference_data: DataType = None, plot=False, broadening=None, temperature_axis=None, temp_offset=0, **kwargs): """ Normalizes data according to a Fermi level reference on separate data or using the same source spectrum. To do this, a linear density of states is multiplied against a resolution broadened Fermi-Dirac distribution (`arpes.fits.fit_models.AffineBroadenedFD`). We then set the density of states to 1 and evaluate this model to obtain a reference that the desired spectrum is normalized by. :param data: Data to be normalized. :param reference_data: A reference spectrum, typically a metal reference. If not provided the integrated data is used. Beware: this is inappropriate if your data is gapped. :param plot: A debug flag, allowing you to view the normalization spectrum and relevant curve-fits. :param broadening: Detector broadening. :param temperature_axis: Temperature coordinate, used to adjust the quality of the reference for temperature dependent data. :param temp_offset: Temperature calibration in the case of low temperature data. Useful if the temperature at the sample is known to be hotter than the value recorded off of a diode. :param kwargs: :return: """ reference_data = data if reference_data is None else reference_data broadening_fit = determine_broadened_fermi_distribution(reference_data, **kwargs) broadening = broadening_fit.params['conv_width'].value if broadening is None else broadening if plot: print('Gaussian broadening is: {} meV (Gaussian sigma)'.format( broadening_fit.params['conv_width'].value * 1000)) print('Fermi edge location is: {} meV (fit chemical potential)'.format( broadening_fit.params['fd_center'].value * 1000)) print('Fermi width is: {} meV (fit fermi width)'.format( broadening_fit.params['fd_width'].value * 1000)) broadening_fit.plot() offset = broadening_fit.params['offset'].value without_offset = broadening_fit.eval(offset=0) cut_index = -np.argmax(without_offset[::-1] > 0.1 * offset) cut_energy = reference_data.coords['eV'].values[cut_index] if temperature_axis is None and 'temp' in data.dims: temperature_axis = 'temp' transpose_order = list(data.dims) transpose_order.remove('eV') if temperature_axis: transpose_order.remove(temperature_axis) transpose_order = transpose_order + [temperature_axis] transpose_order = transpose_order + ['eV'] without_background = (data - data.sel(eV=slice(cut_energy, None)).mean('eV')).transpose(*transpose_order) if temperature_axis: without_background = normalize_to_spectrum(without_background) divided = without_background.T.map_axes( temperature_axis, lambda x, coord: x / broadening_fit.eval( x=x.coords['eV'].values, lin_bkg=0, const_bkg=1, offset=0, conv_width=broadening, fd_width=(coord[temperature_axis] + temp_offset) * K_BOLTZMANN_EV_KELVIN)) else: without_background = normalize_to_spectrum(without_background) divided = without_background / broadening_fit.eval( x=data.coords['eV'].values, conv_width=broadening, lin_bkg=0, const_bkg=1, offset=0) divided.coords['eV'].values = divided.coords['eV'].values - broadening_fit.params['fd_center'].value return divided