Source code for srxraylib.util.data_structures

"""
Utility to mimic IGOR-like vector and matrix classes and functions.
"""
import numpy

[docs]class ScaledMatrix(object): """ ScaledMatrix stores a 2D array and its axes. Constructor. Parameters ---------- x_coord : numpy array the array for X. y_coord : numpy array the array for Y. z_values : numpy array the 2D array for Z. interpolator : boolean, optional True means that the interpolator has been calculates and it is ready. False means that is not ready. interpolator=True means that the interpolator is up to date and stored in the interpolated value. interpolator must be changed to False when something of the data is changed. When interpolation is required, compute_interpolator() is called. Withouth loss of generality, Wavefront2D can always be initialize with interpolator=False. Then it will be switched on when needed. It has been implemented for saving time: the interpolator is recomputed only if needed. """ x_coord = None y_coord = None z_values = None def __init__(self, x_coord=numpy.zeros(0), y_coord=numpy.zeros(0), z_values=numpy.zeros((0, 0)), interpolator=False): if z_values.shape != (len(x_coord), len(y_coord)): raise Exception("z_values shape " + str(z_values.shape) + " != " + str((len(x_coord), len(y_coord)))) self.x_coord = numpy.round(x_coord, 12) self.y_coord = numpy.round(y_coord, 12) self.z_values = numpy.round(z_values, 12) self._set_is_complex_matrix() self.stored_shape = self._shape() self.stored_delta_x = self._delta_x() self.stored_offset_x = self._offset_x() self.stored_size_x = self._size_x() self.stored_delta_y = self._delta_y() self.stored_offset_y = self._offset_y() self.stored_size_y = self._size_y() # # write now interpolator=True means that the interpolator is up to date and stored in the # interpolated value. interpolator must be changed to False when something of the wavefront # is changed. When interpolation is required, compute_interpolator() is called. # # Withouth loss of generality, Wavefront2D can always be initialize with # interpolator=False. Then it will be switched on when needed. # It has been implemented for saving time: the interpolator is recomputed only if needed # DANGER: if self.x_coord, self.y_coord or self.z_values are changed directly by the user # (without using set* methods), then set set.interpolator=False must be manually set, self.interpolator = interpolator self.interpolator_value = None if interpolator: self.compute_interpolator()
[docs] @classmethod def initialize(cls, np_array_z = numpy.zeros((0,0)), interpolator=False): """ Initialize a ScaledMatrix instance from a 2D array. Parameters ---------- np_array_z : numpy array The 2D array. interpolator : boolean, optional True means that the interpolator has been calculates and it is ready. False means that is not ready. Returns ------- ScaledMatrix instance """ return ScaledMatrix(numpy.zeros(np_array_z.shape[0]), numpy.zeros(np_array_z.shape[1]), np_array_z,interpolator=interpolator)
[docs] @classmethod def initialize_from_range(cls, np_array, min_scale_value_x, max_scale_value_x, min_scale_value_y, max_scale_value_y, interpolator=False): """ Initializes a ScaledMatrix instance from a 2D array and the range of the axes. Parameters ---------- np_array : numpy array The 2D array. min_scale_value_x : float max_scale_value_x : float min_scale_value_y : float max_scale_value_y : float interpolator : boolean, optional True means that the interpolator has been calculates and it is ready. False means that is not ready. Returns ------- ScaledMatrix instance """ array = ScaledMatrix.initialize(np_array) array.set_scale_from_range(0,min_scale_value_x, max_scale_value_x) array.set_scale_from_range(1,min_scale_value_y, max_scale_value_y) if interpolator: array.compute_interpolator() return array
[docs] @classmethod def initialize_from_steps(cls, np_array, initial_scale_value_x, scale_step_x, initial_scale_value_y, scale_step_y, interpolator=False): """ Initializes a ScaledMatrix instance from a 2D array and the steps of the axes. Parameters ---------- np_array : numpy array The 2D array. initial_scale_value_x : float scale_step_x : float initial_scale_value_y : float scale_step_y : float interpolator : boolean, optional True means that the interpolator has been calculates and it is ready. False means that is not ready. Returns ------- ScaledMatrix instance """ array = ScaledMatrix.initialize(np_array) array.set_scale_from_steps(0,initial_scale_value_x, scale_step_x) array.set_scale_from_steps(1,initial_scale_value_y, scale_step_y) if interpolator: array.compute_interpolator() return array
def _size_x(self): return len(self.x_coord) def _offset_x(self): if len(self.x_coord) > 1: return self.x_coord[0] else: return numpy.nan def _delta_x(self): if len(self.x_coord) > 1: return abs(self.x_coord[1]-self.x_coord[0]) else: return 0.0
[docs] def size_x(self): """ Returns the size of the X axis. Returns ------- int """ return self.stored_size_x
[docs] def offset_x(self): """ returns the offset of the X axis. Returns ------- float """ return self.stored_offset_x
[docs] def delta_x(self): """ Returns the delta (step) of the X axis. Returns ------- int """ return self.stored_delta_x
def _size_y(self): return len(self.y_coord) def _offset_y(self): if len(self.y_coord) > 1: return self.y_coord[0] else: return numpy.nan def _delta_y(self): if len(self.y_coord) > 1: return abs(self.y_coord[1]-self.y_coord[0]) else: return 0.0
[docs] def size_y(self): """ Returns the size of the Y axis. Returns ------- int """ return self.stored_size_y
[docs] def offset_y(self): """ Returns the offset of the Y axis. Returns ------- float """ return self.stored_offset_y
[docs] def delta_y(self): """ Returns the delta (step) of the Y axis. Returns ------- float """ return self.stored_delta_y
[docs] def get_x_value(self, index): """ Returns the X value for a given index. Parameters ---------- index : int Returns ------- float """ return self.x_coord[index]
[docs] def get_y_value(self, index): """ Returns the Y value for a given index. Parameters ---------- index : int Returns ------- float """ return self.y_coord[index]
[docs] def get_x_values(self): # plural!! """ Returns the X array. Returns ------- numpy array """ return self.x_coord
[docs] def get_y_values(self): # plural!! """ Returns the Y array. Returns ------- numpy array """ return self.y_coord
def _shape(self): return (len(self.x_coord), len(self.y_coord))
[docs] def shape(self): """ Returns the shape. Returns ------- tuple """ return self.stored_shape
[docs] def size(self): """ Returns the size of the array. Returns ------- int """ return self.stored_shape
[docs] def get_z_value(self, x_index, y_index): """ Returns the Z (2D array) value for given X,Y indices. Parameters ---------- x_index : int The index for axis 0. y_index : int The index for axis 1. Returns ------- float """ return self.z_values[x_index][y_index]
[docs] def get_z_values(self): # plural! """ Returns the Z (2D array) array. Returns ------- numpy array """ return self.z_values
[docs] def set_z_value(self, x_index, y_index, z_value): """ Sets a given Z value at given indices. Parameters ---------- x_index : int The index for axis 0. y_index : int The index for axis 1. z_value : float The value to be stored. """ self.z_values[x_index][y_index] = z_value if isinstance(z_value, complex): self._is_complex_matrix = True self.interpolator = False
[docs] def set_z_values(self, new_value): """ Sets a given 2D array. Parameters ---------- new_value : numpy array The 2D array. """ if new_value.shape != self.shape(): raise Exception("New data set must have same shape as old one") else: self.z_values = new_value self._set_is_complex_matrix() self.interpolator = False
def _set_is_complex_matrix(self): self._is_complex_matrix = True in numpy.iscomplex(self.z_values)
[docs] def is_complex_matrix(self): """ Returns True if the data stored is of complex type. Returns ------- boolean """ return self._is_complex_matrix
[docs] def interpolate_value(self, x_coord, y_coord): """ Gives the Z value interpolated at given coordinates (x_coord, y_coord). Parameters ---------- x_coord : float The coordinate at axis 0. y_coord : float The coordinate at axis 1. Returns ------- float """ if self.interpolator == False: self.compute_interpolator() if self.is_complex_matrix(): return self.interpolator_value[0].ev(x_coord, y_coord) + 1j * self.interpolator_value[1].ev(x_coord, y_coord) else: return self.interpolator_value.ev(x_coord, y_coord)
[docs] def compute_interpolator(self): """ Calculates and stores the interpolation object. """ from scipy import interpolate print("ScaledMatrix.compute_interpolator: Computing interpolator...") if self.is_complex_matrix(): self.interpolator_value = ( interpolate.RectBivariateSpline(self.x_coord, self.y_coord, numpy.real(self.z_values)), interpolate.RectBivariateSpline(self.x_coord, self.y_coord, numpy.imag(self.z_values)), ) else: self.interpolator_value = interpolate.RectBivariateSpline(self.x_coord, self.y_coord, self.z_values) self.interpolator = True
[docs] def set_scale_from_steps(self, axis, initial_scale_value, scale_step): """ Equivalent to the IGOR command: SetScale /P (wave, min value, max value). Parameters ---------- axis : int initial_scale_value : float scale_step : float """ if self.stored_shape[axis] > 0: if axis < 0 or axis > 1: raise Exception("Axis must be 0 or 1, found: " + str(axis)) if scale_step <= 0.0: raise Exception("Scale Step must be > 0.0") # Problem in comparison between float64 and numpy.float64: # reduce precision to avoid crazy research results scale = numpy.round(initial_scale_value, 12) + numpy.arange(0, (self.stored_shape[axis])) * numpy.round(scale_step, 12) if axis == 0: self.x_coord = scale self.stored_delta_x = self._delta_x() self.stored_offset_x = self._offset_x() elif axis == 1: self.y_coord = scale self.stored_delta_y = self._delta_y() self.stored_offset_y = self._offset_y() self.stored_shape = self._shape() self.interpolator = False
[docs] def set_scale_from_range(self, axis, min_scale_value, max_scale_value): """ Equivalent to the IGOR command: SetScale /I (wave, min value, max value). Parameters ---------- axis : int min_scale_value : float max_scale_value : float """ if self.stored_shape[axis] > 0: if axis < 0 or axis > 1: raise Exception("Axis must be 0 or 1, found: " + str(axis)) if max_scale_value <= min_scale_value: raise Exception("Max Scale Value ("+ str(max_scale_value) + ") must be > Min Scale Vale(" + str(min_scale_value) + ")") self.set_scale_from_steps(axis, min_scale_value, (max_scale_value - min_scale_value)/((self.stored_shape[axis])-1)) self.interpolator = False
#######################################
[docs]class ScaledArray(object): """ Stores a 1D array and the abscissas information. Constructor. Parameters ---------- np_array : numpy array The array to be stored. scale : float The scale values (np_array and scale must have the same dimension). """ def __init__(self, np_array = numpy.zeros(0), scale = numpy.zeros(0)): if len(np_array) != len(scale): raise Exception("np_array and scale must have the same dimension: (" + str(len(np_array)) + " != " + str(len(scale)) ) if len(np_array) == 0: raise Exception("np_array can't have 0 size") # Problem in comparison between float64 and numpy.float64: # reduce precision to avoid crazy research results self.np_array = numpy.round(np_array, 12) self.scale = numpy.round(scale, 12) self.stored_delta = self._delta() self.stored_offset = self._offset() self.stored_size = self._size() self._v_interpolate_values = numpy.vectorize(self.interpolate_value)
[docs] @classmethod def initialize(cls, np_array = numpy.zeros(0)): """ Initializes a ScaledArray instance. Parameters ---------- np_array : numpy array The array to be stored. Returns ------- ScaledArray instance """ return ScaledArray(np_array, numpy.arange(0, len(np_array)))
[docs] @classmethod def initialize_from_range(cls, np_array , min_scale_value, max_scale_value): """ Parameters ---------- np_array: numpy array The array to be stored. min_scale_value : float max_scale_value : float Returns ------- ScaledArray instance """ array = ScaledArray.initialize(np_array) array.set_scale_from_range(min_scale_value, max_scale_value) return array
[docs] @classmethod def initialize_from_steps(cls, np_array, initial_scale_value, scale_step): """ Parameters ---------- np_array: numpy array The array to be stored. initial_scale_value : float scale_step : float Returns ------- A ScaledArray instance. """ array = ScaledArray.initialize(np_array) array.set_scale_from_steps(initial_scale_value, scale_step) return array
def _size(self): if len(self.np_array) != len(self.scale): raise Exception("np_array and scale must have the same dimension") return len(self.np_array) def _offset(self): if len(self.scale) > 1: return self.scale[0] else: return numpy.nan def _delta(self): if len(self.scale) > 1: return abs(self.scale[1]-self.scale[0]) else: return 0.0
[docs] def size(self): """ Returns the size of the array. Returns ------- int """ return self.stored_size
[docs] def offset(self): """ Returns the offset of the array. Returns ------- float """ return self.stored_offset
[docs] def delta(self): """ Returns the delta (step) of the array. Returns ------- float """ return self.stored_delta
[docs] def get_scale_value(self, index): """ Returns the scale at a particular index. Parameters ---------- index : int Returns ------- float """ return self.scale[index]
[docs] def get_value(self, index): """ Gets the value for a given index. Parameters ---------- index : int Returns ------- float """ return self.np_array[index]
[docs] def get_values(self): # Plural! """ Returns the array. Returns ------- numpy array """ return self.np_array
[docs] def get_abscissas(self): """ Return an array with the abscissas. Returns ------- numpy array """ return self.scale # numpy.linspace(self.offset(),self.offset()+self.delta()*(self.size()-1),self.size())
[docs] def set_value(self, index, value): """ Sets a value at a particular index. Parameters ---------- index : int value : float """ self.np_array[index] = value
[docs] def set_values(self, value): """ Sets an array. Parameters ---------- value : numpy array """ if self.size() != value.size: raise Exception("Incompatible dimensions") self.np_array = value
[docs] def interpolate_values(self, scale_values): """ Get the interpolated values for given scale or abscissas values. Parameters ---------- scale_values : numpy array Returns ------- numpy array """ return self._v_interpolate_values(scale_values)
[docs] def interpolate_value(self, scale_value): """ Get the interpolated value for a given scale or abscissas value. Parameters ---------- scale_value : int Returns ------- float """ scale_value = numpy.round(scale_value, 12) scale_0 = self.scale[0] if scale_value <= scale_0: return self.np_array[0] if scale_value >= self.scale[self.size()-1]: return self.np_array[self.size()-1] approximated_index = (scale_value-scale_0)/self.stored_delta index_0 = int(numpy.floor(approximated_index)) index_1 = int(numpy.ceil(approximated_index)) x_0 = self.scale[index_0] x_1 = self.scale[index_1] y_0 = self.np_array[index_0] y_1 = self.np_array[index_1] if x_0 == x_1: return y_0 else: return y_0 + ((y_1 - y_0) * (scale_value - x_0)/(x_1 - x_0))
[docs] def interpolate_scale_value(self, value): # only for monotonic np_array """ Interpolate only for monotonic np_array. Parameters ---------- value : float Returns ------- float """ return numpy.interp(value, self.np_array, self.scale)
[docs] def set_scale_from_steps(self, initial_scale_value, scale_step): """ Equivalent to the IGOR command: SetScale /P (wave, offset, step). Parameters ---------- initial_scale_value : float scale_step : float """ if self.size() > 0: # Problem in comparison between float64 and numpy.float64: # reduce precision to avoid crazy research results self.scale = numpy.round(initial_scale_value, 12) + numpy.arange(0, len(self.np_array)) * numpy.round(scale_step, 12) self.stored_offset = self._offset() self.stored_delta = self._delta()
[docs] def set_scale_from_range(self, min_scale_value, max_scale_value): """ Equivalent to the IGOR command: SetScale /I (wave, min value, max value). Parameters ---------- min_scale_value : float max_scale_value : float """ if max_scale_value <= min_scale_value: raise Exception("Max Scale Value ("+ str(max_scale_value) + ") must be > Min Scale Vale(" + str(min_scale_value) + ")") self.set_scale_from_steps(min_scale_value, (max_scale_value - min_scale_value)/(len(self.np_array)-1))