Extract fit controls

This commit is contained in:
usov_i 2022-12-23 19:22:03 +01:00
parent 0b340d0bb9
commit 200e5d0a13
5 changed files with 244 additions and 451 deletions

View File

@ -0,0 +1 @@
from pyzebra.app.fit_controls import FitControls

169
pyzebra/app/fit_controls.py Normal file
View File

@ -0,0 +1,169 @@
import types
from bokeh.models import (
Button,
CellEditor,
CheckboxEditor,
CheckboxGroup,
ColumnDataSource,
DataTable,
Dropdown,
MultiSelect,
NumberEditor,
RadioGroup,
Spinner,
TableColumn,
TextAreaInput,
)
import pyzebra
def _params_factory(function):
if function == "linear":
param_names = ["slope", "intercept"]
elif function == "gaussian":
param_names = ["amplitude", "center", "sigma"]
elif function == "voigt":
param_names = ["amplitude", "center", "sigma", "gamma"]
elif function == "pvoigt":
param_names = ["amplitude", "center", "sigma", "fraction"]
elif function == "pseudovoigt1":
param_names = ["amplitude", "center", "g_sigma", "l_sigma", "fraction"]
else:
raise ValueError("Unknown fit function")
n = len(param_names)
params = dict(
param=param_names, value=[None] * n, vary=[True] * n, min=[None] * n, max=[None] * n
)
if function == "linear":
params["value"] = [0, 1]
params["vary"] = [False, True]
params["min"] = [None, 0]
elif function == "gaussian":
params["min"] = [0, None, None]
return params
class FitControls:
def __init__(self):
self.params = {}
def add_function_button_callback(click):
# bokeh requires (str, str) for MultiSelect options
new_tag = f"{click.item}-{function_select.tags[0]}"
function_select.options.append((new_tag, click.item))
self.params[new_tag] = _params_factory(click.item)
function_select.tags[0] += 1
add_function_button = Dropdown(
label="Add fit function",
menu=[
("Linear", "linear"),
("Gaussian", "gaussian"),
("Voigt", "voigt"),
("Pseudo Voigt", "pvoigt"),
# ("Pseudo Voigt1", "pseudovoigt1"),
],
width=145,
)
add_function_button.on_click(add_function_button_callback)
self.add_function_button = add_function_button
def function_list_callback(_attr, old, new):
# Avoid selection of multiple indicies (via Shift+Click or Ctrl+Click)
if len(new) > 1:
# drop selection to the previous one
function_select.value = old
return
if len(old) > 1:
# skip unnecessary update caused by selection drop
return
if new:
params_table_source.data.update(self.params[new[0]])
else:
params_table_source.data.update(dict(param=[], value=[], vary=[], min=[], max=[]))
function_select = MultiSelect(options=[], height=120, width=145)
function_select.tags = [0]
function_select.on_change("value", function_list_callback)
self.function_select = function_select
def remove_function_button_callback():
if function_select.value:
sel_tag = function_select.value[0]
del self.params[sel_tag]
for elem in function_select.options:
if elem[0] == sel_tag:
function_select.options.remove(elem)
break
function_select.value = []
remove_function_button = Button(label="Remove fit function", width=145)
remove_function_button.on_click(remove_function_button_callback)
self.remove_function_button = remove_function_button
params_table_source = ColumnDataSource(dict(param=[], value=[], vary=[], min=[], max=[]))
self.params_table = DataTable(
source=params_table_source,
columns=[
TableColumn(field="param", title="Parameter", editor=CellEditor()),
TableColumn(field="value", title="Value", editor=NumberEditor()),
TableColumn(field="vary", title="Vary", editor=CheckboxEditor()),
TableColumn(field="min", title="Min", editor=NumberEditor()),
TableColumn(field="max", title="Max", editor=NumberEditor()),
],
height=200,
width=350,
index_position=None,
editable=True,
auto_edit=True,
)
# start with `background` and `gauss` fit functions added
add_function_button_callback(types.SimpleNamespace(item="linear"))
add_function_button_callback(types.SimpleNamespace(item="gaussian"))
function_select.value = ["gaussian-1"] # put selection on gauss
self.from_spinner = Spinner(title="Fit from:", width=145)
self.to_spinner = Spinner(title="to:", width=145)
self.area_method_radiogroup = RadioGroup(labels=["Function", "Area"], active=0, width=145)
self.lorentz_checkbox = CheckboxGroup(
labels=["Lorentz Correction"], width=145, margin=(13, 5, 5, 5)
)
self.result_textarea = TextAreaInput(title="Fit results:", width=750, height=200)
def _process_scan(self, scan):
pyzebra.fit_scan(
scan, self.params, fit_from=self.from_spinner.value, fit_to=self.to_spinner.value
)
pyzebra.get_area(
scan,
area_method=pyzebra.AREA_METHODS[self.area_method_radiogroup.active],
lorentz=self.lorentz_checkbox.active,
)
def fit_scan(self, scan):
self._process_scan(scan)
def fit_dataset(self, dataset):
for scan in dataset:
if scan["export"]:
self._process_scan(scan)
def update_result_textarea(self, scan):
fit = scan.get("fit")
if fit is None:
self.result_textarea.value = ""
else:
self.result_textarea.value = fit.fit_report()

