Interactive Bokeh plot¶
This example demonstrates how to fit emission spectra using Moose
and visualize the result with Bokeh
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
from bokeh.plotting import figure
from bokeh.io import output_notebook
from bokeh.models import ColumnDataSource as CDS
import panel as pn
import panel.widgets as pnw
output_notebook()
pn.extension()
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
from bokeh.plotting import figure
from bokeh.io import output_notebook
from bokeh.models import ColumnDataSource as CDS
import panel as pn
import panel.widgets as pnw
output_notebook()
pn.extension()
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!
fig = figure(sizing_mode='stretch_width', height=250)
cds_data = CDS(sample)
cds_sim = CDS({"Wavelength":sample['Wavelength'], 'Guess':model.eval(x=sample['Wavelength'].values, params=params),'Fit': np.zeros(sample.shape[0]),'Residual': np.zeros(sample.shape[0])})
line_data = fig.line(source=cds_data, x='Wavelength', y='Norm', line_width=3, legend_label='Measured')
fig.line(source=cds_sim, x='Wavelength', y='Guess', line_width=3, legend_label='Guess', color='red', line_dash='dashed')
fig.line(source=cds_sim, x='Wavelength', y='Fit', line_width=3, legend_label='Fit', color='green')
fig.line(source=cds_sim, x='Wavelength', y='Residual', line_width=3, legend_label='Residual', color='purple')
# 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)
btn_read = pnw.Button(name='Read file',button_type='primary', **look)
select_file = pnw.Select(name='Select file', options = fs, **look)
layout = pn.Column(pn.pane.Bokeh(fig),pn.Row(*wids.values()), pn.Row(btn_fit, select_file), pn.Row(*wids_result.values(), **look),**look)
# Create and register callbacks
def update_simulation(event):
"""Callback to update the preview for finetuning the initial guess"""
# x = cds_data.data['Wavelength']
x = line_data.data_source.data['Wavelength']
ynew = model.eval(x=x, **{n:w.value for n,w in wids.items()})
data = cds_sim.data.copy()
data['Guess'] = ynew
cds_sim.data = data
def fit_iter_cb(pars, iteration, resid,*args, **kwargs):
"""Callback to update on fit iterations, so we can see the progress"""
for p in pars:
if p in ['mu', 'T_rot', 'T_vib', 'sigma', 'gamma']:
wids_result[p].value = pars[p].value
if iteration % 20 ==0:
data= line_data.data_source.data
sim_data = cds_sim.data.copy()
sim_data['Wavelength'] = data['Wavelength']
sim_data['Fit'] = model.eval(x=data['Wavelength'], **pars)
sim_data['Residual'] = resid
cds_sim.data = sim_data
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'
data= line_data.data_source.data
result = model.fit(data['Norm'], x=data['Wavelength'], params=params, iter_cb= fit_iter_cb, max_nfev=300)
btn_fit.button_type='success'
for p in result.params:
params[p].value=result.params[p].value
fit_iter_cb(result.params, 20, result.residual) # force update of plot with result when done
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())
cds_data.data = {"index": df.index.values,"Wavelength":df['Wavelength'].values, 'I': df['I'].values, 'Norm': df['Norm'].values}
sim_data = cds_sim.data.copy()
sim_data['Wavelength'] = df['Wavelength'].values
sim_data['Guess'] = model.eval(x=df['Wavelength'].values, params=params)
sim_data['Fit'] = np.zeros(df.shape[0])
sim_data['Residual'] = np.zeros(df.shape[0])
cds_sim.data = sim_data
for n,w in wids.items():
w.param.watch(update_simulation,'value')
btn_fit.on_click(cb_fit)
select_file.param.watch(read_file, 'value')
layout
fig = figure(sizing_mode='stretch_width', height=250)
cds_data = CDS(sample)
cds_sim = CDS({"Wavelength":sample['Wavelength'], 'Guess':model.eval(x=sample['Wavelength'].values, params=params),'Fit': np.zeros(sample.shape[0]),'Residual': np.zeros(sample.shape[0])})
line_data = fig.line(source=cds_data, x='Wavelength', y='Norm', line_width=3, legend_label='Measured')
fig.line(source=cds_sim, x='Wavelength', y='Guess', line_width=3, legend_label='Guess', color='red', line_dash='dashed')
fig.line(source=cds_sim, x='Wavelength', y='Fit', line_width=3, legend_label='Fit', color='green')
fig.line(source=cds_sim, x='Wavelength', y='Residual', line_width=3, legend_label='Residual', color='purple')
# 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)
btn_read = pnw.Button(name='Read file',button_type='primary', **look)
select_file = pnw.Select(name='Select file', options = fs, **look)
layout = pn.Column(pn.pane.Bokeh(fig),pn.Row(*wids.values()), pn.Row(btn_fit, select_file), pn.Row(*wids_result.values(), **look),**look)
# Create and register callbacks
def update_simulation(event):
"""Callback to update the preview for finetuning the initial guess"""
# x = cds_data.data['Wavelength']
x = line_data.data_source.data['Wavelength']
ynew = model.eval(x=x, **{n:w.value for n,w in wids.items()})
data = cds_sim.data.copy()
data['Guess'] = ynew
cds_sim.data = data
def fit_iter_cb(pars, iteration, resid,*args, **kwargs):
"""Callback to update on fit iterations, so we can see the progress"""
for p in pars:
if p in ['mu', 'T_rot', 'T_vib', 'sigma', 'gamma']:
wids_result[p].value = pars[p].value
if iteration % 20 ==0:
data= line_data.data_source.data
sim_data = cds_sim.data.copy()
sim_data['Wavelength'] = data['Wavelength']
sim_data['Fit'] = model.eval(x=data['Wavelength'], **pars)
sim_data['Residual'] = resid
cds_sim.data = sim_data
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'
data= line_data.data_source.data
result = model.fit(data['Norm'], x=data['Wavelength'], params=params, iter_cb= fit_iter_cb, max_nfev=300)
btn_fit.button_type='success'
for p in result.params:
params[p].value=result.params[p].value
fit_iter_cb(result.params, 20, result.residual) # force update of plot with result when done
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())
cds_data.data = {"index": df.index.values,"Wavelength":df['Wavelength'].values, 'I': df['I'].values, 'Norm': df['Norm'].values}
sim_data = cds_sim.data.copy()
sim_data['Wavelength'] = df['Wavelength'].values
sim_data['Guess'] = model.eval(x=df['Wavelength'].values, params=params)
sim_data['Fit'] = np.zeros(df.shape[0])
sim_data['Residual'] = np.zeros(df.shape[0])
cds_sim.data = sim_data
for n,w in wids.items():
w.param.watch(update_simulation,'value')
btn_fit.on_click(cb_fit)
select_file.param.watch(read_file, 'value')
layout
Out[2]:
In [ ]:
Copied!