Source code for mlreflect.training.footprint
import numpy as np
from numpy import ndarray
[docs]class FootprintRescaler:
"""Returns reflectivity curves with rescaled ("incorrect") footprint for standard (specular) XRR geometries
Args:
reflectivity: Reflected intensity values of one curve (1D) or several curves (2D).
true_ratio: Assumed "true" ratio of beam width to sample length used to produce the fictional footprint on
the curves. E.g. for beam width of 200 microns and sample length of 10 mm ``true_ratio = 0.02``.
errors: List of error factors that the ``true_ratio`` can be multiplied by to produce the new ("incorrect")
footprint corrections.
q: ndarray of q-values corresponding the provided intensity matrix. If this value is provided, the wavelength
has to be specified as well.
wavelength: Fictional wavelength in Angstroms that is used to calculate the scattering angle.
theta: ndarray angle values corresponding to the provided intensity matrix. If this is provided, ``q`` and
``wavelength`` do not need to be provided.
"""
def __init__(self, reflectivity: ndarray, true_ratio: float, errors: list, q: ndarray = None, wavelength:
float = None, theta: ndarray = None):
self.reflectivity = np.atleast_2d(reflectivity)
self.true_ratio = true_ratio
self.errors = np.atleast_1d(errors)
if q is None and theta is None:
raise ValueError('q or theta values must be provided')
elif q is not None and theta is not None:
raise ValueError('cannot provide both q and theta together')
elif q is not None:
if wavelength is None:
raise ValueError('wavelength must be provided together with q values')
self.theta = self.q_to_angle(q, wavelength)
elif theta is not None:
self.theta = theta
@property
def rescaled_reflectivity(self):
num_curves = self.reflectivity.shape[0]
num_points = self.reflectivity.shape[1]
rescaled_reflectivity = np.empty((num_curves, num_points))
new_ratios = np.empty(num_curves)
for i in range(num_curves):
new_ratio = self.true_ratio * np.random.choice(self.errors)
new_ratios[i] = new_ratio
rescaled_reflectivity[i] = self._swap_footprints(self.reflectivity[i], self.theta, self.true_ratio,
new_ratio)
return {'rescaled_reflectivity': rescaled_reflectivity, 'ratios': new_ratios}
def _swap_footprints(self, reflectivity, theta, true_ratio, new_ratio):
footprint_reflectivity = self.apply_footprint(reflectivity, theta, true_ratio)
return self.normalize_to_max(self.correct_footprint(footprint_reflectivity, theta, new_ratio), reflectivity)
[docs] @staticmethod
def apply_footprint(intensity: ndarray, scattering_angle: ndarray, ratio: float) -> ndarray:
max_angle = 2 * np.arcsin(ratio) / np.pi * 180
corrected_intensity = intensity.copy()
below_max_angle_index = scattering_angle < max_angle
corrected_intensity[below_max_angle_index] = intensity[below_max_angle_index] * np.sin(
scattering_angle[below_max_angle_index] / 2 * np.pi / 180) / ratio
return corrected_intensity
[docs] @staticmethod
def correct_footprint(intensity: ndarray, scattering_angle: ndarray, ratio: float) -> ndarray:
max_angle = 2 * np.arcsin(ratio) / np.pi * 180
corrected_intensity = intensity.copy()
below_max_angle_index = scattering_angle < max_angle
corrected_intensity[below_max_angle_index] = intensity[below_max_angle_index] / np.sin(
scattering_angle[below_max_angle_index] / 2 * np.pi / 180) * ratio
return corrected_intensity
[docs] @staticmethod
def normalize_to_max(rescaled_intensity: ndarray, original_intensity: ndarray):
return rescaled_intensity / np.max(rescaled_intensity) * np.max(original_intensity)
[docs] @staticmethod
def normalize_to_first(rescaled_intensity: ndarray, original_intensity: ndarray):
return rescaled_intensity / rescaled_intensity[0] * original_intensity[0]
[docs] @staticmethod
def angle_to_q(scattering_angle, wavelength):
return 4 * np.pi / wavelength * np.sin(scattering_angle / 2 * np.pi / 180)
[docs] @staticmethod
def q_to_angle(q, wavelength):
return 2 * np.arcsin(q * wavelength / (4 * np.pi)) / np.pi * 180