No BSD License  

Highlights from
GUI for Generalized Nonlinear Non-analytic Chi-Square Fitting

image thumbnail
from GUI for Generalized Nonlinear Non-analytic Chi-Square Fitting by Nathaniel Brahms
Provides a Curve Fitting Toolbox-like interface for chi-square fitting

FitChiTool(varargin)
function varargout = FitChiTool(varargin)
% Please type web('<FitChiToolDir>\html\FitChiToolHelp.html') or open FitChiToolHelp in an HTML
% browser for information on the FitChiTool GUI.
%
% Author: N. Brahms
% Created: 2-28-06
% Last updated: 2-28-06
% Version: 1.0

% Required files:
%   FitChiTool.m               - This file
%   FitChiTool.fig             - GUIDE figure for drawing FitChiTool
%   defaultChiSquareModels.mat - Fitting library
%   fitChiSquare.m             - The chi-square fitting routine.  This can be found in
%                                its latest release on the Matlab File Exchange.
%   java/FitChiToolModel.class - Class to implement the data model for the
%                                guess and result tables.
% Auxiliary files:
%   html/FitChiToolHelp.html   - Web help, accessed from the Help menu
%   html/images/*              - Files for web help
%   ModelEditor.m              - Code for the model editor
%   ModelEditor.fig            - GUIDE figure for the model editor
%   fitChiToolExample.m        - Example use file for FitChiTool.
%
% Version history
%   1.0 - Initial release
%
% Known issues:
% 1. JTable throws an exception when fitChiSquare returns NaN for the
%    dchi2=1 bounds.  Fixing this requires implementing a new cell editor
%    for the bounds columns.
%

% Last Modified by GUIDE v2.5 02-Mar-2006 14:34:09

%====================================================================
% Procedure
%====================================================================

% Set up the java path

warning off
basepath = which('FitChiTool.m');
javaaddpath([basepath(1:findstr(basepath,'FitChiTool.m')-1) 'java']);
warning on

% Import javax stuff

import javax.swing.*;
import javax.swing.table.*;
import ca.odell.renderpack.*;

% Begin initialization code - DO NOT EDIT
gui_Singleton = 0;
gui_State = struct('gui_Name',       mfilename, ...
                   'gui_Singleton',  gui_Singleton, ...
                   'gui_OpeningFcn', @FitChiTool_OpeningFcn, ...
                   'gui_OutputFcn',  @FitChiTool_OutputFcn, ...
                   'gui_LayoutFcn',  [] , ...
                   'gui_Callback',   []);
if nargin && ischar(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT

%=================================================================
% Creation Functions
%=================================================================

% --- Executes just before FitChiTool is made visible.
function FitChiTool_OpeningFcn(hObject, eventdata, handles, varargin)

% Initialize flages
handles.dataFittable = 0;
handles.dataPlottable = 0;
handles.errorsGood = 0;

% Choose default command line output for FitChiTool
handles.output= hObject;

% JAVA OBJECTS:
% Add the Java Swing table objects
[handles.guessscroll,handles.guesstable] = makeParamTable('guess',[],@guesstable_Callback);
[handles.resultscroll,handles.resulttable] = makeParamTable('result',[],@resulttable_Callback);
% Prevent user editing of the results object
% handles.resulttable.setEnabled(0);

javacomponent(handles.guessscroll,[20 100 250 90],hObject);
javacomponent(handles.resultscroll,[300 20 250 90],hObject);

% handles.guessscroll.getColumnHeader().setReorderingAllowed(java.lang.Boolean.FALSE);
% handles.resultscroll.getColumnHeader().setReorderingAllowed(java.lang.Boolean.FALSE);

loadbutton_Callback(handles.loadbutton,[],handles);

% Load library models
DEFAULT_LIBRARY = 'defaultChiSquareModels.mat';
handles.library = DEFAULT_LIBRARY;
handles = loadModels(handles);

% Update handles structure
guidata(hObject, handles);


% ---- Default object creation function
function hObjectOut = defaultCreateFcn(hObjectIn)
hObjectOut = hObjectIn;
if ispc && isequal(get(hObjectIn,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObjectOut,'BackgroundColor','white');
end

%==================================================================
% Output Function
%===================================================================

% --- Outputs from this function are returned to the command line.
function varargout = FitChiTool_OutputFcn(hObject, eventdata, handles) 

% Get default command line output from handles structure
varargout{1} = handles.output;

%===================================================================
% Objects
%===================================================================

% --- guess/results table ------------------------------------------
%
% Acknowledgement: Brad Phelan (certain parts of this function copyright
% Brad Phelan 2005).  http://xtargets.com

function [scroller,table] = makeParamTable(mode,data,callbackFcn)

import javax.swing.*;
import javax.swing.table.*;

center = 0; lower = -Inf; upper = Inf; uncFit = true;

% Create table data and column names
[namevec, data] = fillTable(mode,center,lower,upper,uncFit);

% Create table
switch mode
    case 'result'
        model = FitChiToolModel(data,namevec,0);
    otherwise
        model = FitChiToolModel(data,namevec);
end
table = JTable(model);
table.setAutoResizeMode(JTable.AUTO_RESIZE_LAST_COLUMN);
%Fix for JTable focus bug
table.putClientProperty('terminateEditOnFocusLost',java.lang.Boolean.TRUE);
table.getTableHeader().setReorderingAllowed(false);

set(table.getModel(),'TableChangedCallback',callbackFcn);

% Create scrollbar
scroller = JScrollPane(table);
scroller.setVerticalScrollBarPolicy(...
    ScrollPaneConstants.VERTICAL_SCROLLBAR_AS_NEEDED);



% --- resultstable callback ----------------------------------------
function resulttable_Callback(source, event)



% --- resultstable callback ----------------------------------------
function guesstable_Callback(source, event)



% --- xdatamenu ----------------------------------------------------
function xdatamenu_ButtonDown(hObject, eventdata, handles)


function xdatamenu_Callback(hObject, eventdata, handles)
handles = conditionData(handles);
dataPlot(handles);
guidata(hObject,handles);


function xdatamenu_CreateFcn(hObject, eventdata, handles)
hObject = defaultCreateFcn(hObject);


% --- dxdatamenu -------------------------------------------------
function dxdatamenu_Callback(hObject, eventdata, handles)
guidata(hObject,conditionData(handles));


function dxdatamenu_CreateFcn(hObject, eventdata, handles)
hObject = defaultCreateFcn(hObject);


% --- ydatamenu ----------------------------------------------------
function ydatamenu_Callback(hObject, eventdata, handles)
handles = conditionData(handles);
dataPlot(handles);
guidata(hObject,handles);


function ydatamenu_CreateFcn(hObject, eventdata, handles)
hObject = defaultCreateFcn(hObject);


% --- dydatamenu --------------------------------------------------
function dydatamenu_Callback(hObject, eventdata, handles)
guidata(hObject,conditionData(handles));


function dydatamenu_CreateFcn(hObject, eventdata, handles)
hObject = defaultCreateFcn(hObject);



% --- groupmenu ----------------------------------------------------
function groupmenu_Callback(hObject, eventdata, handles)

i = get(hObject,'Value')-1;

if i>0
    children = handles.models(i).children;
    set(handles.modelmenu,'String',['<select a model>' {children.name}]);
else
    set(handles.modelmenu,'String','<select a group>');
end
set(handles.modelmenu,'Value',1);
set(handles.descriptionedit,'String','');
guidata(hObject,handles);


function groupmenu_CreateFcn(hObject, eventdata, handles)
hObject = defaultCreateFcn(hObject);


% --- modelmenu -------------------------------------------------
function modelmenu_Callback(hObject, eventdata, handles)

i = get(handles.groupmenu,'Value')-1;
j = get(handles.modelmenu,'Value')-1;

if i && j
    set(handles.descriptionedit,'String',...
        handles.models(i).children(j).desc);
else
    set(handles.descriptionedit,'String','');
end

handles = setFitEnable(setGuess(handles));
guidata(hObject,handles);


function modelmenu_CreateFcn(hObject, eventdata, handles)
hObject = defaultCreateFcn(hObject);


% --- guessedit ------------------------------------------------
function guessedit_Callback(hObject, eventdata, handles)


function guessedit_CreateFcn(hObject, eventdata, handles)
hObject = defaultCreateFcn(hObject);


% --- checkboxerrors ------------------------------------------
function checkboxerrors_Callback(hObject, eventdata, handles)

guidata(hObject,setFitEnable(handles));

% --- fitbutton -----------------------------------------------
function fitbutton_Callback(hObject, eventdata, handles)

% Disable the figure so no one does anything now
allhandles = [handles.xdatamenu handles.dxdatamenu handles.ydatamenu...
    handles.dydatamenu handles.loadbutton handles.checkboxerrors...
    handles.fitbutton handles.modelmenu handles.groupmenu];
menuhandles = [handles.savemenu handles.saveasmenu handles.workspacemenu];
set(allhandles,'Enable','off');
guidata(handles.figure1,handles);
drawnow

try
    % Get the data from the guess table
    guessVec = handles.guesstable.getModel().getDataVector();
    for i=1:guessVec.size()
        guessRowVec = guessVec.get(i-1);
        for j=1:guessRowVec.size()
            if ischar(guessRowVec.get(j-1))
                data(i,j)=str2num(guessRowVec.get(j-1));
            else
                data(i,j)=guessRowVec.get(j-1);
            end
        end
    end

    % The guess
    guess = data(:,3)';

    % Fitting options
    op = optimset('Display','off');
    op.ErrorsUnknown = get(handles.checkboxerrors,'Value');
    op.LowerBound = data(:,2)';
    op.UpperBound = data(:,4)';
    op.FitUncertainty = data(:,5)';

    % Model
    i = get(handles.groupmenu,'Value')-1;
    j = get(handles.modelmenu,'Value')-1;
    model = eval(handles.models(i).children(j).model);

    % Perform the fit
    warning off
    [fit.params, fit.dParams, fit.gof, fit.stddev] = fitChiSquare(handles.xdata,...
        handles.ydata,model,guess,...
        handles.dxdata,handles.dydata,op);
    warning on

    % Get the fit
    fit.expected = feval(model,fit.params,handles.xdata);

    % Plot the fit results
    axes(handles.fitplot)
    cla reset
    errorbar(handles.xdata(:,1),handles.ydata,fit.stddev,'.');
    hold on
    plot(handles.xdata(:,1),fit.expected,'-r');

    % Plot the residuals
    axes(handles.residualplot)
    cla reset
    plot(handles.xdata(:,1),(fit.expected-handles.ydata)./fit.stddev,'.');
    hold on
    plot(handles.xdata(:,1),zeros(length(handles.ydata),1),'-r');

    % Output results
    clear data;
    center = fit.params; lower = fit.params-[fit.dParams.dl];...
        upper = fit.params+[fit.dParams.du]; uncFit = op.FitUncertainty;

    [names, data] = fillTable('result',center,lower,upper,uncFit);
    
    handles.resulttable.getModel().setDataVector(data,names);
    
    handles.fit = fit;
    
    set(handles.rchi2edit,'String',num2str(fit.gof.chi2/fit.gof.dof));
    set(handles.dofedit,'String',num2str(fit.gof.dof));
    set(handles.eta2edit,'String',num2str(fit.gof.eta2));
    
    set(menuhandles,'Enable','on');
    
catch
    s = lasterror;
    names = {s.stack.name};
    stackstr = [s.message sprintf('\nStack trace:\n')];
    for j=1:length(names)
        stackstr = [stackstr names{j} sprintf('\n')];
    end
    errordlg(stackstr);
end

% Turn the figure back on
set(allhandles,'Enable','on');
guidata(handles.figure1,handles);

% --- fitbutton enable ----------------------------------------

function handles = setFitEnable(handlesIn)

handles=handlesIn;
if ~get(handles.checkboxerrors,'Value') && ~handles.errorsGood || ...
        ~handles.dataFittable || get(handles.modelmenu,'Value')==1
    set(handles.fitbutton,'Enable','off');
else
    set(handles.fitbutton,'Enable','on');
end


% --- loadbutton ----------------------------------------------
function loadbutton_Callback(hObject, eventdata, handles)

% Get workspace variable names
names = evalin('base','who');
varData=[];
varNames=[];
j=1;
for i=1:length(names)
    % Save workspace variable here if it is numeric
    if evalin('base',['isnumeric(' names{i} ')'])
        varData{j} = evalin('base',names{i});
        varNames{j} = names{i};
        j=j+1;
    end
end

% Load the menu boxes
menus = [handles.xdatamenu handles.dxdatamenu ...
    handles.ydatamenu handles.dydatamenu];
if ~isempty(varNames)
    set(menus,'String',['<choose a variable>' varNames]);
    set(menus,'Value',1);
else
    set(menus,'String',{'<load variables from workspace>'});
    set(menus,'Value',1);
end

axes(handles.fitplot); cla; axes(handles.residualplot); cla;
set(handles.fitbutton,'Enable','off');

guidata(hObject,handles);



% ------ model description edit --------------------------------------
function descriptionedit_Callback(hObject, eventdata, handles)

function descriptionedit_CreateFcn(hObject, eventdata, handles)

if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


% ------ reduced chi-square edit -------------------------------------
function rchi2edit_Callback(hObject, eventdata, handles)

function rchi2edit_CreateFcn(hObject, eventdata, handles)

if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end


% ------ dof edit ----------------------------------------------------
function dofedit_Callback(hObject, eventdata, handles)

function dofedit_CreateFcn(hObject, eventdata, handles)

if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end



% ------ eta2 edit ---------------------------------------------------
function eta2edit_Callback(hObject, eventdata, handles)

function eta2edit_CreateFcn(hObject, eventdata, handles)

if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end




%===================================================================
% Menus
%===================================================================

% --------------------------------------------------------------------
function filemenu_Callback(hObject, eventdata, handles)



% --------------------------------------------------------------------
function librarymenu_Callback(hObject, eventdata, handles)

[file, path] = uigetfile('*.mat','Choose a library file...');

% If a file was chosen
if any(file~=0)
   try
       % Load the file
       warning off
       load([path file],'-mat','models');
       warning on
       % Do some error checking
       wellformed = 1;
       if(isstruct(models))
           for i=1:length(models)
               if(isstruct(models(i).children) && ischar(models(i).group))
                   for j=1:length(models(i).children)
                       if ischar(models(i).children(j).name)
                           wellformed = wellformed && 1;
                       else
                           wellformed = 0;
                       end
                   end
               else
                   wellformed = 0;
               end
           end
       else
           wellformed = 0;
       end
       if wellformed
           handles.models = models;
           handles.library = [path file];
           handles = loadModels(handles);
       else
           errordlg(['File ' path file ' format error.']);
       end
   catch
       errordlg(['File ' path file ' contains no models.']);
   end
end

guidata(hObject,handles);

% --------------------------------------------------------------------
function importmenu_Callback(hObject, eventdata, handles)

msgbox(['Feature coming soon. Use the "Load variables" button to '...
    'load variables from the MATLAB workspace.'],'','help');


% --------------------------------------------------------------------
function savemenu_Callback(hObject, eventdata, handles)

% If a file name exists, save using that path.  Otherwise, save as...
if isfield(handles,'path') && exist(handles.path,'file')
    savefit(handles);
else
    saveasmenu_Callback(handles.saveasmenu,eventdata,handles);
end

% --------------------------------------------------------------------
function saveasmenu_Callback(hObject, eventdata, handles)

[file, path] = uiputfile('*.mat');

if any(file~=0)
    handles.path = path;
    handles.file = file;
    savefit(handles)
end
guidata(hObject,handles);

function savefit(handles)

if isfield(handles,'fit') && ~isempty(handles.fit) &&...
        exist(handles.path,'file') && any(handles.file~=0)
    fit = handles.fit;
    save([handles.path handles.file],'fit');
end


% --------------------------------------------------------------------
function workspacemenu_Callback(hObject, eventdata, handles)

if (isfield(handles,'fit') && ~isempty(handles.fit))
    answer = inputdlg({'Save fit to workspace in a sruct named:'},'',1,...
        {'fit'});
else
    errordlg('Please perform a fit first.');
end

if(ischar(answer{1}))
    try
        assignin('base',answer{1},handles.fit);
    catch
        s = lasterror;
        errordlg(s.message);
    end
end


% --------------------------------------------------------------------
function modeleditmenu_Callback(hObject, eventdata, handles)
ModelEditor(handles.library);


% --------------------------------------------------------------------
function fitoptionsmenu_Callback(hObject, eventdata, handles)

msgbox(['Feature coming soon. Use the command-line interface to '...
    'fitChiSquare to access additional options'],'','help');

% --------------------------------------------------------------------
function menutools_Callback(hObject, eventdata, handles)


% --------------------------------------------------------------------
function helpmenu_Callback(hObject, eventdata, handles)


% --------------------------------------------------------------------
function helpdisplaymenu_Callback(hObject, eventdata, handles)
path = which('FitChiTool.m');
web([path(1:findstr(path,'FitChiTool.m')-1) 'html\FitChiToolHelp.html']);


%===================================================================
% Called functions
%===================================================================

% ---- Guess length -------------------------------------------------

function handles = setGuess(handlesIn)

handles = handlesIn;
i = get(handles.groupmenu,'Value')-1;
j = get(handles.modelmenu,'Value')-1;
if(get(handles.modelmenu,'Value')==1 | get(handles.xdatamenu,'Value')==1)
    center = 0; lower = -Inf; upper = Inf; uncFit = true;
else
    lengthfcn = eval(handles.models(i).children(j).length);
    numrows=feval(lengthfcn,handles.xdata);
    center = zeros(1,numrows); lower = -Inf*ones(1,numrows);...
        upper = Inf*ones(1,numrows); uncFit = ones(1,numrows);
end

[namevec, data] = fillTable('guess',center,lower,upper,uncFit);

handles.guesstable.getModel().setDataVector(data,namevec);
guidata(handles.figure1,handles);

% ---- Table filling ------------------------------------------------

function [names,data] = fillTable(mode,center,lower,upper,uncFit)

switch mode
    case 'guess'
        names = {'#' 'Lower Bound' 'Guess' 'Upper Bound' 'Unc. Fit?'};
    case 'result'
        names = {'#' 'Lower dChi2=1' 'Parameter' 'Upper dChi2=1' 'Unc. Fit?'};
    otherwise
        names = {' ' ' ' ' ' ' ' ' '};
end

for k=1:length(center)
    data{k,1} = java.lang.Integer(k);
    data{k,2} = lower(k);
    data{k,3} = center(k);
    data{k,4} = upper(k);
    if uncFit(k)
        data{k,5} = java.lang.Boolean.TRUE;
    else
        data{k,5} = java.lang.Boolean.FALSE;
    end
end
        


% ---- Model loading ------------------------------------------------

function handles = loadModels(handlesIn)

handles = handlesIn;

load(handles.library);
handles.models = models;
set(handles.groupmenu,'String',['<select a group>' {models.group}]);
set(handles.groupmenu,'Value',1);
set(handles.modelmenu,'String','<select a group>');
set(handles.modelmenu,'Value',1);

% ---- Plotting -----------------------------------------------------
% Used to plot the data when a new data vector is entered
% Also clears any fitting results
function dataPlot(handles)

handles.fit = []; handles.file = ''; handles.path = '';

set([handles.savemenu handles.saveasmenu handles.workspacemenu],...
    'Enable','off');
set([handles.rchi2edit handles.dofedit handles.eta2edit],'String','');

[names,data] = fillTable('result',0,-Inf,Inf,true);
handles.resulttable.getModel().setDataVector(data,names);

clearplot(handles.residualplot)
axes(handles.residualplot)
set(handles.residualplot,'Box','on');

clearplot(handles.fitplot)
axes(handles.fitplot)
set(handles.residualplot,'Box','on');
% If there was an error
if ~handles.dataPlottable
    loadbutton_Callback(handles.loadbutton,[],handles);
    clearplot(handles.fitplot);
% Plot the data
else
    plot(handles.xdata(:,1),handles.ydata(:,1),'.');
    xlabel('x1');
    ylabel('y');
end

guidata(handles.figure1,handles);

% --- Checks existence of a variable in the workspace ---------------
% also checks if variable is numeric

function bool = existsInBase(name)
bool = evalin('base',['exist(''' name ''',''var'')']) &&...
    evalin('base',['isnumeric(' name ')']);

% --- Clears the plot -----------------------------------------------

function clearplot(handle)
axes(handle);
cla
xlabel('');
ylabel('');
warning off
legend({});
warning on
    
% --- Conditions data ----------------------------------------------

function handlesOut = conditionData(handles)

entries = [handles.xdatamenu handles.dxdatamenu...
    handles.ydatamenu handles.dydatamenu];
handles.dataPlottable = 1;
handles.dataFittable = 1;
handles.errorsGood = 1;

% Get the chosen variable names
menuValues = get(entries,'Value');
menuNames = get(entries,'String');
for i=1:length(menuNames)
    names{i} = menuNames{i}{menuValues{i}};
    if menuValues{i}>1
        existing(i) = existsInBase(names{i});
        if existing(i)
            values{i} = forceColumn(evalin('base',names{i}));
            sizes{i} = size(values{i});
        else
            values{i} = [];
            sizes{i} = [0 0];
        end
    else
        existing(i) = 0;
        values{i} = [];
        sizes{i} = [0 0];
    end
end

% If y exists, rotate x and dx if necessary
if existing(3) && existing(1)
    six = sizes{1}; siy = sizes{3};
    if six(1)~=siy(1) && six(2)==siy(1)
        values{1} = values{1}';
        sizes{1} = size(values{1});
    end
    if existing(2)
        sidx = sizes{2};
        if sidx(2)~=siy(1) && sidx(2)==siy(1)
            values{2} = values{2}';
            sizes{2} = size(values{2});
        end
    end
elseif existing(3)
    siy = sizes{3};
    values{1} = (1:siy(1))';
elseif existing(1)
    six = sizes{1};
    values{3} = (1:six(1))';
else
    handles.dataPlottable = 0;
    handles.dataFittable = 0;
end

% Check error vectors
if ~all(existing)
    handles.errorsGood = 0;
else
    six = sizes{1}; sidx = sizes{2}; siy = sizes{3}; sidy = sizes{4};
    if sidx~=six
        if circshift(sidx,[0 1])==six
            values{2}=values{2}';
        else
            handles.errorsGood = 0;
        end
    end
    if sidy~=siy
        if circshift(sidy,[0 1])==siy
            values{4}=values{4}';
        else
            handles.errorsGood = 0;
        end
    end
end

%Assign data
handles.xdata = values{1}; handles.dxdata = values{2};
handles.ydata = values{3}; handles.dydata = values{4};

handles = setFitEnable(handles);

handlesOut = handles;

% ---- Forces vector to be a column vector -------------------

function vector = forceColumn(vectorIn)

vector = vectorIn;
si = size(vectorIn);
if si(1)==1
    vector = vector';
end







Contact us at files@mathworks.com