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)
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
    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
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
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()
Ejemplo n.º 7
0
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)
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
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)
Ejemplo n.º 13
0
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)
Ejemplo n.º 15
0
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
Ejemplo n.º 16
0
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
Ejemplo n.º 17
0
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
Ejemplo n.º 19
0
 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'])
Ejemplo n.º 20
0
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])
Ejemplo n.º 21
0
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]
Ejemplo n.º 22
0
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)
Ejemplo n.º 23
0
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
Ejemplo n.º 24
0
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
Ejemplo n.º 25
0
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
Ejemplo n.º 26
0
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]
Ejemplo n.º 27
0
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
Ejemplo n.º 28
0
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
Ejemplo n.º 29
0
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