def estimate_parameters(self, signal, x1, x2, only_current=False): """Estimate the parameters by the two area method Parameters ---------- signal : Signal1D instance x1 : float Defines the left limit of the spectral range to use for the estimation. x2 : float Defines the right limit of the spectral range to use for the estimation. only_current : bool If False estimates the parameters for the full dataset. Returns ------- bool """ super(Polynomial, self)._estimate_parameters(signal) axis = signal.axes_manager.signal_axes[0] i1, i2 = axis.value_range_to_indices(x1, x2) if only_current is True: estimation = np.polyfit(axis.axis[i1:i2], signal()[i1:i2], self.get_polynomial_order()) if is_binned(signal) is True: # in v2 replace by #if axis.is_binned: self.coefficients.value = estimation / axis.scale else: self.coefficients.value = estimation return True else: if self.coefficients.map is None: self._create_arrays() nav_shape = signal.axes_manager._navigation_shape_in_array with signal.unfolded(): dc = signal.data # For polyfit the spectrum goes in the first axis if axis.index_in_array > 0: dc = dc.T # Unfolded, so simply transpose cmaps = np.polyfit(axis.axis[i1:i2], dc[i1:i2, ...], self.get_polynomial_order()) if axis.index_in_array > 0: cmaps = cmaps.T # Transpose back if needed # Shape needed to fit coefficients.map: cmap_shape = nav_shape + (self.get_polynomial_order() + 1, ) self.coefficients.map['values'][:] = cmaps.reshape(cmap_shape) if is_binned(signal) is True: # in v2 replace by #if axis.is_binned: self.coefficients.map["values"] /= axis.scale self.coefficients.map['is_set'][:] = True self.fetch_stored_values() return True
def estimate_parameters(self, signal, x1, x2, only_current=False): """Estimate the parameters by the two area method Parameters ---------- signal : BaseSignal instance x1 : float Defines the left limit of the spectral range to use for the estimation. x2 : float Defines the right limit of the spectral range to use for the estimation. only_current : bool If False estimates the parameters for the full dataset. Returns ------- bool """ super()._estimate_parameters(signal) axis = signal.axes_manager.signal_axes[0] i1, i2 = axis.value_range_to_indices(x1, x2) if is_binned(signal): # in v2 replace by #if axis.is_binned: # using the mean of the gradient for non-uniform axes is a best # guess to the scaling of binned signals for the estimation scaling_factor = axis.scale if axis.is_uniform \ else np.mean(np.gradient(axis.axis), axis=-1) if only_current is True: self.offset.value = signal()[i1:i2].mean() if is_binned(signal): # in v2 replace by #if axis.is_binned: self.offset.value /= scaling_factor return True else: if self.offset.map is None: self._create_arrays() dc = signal.data gi = [ slice(None), ] * len(dc.shape) gi[axis.index_in_array] = slice(i1, i2) self.offset.map['values'][:] = dc[tuple(gi)].mean( axis.index_in_array) if is_binned(signal): # in v2 replace by #if axis.is_binned: self.offset.map['values'] /= scaling_factor self.offset.map['is_set'][:] = True self.fetch_stored_values() return True
def _get_scaling_factor(signal, axis, parameter): """ Convenience function to get the scaling factor required to take into account binned and/or non-uniform axes. Parameters ---------- signal : BaseSignal axis : BaseDataAxis parameter : float or numpy array The axis value at which scaling factor is evaluated (ignored if the axis is uniform) Returns ------- scaling_factor """ if is_binned(signal): # in v2 replace by #if axis.is_binned: if axis.is_uniform: scaling_factor = axis.scale else: parameter_idx = axis.value2index(parameter) scaling_factor = np.gradient(axis.axis)[parameter_idx] else: scaling_factor = 1 return scaling_factor
def __call__(self, non_convolved=False, onlyactive=False, component_list=None): """Returns the corresponding model for the current coordinates Parameters ---------- non_convolved : bool If True it will return the deconvolved model only_active : bool If True, only the active components will be used to build the model. component_list : list or None If None, the sum of all the components is returned. If list, only the provided components are returned cursor: 1 or 2 Returns ------- numpy array """ if component_list is None: component_list = self if not isinstance(component_list, (list, tuple)): raise ValueError( "'Component_list' parameter need to be a list or None") if onlyactive: component_list = [ component for component in component_list if component.active ] if self.convolved is False or non_convolved is True: axis = self.axis.axis[self.channel_switches] sum_ = np.zeros(len(axis)) for component in component_list: sum_ += component.function(axis) to_return = sum_ else: # convolved sum_convolved = np.zeros(len(self.convolution_axis)) sum_ = np.zeros(len(self.axis.axis)) for component in component_list: if component.convolved: sum_convolved += component.function(self.convolution_axis) else: sum_ += component.function(self.axis.axis) to_return = sum_ + np.convolve( self.low_loss(self.axes_manager), sum_convolved, mode="valid") to_return = to_return[self.channel_switches] if is_binned(self.signal) is True: # in v2 replace by #if self.signal.axes_manager[-1].is_binned is True: to_return *= self.signal.axes_manager[-1].scale return to_return
def _function(self, x, xscale, yscale, shift): if self.interpolate is True: result = yscale * self.f(x * xscale - shift) else: result = yscale * self.signal.data if is_binned(self.signal) is True: # in v2 replace by #if self.signal.axes_manager.signal_axes[0].is_binned is True: return result / self.signal.axes_manager.signal_axes[0].scale else: return result
def estimate_parameters(self, signal, x1, x2, only_current=False): """Estimate the Donach by calculating the median (centre) and the variance parameter (sigma). Note that an insufficient range will affect the accuracy of this method and that this method doesn't estimate the asymmetry parameter (alpha). Parameters ---------- signal : Signal1D instance x1 : float Defines the left limit of the spectral range to use for the estimation. x2 : float Defines the right limit of the spectral range to use for the estimation. only_current : bool If False estimates the parameters for the full dataset. Returns ------- bool Returns True when the parameters estimation is successful Examples -------- >>> g = hs.model.components1D.Lorentzian() >>> x = np.arange(-10, 10, 0.01) >>> data = np.zeros((32, 32, 2000)) >>> data[:] = g.function(x).reshape((1, 1, 2000)) >>> s = hs.signals.Signal1D(data) >>> s.axes_manager[-1].offset = -10 >>> s.axes_manager[-1].scale = 0.01 >>> g.estimate_parameters(s, -10, 10, False) """ super()._estimate_parameters(signal) axis = signal.axes_manager.signal_axes[0] centre, height, sigma = _estimate_gaussian_parameters( signal, x1, x2, only_current) scaling_factor = _get_scaling_factor(signal, axis, centre) if only_current is True: self.centre.value = centre self.sigma.value = sigma self.A.value = height * 1.3 if is_binned(signal): # in v2 replace by #if axis.is_binned: self.A.value /= scaling_factor return True else: if self.A.map is None: self._create_arrays() self.A.map['values'][:] = height * 1.3 if is_binned(signal): # in v2 replace by #if axis.is_binned: self.A.map['values'][:] /= scaling_factor self.A.map['is_set'][:] = True self.sigma.map['values'][:] = sigma self.sigma.map['is_set'][:] = True self.centre.map['values'][:] = centre self.centre.map['is_set'][:] = True self.fetch_stored_values() return True
def estimate_parameters(self, signal, x1, x2, only_current=False): """Estimate the Lorentzian by calculating the median (centre) and half the interquartile range (gamma). Note that an insufficient range will affect the accuracy of this method. Parameters ---------- signal : Signal1D instance x1 : float Defines the left limit of the spectral range to use for the estimation. x2 : float Defines the right limit of the spectral range to use for the estimation. only_current : bool If False estimates the parameters for the full dataset. Returns ------- bool Notes ----- Adapted from gaussian.py and https://en.wikipedia.org/wiki/Cauchy_distribution Examples -------- >>> g = hs.model.components1D.Lorentzian() >>> x = np.arange(-10, 10, 0.01) >>> data = np.zeros((32, 32, 2000)) >>> data[:] = g.function(x).reshape((1, 1, 2000)) >>> s = hs.signals.Signal1D(data) >>> s.axes_manager[-1].offset = -10 >>> s.axes_manager[-1].scale = 0.01 >>> g.estimate_parameters(s, -10, 10, False) """ super()._estimate_parameters(signal) axis = signal.axes_manager.signal_axes[0] centre, height, gamma = _estimate_lorentzian_parameters(signal, x1, x2, only_current) scaling_factor = _get_scaling_factor(signal, axis, centre) if only_current is True: self.centre.value = centre self.gamma.value = gamma self.A.value = height * gamma * np.pi if is_binned(signal): # in v2 replace by #if axis.is_binned: self.A.value /= scaling_factor return True else: if self.A.map is None: self._create_arrays() self.A.map['values'][:] = height * gamma * np.pi if is_binned(signal): # in v2 replace by #if axis.is_binned: self.A.map['values'] /= scaling_factor self.A.map['is_set'][:] = True self.gamma.map['values'][:] = gamma self.gamma.map['is_set'][:] = True self.centre.map['values'][:] = centre self.centre.map['is_set'][:] = True self.fetch_stored_values() return True
def estimate_parameters(self, signal, x1, x2, only_current=False): """Estimate the gaussian by calculating the momenta. Parameters ---------- signal : Signal1D instance x1 : float Defines the left limit of the spectral range to use for the estimation. x2 : float Defines the right limit of the spectral range to use for the estimation. only_current : bool If False estimates the parameters for the full dataset. Returns ------- bool Notes ----- Adapted from http://www.scipy.org/Cookbook/FittingData Examples -------- >>> g = hs.model.components1D.GaussianHF() >>> x = np.arange(-10, 10, 0.01) >>> data = np.zeros((32, 32, 2000)) >>> data[:] = g.function(x).reshape((1, 1, 2000)) >>> s = hs.signals.Signal1D(data) >>> s.axes_manager[-1].offset = -10 >>> s.axes_manager[-1].scale = 0.01 >>> g.estimate_parameters(s, -10, 10, False) """ super()._estimate_parameters(signal) axis = signal.axes_manager.signal_axes[0] centre, height, sigma = _estimate_gaussian_parameters( signal, x1, x2, only_current) scaling_factor = _get_scaling_factor(signal, axis, centre) if only_current is True: self.centre.value = centre self.fwhm.value = sigma * sigma2fwhm self.height.value = float(height) if is_binned(signal): # in v2 replace by #if axis.is_binned: self.height.value /= scaling_factor return True else: if self.height.map is None: self._create_arrays() self.height.map['values'][:] = height if is_binned(signal): # in v2 replace by #if axis.is_binned: self.height.map['values'][:] /= scaling_factor self.height.map['is_set'][:] = True self.fwhm.map['values'][:] = sigma * sigma2fwhm self.fwhm.map['is_set'][:] = True self.centre.map['values'][:] = centre self.centre.map['is_set'][:] = True self.fetch_stored_values() return True
def _jacobian(self, param, y, weights=None): if weights is None: weights = 1. if self.convolved is True: counter = 0 grad = np.zeros(len(self.axis.axis)) for component in self: # Cut the parameters list if component.active: component.fetch_values_from_array( param[counter:counter + component._nfree_param], onlyfree=True) if component.convolved: for parameter in component.free_parameters: par_grad = np.convolve( parameter.grad(self.convolution_axis), self.low_loss(self.axes_manager), mode="valid") if parameter._twins: for par in parameter._twins: np.add( par_grad, np.convolve( par.grad(self.convolution_axis), self.low_loss(self.axes_manager), mode="valid"), par_grad) grad = np.vstack((grad, par_grad)) else: for parameter in component.free_parameters: par_grad = parameter.grad(self.axis.axis) if parameter._twins: for par in parameter._twins: np.add(par_grad, par.grad(self.axis.axis), par_grad) grad = np.vstack((grad, par_grad)) counter += component._nfree_param to_return = grad[1:, self.channel_switches] * weights else: axis = self.axis.axis[self.channel_switches] counter = 0 grad = axis for component in self: # Cut the parameters list if component.active: component.fetch_values_from_array( param[counter:counter + component._nfree_param], onlyfree=True) for parameter in component.free_parameters: par_grad = parameter.grad(axis) if parameter._twins: for par in parameter._twins: np.add(par_grad, par.grad(axis), par_grad) grad = np.vstack((grad, par_grad)) counter += component._nfree_param to_return = grad[1:, :] * weights if is_binned(self.signal) is True: # in v2 replace by #if self.signal.axes_manager[-1].is_binned is True: to_return *= self.signal.axes_manager[-1].scale return to_return
def estimate_parameters(self, signal, E1, E2, only_current=False): """Estimate the Voigt function by calculating the momenta of the Gaussian. Parameters ---------- signal : Signal1D instance x1 : float Defines the left limit of the spectral range to use for the estimation. x2 : float Defines the right limit of the spectral range to use for the estimation. only_current : bool If False estimates the parameters for the full dataset. Returns ------- : bool Exit status required for the :meth:`remove_background` function. Notes ----- Adapted from http://www.scipy.org/Cookbook/FittingData Examples -------- >>> g = hs.model.components1D.PESVoigt() >>> x = np.arange(-10, 10, 0.01) >>> data = np.zeros((32, 32, 2000)) >>> data[:] = g.function(x).reshape((1, 1, 2000)) >>> s = hs.signals.Signal1D(data) >>> s.axes_manager[-1].offset = -10 >>> s.axes_manager[-1].scale = 0.01 >>> g.estimate_parameters(s, -10, 10, False) """ super(PESVoigt, self)._estimate_parameters(signal) axis = signal.axes_manager.signal_axes[0] centre, height, sigma = _estimate_gaussian_parameters( signal, E1, E2, only_current) if only_current is True: self.centre.value = centre self.FWHM.value = sigma * sigma2fwhm self.area.value = height * sigma * sqrt2pi if is_binned(signal) is True: # in v2 replace by #if axis.is_binned: self.area.value /= axis.scale return True else: if self.area.map is None: self._create_arrays() self.area.map['values'][:] = height * sigma * sqrt2pi if is_binned(signal) is True: # in v2 replace by #if axis.is_binned: self.area.map['values'][:] /= axis.scale self.area.map['is_set'][:] = True self.FWHM.map['values'][:] = sigma * sigma2fwhm self.FWHM.map['is_set'][:] = True self.centre.map['values'][:] = centre self.centre.map['is_set'][:] = True self.fetch_stored_values() return True
def estimate_parameters(self, signal, x1, x2, only_current=False): """Estimate the parameters by the two area method Parameters ---------- signal : Signal1D instance x1 : float Defines the left limit of the spectral range to use for the estimation. x2 : float Defines the right limit of the spectral range to use for the estimation. only_current : bool If False estimates the parameters for the full dataset. Returns ------- bool """ super()._estimate_parameters(signal) axis = signal.axes_manager.signal_axes[0] i1, i2 = axis.value_range_to_indices(x1, x2) if is_binned(signal): # in v2 replace by #if axis.is_binned: # using the mean of the gradient for non-uniform axes is a best # guess to the scaling of binned signals for the estimation scaling_factor = axis.scale if axis.is_uniform \ else np.mean(np.gradient(axis.axis), axis=-1) if only_current is True: estimation = np.polyfit(axis.axis[i1:i2], signal()[i1:i2], self.get_polynomial_order()) if is_binned(signal): # in v2 replace by #if axis.is_binned: for para, estim in zip(self.parameters[::-1], estimation): para.value = estim / scaling_factor else: for para, estim in zip(self.parameters[::-1], estimation): para.value = estim return True else: if self.parameters[0].map is None: self._create_arrays() nav_shape = signal.axes_manager._navigation_shape_in_array with signal.unfolded(): data = signal.data # For polyfit the spectrum goes in the first axis if axis.index_in_array > 0: data = data.T # Unfolded, so simply transpose fit = np.polyfit(axis.axis[i1:i2], data[i1:i2, ...], self.get_polynomial_order()) if axis.index_in_array > 0: fit = fit.T # Transpose back if needed # Shape needed to fit parameter.map: cmap_shape = nav_shape + (self.get_polynomial_order() + 1, ) fit = fit.reshape(cmap_shape) if is_binned(signal): # in v2 replace by #if axis.is_binned: for i, para in enumerate(self.parameters[::-1]): para.map['values'][:] = fit[..., i] / scaling_factor para.map['is_set'][:] = True else: for i, para in enumerate(self.parameters[::-1]): para.map['values'][:] = fit[..., i] para.map['is_set'][:] = True self.fetch_stored_values() return True
def test_is_binned(): s = signals.Signal1D(np.zeros((5, 5))) assert is_binned(s) == s.axes_manager[-1].is_binned with pytest.warns(VisibleDeprecationWarning, match="Use of the `binned`"): s.metadata.set_item("Signal.binned", True) assert is_binned(s) == s.metadata.Signal.binned
def estimate_parameters(self, signal, x1, x2, only_current=False): """Estimate the parameters for the exponential component by splitting the signal window into two regions and using their geometric means Parameters ---------- signal : BaseSignal instance x1 : float Defines the left limit of the spectral range to use for the estimation. x2 : float Defines the right limit of the spectral range to use for the estimation. only_current : bool If False estimates the parameters for the full dataset. Returns ------- bool """ super()._estimate_parameters(signal) axis = signal.axes_manager.signal_axes[0] i1, i2 = axis.value_range_to_indices(x1, x2) if i1 + 1 == i2: if i2 < axis.high_index: i2 += 1 elif i1 > axis.low_index: i1 -= 1 i_mid = (i1 + i2) // 2 x_start = axis.index2value(i1) x_mid = axis.index2value(i_mid) x_end = axis.index2value(i2) if only_current is True: s = signal.get_current_signal() else: s = signal if s._lazy: import dask.array as da exp = da.exp log = da.log else: exp = np.exp log = np.log with np.errstate(divide='raise', invalid='raise'): try: # use log and exp to compute geometric mean to avoid overflow a1 = s.isig[i1:i_mid].data b1 = log(a1) a2 = s.isig[i_mid:i2].data b2 = log(a2) geo_mean1 = exp(b1.mean(axis=-1)) geo_mean2 = exp(b2.mean(axis=-1)) x1 = (x_start + x_mid) / 2 x2 = (x_mid + x_end) / 2 A = exp((log(geo_mean1) - (x1 / x2) * log(geo_mean2)) / (1 - x1 / x2)) t = -x2 / (log(geo_mean2) - log(A)) if s._lazy: A = A.map_blocks(np.nan_to_num) t = t.map_blocks(np.nan_to_num) else: A = np.nan_to_num(A) t = np.nan_to_num(t) except (FloatingPointError): if i1 == i2: _logger.warning('Exponential parameters estimation failed ' 'because signal range includes only one ' 'point.') else: _logger.warning( 'Exponential parameters estimation failed ' 'with a "divide by zero" error (likely log of ' 'a zero or negative value).') return False if is_binned(signal): # in v2 replace by #if axis.is_binned: if axis.is_uniform: A /= axis.scale else: # using the mean of the gradient for non-uniform axes is a best # guess to the scaling of binned signals for the estimation A /= np.mean(np.gradient(axis.axis)) if only_current is True: self.A.value = A self.tau.value = t return True if self.A.map is None: self._create_arrays() self.A.map['values'][:] = A self.A.map['is_set'][:] = True self.tau.map['values'][:] = t self.tau.map['is_set'][:] = True self.fetch_stored_values() return True
def estimate_parameters(self, signal, x1, x2, only_current=False): """Estimate the skew normal distribution by calculating the momenta. Parameters ---------- signal : Signal1D instance x1 : float Defines the left limit of the spectral range to use for the estimation. x2 : float Defines the right limit of the spectral range to use for the estimation. only_current : bool If False estimates the parameters for the full dataset. Returns ------- bool Notes ----- Adapted from Lin, Lee and Yen, Statistica Sinica 17, 909-927 (2007) https://www.jstor.org/stable/24307705 Examples -------- >>> g = hs.model.components1D.SkewNormal() >>> x = np.arange(-10, 10, 0.01) >>> data = np.zeros((32, 32, 2000)) >>> data[:] = g.function(x).reshape((1, 1, 2000)) >>> s = hs.signals.Signal1D(data) >>> s.axes_manager._axes[-1].offset = -10 >>> s.axes_manager._axes[-1].scale = 0.01 >>> g.estimate_parameters(s, -10, 10, False) """ super(SkewNormal, self)._estimate_parameters(signal) axis = signal.axes_manager.signal_axes[0] x0, height, scale, shape = _estimate_skewnormal_parameters( signal, x1, x2, only_current) if only_current is True: self.x0.value = x0 self.A.value = height * sqrt2pi self.scale.value = scale self.shape.value = shape if is_binned(signal) is True: # in v2 replace by #if axis.is_binned: self.A.value /= axis.scale return True else: if self.A.map is None: self._create_arrays() self.A.map['values'][:] = height * sqrt2pi if is_binned(signal) is True: # in v2 replace by #if axis.is_binned: self.A.map['values'] /= axis.scale self.A.map['is_set'][:] = True self.x0.map['values'][:] = x0 self.x0.map['is_set'][:] = True self.scale.map['values'][:] = scale self.scale.map['is_set'][:] = True self.shape.map['values'][:] = shape self.shape.map['is_set'][:] = True self.fetch_stored_values() return True