Interactive Plotly plot¶
This example demonstrates how to fit emission spectra using Moose
and visualize the result with Plotly
in interactive mode.
The initial guess of the parameters for the fits can be adjusted using the slider widgets in the minimal UI below.
Once satisfied with the guess, pressing the Fit
button will execute the cb_fit
callback.
This callback contains the actual call to fit the data.
The fit procedure can be performed on either of two included sample files in the folder ./N2SPS
:
A spectrum of the second positive system (0,0) band, as recorded at the start of a nanosecond repetitively pulsed discharge:
20220302_s01_sig.cam
A spectrum simulated by SpecAir as part of the unit test from RADIS
- See also: the corresponding GitHub page
- Or the notebook by the RADIS developers that demonstrates using MassiveOES
In [1]:
Copied!
from pathlib import Path
import numpy as np
import pandas as pd
import lmfit
import Moose
import plotly.graph_objects as go
import panel as pn
import panel.widgets as pnw
pn.extension('plotly')
fs = list(Path("N2SPS").glob("*.*"))
sample = pd.read_csv(fs[0], sep='\t', header=1, names=['Wavelength', 'I'])
sample['Norm'] = (sample['I']-sample['I'].min())/(sample['I'].max()-sample['I'].min())
db = Moose.query_DB('N2CB')
model = lmfit.Model(Moose.model_for_fit, sim_db = db)
params = lmfit.create_params(**Moose.default_params)
from pathlib import Path
import numpy as np
import pandas as pd
import lmfit
import Moose
import plotly.graph_objects as go
import panel as pn
import panel.widgets as pnw
pn.extension('plotly')
fs = list(Path("N2SPS").glob("*.*"))
sample = pd.read_csv(fs[0], sep='\t', header=1, names=['Wavelength', 'I'])
sample['Norm'] = (sample['I']-sample['I'].min())/(sample['I'].max()-sample['I'].min())
db = Moose.query_DB('N2CB')
model = lmfit.Model(Moose.model_for_fit, sim_db = db)
params = lmfit.create_params(**Moose.default_params)
In [2]:
Copied!
# Set up the plot
# fig = go.FigureWidget()
fig = go.Figure()
fig.add_scatter(x=sample['Wavelength'], y=sample['Norm'], name='Measured')
fig.add_scatter(x=sample['Wavelength'], y=model.eval(x=sample['Wavelength'].values, params=params), name='Simulation', line_dash='dash')
fig.add_scatter(x=sample['Wavelength'], y=np.zeros(sample.shape[0]), name='Fit')
fig.add_scatter(x=sample['Wavelength'], y=np.zeros(sample.shape[0]), name='Residual')
fig.update_xaxes(title_text='Wavelength (nm)')
fig.update_yaxes(title_text='I (a.u.)')
# Create some widgets to interact with and show the output
look = {'sizing_mode': 'stretch_width'}
wids = {p:pnw.FloatSlider(name = p, start=params[p].min, end=params[p].max, value=params[p].value, step=0.01,**look) for p in params if p in ['mu', 'T_rot', 'T_vib', 'sigma', 'gamma']}
wids_result = {p:pnw.FloatInput(name = p,**look) for p in ['mu', 'T_rot', 'T_vib', 'sigma', 'gamma']}
btn_fit = pnw.Button(name='Fit',button_type='primary', **look)
select_file = pnw.Select(name='Select file', options = fs, **look)
# Create and register callbacks to actually respond and do stuff
def update_simulation(event):
"""Callback to update the preview for finetuning the initial guess"""
params[event.obj.name].value=event.new
with fig.batch_update():
ynew = model.eval(x=fig.data[0].x, **{n:w.value for n,w in wids.items()})
fig.data[1].x = fig.data[0].x
fig.data[1].y = ynew
def fit_iter_cb(pars, iteration, resid,*args, **kwargs):
"""Callback to update on fit iterations, so we can see the progress"""
if iteration % 10 ==0:
with fig.batch_update():
ynew = model.eval(x=fig.data[0].x, **pars)
fig.data[2].x =fig.data[0].x
fig.data[2].y = ynew
fig.data[3].x = fig.data[0].x
fig.data[3].y = resid
for p in pars:
if p in ['mu', 'T_rot', 'T_vib', 'sigma', 'gamma']:
wids_result[p].value = pars[p].value
else:
pass
def cb_fit(*args):
"""Callback to start the fitting routine.
Upon completion it makes the button green, to show the fit completed.
The fitted parameters are updated in `params` upon completion"""
btn_fit.button_type='danger'
result = model.fit(fig.data[0].y, x=fig.data[0].x, params=params, iter_cb= fit_iter_cb, max_nfev=300)
btn_fit.button_type='success'
fit_iter_cb(result.params, 10, result.residual)
for p in result.params:
params[p].value=result.params[p].value
def read_file(*args):
df = pd.read_csv(select_file.value, sep='\t', header=1, names=['Wavelength', 'I'])
df['Norm'] = (df['I']-df['I'].min())/(df['I'].max()-df['I'].min())
with fig.batch_update():
fig.data[0].x = df['Wavelength'].to_numpy()
fig.data[0].y = df['Norm'].to_numpy()
for n,w in wids.items():
w.param.watch(update_simulation,'value_throttled')
btn_fit.on_click(cb_fit)
select_file.param.watch(read_file, 'value')
layout = pn.Column(pn.pane.Plotly(fig,**look),pn.Row(*wids.values()), pn.Row(btn_fit, select_file), pn.Row(*wids_result.values(), **look), height=650,**look)
display(layout)
# Set up the plot
# fig = go.FigureWidget()
fig = go.Figure()
fig.add_scatter(x=sample['Wavelength'], y=sample['Norm'], name='Measured')
fig.add_scatter(x=sample['Wavelength'], y=model.eval(x=sample['Wavelength'].values, params=params), name='Simulation', line_dash='dash')
fig.add_scatter(x=sample['Wavelength'], y=np.zeros(sample.shape[0]), name='Fit')
fig.add_scatter(x=sample['Wavelength'], y=np.zeros(sample.shape[0]), name='Residual')
fig.update_xaxes(title_text='Wavelength (nm)')
fig.update_yaxes(title_text='I (a.u.)')
# Create some widgets to interact with and show the output
look = {'sizing_mode': 'stretch_width'}
wids = {p:pnw.FloatSlider(name = p, start=params[p].min, end=params[p].max, value=params[p].value, step=0.01,**look) for p in params if p in ['mu', 'T_rot', 'T_vib', 'sigma', 'gamma']}
wids_result = {p:pnw.FloatInput(name = p,**look) for p in ['mu', 'T_rot', 'T_vib', 'sigma', 'gamma']}
btn_fit = pnw.Button(name='Fit',button_type='primary', **look)
select_file = pnw.Select(name='Select file', options = fs, **look)
# Create and register callbacks to actually respond and do stuff
def update_simulation(event):
"""Callback to update the preview for finetuning the initial guess"""
params[event.obj.name].value=event.new
with fig.batch_update():
ynew = model.eval(x=fig.data[0].x, **{n:w.value for n,w in wids.items()})
fig.data[1].x = fig.data[0].x
fig.data[1].y = ynew
def fit_iter_cb(pars, iteration, resid,*args, **kwargs):
"""Callback to update on fit iterations, so we can see the progress"""
if iteration % 10 ==0:
with fig.batch_update():
ynew = model.eval(x=fig.data[0].x, **pars)
fig.data[2].x =fig.data[0].x
fig.data[2].y = ynew
fig.data[3].x = fig.data[0].x
fig.data[3].y = resid
for p in pars:
if p in ['mu', 'T_rot', 'T_vib', 'sigma', 'gamma']:
wids_result[p].value = pars[p].value
else:
pass
def cb_fit(*args):
"""Callback to start the fitting routine.
Upon completion it makes the button green, to show the fit completed.
The fitted parameters are updated in `params` upon completion"""
btn_fit.button_type='danger'
result = model.fit(fig.data[0].y, x=fig.data[0].x, params=params, iter_cb= fit_iter_cb, max_nfev=300)
btn_fit.button_type='success'
fit_iter_cb(result.params, 10, result.residual)
for p in result.params:
params[p].value=result.params[p].value
def read_file(*args):
df = pd.read_csv(select_file.value, sep='\t', header=1, names=['Wavelength', 'I'])
df['Norm'] = (df['I']-df['I'].min())/(df['I'].max()-df['I'].min())
with fig.batch_update():
fig.data[0].x = df['Wavelength'].to_numpy()
fig.data[0].y = df['Norm'].to_numpy()
for n,w in wids.items():
w.param.watch(update_simulation,'value_throttled')
btn_fit.on_click(cb_fit)
select_file.param.watch(read_file, 'value')
layout = pn.Column(pn.pane.Plotly(fig,**look),pn.Row(*wids.values()), pn.Row(btn_fit, select_file), pn.Row(*wids_result.values(), **look), height=650,**look)
display(layout)
In [ ]:
Copied!