"""Decorator functions for convenient fitting of Mueller matrices"""
# Encoding: utf-8
from ipywidgets import widgets
from IPython.display import display
import pandas as pd
import numpy.typing as npt
from lmfit import minimize, Parameters
from lmfit.minimizer import MinimizerResult
import plotly.graph_objects as go
from typing import Callable
from ..result import Result
from ..plot.mueller_matrix import plot_mmatrix
[docs]def mmatrix_to_dataframe(exp_df:pd.DataFrame, mueller_matrix:npt.NDArray) -> pd.DataFrame:
"""Reshape a numpy 4x4 array containing mueller matrix elements
to a dataframe with columns Mxy. The index labels for each column
are taken from the provided exp_df.
Args:
exp_df (pd.DataFrame): The experimental dataframe providing the index and columns for
the newly generated dataframe.
mueller_matrix (npt.NDArray): Data to be reshaped into a dataframe
Returns:
pd.DataFrame: Contains the data from mueller_matrix in the shape of exp_df
"""
mueller_df = pd.DataFrame(index=exp_df.index, columns=exp_df.columns, dtype='float64')
mueller_df.values[:] = mueller_matrix.reshape(-1, 16)
return mueller_df
[docs]class FitMuellerMatrix():
"""A class to fit mueller matrices to experimental data"""
[docs] def update_params(self, change:dict):
"""Update plot after a change of fitting parameters
Args:
change (dict): A dictionary containing the ipywidgets change event
"""
self.params[change.owner.description].value = change.new
with self.fig.batch_update():
model_df = mmatrix_to_dataframe(self.exp_mm,
self.model(self.exp_mm.index.values,
self.params).mueller_matrix)
for i, melem in enumerate(model_df):
self.fig.data[2*i+1].y = model_df[melem]
[docs] def fit_function(self,
params:Parameters,
lbda:npt.NDArray,
mueller_matrix: pd.DataFrame) -> npt.NDArray:
"""The fit function to minimize the fitting problem
Args:
params (Parameters): The lmfit fitting Parameters to construct the simulation
lbda (npt.NDArray): Wavelengths in nm
mueller_matrix (pd.DataFrame): The experimental data to compare to the fitted model
Returns:
npt.NDArray: Residual between the calculation with current parameters and experimental data
"""
return mueller_matrix.values.reshape(-1, 4, 4) - self.model(lbda, params).mueller_matrix
[docs] def fit(self, method:str='leastsq') -> MinimizerResult:
"""Execute lmfit with the current fitting parameters
Args:
method (str, optional): The fitting method to use. Any method supported by scipys curve_fit is allowed.
Defaults to 'leastsq'.
Returns:
Result: The fitting result
"""
res = minimize(self.fit_function,
self.params,
args=(self.exp_mm.index.values, self.exp_mm),
method=method)
self.fitted_params = res.params
return res
[docs] def get_fit_data(self) -> pd.DataFrame:
"""Gets the fit results as dataframe
Returns:
pd.DataFrame: The fit results
"""
return mmatrix_to_dataframe(self.exp_mm,
self.model(self.exp_mm.index.values,
self.fitted_params).mueller_matrix)
[docs] def plot(self, **kwargs) -> go.Figure:
"""Plot the fit results
Args:
**display_single (bool): Returns a figure containing a single graph, if set to true. Returns a grid of figures otherwise.
**sharex (bool): Ties the zoom of the x-axes together for grid view.
**full_scale (bool): Sets the y-axis scale to [-1, 1] if set to True.
Returns:
go.Figure: The figure containing the data
"""
fit_result = mmatrix_to_dataframe(self.exp_mm,
self.model(self.exp_mm.index.values,
self.fitted_params).mueller_matrix)
return plot_mmatrix([self.exp_mm, fit_result],
single=self.display_single
if kwargs.get('display_single') is None else kwargs.get('display_single'),
sharex=self.sharex
if kwargs.get('sharex') is None else kwargs.get('sharex'),
full_scale=self.full_scale
if kwargs.get('full_scale') is None else kwargs.get('full_scale'))
def __init__(self,
exp_mm:pd.DataFrame,
params:Parameters,
model:Callable[[npt.NDArray, Parameters], Result],
**kwargs):
"""Intialize the mueller matrix fitting class
Args:
exp_mm (pd.DataFrame): The dataframe containing an experimental mueller matrix.
It should contain 16 columns with labels Mxy,
where xy are the matrix positions.
params (Parameters): Fitting start parameters
model (Callable[[npt.NDArray, Parameters], Result]): A function taking wavelengths as first parameter
and fitting parameters as second,
which returns a pyEllis Result object.
This function contains the actual model which should be fitted
**display_single (bool): Returns a figure containing a single graph, if set to true. Returns a grid of figures otherwise.
**sharex (bool): Ties the zoom of the x-axes together for grid view.
**full_scale (bool): Sets the y-axis scale to [-1, 1] if set to True.
"""
self.exp_mm = exp_mm
self.params = params
self.fitted_params = params.copy()
self.model = model
self.display_single = kwargs.get('display_single')
self.sharex = kwargs.get('sharex')
self.full_scale = kwargs.get('full_scale')
model_df = mmatrix_to_dataframe(exp_mm, model(exp_mm.index.values, params).mueller_matrix)
self.fig = plot_mmatrix([exp_mm,
model_df],
single=self.display_single,
sharex=self.sharex,
full_scale=self.full_scale)
self.create_widgets()
[docs]def fit_mueller_matrix(exp_mm:pd.DataFrame,
params:Parameters,
**kwargs) -> Callable[[npt.NDArray, Parameters], Result]:
"""A parameters decorator for fitting mueller matrices. Displays an ipywidget float box for
each fitting parameter and an interactive plot to estimate parameters.
Args:
exp_mm (pd.DataFrame): The dataframe containing an experimental mueller matrix.
It should contain 16 columns with labels Mxy,
where xy are the matrix positions.
params (Parameters): Fitting start parameters
**display_single (bool): Returns a figure containing a single graph, if set to true. Returns a grid of figures otherwise.
**sharex (bool): Ties the zoom of the x-axes together for grid view.
**full_scale (bool): Sets the y-axis scale to [-1, 1] if set to True.
Returns:
Callable[[npt.NDArray, Parameters], Result]: A function taking wavelengths as first parameter and fitting parameters as second,
which returns a pyEllis Result object.
This function contains the actual model which should be fitted and is automatically
provided when used as a decorator.
"""
return lambda model: FitMuellerMatrix(exp_mm, params, model, **kwargs)