diff --git a/pyzebra/__init__.py b/pyzebra/__init__.py index 6954149..34983d1 100644 --- a/pyzebra/__init__.py +++ b/pyzebra/__init__.py @@ -4,6 +4,6 @@ from pyzebra.ccl_io import export_1D, load_1D, parse_1D from pyzebra.fit2 import fitccl from pyzebra.h5 import * from pyzebra.xtal import * -from pyzebra.ccl_process import normalize_dataset, merge_duplicates, merge_datasets, merge_scans +from pyzebra.ccl_process import * __version__ = "0.2.2" diff --git a/pyzebra/app/panel_ccl_integrate.py b/pyzebra/app/panel_ccl_integrate.py index 7654318..bce4942 100644 --- a/pyzebra/app/panel_ccl_integrate.py +++ b/pyzebra/app/panel_ccl_integrate.py @@ -211,41 +211,23 @@ def create(): peak_pos_textinput_lock = False - fit = scan.get("fit") - if fit is not None: - x = scan["fit"]["x_fit"] - plot_gauss_source.data.update(x=x, y=scan["fit"]["comps"]["gaussian"]) - plot_bkg_source.data.update(x=x, y=scan["fit"]["comps"]["background"]) - params = fit["result"].params - fit_output_textinput.value = ( - "Gaussian: centre = %9.4f, sigma = %9.4f, area = %9.4f \n" - "background: slope = %9.4f, intercept = %9.4f \n" - "Int. area = %9.4f +/- %9.4f \n" - "fit area = %9.4f +/- %9.4f \n" - "ratio((fit-int)/fit) = %9.4f" - % ( - params["g_cen"].value, - params["g_width"].value, - params["g_amp"].value, - params["slope"].value, - params["intercept"].value, - fit["int_area"].n, - fit["int_area"].s, - params["g_amp"].value, - params["g_amp"].stderr, - (params["g_amp"].value - fit["int_area"].n) / params["g_amp"].value, - ) - ) - numfit_min, numfit_max = fit["numfit"] - if numfit_min is None: - numfit_min_span.location = None - else: - numfit_min_span.location = x[numfit_min] + fit_result = scan.get("fit_result") + if fit_result is not None: + comps = fit_result.eval_components() + plot_gauss_source.data.update(x=x, y=comps["f1_"]) + plot_bkg_source.data.update(x=x, y=comps["f0_"]) + fit_output_textinput.value = fit_result.fit_report() - if numfit_max is None: - numfit_max_span.location = None - else: - numfit_max_span.location = x[numfit_max] + # numfit_min, numfit_max = fit_result["numfit"] + # if numfit_min is None: + # numfit_min_span.location = None + # else: + # numfit_min_span.location = x[numfit_min] + + # if numfit_max is None: + # numfit_max_span.location = None + # else: + # numfit_max_span.location = x[numfit_max] else: plot_gauss_source.data.update(x=[], y=[]) @@ -505,22 +487,9 @@ def create(): peakfind_button = Button(label="Peak Find Current", default_size=145) peakfind_button.on_click(peakfind_button_callback) - def _get_fit_params(): - return dict( - value=fit_params["gauss-1"]["value"] + fit_params["background-0"]["value"], - vary=fit_params["gauss-1"]["vary"] + fit_params["background-0"]["vary"], - constraints_min=fit_params["gauss-1"]["min"] + fit_params["background-0"]["min"], - constraints_max=fit_params["gauss-1"]["max"] + fit_params["background-0"]["max"], - numfit_min=integ_from.value, - numfit_max=integ_to.value, - binning=bin_size_spinner.value, - ) - def fit_all_button_callback(): - fit_params = _get_fit_params() for scan in det_data: - # fit_params are updated inplace within `fitccl` - pyzebra.fitccl(scan, **deepcopy(fit_params)) + pyzebra.fit_scan(scan, fit_params) _update_plot(_get_selected_scan()) _update_table() @@ -530,7 +499,7 @@ def create(): def fit_button_callback(): scan = _get_selected_scan() - pyzebra.fitccl(scan, **_get_fit_params()) + pyzebra.fit_scan(scan, fit_params) _update_plot(scan) _update_table() diff --git a/pyzebra/app/panel_param_study.py b/pyzebra/app/panel_param_study.py index aec7d2c..28771a7 100644 --- a/pyzebra/app/panel_param_study.py +++ b/pyzebra/app/panel_param_study.py @@ -226,41 +226,23 @@ def create(): peak_pos_textinput_lock = False - fit = scan.get("fit") - if fit is not None: - x = scan["fit"]["x_fit"] - plot_gauss_source.data.update(x=x, y=scan["fit"]["comps"]["gaussian"]) - plot_bkg_source.data.update(x=x, y=scan["fit"]["comps"]["background"]) - params = fit["result"].params - fit_output_textinput.value = ( - "Gaussian: centre = %9.4f, sigma = %9.4f, area = %9.4f \n" - "background: slope = %9.4f, intercept = %9.4f \n" - "Int. area = %9.4f +/- %9.4f \n" - "fit area = %9.4f +/- %9.4f \n" - "ratio((fit-int)/fit) = %9.4f" - % ( - params["g_cen"].value, - params["g_width"].value, - params["g_amp"].value, - params["slope"].value, - params["intercept"].value, - fit["int_area"].n, - fit["int_area"].s, - params["g_amp"].value, - params["g_amp"].stderr, - (params["g_amp"].value - fit["int_area"].n) / params["g_amp"].value, - ) - ) - numfit_min, numfit_max = fit["numfit"] - if numfit_min is None: - numfit_min_span.location = None - else: - numfit_min_span.location = x[numfit_min] + fit_result = scan.get("fit_result") + if fit_result is not None: + comps = fit_result.eval_components() + plot_gauss_source.data.update(x=x, y=comps["f1_"]) + plot_bkg_source.data.update(x=x, y=comps["f0_"]) + fit_output_textinput.value = fit_result.fit_report() - if numfit_max is None: - numfit_max_span.location = None - else: - numfit_max_span.location = x[numfit_max] + # numfit_min, numfit_max = fit_result["numfit"] + # if numfit_min is None: + # numfit_min_span.location = None + # else: + # numfit_min_span.location = x[numfit_min] + + # if numfit_max is None: + # numfit_max_span.location = None + # else: + # numfit_max_span.location = x[numfit_max] else: plot_gauss_source.data.update(x=[], y=[]) @@ -586,22 +568,9 @@ def create(): peakfind_button = Button(label="Peak Find Current", default_size=145) peakfind_button.on_click(peakfind_button_callback) - def _get_fit_params(): - return dict( - value=fit_params["gauss-1"]["value"] + fit_params["background-0"]["value"], - vary=fit_params["gauss-1"]["vary"] + fit_params["background-0"]["vary"], - constraints_min=fit_params["gauss-1"]["min"] + fit_params["background-0"]["min"], - constraints_max=fit_params["gauss-1"]["max"] + fit_params["background-0"]["max"], - numfit_min=integ_from.value, - numfit_max=integ_to.value, - binning=bin_size_spinner.value, - ) - def fit_all_button_callback(): - fit_params = _get_fit_params() for scan in det_data: - # fit_params are updated inplace within `fitccl` - pyzebra.fitccl(scan, **deepcopy(fit_params)) + pyzebra.fit_scan(scan, fit_params) _update_plot() _update_table() @@ -611,7 +580,7 @@ def create(): def fit_button_callback(): scan = _get_selected_scan() - pyzebra.fitccl(scan, **_get_fit_params()) + pyzebra.fit_scan(scan, fit_params) _update_plot() _update_table() diff --git a/pyzebra/ccl_process.py b/pyzebra/ccl_process.py index c6c4ea8..25fffcb 100644 --- a/pyzebra/ccl_process.py +++ b/pyzebra/ccl_process.py @@ -1,6 +1,7 @@ import itertools import numpy as np +from lmfit.models import GaussianModel, LinearModel, PseudoVoigtModel, VoigtModel from .ccl_io import CCL_ANGLES @@ -80,3 +81,41 @@ def merge_scans(scan1, scan2): scan2["active"] = False print(f'Merging scans: {scan1["idx"]} <-- {scan2["idx"]}') + + +def _create_fit_model(model_dict): + model = None + for model_index, (model_name, model_param) in enumerate(model_dict.items()): + model_name, _ = model_name.split("-") + prefix = f"f{model_index}_" + + if model_name == "background": + _model = LinearModel(prefix=prefix, name="background") + elif model_name == "gauss": + _model = GaussianModel(prefix=prefix, name="gauss") + elif model_name == "voigt": + _model = VoigtModel(prefix=prefix) + elif model_name == "pseudovoigt": + _model = PseudoVoigtModel(prefix=prefix) + else: + raise ValueError(f"Unknown model name: '{model_name}'") + + for param_index, param_name in enumerate(model_param["param"]): + param_hints = {} + for hint_name in ("value", "vary", "min", "max"): + tmp = model_param[hint_name][param_index] + if tmp is not None: + param_hints[hint_name] = tmp + _model.set_param_hint(param_name, **param_hints) + + if model is None: + model = _model + else: + model += _model + + return model + + +def fit_scan(scan, model_dict): + model = _create_fit_model(model_dict) + scan["fit_result"] = model.fit(scan["Counts"], x=scan[scan["scan_motor"]])