def apply_copper_fermi_edge_correction(arr: DataType, copper_ref: DataType, *args, **kwargs): # this maybe isn't best because we don't correct anything other than the spectrum, # but that's the only thing with an energy axis in ARPES datasets so whatever arr = normalize_to_spectrum(arr) copper_ref = normalize_to_spectrum(copper_ref) quadratic_corr = build_quadratic_fermi_edge_correction( copper_ref, *args, **kwargs) return apply_quadratic_fermi_edge_correction(arr, quadratic_corr)
def determine_broadened_fermi_distribution(reference_data: DataType, fixed_temperature=True): """ Determine the parameters for broadening by temperature and instrumental resolution for a piece of data. As a general rule, we first try to estimate the instrumental broadening and linewidth broadening according to calibrations provided for the beamline + instrument, as a starting point. We also calculate the thermal broadening to expect, and fit an edge location. Then we use a Gaussian convolved Fermi-Dirac distribution against an affine density of states near the Fermi level, with a constant offset background above the Fermi level as a simple but effective model when away from lineshapes. These parameters can be used to bootstrap a fit to actual data or used directly in ``normalize_by_fermi_dirac``. :param reference_data: :return: """ params = {} if fixed_temperature: params['fd_width'] = { 'value': reference_data.S.temp * K_BOLTZMANN_EV_KELVIN, 'vary': False, } reference_data = normalize_to_spectrum(reference_data) sum_dims = list(reference_data.dims) sum_dims.remove('eV') return AffineBroadenedFD().guess_fit(reference_data.sum(sum_dims), params=params)
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 make_psf(data: DataType, sigmas): """Not yet operational; produces an n-dimensional gaussian point spread function for use in deconvolve_rl. :param data: :param dim: :param sigma: :return DataArray: """ raise NotImplementedError() arr = normalize_to_spectrum(data) dims = arr.dims psf = arr.copy(deep=True) * 0 + 1 for dim in dims: other_dims = list(arr.dims) other_dims.remove(dim) psf1d = arr.copy(deep=True) * 0 + 1 for od in other_dims: psf1d = psf1d[{od: 0}] if sigmas[dim] == 0: # TODO may need to do subpixel correction for when the dimension has an even length psf1d = psf1d * 0 # psf1d[{dim:np.mean(psf1d.coords[dim])}] = 1 psf1d[{dim: len(psf1d.coords[dim]) / 2}] = 1 else: psf1d = psf1d * gaussian(psf1d.coords[dim], np.mean(psf1d.coords[dim]), sigmas[dim]) psf = psf * psf1d return psf
def plot_dos(data, title=None, out=None, norm=None, dos_pow=1, **kwargs): data = normalize_to_spectrum(data) fig = plt.figure(figsize=(14, 6)) fig.subplots_adjust(hspace=0.00) gs = gridspec.GridSpec(2, 1, height_ratios=[3, 1]) ax0 = plt.subplot(gs[0]) axes = (ax0, plt.subplot(gs[1], sharex=ax0)) data.values[np.isnan(data.values)] = 0 cbar_axes = matplotlib.colorbar.make_axes(axes, pad=0.01) mesh = data.plot(ax=axes[0], norm=norm or colors.PowerNorm(gamma=0.15)) axes[1].set_facecolor((0.95, 0.95, 0.95)) density_of_states = data.S.sum_other(['eV']) (density_of_states**dos_pow).plot(ax=axes[1]) cbar = plt.colorbar(mesh, cax=cbar_axes[0]) cbar.set_label('Photoemission Intensity (Arb.)') axes[1].set_ylabel('Spectrum DOS', labelpad=12) axes[0].set_title(title or '') if out is not None: savefig(out, dpi=400) return path_for_plot(out) else: return fig, axes, cbar
def beamline_resolution_estimate(data: DataType, meV=False): data = normalize_to_spectrum(data) resolution_table = ENDSTATIONS_BEAMLINE_RESOLUTION[data.S.endstation] if isinstance(list(resolution_table.keys())[0], str): # need grating information settings = data.S.beamline_settings resolution_table = resolution_table[settings['grating']] all_keys = list(resolution_table.keys()) hvs = set(k[0] for k in all_keys) low_hv = max(hv for hv in hvs if hv < settings['hv']) high_hv = min(hv for hv in hvs if hv >= settings['hv']) slit_size = (settings['entrance_slit'], settings['exit_slit'],) low_hv_res = energy_resolution_from_beamline_slit( resolution_table, low_hv, slit_size) high_hv_res = energy_resolution_from_beamline_slit( resolution_table, high_hv, slit_size) # interpolate between nearest values return low_hv_res + (high_hv_res - low_hv_res) * \ (settings['hv'] - low_hv) / (high_hv - low_hv) * (1000 if meV else 1) raise NotImplementedError()
def thermal_broadening_estimate(data: DataType, meV=False): """ Simple Fermi-Dirac broadening :param data: :return: """ return normalize_to_spectrum(data).S.temp * K_BOLTZMANN_MEV_KELVIN * (1 if meV else 0.001)
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 approximate_core_levels(data: DataType, window_size=None, order=5, binning=3, promenance=5): """ Approximately locates core levels in a spectrum. Data is first smoothed, and then local maxima with sufficient prominence over other nearby points are selected as peaks. This can be helfpul to "seed" a curve fitting analysis for XPS. :param data: :param window_size: :param order: :param binning: :param promenance: :return: """ data = normalize_to_spectrum(data) dos = data.S.sum_other(['eV']).sel(eV=slice(None, -20)) if window_size is None: window_size = int(len(dos) / 40) # empirical, may change if window_size % 2 == 0: window_size += 1 smoothed = rebin(savitzky_golay(dos, window_size, order), eV=binning) indices = np.argwhere(local_maxima(smoothed.values, promenance=promenance)) energies = [smoothed.coords['eV'][idx].item() for idx in indices] return energies
def relative_change(data: DataType, t0=None, buffer=0.3, normalize_delay=True): """ Like normalized_relative_change, but only subtracts the before t0 data rather than normalizing by the original spectrum's intensity in each frame. :param data: :param t0: :param buffer: :param normalize_delay: :return: """ spectrum = normalize_to_spectrum(data) if normalize_delay: spectrum = normalize_dim(spectrum, 'delay') delay_coords = spectrum.coords['delay'] delay_start = np.min(delay_coords) if t0 is None: t0 = spectrum.S.t0 or find_t0(spectrum) assert t0 - buffer > delay_start before_t0 = spectrum.sel(delay=slice(None, t0 - buffer)) subtracted = spectrum - before_t0.mean('delay') return subtracted
def dyn(dynamic_function: typing.Callable, data: DataType, widget_specifications=None): data = normalize_to_spectrum(data) tool = DynamicTool(dynamic_function, widget_specifications) return tool.make_tool(data)
def fit_for_effective_mass(data: DataType, fit_kwargs=None): """ Performs an effective mass fit by first fitting for Lorentzian lineshapes and then fitting a quadratic model to the result. This is an alternative to global effective mass fitting. In the case that data is provided in anglespace, the Lorentzian fits are performed in anglespace before being converted to momentum where the effective mass is extracted. We should probably include uncertainties here. :param data: :param fit_kwargs: Passthrough for arguments to `broadcast_model`, used internally to obtain the Lorentzian peak locations :return: """ if fit_kwargs is None: fit_kwargs = {} data = normalize_to_spectrum(data) mom_dim = [d for d in ['kp', 'kx', 'ky', 'kz', 'phi', 'beta', 'theta'] if d in data.dims][0] results = broadcast_model([LorentzianModel, AffineBackgroundModel], data, mom_dim, **fit_kwargs) if mom_dim in {'phi', 'beta', 'theta'}: forward = convert_coordinates_to_kspace_forward(data) final_mom = [d for d in ['kx', 'ky', 'kp', 'kz'] if d in forward][0] eVs = results.F.p('a_center').values kps = [forward[final_mom].sel(eV=eV, **dict([[mom_dim, ang]]), method='nearest') for eV, ang in zip(eVs, data.coords[mom_dim].values)] quad_fit = QuadraticModel().fit(eVs, x=np.array(kps)) return HBAR_SQ_EV_PER_ELECTRON_MASS_ANGSTROM_SQ / (2 * quad_fit.params['a'].value) quad_fit = QuadraticModel().guess_fit(results.F.p('a_center')) return HBAR_SQ_EV_PER_ELECTRON_MASS_ANGSTROM_SQ / (2 * quad_fit.params['a'].value)
def apply_from_reference_set(data: DataType, reference_set, **kwargs): correction = correction_from_reference_set(data, reference_set) data = normalize_to_spectrum(data) if correction is None: return data return apply_quadratic_fermi_edge_correction(data, correction, **kwargs)
def apply_direct_copper_fermi_edge_correction(arr: DataType, copper_ref: DataType, *args, **kwargs): """ Applies a *direct* fermi edge correction. :param arr: :param copper_ref: :param args: :param kwargs: :return: """ arr = normalize_to_spectrum(arr) copper_ref = normalize_to_spectrum(copper_ref) direct_corr = build_direct_fermi_edge_correction(copper_ref, *args, **kwargs) shift = np.interp(arr.coords['phi'].values, direct_corr.coords['phi'].values, direct_corr.values) return apply_direct_fermi_edge_correction(arr, shift)
def calculate_aspect_ratio(data: DataType): data = normalize_to_spectrum(data) assert len(data.dims) == 2 x_extent = np.ptp(data.coords[data.dims[0]].values) y_extent = np.ptp(data.coords[data.dims[1]].values) return y_extent / x_extent
def plot_data_to_bz2d(data: DataType, cell, rotate=None, shift=None, scale=None, ax=None, mask=True, out=None, bz_number=None, **kwargs): data = normalize_to_spectrum(data) assert('You must k-space convert data before plotting to BZs' and data.S.is_kspace) if bz_number is None: bz_number = (0,0) fig = None if ax is None: fig, ax = plt.subplots(figsize=(9,9)) bz2d_plot(cell, paths='all', ax=ax) if len(cell) == 2: cell = [list(c) + [0] for c in cell] + [[0, 0, 1]] icell = np.linalg.inv(cell).T # Prep coordinates and mask raveled = data.T.meshgrid(as_dataset=True) dims = data.dims if rotate is not None: c, s = np.cos(rotate), np.sin(rotate) rotation = np.array([(c, -s), (s, c)]) raveled = raveled.T.transform_coords(dims, rotation) if scale is not None: raveled = raveled.T.scale_coords(dims, scale) if shift is not None: raveled = raveled.T.shift_coords(dims, shift) copied = data.values.copy() if mask: built_mask = apply_mask_to_coords(raveled, build_2dbz_poly(cell=cell), dims) copied[built_mask.T] = np.nan cmap = kwargs.get('cmap', matplotlib.cm.Blues) if isinstance(cmap, str): cmap = matplotlib.cm.get_cmap(cmap) cmap.set_bad((1, 1, 1, 0)) delta_x = np.dot(np.array(bz_number), icell[:2, 0]) delta_y = np.dot(np.array(bz_number), icell[:2, 1]) ax.pcolormesh(raveled.data_vars[dims[0]].values + delta_x, raveled.data_vars[dims[1]].values + delta_y, copied.T, cmap=cmap) if out is not None: plt.savefig(path_for_plot(out), dpi=400) return path_for_plot(out) return fig, ax
def remove_shirley_background(xps: DataType, **kwargs): """ Calculates and removes a Shirley background from a spectrum. Only the background corrected spectrum is retrieved. :param xps: :param kwargs: :return: """ xps = normalize_to_spectrum(xps) return xps - calculate_shirley_background(xps, **kwargs)
def deconvolve_ice(data: DataType, psf, n_iterations=5, deg=None): """ Deconvolves data by a given point spread function using the iterative convolution extrapolation method. :param data: :param psf: :param n_iterations -- the number of convolutions to use for the fit (default 5): :param deg -- the degree of the fitting polynominal (default n_iterations-3): :return DataArray or numpy.ndarray -- based on input type: """ arr = normalize_to_spectrum(data) if type(data) is np.ndarray: pass else: arr = arr.values if deg is None: deg = n_iterations - 3 iteration_steps = list(range(1, n_iterations + 1)) iteration_list = [arr] for i in range(n_iterations - 1): iteration_list.append(scipy.ndimage.convolve(iteration_list[-1], psf)) iteration_list = np.asarray(iteration_list) deconv = arr * 0 for t, series in enumerate(iteration_list.T): coefs = np.polyfit(iteration_steps, series, deg=deg) poly = np.poly1d(coefs) deconv[t] = poly(0) if type(data) is np.ndarray: result = deconv else: result = normalize_to_spectrum(data).copy(deep=True) result.values = deconv return result
def fit(override_data=None): packed_bands = pack_bands() dims = list(self.arr.dims) if 'eV' in dims: dims.remove('eV') angular_direction = dims[0] if isinstance(override_data, xr.Dataset): override_data = normalize_to_spectrum(override_data) return fit_patterned_bands( override_data if override_data is not None else self.arr, packed_bands, fit_direction='eV' if self.app_context['fit_mode'] == 'edc' else angular_direction, direction_normal=self.app_context['direction_normal'])
def select_disk_mask(data: DataType, radius, outer_radius=None, around: Optional[Union[Dict, xr.Dataset]] = None, flat=False, **kwargs) -> np.ndarray: """ A complement to select_disk which only generates the mask. Selects the data in a disk around the point described by `around` and `kwargs`. A point is a labelled collection of coordinates that matches all of the dimensions of `data`. The coordinates can either be passed through a dict as `around`, as the coordinates of a Dataset through `around` or explicitly in keyword argument syntax through `kwargs`. The radius for the disk is specified through the required `radius` parameter. Returns the ND mask that represents the filtered coordinates. :param data: :param around: :param flat: Whether to return the mask as a 1D (raveled) mask (flat=True) or as a ND mask with the same dimensionality as the input data (flat=False). :param kwargs: :return: """ if outer_radius is not None and radius > outer_radius: radius, outer_radius = outer_radius, radius data = normalize_to_spectrum(data) around = _normalize_point(data, around, **kwargs) raveled = data.T.ravel() dim_order = list(around.keys()) dist = np.sqrt( np.sum(np.stack([(raveled[d] - around[d])**2 for d in dim_order], axis=1), axis=1)) mask = dist <= radius if outer_radius is not None: mask = np.logical_or(mask, dist > outer_radius) if flat: return mask return mask.reshape(data.shape[::-1])
def select_disk(data: DataType, radius, outer_radius=None, around: Optional[Union[Dict, xr.Dataset]] = None, invert=False, **kwargs) \ -> Tuple[Dict[str, np.ndarray], np.ndarray, np.ndarray]: """ Selects the data in a disk (or annulus if `outer_radius` is provided) around the point described by `around` and `kwargs`. A point is a labeled collection of coordinates that matches all of the dimensions of `data`. The coordinates can either be passed through a dict as `around`, as the coordinates of a Dataset through `around` or explicitly in keyword argument syntax through `kwargs`. The radius for the disk is specified through the required `radius` parameter. Data is returned as a tuple with the type Tuple[Dict[str, np.ndarray], np.ndarray, np.ndarray] containing a dictionary with the filtered lists of coordinates, an array with the original data values at these coordinates, and finally an array of the distances to the requested point. :param data: :param radius: :param outer_radius: :param around: :param invert: :param kwargs: :return: """ data = normalize_to_spectrum(data) around = _normalize_point(data, around, **kwargs) mask = select_disk_mask(data, radius, outer_radius=outer_radius, around=around, flat=True) if invert: mask = np.logical_not(mask) # at this point, around is now a dictionary specifying a point to do the selection around raveled = data.T.ravel() data_arr = raveled['data'] dim_order = list(around.keys()) dist = np.sqrt( np.sum(np.stack([(raveled[d] - around[d])**2 for d in dim_order], axis=1), axis=1)) masked_coords = {d: cs[mask] for d, cs in raveled.items()} return masked_coords, masked_coords['data'], dist[mask]
def analyzer_resolution_estimate(data: DataType, meV=False): """ For hemispherical analyzers, this can be determined by the slit and pass energy settings. Roughly, :param data: :return: """ data = normalize_to_spectrum(data) endstation = data.S.endstation spectrometer_info = SPECTROMETER_INFORMATION[endstation] spectrometer_settings = data.S.spectrometer_settings return analyzer_resolution(spectrometer_info, slit_number=spectrometer_settings['slit'], pass_energy=spectrometer_settings['pass_energy']) * (1 if meV else 0.001)
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 gradient_modulus(data: DataType, delta=1): spectrum = normalize_to_spectrum(data) values = spectrum.values gradient_vector = np.zeros(shape=(8, ) + values.shape) gradient_vector[0, :-delta, :] = vector_diff(values, ( delta, 0, )) gradient_vector[1, :, :-delta] = vector_diff(values, ( 0, delta, )) gradient_vector[2, delta:, :] = vector_diff(values, ( -delta, 0, )) gradient_vector[3, :, delta:] = vector_diff(values, ( 0, -delta, )) gradient_vector[4, :-delta, :-delta] = vector_diff(values, ( delta, delta, )) gradient_vector[5, :-delta, delta:] = vector_diff(values, ( delta, -delta, )) gradient_vector[6, delta:, :-delta] = vector_diff(values, ( -delta, delta, )) gradient_vector[7, delta:, delta:] = vector_diff(values, ( -delta, -delta, )) data_copy = spectrum.copy(deep=True) data_copy.values = np.linalg.norm(gradient_vector, axis=0) return data_copy
def normalized_relative_change(data: DataType, t0=None, buffer=0.3, normalize_delay=True): """ Calculates a normalized relative change, obtained by normalizing along the pump-probe "delay" axis and then subtracting the mean before t0 data and dividing by the original spectrum. :param data: :param t0: :param buffer: :param normalize_delay: :return: """ spectrum = normalize_to_spectrum(data) if normalize_delay: spectrum = normalize_dim(spectrum, 'delay') subtracted = relative_change(spectrum, t0, buffer, normalize_delay=False) normalized = subtracted / spectrum normalized.values[np.isinf(normalized.values)] = 0 normalized.values[np.isnan(normalized.values)] = 0 return normalized
def find_t0(data: DataType, e_bound=0.02, approx=True): """ Attempts to find the effective t0 in a spectrum by fitting a peak to the counts that occur far enough above e_F :param data: :param e_bound: Lower bound on the energy to use for the fitting :return: """ spectrum = normalize_to_spectrum(data) assert 'delay' in spectrum.dims assert 'eV' in spectrum.dims sum_dims = set(spectrum.dims) sum_dims.remove('delay') sum_dims.remove('eV') summed = spectrum.sum( list(sum_dims)).sel(eV=slice(e_bound, None)).mean('eV') coord_max = summed.argmax().item() return summed.coords['delay'].values[coord_max]
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_distribution( data: DataType, max_gain=None, rigid_shift=0, instrumental_broadening=None, total_broadening=None): """ Normalizes a scan by 1/the fermi dirac distribution. You can control the maximum gain with ``clamp``, and whether the Fermi edge needs to be shifted (this is for those desperate situations where you want something that "just works") via ``rigid_shift``. :param data: Input :param max_gain: Maximum value for the gain. By default the value used is the mean of the spectrum. :param rigid_shift: How much to shift the spectrum chemical potential. Pass the nominal value for the chemical potential in the scan. I.e. if the chemical potential is at BE=0.1, pass rigid_shift=0.1. :param instrumental_broadening: Instrumental broadening to use for convolving the distribution :return: Normalized DataArray """ data = normalize_to_spectrum(data) if total_broadening: distrib = fermi_distribution(data.coords['eV'].values - rigid_shift, total_broadening / arpes.constants.K_BOLTZMANN_EV_KELVIN) else: distrib = fermi_distribution(data.coords['eV'].values - rigid_shift, data.S.temp) # don't boost by more than 90th percentile of input, by default if max_gain is None: max_gain = min(np.mean(data.values), np.percentile(data.values, 10)) distrib[distrib < 1/max_gain] = 1/max_gain distrib_arr = xr.DataArray( distrib, {'eV': data.coords['eV'].values}, ['eV'] ) if instrumental_broadening is not None: distrib_arr = gaussian_filter_arr(distrib_arr, sigma={'eV': instrumental_broadening}) return data / distrib_arr
def apply_mask(data: DataType, mask, replace=np.nan, radius=None, invert=False): """ Applies a logical mask, i.e. one given in terms of polygons, to a specific piece of data. This can be used to set values outside or inside a series of polygon masks to a given value or to NaN. Expanding or contracting the masked region can be accomplished with the radius argument, but by default strict inclusion is used. Some masks include a `fermi` parameter which allows for clipping the detector boundaries in a semi-automated fashion. If this is included, only 200meV above the Fermi level will be included in the returned data. This helps to prevent very large and undesirable regions filled with only the replacement value which can complicate automated analyses that rely on masking. :param data: Data to mask. :param mask: Logical definition of the mask, appropriate for passing to `polys_to_mask` :param replace: The value to substitute for pixels masked. :param radius: Radius by which to expand the masked area. :param invert: Allows logical inversion of the masked parts of the data. By default, the area inside the polygon sequence is replaced by `replace`. :return: """ data = normalize_to_spectrum(data) fermi = mask.get('fermi') if isinstance(mask, dict): dims = mask.get('dims', data.dims) mask = polys_to_mask(mask, data.coords, [s for i, s in enumerate(data.shape) if data.dims[i] in dims], radius=radius, invert=invert) masked_data = data.copy(deep=True) masked_data.values = masked_data.values * 1.0 masked_data.values[mask] = replace if fermi is not None: return masked_data.sel(eV=slice(None, fermi + 0.2)) return masked_data
def make_psf1d(data: DataType, dim, sigma): """Produces a 1-dimensional gaussian point spread function for use in deconvolve_rl. :param data: :param dim: :param sigma: :return DataArray: """ arr = normalize_to_spectrum(data) dims = arr.dims psf = arr.copy(deep=True) * 0 + 1 other_dims = list(arr.dims) other_dims.remove(dim) for od in other_dims: psf = psf[{od: 0}] psf = psf * gaussian(psf.coords[dim], np.mean(psf.coords[dim]), sigma) return psf