# -*- coding: utf-8 -*-
################################ Begin license #################################
# Copyright (C) Laboratory of Imaging technologies,
# Faculty of Electrical Engineering,
# University of Ljubljana.
#
# This file is part of PyXOpto.
#
# PyXOpto is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# PyXOpto is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with PyXOpto. If not, see <https://www.gnu.org/licenses/>.
################################# End license ##################################
from typing import Tuple
import numpy as np
[docs]class Normalize:
def __init__(self, src_range: Tuple[float, float],
dest_range: Tuple[float, float]=(-1.0, 1.0)):
'''
Linearly transforms the input data range so that they tightly fit the
specified output range.
Parameters
----------
src_range: numpy.ndarray, list or tuple of 2 values
The range of input values as [min, max]. Can also be a full
array of values in which case the minimum and maximum values
are taken as the range.
dest_range: numpy.ndarray, list or tuple of 2 values
The range of normalized values as [min, max].
'''
self._dest_range = (float(dest_range[0]), float(dest_range[1]))
src_range = np.asarray(src_range, dtype=np.float64)
self._src_range = (float(src_range.min()), float(src_range.max()))
out_span = self._dest_range[1] - self._dest_range[0]
in_span = self._src_range[1] - self._src_range[0]
self._k = out_span/in_span
def __call__(self, data: np.ndarray) -> np.ndarray:
'''
Applies the normalization/transformation to the data.
Parameters
----------
data: numpy.ndarray, list or tuple
Data to transfor.
Returns
-------
normalized: numpy.ndarray, list or tuple
The input data transformed to the output range specified
in the constructor call.
'''
data = np.asarray(data, dtype=np.float64)
return self._dest_range[0] + (data - self._low)*self._k
[docs] def undo(self, data: np.ndarray) -> np.ndarray:
'''
Apply inverse of the normalization/transformation to the data.
Parameters
----------
data: numpy.ndarray, list or tuple
Data to transfor.
Returns
-------
denormalized: numpy.ndarray, list or tuple
The input data inversely transformed to the input range specified
in the constructor call.
'''
data = np.asarray(data, dtype=np.float64)
return (data - self._dest_range[0])/self._k + self._low
[docs] def render(self, input='wavelength', output='wn'):
'''
Render the preprocessor equation to a string.
Parameters
----------
input: str
Input symbol as a string.
output: str
Output symbol as a string.
'''
return '{:s} = {:.8e} + ({:s} - {:.8e})*{:.8e}'.format(
output, self._dest_range[0], input, self._low, self._k)
def __str__(self):
return 'Normalize(values={}, interval={}) '\
'# Transformation: "{} + (x - {})*{}"'.format(
self._values, self._interval,
self._dest_range[0], self._low, self._k)
def __repr__(self):
return '{} # id {}'.format(self.__str__(), id(self))
[docs]class Scale:
def __init__(self, factor: float, offset: float = 0.0):
'''
Constructs an object that transforms the input data as
(data - offset)*factor.
Parameters
----------
factor: float
The multiplicative term.
offset: float
The additive term.
'''
self._factor = float(factor)
self._offset = float(offset)
def __call__(self, data: np.ndarray) -> np.ndarray:
'''
Applies the normalization/transformation to the data.
Parameters
----------
data: numpy.ndarray, list or tuple
Data to transfor.
Returns
-------
scaled: numpy.ndarray, list or tuple
The input data shifted and scaled by the values specified
'''
return (np.asarray(data, dtype=np.float64) - self._offset)*self._factor
[docs] def undo(self, data: np.ndarray) -> np.ndarray:
'''
Apply inverse of the transformation to the data.
Parameters
----------
data: numpy.ndarray, list or tuple
Data to transfor.
Returns
-------
descaled: numpy.ndarray, list or tuple
The input data inversely transformed.
'''
return np.asarray(data, dtype=np.float64)/self._factor + self._offset
[docs] def render(self, input='wavelength', output='wn'):
'''
Render the preprocessor equation to a string.
Parameters
----------
input: str
Input symbol as a string.
output: str
Output symbol as a string.
'''
if self._offset == 0.0:
res = '{:s} = {:s}*{:.8e}'.format(output, input, self._factor)
else:
res = '{:s} = ({:s} - {:.8e})*{:.8e}'.format(
output, input, self._offset, self._factor)
return res
def __str__(self):
return 'Scale(factor={offset}, offset={factor}) '\
'# Transformation: "(x - {offset})*{factor}"'.format(
offset=self._offset, factor=self._factor)
def __repr__(self):
return '{} # id {}'.format(self.__str__(), id(self))
[docs]class Model:
def __init__(self, formatstr:str, params: np.ndarray or None = None,
pp: Scale or Normalize = None):
'''
Creates a refractive index model with the give wavelength preprocessor.
Parameters
----------
formatstr: str
Format string that can be used to print the model equation.
params: np.ndarray
Default values of the model parameters.
pp: Scale or Normalize
Wavelength preprocesor.
'''
if params is not None:
params = np.asarray(params)
if pp is None:
pp = Scale(1.0)
self._pp = pp
self._params = params
self._formatstr = formatstr
[docs] def guess(self, wavelengths: np.ndarray, n: np.ndarray) -> list:
'''
Returns an initial guess for optimization/fit.
Parameters
----------
wavelengths: np.ndarray
The wavelengths of light at which the values of refractive index
are defined.
n: np.ndarray
The values of refractive index at the given wavelengths of light.
Returns
-------
params0: np.ndarray
Initial guess for the values of the model parameters
'''
if self.params is None:
raise RuntimeError('Cannot derive the number of model parameters!')
return [0.0]*len(self.params)
def _get_pp(self) -> Scale or Normalize:
return self._pp
pp = property(_get_pp, None, None,
'Preprocessor that scales/normalizes the wavelengths')
def _get_params(self) -> Scale or Normalize:
return self._params
params = property(_get_params, None, None,
'Default parameter values of the model.')
def _get_formatstr(self) -> str:
return self._formatstr
formatstr = property(_get_formatstr, None, None,
'Format string for rendering the model equation.')
def __call__(self, wavelengths:np.ndarray, params=None) -> np.ndarray:
'''
Evaluate the model for the given wavelengths. Use the given parameters
or default values from constructor if params is None.
Parameters
----------
wavelength: np.ndarray
Wavelengths of light (m) at which to evaluate the model.
params: np.ndarray or None
Parameters of the model. If none, uses default values that were
passed to the constructor.
Returns
-------
n: np.ndarray
Refractive index at the given wavelengths as estimated by the model.
'''
if params is None:
params = self.params
return self.ri(params, wavelengths)
def _get_name(self):
return self.__class__.__name__
name = property(_get_name, None, None, 'Model name.')
[docs] def render(self, params: np.ndarray = None) -> str:
'''
Render the model equation with the given set of parameters
(use default parameter values passed to the constructor if None).
Parameters
----------
params: np.ndarray or None
Model parameters. If None, use the default parameter values as
passed to the constructor.
Returns
-------
eq: str
Model equation rendered as a string.
pp: str
Preprocessor equation rendered as a string.
'''
if params is None:
if self.params is None:
raise ValueError(
'Cannot derive the values of model parameters!')
params = self.params
return self._formatstr.format(*np.asarray(params).tolist()), \
self.pp.render()