View File

@ -2,7 +2,6 @@ import base64
import io import io
import os import os
import tempfile import tempfile
import types
import numpy as np import numpy as np
from bokeh.io import curdoc from bokeh.io import curdoc
@ -11,15 +10,12 @@ from bokeh.models import (
Button, Button,
CellEditor, CellEditor,
CheckboxEditor, CheckboxEditor,
CheckboxGroup,
ColumnDataSource, ColumnDataSource,
CustomJS, CustomJS,
DataTable, DataTable,
Div, Div,
Dropdown,
FileInput, FileInput,
MultiSelect, MultiSelect,
NumberEditor,
Panel, Panel,
RadioGroup, RadioGroup,
Select, Select,
@ -33,7 +29,7 @@ from bokeh.models import (
from bokeh.plotting import figure from bokeh.plotting import figure
import pyzebra import pyzebra
from pyzebra import AREA_METHODS, EXPORT_TARGETS from pyzebra import EXPORT_TARGETS, app
javaScript = """ javaScript = """
let j = 0; let j = 0;
@ -61,7 +57,6 @@ def create():
doc = curdoc() doc = curdoc()
dataset1 = [] dataset1 = []
dataset2 = [] dataset2 = []
fit_params = {}
js_data = ColumnDataSource(data=dict(content=["", ""], fname=["", ""], ext=["", ""])) js_data = ColumnDataSource(data=dict(content=["", ""], fname=["", ""], ext=["", ""]))
def file_select_update_for_proposal(): def file_select_update_for_proposal():
@ -244,7 +239,7 @@ def create():
xs_peak = [] xs_peak = []
ys_peak = [] ys_peak = []
comps = fit.eval_components(x=x_fit) comps = fit.eval_components(x=x_fit)
for i, model in enumerate(fit_params): for i, model in enumerate(app_fitctrl.params):
if "linear" in model: if "linear" in model:
x_bkg = x_fit x_bkg = x_fit
y_bkg = comps[f"f{i}_"] y_bkg = comps[f"f{i}_"]
@ -264,7 +259,7 @@ def create():
bkg_source.data.update(x=[], y=[]) bkg_source.data.update(x=[], y=[])
peak_source.data.update(xs=[], ys=[]) peak_source.data.update(xs=[], ys=[])
fit_output_textinput.value = fit_output app_fitctrl.result_textarea.value = fit_output
# Main plot # Main plot
plot = figure( plot = figure(
@ -425,136 +420,21 @@ def create():
restore_button = Button(label="Restore scan", width=145) restore_button = Button(label="Restore scan", width=145)
restore_button.on_click(restore_button_callback) restore_button.on_click(restore_button_callback)
app_fitctrl = app.FitControls()
def fit_from_spinner_callback(_attr, _old, new): def fit_from_spinner_callback(_attr, _old, new):
fit_from_span.location = new fit_from_span.location = new
fit_from_spinner = Spinner(title="Fit from:", width=145) app_fitctrl.from_spinner.on_change("value", fit_from_spinner_callback)
fit_from_spinner.on_change("value", fit_from_spinner_callback)
def fit_to_spinner_callback(_attr, _old, new): def fit_to_spinner_callback(_attr, _old, new):
fit_to_span.location = new fit_to_span.location = new
fit_to_spinner = Spinner(title="to:", width=145) app_fitctrl.to_spinner.on_change("value", fit_to_spinner_callback)
fit_to_spinner.on_change("value", fit_to_spinner_callback)
def fitparams_add_dropdown_callback(click):
# bokeh requires (str, str) for MultiSelect options
new_tag = f"{click.item}-{fitparams_select.tags[0]}"
fitparams_select.options.append((new_tag, click.item))
fit_params[new_tag] = fitparams_factory(click.item)
fitparams_select.tags[0] += 1
fitparams_add_dropdown = Dropdown(
label="Add fit function",
menu=[
("Linear", "linear"),
("Gaussian", "gaussian"),
("Voigt", "voigt"),
("Pseudo Voigt", "pvoigt"),
# ("Pseudo Voigt1", "pseudovoigt1"),
],
width=145,
)
fitparams_add_dropdown.on_click(fitparams_add_dropdown_callback)
def fitparams_select_callback(_attr, old, new):
# Avoid selection of multiple indicies (via Shift+Click or Ctrl+Click)
if len(new) > 1:
# drop selection to the previous one
fitparams_select.value = old
return
if len(old) > 1:
# skip unnecessary update caused by selection drop
return
if new:
fitparams_table_source.data.update(fit_params[new[0]])
else:
fitparams_table_source.data.update(dict(param=[], value=[], vary=[], min=[], max=[]))
fitparams_select = MultiSelect(options=[], height=120, width=145)
fitparams_select.tags = [0]
fitparams_select.on_change("value", fitparams_select_callback)
def fitparams_remove_button_callback():
if fitparams_select.value:
sel_tag = fitparams_select.value[0]
del fit_params[sel_tag]
for elem in fitparams_select.options:
if elem[0] == sel_tag:
fitparams_select.options.remove(elem)
break
fitparams_select.value = []
fitparams_remove_button = Button(label="Remove fit function", width=145)
fitparams_remove_button.on_click(fitparams_remove_button_callback)
def fitparams_factory(function):
if function == "linear":
params = ["slope", "intercept"]
elif function == "gaussian":
params = ["amplitude", "center", "sigma"]
elif function == "voigt":
params = ["amplitude", "center", "sigma", "gamma"]
elif function == "pvoigt":
params = ["amplitude", "center", "sigma", "fraction"]
elif function == "pseudovoigt1":
params = ["amplitude", "center", "g_sigma", "l_sigma", "fraction"]
else:
raise ValueError("Unknown fit function")
n = len(params)
fitparams = dict(
param=params, value=[None] * n, vary=[True] * n, min=[None] * n, max=[None] * n
)
if function == "linear":
fitparams["value"] = [0, 1]
fitparams["vary"] = [False, True]
fitparams["min"] = [None, 0]
elif function == "gaussian":
fitparams["min"] = [0, None, None]
return fitparams
fitparams_table_source = ColumnDataSource(dict(param=[], value=[], vary=[], min=[], max=[]))
fitparams_table = DataTable(
source=fitparams_table_source,
columns=[
TableColumn(field="param", title="Parameter", editor=CellEditor()),
TableColumn(field="value", title="Value", editor=NumberEditor()),
TableColumn(field="vary", title="Vary", editor=CheckboxEditor()),
TableColumn(field="min", title="Min", editor=NumberEditor()),
TableColumn(field="max", title="Max", editor=NumberEditor()),
],
height=200,
width=350,
index_position=None,
editable=True,
auto_edit=True,
)
# start with `background` and `gauss` fit functions added
fitparams_add_dropdown_callback(types.SimpleNamespace(item="linear"))
fitparams_add_dropdown_callback(types.SimpleNamespace(item="gaussian"))
fitparams_select.value = ["gaussian-1"] # add selection to gauss
fit_output_textinput = TextAreaInput(title="Fit results:", width=750, height=200)
def proc_all_button_callback(): def proc_all_button_callback():
for scan in [*dataset1, *dataset2]: app_fitctrl.fit_dataset(dataset1)
if scan["export"]: app_fitctrl.fit_dataset(dataset2)
pyzebra.fit_scan(
scan, fit_params, fit_from=fit_from_spinner.value, fit_to=fit_to_spinner.value
)
pyzebra.get_area(
scan,
area_method=AREA_METHODS[area_method_radiobutton.active],
lorentz=lorentz_checkbox.active,
)
_update_plot() _update_plot()
_update_table() _update_table()
@ -563,15 +443,9 @@ def create():
proc_all_button.on_click(proc_all_button_callback) proc_all_button.on_click(proc_all_button_callback)
def proc_button_callback(): def proc_button_callback():
for scan in _get_selected_scan(): scan1, scan2 = _get_selected_scan()
pyzebra.fit_scan( app_fitctrl.fit_scan(scan1)
scan, fit_params, fit_from=fit_from_spinner.value, fit_to=fit_to_spinner.value app_fitctrl.fit_scan(scan2)
)
pyzebra.get_area(
scan,
area_method=AREA_METHODS[area_method_radiobutton.active],
lorentz=lorentz_checkbox.active,
)
_update_plot() _update_plot()
_update_table() _update_table()
@ -579,16 +453,11 @@ def create():
proc_button = Button(label="Process Current", width=145) proc_button = Button(label="Process Current", width=145)
proc_button.on_click(proc_button_callback) proc_button.on_click(proc_button_callback)
area_method_div = Div(text="Intensity:", margin=(5, 5, 0, 5))
area_method_radiobutton = RadioGroup(labels=["Function", "Area"], active=0, width=145)
intensity_diff_div = Div(text="Intensity difference:", margin=(5, 5, 0, 5)) intensity_diff_div = Div(text="Intensity difference:", margin=(5, 5, 0, 5))
intensity_diff_radiobutton = RadioGroup( intensity_diff_radiobutton = RadioGroup(
labels=["file1 - file2", "file2 - file1"], active=0, width=145 labels=["file1 - file2", "file2 - file1"], active=0, width=145
) )
lorentz_checkbox = CheckboxGroup(labels=["Lorentz Correction"], width=145, margin=(13, 5, 5, 5))
export_preview_textinput = TextAreaInput(title="Export file(s) preview:", width=500, height=400) export_preview_textinput = TextAreaInput(title="Export file(s) preview:", width=500, height=400)
def _update_preview(): def _update_preview():
@ -648,19 +517,24 @@ def create():
save_button = Button(label="Download File(s)", button_type="success", width=200) save_button = Button(label="Download File(s)", button_type="success", width=200)
save_button.js_on_click(CustomJS(args={"js_data": js_data}, code=javaScript)) save_button.js_on_click(CustomJS(args={"js_data": js_data}, code=javaScript))
area_method_div = Div(text="Intensity:", margin=(5, 5, 0, 5))
fitpeak_controls = row( fitpeak_controls = row(
column(fitparams_add_dropdown, fitparams_select, fitparams_remove_button), column(
fitparams_table, app_fitctrl.add_function_button,
app_fitctrl.function_select,
app_fitctrl.remove_function_button,
),
app_fitctrl.params_table,
Spacer(width=20), Spacer(width=20),
column( column(
fit_from_spinner, app_fitctrl.from_spinner,
lorentz_checkbox, app_fitctrl.lorentz_checkbox,
area_method_div, area_method_div,
area_method_radiobutton, app_fitctrl.area_method_radiogroup,
intensity_diff_div, intensity_diff_div,
intensity_diff_radiobutton, intensity_diff_radiobutton,
), ),
column(fit_to_spinner, proc_button, proc_all_button), column(app_fitctrl.to_spinner, proc_button, proc_all_button),
) )
scan_layout = column( scan_layout = column(
@ -680,7 +554,7 @@ def create():
tab_layout = column( tab_layout = column(
row(import_layout, scan_layout, plot, Spacer(width=30), export_layout), row(import_layout, scan_layout, plot, Spacer(width=30), export_layout),
row(fitpeak_controls, fit_output_textinput), row(fitpeak_controls, app_fitctrl.result_textarea),
) )
return Panel(child=tab_layout, title="ccl compare") return Panel(child=tab_layout, title="ccl compare")

View File

@ -2,7 +2,6 @@ import base64
import io import io
import os import os
import tempfile import tempfile
import types
import numpy as np import numpy as np
from bokeh.io import curdoc from bokeh.io import curdoc
@ -11,17 +10,13 @@ from bokeh.models import (
Button, Button,
CellEditor, CellEditor,
CheckboxEditor, CheckboxEditor,
CheckboxGroup,
ColumnDataSource, ColumnDataSource,
CustomJS, CustomJS,
DataTable, DataTable,
Div, Div,
Dropdown,
FileInput, FileInput,
MultiSelect, MultiSelect,
NumberEditor,
Panel, Panel,
RadioGroup,
Select, Select,
Spacer, Spacer,
Span, Span,
@ -33,7 +28,7 @@ from bokeh.models import (
from bokeh.plotting import figure from bokeh.plotting import figure
import pyzebra import pyzebra
from pyzebra import AREA_METHODS, EXPORT_TARGETS from pyzebra import EXPORT_TARGETS, app
javaScript = """ javaScript = """
let j = 0; let j = 0;
@ -60,7 +55,6 @@ for (let i = 0; i < js_data.data['fname'].length; i++) {
def create(): def create():
doc = curdoc() doc = curdoc()
dataset = [] dataset = []
fit_params = {}
js_data = ColumnDataSource(data=dict(content=["", ""], fname=["", ""], ext=["", ""])) js_data = ColumnDataSource(data=dict(content=["", ""], fname=["", ""], ext=["", ""]))
def file_select_update_for_proposal(): def file_select_update_for_proposal():
@ -259,7 +253,7 @@ def create():
xs_peak = [] xs_peak = []
ys_peak = [] ys_peak = []
comps = fit.eval_components(x=x_fit) comps = fit.eval_components(x=x_fit)
for i, model in enumerate(fit_params): for i, model in enumerate(app_fitctrl.params):
if "linear" in model: if "linear" in model:
x_bkg = x_fit x_bkg = x_fit
y_bkg = comps[f"f{i}_"] y_bkg = comps[f"f{i}_"]
@ -271,13 +265,12 @@ def create():
bkg_source.data.update(x=x_bkg, y=y_bkg) bkg_source.data.update(x=x_bkg, y=y_bkg)
peak_source.data.update(xs=xs_peak, ys=ys_peak) peak_source.data.update(xs=xs_peak, ys=ys_peak)
fit_output_textinput.value = fit.fit_report()
else: else:
fit_source.data.update(x=[], y=[]) fit_source.data.update(x=[], y=[])
bkg_source.data.update(x=[], y=[]) bkg_source.data.update(x=[], y=[])
peak_source.data.update(xs=[], ys=[]) peak_source.data.update(xs=[], ys=[])
fit_output_textinput.value = ""
app_fitctrl.update_result_textarea(scan)
# Main plot # Main plot
plot = figure( plot = figure(
@ -403,136 +396,20 @@ def create():
restore_button = Button(label="Restore scan", width=145) restore_button = Button(label="Restore scan", width=145)
restore_button.on_click(restore_button_callback) restore_button.on_click(restore_button_callback)
app_fitctrl = app.FitControls()
def fit_from_spinner_callback(_attr, _old, new): def fit_from_spinner_callback(_attr, _old, new):
fit_from_span.location = new fit_from_span.location = new
fit_from_spinner = Spinner(title="Fit from:", width=145) app_fitctrl.from_spinner.on_change("value", fit_from_spinner_callback)
fit_from_spinner.on_change("value", fit_from_spinner_callback)
def fit_to_spinner_callback(_attr, _old, new): def fit_to_spinner_callback(_attr, _old, new):
fit_to_span.location = new fit_to_span.location = new
fit_to_spinner = Spinner(title="to:", width=145) app_fitctrl.to_spinner.on_change("value", fit_to_spinner_callback)
fit_to_spinner.on_change("value", fit_to_spinner_callback)
def fitparams_add_dropdown_callback(click):
# bokeh requires (str, str) for MultiSelect options
new_tag = f"{click.item}-{fitparams_select.tags[0]}"
fitparams_select.options.append((new_tag, click.item))
fit_params[new_tag] = fitparams_factory(click.item)
fitparams_select.tags[0] += 1
fitparams_add_dropdown = Dropdown(
label="Add fit function",
menu=[
("Linear", "linear"),
("Gaussian", "gaussian"),
("Voigt", "voigt"),
("Pseudo Voigt", "pvoigt"),
# ("Pseudo Voigt1", "pseudovoigt1"),
],
width=145,
)
fitparams_add_dropdown.on_click(fitparams_add_dropdown_callback)
def fitparams_select_callback(_attr, old, new):
# Avoid selection of multiple indicies (via Shift+Click or Ctrl+Click)
if len(new) > 1:
# drop selection to the previous one
fitparams_select.value = old
return
if len(old) > 1:
# skip unnecessary update caused by selection drop
return
if new:
fitparams_table_source.data.update(fit_params[new[0]])
else:
fitparams_table_source.data.update(dict(param=[], value=[], vary=[], min=[], max=[]))
fitparams_select = MultiSelect(options=[], height=120, width=145)
fitparams_select.tags = [0]
fitparams_select.on_change("value", fitparams_select_callback)
def fitparams_remove_button_callback():
if fitparams_select.value:
sel_tag = fitparams_select.value[0]
del fit_params[sel_tag]
for elem in fitparams_select.options:
if elem[0] == sel_tag:
fitparams_select.options.remove(elem)
break
fitparams_select.value = []
fitparams_remove_button = Button(label="Remove fit function", width=145)
fitparams_remove_button.on_click(fitparams_remove_button_callback)
def fitparams_factory(function):
if function == "linear":
params = ["slope", "intercept"]
elif function == "gaussian":
params = ["amplitude", "center", "sigma"]
elif function == "voigt":
params = ["amplitude", "center", "sigma", "gamma"]
elif function == "pvoigt":
params = ["amplitude", "center", "sigma", "fraction"]
elif function == "pseudovoigt1":
params = ["amplitude", "center", "g_sigma", "l_sigma", "fraction"]
else:
raise ValueError("Unknown fit function")
n = len(params)
fitparams = dict(
param=params, value=[None] * n, vary=[True] * n, min=[None] * n, max=[None] * n
)
if function == "linear":
fitparams["value"] = [0, 1]
fitparams["vary"] = [False, True]
fitparams["min"] = [None, 0]
elif function == "gaussian":
fitparams["min"] = [0, None, None]
return fitparams
fitparams_table_source = ColumnDataSource(dict(param=[], value=[], vary=[], min=[], max=[]))
fitparams_table = DataTable(
source=fitparams_table_source,
columns=[
TableColumn(field="param", title="Parameter", editor=CellEditor()),
TableColumn(field="value", title="Value", editor=NumberEditor()),
TableColumn(field="vary", title="Vary", editor=CheckboxEditor()),
TableColumn(field="min", title="Min", editor=NumberEditor()),
TableColumn(field="max", title="Max", editor=NumberEditor()),
],
height=200,
width=350,
index_position=None,
editable=True,
auto_edit=True,
)
# start with `background` and `gauss` fit functions added
fitparams_add_dropdown_callback(types.SimpleNamespace(item="linear"))
fitparams_add_dropdown_callback(types.SimpleNamespace(item="gaussian"))
fitparams_select.value = ["gaussian-1"] # add selection to gauss
fit_output_textinput = TextAreaInput(title="Fit results:", width=750, height=200)
def proc_all_button_callback(): def proc_all_button_callback():
for scan in dataset: app_fitctrl.fit_dataset(dataset)
if scan["export"]:
pyzebra.fit_scan(
scan, fit_params, fit_from=fit_from_spinner.value, fit_to=fit_to_spinner.value
)
pyzebra.get_area(
scan,
area_method=AREA_METHODS[area_method_radiobutton.active],
lorentz=lorentz_checkbox.active,
)
_update_plot() _update_plot()
_update_table() _update_table()
@ -541,15 +418,7 @@ def create():
proc_all_button.on_click(proc_all_button_callback) proc_all_button.on_click(proc_all_button_callback)
def proc_button_callback(): def proc_button_callback():
scan = _get_selected_scan() app_fitctrl.fit_scan(_get_selected_scan())
pyzebra.fit_scan(
scan, fit_params, fit_from=fit_from_spinner.value, fit_to=fit_to_spinner.value
)
pyzebra.get_area(
scan,
area_method=AREA_METHODS[area_method_radiobutton.active],
lorentz=lorentz_checkbox.active,
)
_update_plot() _update_plot()
_update_table() _update_table()
@ -557,11 +426,6 @@ def create():
proc_button = Button(label="Process Current", width=145) proc_button = Button(label="Process Current", width=145)
proc_button.on_click(proc_button_callback) proc_button.on_click(proc_button_callback)
area_method_div = Div(text="Intensity:", margin=(5, 5, 0, 5))
area_method_radiobutton = RadioGroup(labels=["Function", "Area"], active=0, width=145)
lorentz_checkbox = CheckboxGroup(labels=["Lorentz Correction"], width=145, margin=(13, 5, 5, 5))
export_preview_textinput = TextAreaInput(title="Export file(s) preview:", width=500, height=400) export_preview_textinput = TextAreaInput(title="Export file(s) preview:", width=500, height=400)
def _update_preview(): def _update_preview():
@ -615,12 +479,22 @@ def create():
save_button = Button(label="Download File(s)", button_type="success", width=200) save_button = Button(label="Download File(s)", button_type="success", width=200)
save_button.js_on_click(CustomJS(args={"js_data": js_data}, code=javaScript)) save_button.js_on_click(CustomJS(args={"js_data": js_data}, code=javaScript))
area_method_div = Div(text="Intensity:", margin=(5, 5, 0, 5))
fitpeak_controls = row( fitpeak_controls = row(
column(fitparams_add_dropdown, fitparams_select, fitparams_remove_button), column(
fitparams_table, app_fitctrl.add_function_button,
app_fitctrl.function_select,
app_fitctrl.remove_function_button,
),
app_fitctrl.params_table,
Spacer(width=20), Spacer(width=20),
column(fit_from_spinner, lorentz_checkbox, area_method_div, area_method_radiobutton), column(
column(fit_to_spinner, proc_button, proc_all_button), app_fitctrl.from_spinner,
app_fitctrl.lorentz_checkbox,
area_method_div,
app_fitctrl.area_method_radiogroup,
),
column(app_fitctrl.to_spinner, proc_button, proc_all_button),
) )
scan_layout = column( scan_layout = column(
@ -647,7 +521,7 @@ def create():
tab_layout = column( tab_layout = column(
row(import_layout, scan_layout, plot, Spacer(width=30), export_layout), row(import_layout, scan_layout, plot, Spacer(width=30), export_layout),
row(fitpeak_controls, fit_output_textinput), row(fitpeak_controls, app_fitctrl.result_textarea),
) )
return Panel(child=tab_layout, title="ccl integrate") return Panel(child=tab_layout, title="ccl integrate")

View File

@ -3,7 +3,6 @@ import io
import itertools import itertools
import os import os
import tempfile import tempfile
import types
import numpy as np import numpy as np
from bokeh.io import curdoc from bokeh.io import curdoc
@ -12,19 +11,16 @@ from bokeh.models import (
Button, Button,
CellEditor, CellEditor,
CheckboxEditor, CheckboxEditor,
CheckboxGroup,
ColumnDataSource, ColumnDataSource,
CustomJS, CustomJS,
DataTable, DataTable,
Div, Div,
Dropdown,
FileInput, FileInput,
HoverTool, HoverTool,
LinearColorMapper, LinearColorMapper,
MultiSelect, MultiSelect,
NumberEditor, NumberEditor,
Panel, Panel,
RadioGroup,
Range1d, Range1d,
Select, Select,
Spacer, Spacer,
@ -40,7 +36,7 @@ from bokeh.plotting import figure
from scipy import interpolate from scipy import interpolate
import pyzebra import pyzebra
from pyzebra import AREA_METHODS from pyzebra import app
javaScript = """ javaScript = """
let j = 0; let j = 0;
@ -72,7 +68,6 @@ def color_palette(n_colors):
def create(): def create():
doc = curdoc() doc = curdoc()
dataset = [] dataset = []
fit_params = {}
js_data = ColumnDataSource(data=dict(content=[""], fname=[""], ext=[""])) js_data = ColumnDataSource(data=dict(content=[""], fname=[""], ext=[""]))
def file_select_update_for_proposal(): def file_select_update_for_proposal():
@ -281,7 +276,7 @@ def create():
xs_peak = [] xs_peak = []
ys_peak = [] ys_peak = []
comps = fit.eval_components(x=x_fit) comps = fit.eval_components(x=x_fit)
for i, model in enumerate(fit_params): for i, model in enumerate(app_fitctrl.params):
if "linear" in model: if "linear" in model:
x_bkg = x_fit x_bkg = x_fit
y_bkg = comps[f"f{i}_"] y_bkg = comps[f"f{i}_"]
@ -293,13 +288,12 @@ def create():
bkg_source.data.update(x=x_bkg, y=y_bkg) bkg_source.data.update(x=x_bkg, y=y_bkg)
peak_source.data.update(xs=xs_peak, ys=ys_peak) peak_source.data.update(xs=xs_peak, ys=ys_peak)
fit_output_textinput.value = fit.fit_report()
else: else:
fit_source.data.update(x=[], y=[]) fit_source.data.update(x=[], y=[])
bkg_source.data.update(x=[], y=[]) bkg_source.data.update(x=[], y=[])
peak_source.data.update(xs=[], ys=[]) peak_source.data.update(xs=[], ys=[])
fit_output_textinput.value = ""
app_fitctrl.update_result_textarea(scan)
def _update_overview(): def _update_overview():
xs = [] xs = []
@ -562,136 +556,20 @@ def create():
) )
param_select.on_change("value", param_select_callback) param_select.on_change("value", param_select_callback)
app_fitctrl = app.FitControls()
def fit_from_spinner_callback(_attr, _old, new): def fit_from_spinner_callback(_attr, _old, new):
fit_from_span.location = new fit_from_span.location = new
fit_from_spinner = Spinner(title="Fit from:", width=145) app_fitctrl.from_spinner.on_change("value", fit_from_spinner_callback)
fit_from_spinner.on_change("value", fit_from_spinner_callback)
def fit_to_spinner_callback(_attr, _old, new): def fit_to_spinner_callback(_attr, _old, new):
fit_to_span.location = new fit_to_span.location = new
fit_to_spinner = Spinner(title="to:", width=145) app_fitctrl.to_spinner.on_change("value", fit_to_spinner_callback)
fit_to_spinner.on_change("value", fit_to_spinner_callback)
def fitparams_add_dropdown_callback(click):
# bokeh requires (str, str) for MultiSelect options
new_tag = f"{click.item}-{fitparams_select.tags[0]}"
fitparams_select.options.append((new_tag, click.item))
fit_params[new_tag] = fitparams_factory(click.item)
fitparams_select.tags[0] += 1
fitparams_add_dropdown = Dropdown(
label="Add fit function",
menu=[
("Linear", "linear"),
("Gaussian", "gaussian"),
("Voigt", "voigt"),
("Pseudo Voigt", "pvoigt"),
# ("Pseudo Voigt1", "pseudovoigt1"),
],
width=145,
)
fitparams_add_dropdown.on_click(fitparams_add_dropdown_callback)
def fitparams_select_callback(_attr, old, new):
# Avoid selection of multiple indicies (via Shift+Click or Ctrl+Click)
if len(new) > 1:
# drop selection to the previous one
fitparams_select.value = old
return
if len(old) > 1:
# skip unnecessary update caused by selection drop
return
if new:
fitparams_table_source.data.update(fit_params[new[0]])
else:
fitparams_table_source.data.update(dict(param=[], value=[], vary=[], min=[], max=[]))
fitparams_select = MultiSelect(options=[], height=120, width=145)
fitparams_select.tags = [0]
fitparams_select.on_change("value", fitparams_select_callback)
def fitparams_remove_button_callback():
if fitparams_select.value:
sel_tag = fitparams_select.value[0]
del fit_params[sel_tag]
for elem in fitparams_select.options:
if elem[0] == sel_tag:
fitparams_select.options.remove(elem)
break
fitparams_select.value = []
fitparams_remove_button = Button(label="Remove fit function", width=145)
fitparams_remove_button.on_click(fitparams_remove_button_callback)
def fitparams_factory(function):
if function == "linear":
params = ["slope", "intercept"]
elif function == "gaussian":
params = ["amplitude", "center", "sigma"]
elif function == "voigt":
params = ["amplitude", "center", "sigma", "gamma"]
elif function == "pvoigt":
params = ["amplitude", "center", "sigma", "fraction"]
elif function == "pseudovoigt1":
params = ["amplitude", "center", "g_sigma", "l_sigma", "fraction"]
else:
raise ValueError("Unknown fit function")
n = len(params)
fitparams = dict(
param=params, value=[None] * n, vary=[True] * n, min=[None] * n, max=[None] * n
)
if function == "linear":
fitparams["value"] = [0, 1]
fitparams["vary"] = [False, True]
fitparams["min"] = [None, 0]
elif function == "gaussian":
fitparams["min"] = [0, None, None]
return fitparams
fitparams_table_source = ColumnDataSource(dict(param=[], value=[], vary=[], min=[], max=[]))
fitparams_table = DataTable(
source=fitparams_table_source,
columns=[
TableColumn(field="param", title="Parameter", editor=CellEditor()),
TableColumn(field="value", title="Value", editor=NumberEditor()),
TableColumn(field="vary", title="Vary", editor=CheckboxEditor()),
TableColumn(field="min", title="Min", editor=NumberEditor()),
TableColumn(field="max", title="Max", editor=NumberEditor()),
],
height=200,
width=350,
index_position=None,
editable=True,
auto_edit=True,
)
# start with `background` and `gauss` fit functions added
fitparams_add_dropdown_callback(types.SimpleNamespace(item="linear"))
fitparams_add_dropdown_callback(types.SimpleNamespace(item="gaussian"))
fitparams_select.value = ["gaussian-1"] # add selection to gauss
fit_output_textinput = TextAreaInput(title="Fit results:", width=750, height=200)
def proc_all_button_callback(): def proc_all_button_callback():
for scan in dataset: app_fitctrl.fit_dataset(dataset)
if scan["export"]:
pyzebra.fit_scan(
scan, fit_params, fit_from=fit_from_spinner.value, fit_to=fit_to_spinner.value
)
pyzebra.get_area(
scan,
area_method=AREA_METHODS[area_method_radiobutton.active],
lorentz=lorentz_checkbox.active,
)
_update_single_scan_plot() _update_single_scan_plot()
_update_overview() _update_overview()
@ -708,15 +586,7 @@ def create():
proc_all_button.on_click(proc_all_button_callback) proc_all_button.on_click(proc_all_button_callback)
def proc_button_callback(): def proc_button_callback():
scan = _get_selected_scan() app_fitctrl.fit_scan(_get_selected_scan())
pyzebra.fit_scan(
scan, fit_params, fit_from=fit_from_spinner.value, fit_to=fit_to_spinner.value
)
pyzebra.get_area(
scan,
area_method=AREA_METHODS[area_method_radiobutton.active],
lorentz=lorentz_checkbox.active,
)
_update_single_scan_plot() _update_single_scan_plot()
_update_overview() _update_overview()
@ -732,11 +602,6 @@ def create():
proc_button = Button(label="Process Current", width=145) proc_button = Button(label="Process Current", width=145)
proc_button.on_click(proc_button_callback) proc_button.on_click(proc_button_callback)
area_method_div = Div(text="Intensity:", margin=(5, 5, 0, 5))
area_method_radiobutton = RadioGroup(labels=["Function", "Area"], active=0, width=145)
lorentz_checkbox = CheckboxGroup(labels=["Lorentz Correction"], width=145, margin=(13, 5, 5, 5))
export_preview_textinput = TextAreaInput(title="Export file preview:", width=450, height=400) export_preview_textinput = TextAreaInput(title="Export file preview:", width=450, height=400)
def _update_preview(): def _update_preview():
@ -769,12 +634,22 @@ def create():
save_button = Button(label="Download File", button_type="success", width=220) save_button = Button(label="Download File", button_type="success", width=220)
save_button.js_on_click(CustomJS(args={"js_data": js_data}, code=javaScript)) save_button.js_on_click(CustomJS(args={"js_data": js_data}, code=javaScript))
area_method_div = Div(text="Intensity:", margin=(5, 5, 0, 5))
fitpeak_controls = row( fitpeak_controls = row(
column(fitparams_add_dropdown, fitparams_select, fitparams_remove_button), column(
fitparams_table, app_fitctrl.add_function_button,
app_fitctrl.function_select,
app_fitctrl.remove_function_button,
),
app_fitctrl.params_table,
Spacer(width=20), Spacer(width=20),
column(fit_from_spinner, lorentz_checkbox, area_method_div, area_method_radiobutton), column(
column(fit_to_spinner, proc_button, proc_all_button), app_fitctrl.from_spinner,
app_fitctrl.lorentz_checkbox,
area_method_div,
app_fitctrl.area_method_radiogroup,
),
column(app_fitctrl.to_spinner, proc_button, proc_all_button),
) )
scan_layout = column( scan_layout = column(
@ -796,7 +671,7 @@ def create():
tab_layout = column( tab_layout = column(
row(import_layout, scan_layout, plots, Spacer(width=30), export_layout), row(import_layout, scan_layout, plots, Spacer(width=30), export_layout),
row(fitpeak_controls, fit_output_textinput), row(fitpeak_controls, app_fitctrl.result_textarea),
) )
return Panel(child=tab_layout, title="param study") return Panel(child=tab_layout, title="param study")