diff --git a/@MyFit/MyFit.m b/@MyFit/MyFit.m index 3dedaa9..bffa09f 100644 --- a/@MyFit/MyFit.m +++ b/@MyFit/MyFit.m @@ -1,936 +1,872 @@ % Class that implements fitting routines with GUI capabilities. classdef MyFit < dynamicprops & matlab.mixin.CustomDisplay properties (Access = public) % MyTrace object contains the data to be fitted to Data MyTrace lim_lower % Lower limits for fit parameters lim_upper % Upper limits for fit parameters % If enabled, plots fit curve in the Axes every time the parameter % values are updated enable_plot fit_color = 'black' % Color of the fit line fit_length = 1e3 % Number of points in the fit trace end properties (GetAccess = public, SetAccess = protected) Axes % The handle which the fit is plotted in Fit % MyTrace object containing the fit Gui = struct() % Handles of GUI elements % Array of cursors with length=2 for the selection of fitting range RangeCursors MyCursor % Output structures from fit() function: FitResult cfit Gof struct FitInfo struct param_vals % Numeric values of fit parameters fit_name fit_tex % tex representation of the fit formula - fit_function % fit expression represented by character string + fit_function % fit formula as character string fit_params % character names of fit parameters in fit_function fit_param_names % long informative names of fit parameters anon_fit_fun % fit expression represented by anonimous function + + % Additional parameters that are calculated from the fit parameters + % or inputed externally. Properties of user parameters including + % long name and write attribute + UserParamList end properties (Dependent = true, GetAccess = public) n_params % Indices of data points selected for fitting data_selection % Enable cursors for the selection of fit range enable_range_cursors end properties (Access = protected) - - % Structure used for initializing GUI of userpanel - UserGui struct - - enable_gui=1 + enable_gui = 1 % Vectors for varying the range of the sliders in GUI slider_vecs end - properties (Dependent = true, Access = protected) - - % These are used to create the usergui - n_user_fields - user_field_tags - user_field_names - end - % Events for communicating with outside entities events NewFit % Triggered any time fitting is performed NewAcceptedFit % Triggered when fitting is accepted by the user end methods (Access = public) function this = MyFit(varargin) % Parse the arguments supplied to the constructor p = inputParser(); addParameter(p, 'fit_name', '') addParameter(p, 'fit_function', 'x') addParameter(p, 'fit_tex', '') addParameter(p, 'fit_params', {}) addParameter(p, 'fit_param_names', {}) addParameter(p, 'Data', MyTrace()); addParameter(p, 'x', []); addParameter(p, 'y', []); addParameter(p, 'Axes', [], @isaxes); addParameter(p, 'enable_gui', true); addParameter(p, 'enable_plot', true); addParameter(p, 'enable_range_cursors', false) % The parameters below are only active when GUI is enabled % If true, adds save trace panel to the fit gui addParameter(p,'save_panel',true,@islogical); addParameter(p,'base_dir', ''); addParameter(p,'session_name','placeholder'); addParameter(p,'file_name','placeholder'); parse(p, varargin{:}); for i=1:length(p.Parameters) % Takes the value from the inputParser to the appropriate % property. if isprop(this, p.Parameters{i}) this.(p.Parameters{i}) = p.Results.(p.Parameters{i}); end end this.Fit = MyTrace(); %Generates the anonymous fit function from the input fit %function. This is used for fast plotting of the initial %values. args=['@(', strjoin([{'x'}, this.fit_params], ','),')']; this.anon_fit_fun=... str2func(vectorize([args,this.fit_function])); %Sets dummy values for the GUI this.param_vals=zeros(1,this.n_params); this.lim_lower=-Inf(1,this.n_params); this.lim_upper=Inf(1,this.n_params); %Allows us to load either x/y data or a MyTrace object directly if ismember('Data',p.UsingDefaults) &&... ~ismember('x',p.UsingDefaults) &&... ~ismember('y',p.UsingDefaults) this.Data.x=p.Results.x; this.Data.y=p.Results.y; end %Creates the structure that contains variables for calibration %of fit results - createUserGuiStruct(this); + createUserParamList(this); %Creates the gui if the flag is enabled. This function is in a %separate file. if this.enable_gui createGui(this, 'save_panel', p.Results.save_panel) %Generates the slider lookup table genSliderVecs(this); if isempty(p.Results.base_dir) try bd = getLocalSettings('measurement_base_dir'); catch ME warning(ME.message) bd = ''; end else bd = ''; end this.Gui.BaseDir.String = bd; this.Gui.SessionName.String = p.Results.session_name; this.Gui.FileName.String = p.Results.file_name; end if ~isempty(this.Axes) % Add two vertical cursors to the axes xlim = this.Axes.XLim; x1 = xlim(1)+0.1*(xlim(2)-xlim(1)); x2 = xlim(2)-0.1*(xlim(2)-xlim(1)); this.RangeCursors = ... [MyCursor(this.Axes, x1, 'orientation', 'vertical', ... 'Label','Fit range 1'),... MyCursor(this.Axes, x2, 'orientation', 'vertical', ... 'Label','Fit range 2')]; % Enabling/disabling of the cursors by setting the class % property can be done only after the cursors are % initialized this.enable_range_cursors = p.Results.enable_range_cursors; end %If data was supplied, generates initial fit parameters if ~isDataEmpty(this.Data) genInitParams(this) end end %Deletion function of object function delete(this) if this.enable_gui %Avoids loops set(this.Gui.Window,'CloseRequestFcn',''); %Deletes the figure delete(this.Gui.Window); %Removes the figure handle to prevent memory leaks this.Gui=[]; end if ismethod(this.Fit, 'delete') % Delete the fit trace, in particular, in order to remove % the fit curve from the axes delete(this.Fit); end if ~isempty(this.RangeCursors) delete(this.RangeCursors); end end - %Close figure callback simply calls delete function for class - function closeFigure(this,~,~) - delete(this); - end - %Saves the metadata function saveParams(this,varargin) p=inputParser; addParameter(p,'save_user_params',true); addParameter(p,'save_gof',true); parse(p,varargin{:}); %Flags for saving the user parameters or goodness of fit save_user_params=p.Results.save_user_params; save_gof=p.Results.save_gof; assert(~isempty(this.param_vals) && ... length(this.param_vals)==this.n_params,... ['The number of calculated coefficients (%i) is not',... ' equal to the number of parameters (%i).', ... ' Perform a fit before trying to save parameters.'],... length(this.param_vals),this.n_params); %Creates combined strings of form: Linewidth (b), where %Linewidth is the parameter name and b is the parameter tag headers=cellfun(@(x,y) sprintf('%s (%s)',x,y),... this.fit_param_names, this.fit_params,'UniformOutput',0); save_data=this.param_vals; if save_user_params %Creates headers for the user fields user_field_headers=cellfun(@(x,y) ... sprintf('%s. %s',this.UserGui.Fields.(x).parent,y),... this.user_field_tags,this.user_field_names,... 'UniformOutput',0)'; %Appends the user headers and data to the save data headers=[headers, user_field_headers]; save_data=[save_data,this.user_field_vals'] end if save_gof %Appends GOF headers and data to the save data headers=[headers,fieldnames(this.Gof)']; save_data=[save_data,struct2array(this.Gof)]; end %Find out at the end how many columns we have n_columns=length(headers); %Sets the column width. Pads 2 for legibility. col_width=cellfun(@(x) length(x), headers)+2; %Min column width of 24 col_width(col_width<24)=24; %Create the right directories if ~exist(this.base_dir,'dir') mkdir(this.base_dir) end if ~exist(this.save_path,'dir') mkdir(this.save_path) end fullpath=[this.save_path,this.filename,'.txt']; %We automatically append to the file if it already exists, %otherwise create a new file if exist(fullpath,'file') fileID=fopen(fullpath,'a'); fprintf('Appending data to %s \n',fullpath); else fileID=fopen(fullpath,'w'); pre_fmt_str=repmat('%%%is\\t',1,n_columns); fmt_str=sprintf([pre_fmt_str,'\r\n'],col_width); fprintf(fileID,fmt_str,headers{:}); end pre_fmt_str_nmb=repmat('%%%i.15e\\t',1,n_columns); nmb_fmt_str=sprintf([pre_fmt_str_nmb,'\r\n'],col_width); fprintf(fileID,nmb_fmt_str,save_data); fclose(fileID); end %We can load a fit from a file with appropriately formatted columns %We simply load the coefficients from the file into the fit. function loadFit(this,fullfilename,varargin) p=inputParser addParameter(p,'line_no',1); parse(p,varargin{:}) n=p.Results.line_no; load_table=readtable(fullfilename); load_names=fieldnames(load_table); for i=1:this.n_params this.param_vals(i)=load_table.(load_names{i})(n); end end %This function is used to set the coefficients, to avoid setting it %to a number not equal to the number of parameters function setFitParams(this,param_vals) assert(length(param_vals)==this.n_params,... ['The length of the coefficient vector (currently %i) ',... 'must be equal to the number of parameters (%i)'],... length(this.param_vals),this.n_params) this.param_vals=param_vals; end %Fits the trace using currently set parameters, depending on the %model. function fitTrace(this) %Check the validity of data validateData(this); %Check for valid limits lim_check=this.lim_upper>this.lim_lower; assert(all(lim_check),... sprintf(['All upper limits must exceed lower limits. ',... 'Check limit %i, fit parameter %s'],find(~lim_check,1),... this.fit_params{find(~lim_check,1)})); %Check the consistency of initial parameters assert(isnumeric(this.param_vals) && isvector(this.param_vals) && ... length(this.param_vals)==this.n_params, ['Starting points must be given as ' ... 'a vector of size %d'],this.n_params); assert(isnumeric(this.lim_lower) && isvector(this.lim_lower) && ... length(this.lim_lower)==this.n_params, ['Lower limits must be given as ' ... 'a vector of size %d'], this.n_params); assert(isnumeric(this.lim_upper) && isvector(this.lim_upper) && ... length(this.lim_upper)==this.n_params, ['Upper limits must be given as ' ... 'a vector of size %d'], this.n_params); %Perform the fit. doFit(this); %Calculate the fit curve. calcFit(this); %Calculate user parameters calcUserParams(this); %Update fit metadata this.Fit.UserMetadata = createMetadata(this); %Updates the gui if it is enabled if this.enable_gui genSliderVecs(this); updateGui(this); end %Plots the fit if the flag is on if this.enable_plot plotFit(this); end %Triggers new fit event triggerNewFit(this); end %Clears the plots function clearFit(this) cellfun(@(x) delete(x), this.Fit.PlotLines); end %Plots the trace contained in the Fit MyTrace object function plotFit(this, varargin) plot(this.Fit, this.Axes, 'Color', this.fit_color, varargin{:}); end %Generates model-dependent initial parameters, lower and upper %boundaries. function genInitParams(this) validateData(this); calcInitParams(this); calcFit(this); + calcUserParams(this); %Plots the fit function with the new initial parameters if this.enable_plot plotFit(this) end %Updates the GUI and creates new lookup tables for the init %param sliders if this.enable_gui genSliderVecs(this); updateGui(this); end end %Triggers the NewFit event such that other objects can use this to %e.g. plot new fits function triggerNewFit(this) notify(this,'NewFit'); end function triggerNewAcceptedFit(this) notify(this,'NewAcceptedFit'); end % Create metadata with all the fitting and user-defined parameters function Mdt = createMetadata(this) % Field for the fit parameters InfoMdt = MyMetadata('title', 'FitInfo'); addObjProp(InfoMdt, this, 'fit_name'); addObjProp(InfoMdt, this, 'fit_function'); % Indicate if the parameter values were obtained manually or % from performing a fit if isempty(this.Gof) param_val_mode = 'manual'; else param_val_mode = 'fit'; end addParam(InfoMdt, 'param_val_mode', param_val_mode, ... 'comment', ['If the parameter values were set manually '... 'or obtained from fit']); % Field for the fit parameters ParValMdt = MyMetadata('title', 'FittingParameters'); if ~isempty(this.Gof) % Add fit parameters with confidence intervals ci = confint(this.FitResult, 0.95); for i=1:length(this.fit_params) str = sprintf('%8.4g (%.4g, %.4g)', ... this.param_vals(i), ci(1,i), ci(2,i)); addParam(ParValMdt, this.fit_params{i}, str, ... 'comment', [this.fit_param_names{i} ... ' (95% confidence interval)']); end else % Add only fit parameters for i=1:length(this.fit_params) addParam(ParValMdt, this.fit_params{i}, ... this.param_vals(i), 'comment', ... this.fit_param_names{i}); end end % Field for the user parameters UserParMdt = MyMetadata('title', 'UserParameters'); - user_params = this.user_field_tags; + user_params = fieldnames(this.UserParamList); for i=1:length(user_params) tag = user_params{i}; addParam(UserParMdt, tag, this.(tag), ... - 'comment', this.UserGui.Fields.(tag).title); + 'comment', this.UserParamList.(tag).title); end if ~isempty(this.Gof) % Field for the goodness of fit which copies the fields of % corresponding structure GofMdt = MyMetadata('title', 'GoodnessOfFit'); addParam(GofMdt, 'sse', this.Gof.sse, 'comment', ... 'Sum of squares due to error'); addParam(GofMdt, 'rsquare', this.Gof.rsquare, 'comment',... 'R-squared (coefficient of determination)'); addParam(GofMdt, 'dfe', this.Gof.dfe, 'comment', ... 'Degrees of freedom in the error'); addParam(GofMdt, 'adjrsquare', this.Gof.adjrsquare, ... 'comment', ['Degree-of-freedom adjusted ' ... 'coefficient of determination']); addParam(GofMdt, 'rmse', this.Gof.rmse, 'comment', ... 'Root mean squared error (standard error)'); else GofMdt = MyMetadata.empty(); end Mdt = [InfoMdt, ParValMdt, UserParMdt, GofMdt]; end end - methods (Access=protected) + methods (Access = protected) + %Creates the GUI of MyFit, in separate file. createGui(this, varargin); %Does the fit with the currently set parameters. This method is %often overloaded in subclasses to improve performance. function doFit(this) %Use current coefficients as initial paramters init_params = this.param_vals; Ft=fittype(this.fit_function,'coefficients',this.fit_params); Opts=fitoptions('Method','NonLinearLeastSquares',... 'Lower',this.lim_lower,... 'Upper',this.lim_upper,... 'StartPoint',init_params,... 'MaxFunEvals',2000,... 'MaxIter',2000,... 'TolFun',1e-6,... 'TolX',1e-6); %Fits with the below properties. Chosen for maximum accuracy. ind = this.data_selection; [this.FitResult,this.Gof,this.FitInfo] = ... fit(this.Data.x(ind), this.Data.y(ind), Ft, Opts); %Puts the coefficients into the class variable. this.param_vals=coeffvalues(this.FitResult); end - %This struct is used to generate the UserGUI. Fields are seen under - %tabs in the GUI. To create a new tab, you have to enter it under - %this.UserGui.Tabs. A tab must have a tab_title and a field to add - %Children. To add a field, use the addUserField function. - function createUserGuiStruct(this) - this.UserGui=struct('Fields',struct(),'Tabs',struct()); - end - %Low level function that generates initial parameters. %The default version of this function is not meaningful, it %should be overloaded in subclasses. function [init_params,lim_lower,lim_upper]=calcInitParams(this) init_params=ones(1,this.n_params); lim_lower=-Inf(1,this.n_params); lim_upper=Inf(1,this.n_params); %Loads the results into the class variables this.param_vals=init_params; this.lim_lower=lim_lower; this.lim_upper=lim_upper; end - %Calculate user parameters from fit parameters. - %Dummy method that needs to be overloaded in subclasses. + % Calculate user parameters from fit parameters. + % Dummy method that needs to be overloaded in subclasses. function calcUserParams(this) %#ok end - %Parent is the parent tab for the userfield, tag is the tag given - %to the GUI element, title is the text written next to the field, - %initial value is the initial value of the property and change_flag - %determines whether the gui element is enabled for writing or not. - %conv_factor is used to have different units in the field. In the - %program, the value is always saved as the bare value. - function addUserField(this, parent, tag, title, ... - init_val,varargin) - %Parsing inputs - p=inputParser(); - addRequired(p,'Parent'); - addRequired(p,'Tag'); - addRequired(p,'Title'); - addRequired(p,'init_val'); - addParameter(p,'enable_flag','on'); - addParameter(p,'Callback',''); - addParameter(p,'conv_factor',1); - - parse(p,parent,tag,title,init_val,varargin{:}); - tag=p.Results.Tag; - - %Populates the UserGui struct - this.UserGui.Fields.(tag).parent=p.Results.Parent; - this.UserGui.Fields.(tag).title=p.Results.Title; - this.UserGui.Fields.(tag).init_val=p.Results.init_val; - this.UserGui.Fields.(tag).enable_flag=... - p.Results.enable_flag; - this.UserGui.Fields.(tag).conv_factor=p.Results.conv_factor; - this.UserGui.Fields.(tag).Callback=... - p.Results.Callback; - - this.UserGui.Tabs.(p.Results.Parent).Children{end+1}=tag; - %Adds the new property to the class - addUserProp(this, tag); - end - - %Every user field has an associated property, which is added by - %this function. The get and set functions are set to use the GUI - %through the getUserVal and setUserVal functions if the GUI is - %enabled. - function addUserProp(this,tag) - prop=addprop(this,tag); - if this.enable_gui - prop.GetMethod=@(this) getUserVal(this,tag); - prop.SetMethod=@(this, val) setUserVal(this, val, tag); - prop.Dependent=true; + function addUserParam(this, name, varargin) + + % Process inputs + p = inputParser(); + addRequired(p, 'name', @ischar); + addParameter(p, 'title', ''); + addParameter(p, 'editable', @(x)assert(isequal(x, 'on') || ... + isequal(x, 'off'), ['''editable'' property must be ' ... + '''on'' or ''off'''])); + + % Value in GUI is displayed as val/conv_factor + addParameter(p, 'gui_conv_factor', 1); + + parse(p, name, varargin{:}); + + % Store the information about the user parameter + this.UserParamList.(name).title = p.Results.title; + this.UserParamList.(name).editable = p.Results.editable; + this.UserParamList.(name).gui_conv_factor = ... + p.Results.gui_conv_factor; + + % Create a dynamic property for easy access + Mp = addprop(this, name); + this.UserParamList.(name).Metaprop = Mp; + + Mp.GetAccess = 'public'; + + if this.UserParamList.(name).editable + Mp.SetAccess = 'public'; + else + Mp.SetAccess = 'private'; end end - %This function gets the value of the userprop from the GUI. The GUI - %is the single point of truth - function val=getUserVal(this, tag) - conv_factor=this.UserGui.Fields.(tag).conv_factor; - val=str2double(this.Gui.([tag,'Edit']).String)*conv_factor; - end - - %As above, but instead we set the GUI through setting the property - function setUserVal(this, val, tag) - conv_factor=this.UserGui.Fields.(tag).conv_factor; - this.Gui.([tag,'Edit']).String=num2str(val/conv_factor); - end - - %Creates the user values panel with associated tabs. The cellfun here - %creates the appropriately named tabs. To add a tab, add a new field to the - %UserGuiStruct using the class functions in MyFit. This function - %can be overloaded, though some care must be taken to not exceed - %the size given by the GUI - function createUserGui(this, bg_color, button_h) - usertabs=fieldnames(this.UserGui.Tabs); - if ~isempty(usertabs) - cellfun(@(x) createTab(this,x,bg_color,button_h),usertabs); - this.Gui.TabPanel.TabTitles=... - cellfun(@(x) this.UserGui.Tabs.(x).tab_title, usertabs,... - 'UniformOutput',0); - end + % addUserParam statements must be contained in this function + % overloaded in subclasses. + function createUserParamList(this) %#ok end - %Can be overloaded to have more convenient sliders function genSliderVecs(this) %Return values of the slider slider_vals=1:101; %Default scaling vector def_vec=10.^((slider_vals-51)/50); %Sets the cell to the default value for i=1:this.n_params this.slider_vecs{i}=def_vec*this.param_vals(i); set(this.Gui.(sprintf('Slider_%s', this.fit_params{i})),... 'Value',50); end end %Checks if the class is ready to perform a fit function validateData(this) assert(~isempty(this.Data.x) && ~isempty(this.Data.y) && ... length(this.Data.x)==length(this.Data.y) && ... length(this.Data.x)>=this.n_params, ... ['The data must be vectors of equal length greater ' ... 'than the number of fit parameters.', ... ' Currently the number of fit parameters is %i, the', ... ' length of x is %i and the length of y is %i'], ... this.n_params, length(this.Data.x), length(this.Data.y)); end %Calculates the trace object that represents the fitted curve function calcFit(this) xmin = this.Data.x(1); xmax = this.Data.x(end); if this.enable_range_cursors % If range cursors are active, restrict to the selected % range xmin = max(xmin, min(this.RangeCursors.value)); xmax = min(xmax, max(this.RangeCursors.value)); end this.Fit.x=linspace(xmin, xmax, this.fit_length); input_coeffs=num2cell(this.param_vals); this.Fit.y=this.anon_fit_fun(this.Fit.x, input_coeffs{:}); end %Overload a method of matlab.mixin.CustomDisplay in order to %separate the display of user properties from the others. function PrGroups = getPropertyGroups(this) - user_params = this.user_field_tags; + user_params = fieldnames(this.UserParamList); static_props = setdiff(properties(this), user_params); PrGroups = [matlab.mixin.util.PropertyGroup(static_props), ... matlab.mixin.util.PropertyGroup(user_params)]; end end %Callbacks - methods (Access=protected) + methods (Access = protected) %Callback for saving the fit trace function saveFitCallback(this,~,~) base_dir=this.Gui.BaseDir.String; session_name=this.Gui.SessionName.String; file_name=this.Gui.FileName.String; % Add extension to the file name if missing [~,~,ext]=fileparts(file_name); if isempty(ext) || (length(ext) > 5) || any(isspace(ext)) file_name=[file_name, '.txt']; end assert(~isempty(base_dir),'Save directory is not specified'); save_path=createSessionPath(base_dir, session_name); save(this.Fit, fullfile(save_path, file_name)); end %Creates callback functions for sliders in GUI. Uses ind to find %out which slider the call is coming from. Note that this gets %triggered whenever the value of the slider is changed. function f = createSliderStateChangedCallback(this, ind) edit_field_name = sprintf('Edit_%s',this.fit_params{ind}); function sliderStateChangedCallback(hObject, ~) %Gets the value from the slider val=hObject.Value; %Find out if the current slider value is correct for the %current init param value. If so, do not change anything. %This is required as the callback also gets called when %the slider values are changed programmatically [~, slider_ind]=... min(abs(this.param_vals(ind)-this.slider_vecs{ind})); if slider_ind~=(val+1) %Updates the scale with a new value from the lookup %table this.param_vals(ind)=... this.slider_vecs{ind}(val+1); %Updates the edit box with the new value from the %slider set(this.Gui.(edit_field_name),... 'String', sprintf('%3.3e',this.param_vals(ind))); %Re-calculate the fit curve. calcFit(this); if this.enable_plot plotFit(this); end end end f = @sliderStateChangedCallback; end function f = createParamFieldEditedCallback(this, ind) function paramEditFieldCallback(hObject, ~) val=str2double(hObject.String); manSetParamVal(this, ind, val); end f = @paramEditFieldCallback; end function f = createSliderMouseReleasedCallback(this, ind) function sliderMouseReleasedCallback(hObject, ~) slider_ind=hObject.Value; val = this.slider_vecs{ind}(slider_ind+1); manSetParamVal(this, ind, val); end f = @sliderMouseReleasedCallback; end %Callback function for the manual update of the values of fit %parameters in GUI. Triggered when values in the boxes are editted %and when pulling a slider is over. function manSetParamVal(this, ind, new_val) %Updates the correct initial parameter this.param_vals(ind)=new_val; %Re-calculate the fit curve. calcFit(this); if this.enable_plot plotFit(this) end %Centers the slider set(this.Gui.(sprintf('Slider_%s',this.fit_params{ind})),... 'Value',50); %Generate the new slider vectors genSliderVecs(this); %Reset fit structures to indicate that the current parameters %were set manually this.FitResult=cfit.empty(); this.Gof=struct.empty(); this.FitInfo=struct.empty(); %Calculate user parameters calcUserParams(this); %Update fit metadata this.Fit.UserMetadata=createMetadata(this); end function f = createLowerLimEditCallback(this, ind) function lowerLimEditCallback(hObject, ~) this.lim_lower(ind)=str2double(hObject.String); end f = @lowerLimEditCallback; end function f = createUpperLimEditCallback(this, ind) function upperLimEditCallback(hObject, ~) this.lim_upper(ind)=str2double(hObject.String); end f = @upperLimEditCallback; end + %Create a callback that is executed when an editable user parameter + %is set in the GUI + function f = createUserParamCallback(this, param_name) + function userParamCallback(hObject, ~) + conv_factor = UserParamList.(param_name).gui_conv_factor; + this.(param_name) = str2double(hObject.String)*conv_factor; + calcUserParams(this); + end + + f = @userParamCallback; + end + %Callback function for analyze button in GUI. Checks if the data is %ready for fitting. function analyzeCallback(this, ~, ~) fitTrace(this); end - function acceptFitCallback(this,~,~) + function acceptFitCallback(this, ~, ~) triggerNewAcceptedFit(this); end function enableCursorsCallback(this, hObject, ~) this.enable_range_cursors = hObject.Value; end %Callback for clearing the fits on the axis. - function clearFitCallback(this,~,~) + function clearFitCallback(this, ~, ~) clearFit(this); end %Callback function for the button that generates init parameters. - function initParamCallback(this,~,~) + function initParamCallback(this, ~, ~) genInitParams(this); end + + %Close figure callback simply calls delete function for class + function closeFigureCallback(this,~,~) + delete(this); + end end %Private methods - methods(Access=private) + methods(Access = private) %Creates a panel for the GUI, in separate file - createTab(this, tab_tag, bg_color, button_h); - - %Creats two vboxes (from GUI layouts) to display values of - %quantities - createUnitBox(this, bg_color, h_parent, name); - - %Creates an edit box inside a UnitDisp for showing label and value of - %a quantity. Used in conjunction with createUnitBox - createUnitDisp(this,varargin); + createUserControls(this, varargin); %Updates the GUI if the edit or slider boxes are changed from %elsewhere. function updateGui(this) for i=1:this.n_params str=this.fit_params{i}; set(this.Gui.(sprintf('Edit_%s',str)),... 'String',sprintf('%3.3e',this.param_vals(i))); set(this.Gui.(sprintf('Lim_%s_upper',str)),... 'String',sprintf('%3.3e',this.lim_upper(i))); set(this.Gui.(sprintf('Lim_%s_lower',str)),... 'String',sprintf('%3.3e',this.lim_lower(i))); end end end methods % Can set enable_plot to true only if Axes are present function set.enable_plot(this, val) val = logical(val); this.enable_plot = val & ~isempty(this.Axes); %#ok end function set.enable_range_cursors(this, val) if ~isempty(this.RangeCursors) for i=1:length(this.RangeCursors) this.RangeCursors(i).Line.Visible = val; end end try if this.enable_gui && ... this.Gui.CursorsCheckbox.Value ~= val this.Gui.CursorsCheckbox.Value = val; end catch end end % Visibility of the range cursors is the reference if they are % enabled or not function val = get.enable_range_cursors(this) if ~isempty(this.RangeCursors) val = strcmpi(this.RangeCursors(1).Line.Visible, 'on'); else val = false; end end function ind = get.data_selection(this) if this.enable_range_cursors xmin = min(this.RangeCursors.value); xmax = max(this.RangeCursors.value); ind = (this.Data.x>xmin & this.Data.x<=xmax); else ind = true(1, length(this.Data.x)); end end %Calculates the number of parameters in the fit function function n_params=get.n_params(this) n_params=length(this.fit_params); end - - %Used when creating the UserGUI, finds the number of user fields. - function n_user_fields=get.n_user_fields(this) - n_user_fields=length(this.user_field_tags); - end - - %Finds all the user field tags - function user_field_tags=get.user_field_tags(this) - user_field_tags=fieldnames(this.UserGui.Fields); - end - - %Finds all the titles of the user field tags - function user_field_names=get.user_field_names(this) - user_field_names=cellfun(@(x) this.UserGui.Fields.(x).title,... - this.user_field_tags,'UniformOutput',0); - end end end \ No newline at end of file diff --git a/@MyFit/createGui.m b/@MyFit/createGui.m index 7731628..08addfa 100644 --- a/@MyFit/createGui.m +++ b/@MyFit/createGui.m @@ -1,302 +1,298 @@ function createGui(this, varargin) p=inputParser(); %Parameter that tells the function if save panel should be created addParameter(p,'save_panel',true,@islogical); parse(p,varargin{:}); enable_save_panel = p.Results.save_panel; if ~isempty(this.fit_name) %Makes the fit name have the first letter capitalized fit_name=[upper(this.fit_name(1)),this.fit_name(2:end)]; else fit_name=''; end %Defines the colors for the Gui rgb_blue=[0.1843,0.4157,1]; rgb_white=[1,1,1]; %Width of the edit boxes in the GUI edit_width=140; %Height of buttons in GUI button_h=25; %Minimum height of the four vboxes composing the gui. title_h=40; equation_h=100; savebox_h=100; %Only relevant when save_panel input argument is true slider_h=130; min_fig_width=560; -%Finds the minimum height in button heights of the user field panel. This +%Finds the height in button heights of the user field panel. This %is used to calculate the height of the figure. -tab_fields=fieldnames(this.UserGui.Tabs); -max_fields=max(cellfun(@(x) length(this.UserGui.Tabs.(x).Children),tab_fields)); -if max_fields>3 - min_user_h=max_fields+2; +n_user_params = length(fieldnames(this.UserParamList)); +if n_user_params>3 + userpanel_h=(n_user_params+2)*button_h; else - min_user_h=5; + userpanel_h=5*button_h; end -userpanel_h=min_user_h*button_h; - if enable_save_panel fig_h=title_h+equation_h+slider_h+savebox_h+userpanel_h; else fig_h=title_h+equation_h+slider_h+userpanel_h; end %Sets a minimum width if this.n_params < 4 if this.n_params ~= 0 edit_width=min_fig_width/this.n_params; else % Support the case of dummy fit with no parameters edit_width=min_fig_width; end end fig_width=edit_width*this.n_params; %Name sets the title of the window, NumberTitle turns off the FigureN text %that would otherwise be before the title, MenuBar is the menu normally on %the figure, toolbar is the toolbar normally on the figure. %HandleVisibility refers to whether gcf, gca etc will grab this figure. this.Gui.Window = figure('Name', 'MyFit', 'NumberTitle', 'off', ... 'MenuBar', 'none', 'Toolbar', 'none', 'HandleVisibility', 'off',... 'Units','Pixels','Position',[100,100,fig_width,fig_h]); %Place the figure in the center of the screen centerFigure(this.Gui.Window); %Sets the close function (runs when x is pressed) to be class function -set(this.Gui.Window, 'CloseRequestFcn',... - @(hObject,eventdata) closeFigure(this, hObject,eventdata)); +set(this.Gui.Window, 'CloseRequestFcn', @this.closeFigureCallback); + %The main vertical box. The four main panes of the GUI are stacked in the %box. We create these four boxes first so that we do not need to redraw %them later this.Gui.MainVbox=uix.VBox('Parent',this.Gui.Window,'BackgroundColor',rgb_white); + %The title box this.Gui.Title=annotation(this.Gui.MainVbox,'textbox',[0.5,0.5,0.3,0.3],... 'String',fit_name,'Units','Normalized',... 'HorizontalAlignment','center','VerticalAlignment','middle',... 'FontSize',16,'BackgroundColor',rgb_white); + %Displays the fitted equation this.Gui.Equation=annotation(this.Gui.MainVbox,'textbox',[0.5,0.5,0.3,0.3],... 'String',this.fit_tex,... 'Units','Normalized','Interpreter','LaTeX',... 'HorizontalAlignment','center','VerticalAlignment','middle',... 'FontSize',20,'BackgroundColor',rgb_white); + %Creates an HBox for extracted parameters and user interactions with GUI this.Gui.UserHbox=uix.HBox('Parent',this.Gui.MainVbox,... 'BackgroundColor',rgb_white); %Sets the heights and minimum heights of the five vertical boxes. -1 means %it resizes with the window if enable_save_panel %Creates the HBox for saving parameters this.Gui.SaveHbox=uix.HBox('Parent', this.Gui.MainVbox,... 'BackgroundColor',rgb_white); end %Creates the HBox for the fitting parameters this.Gui.FitHbox=uix.HBox('Parent',this.Gui.MainVbox,'BackgroundColor',... rgb_white); if enable_save_panel set(this.Gui.MainVbox,'Heights',[title_h,-1,userpanel_h,savebox_h,slider_h],... 'MinimumHeights',[title_h,equation_h,userpanel_h,savebox_h,slider_h]); else set(this.Gui.MainVbox,'Heights',[title_h,-1,userpanel_h,slider_h],... 'MinimumHeights',[title_h,equation_h,userpanel_h,slider_h]); end %Here we create the fit panel in the GUI. this.Gui.FitPanel=uix.BoxPanel( 'Parent', this.Gui.UserHbox,... 'Padding',0,'BackgroundColor', rgb_white,... 'Title','Fit Panel','TitleColor',rgb_blue); %Here we create the panel for the useful parameters this.Gui.UserPanel=uix.BoxPanel( 'Parent', this.Gui.UserHbox,... 'Padding',0,'BackgroundColor', 'w',... 'Title','Calculated parameters','TitleColor',rgb_blue); %Sets the widths of the above set(this.Gui.UserHbox,'Widths',[-1,-2],'MinimumWidths',[0,375]); %This makes the buttons that go inside the FitPanel this.Gui.FitVbox=uix.VBox('Parent',this.Gui.FitPanel,'BackgroundColor',... rgb_white); %Creates the button for analysis inside the VBox this.Gui.AnalyzeButton=uicontrol('Parent',this.Gui.FitVbox,... - 'style','pushbutton','Background','w','String','Analyze','Callback',... - @(hObject, eventdata) analyzeCallback(this, hObject, eventdata)); + 'style','pushbutton','Background','w','String','Analyze', ... + 'Callback', @this.analyzeCallback); %Creates button for generating new initial parameters this.Gui.InitButton=uicontrol('Parent',this.Gui.FitVbox,... 'style','pushbutton','Background','w',... - 'String','Generate initial parameters','Callback',... - @(hObject, eventdata) initParamCallback(this, hObject, eventdata)); + 'String','Generate initial parameters', ... + 'Callback', @this.initParamCallback); %Creates button for clearing fits this.Gui.ClearButton=uicontrol('Parent',this.Gui.FitVbox,... - 'style','pushbutton','Background','w','String','Clear fit','Callback',... - @(hObject, eventdata) clearFitCallback(this, hObject, eventdata)); + 'style','pushbutton','Background','w','String','Clear fit', ... + 'Callback', @this.clearFitCallback); %Button for triggering NewAcceptedFit event this.Gui.AcceptFitButton=uicontrol('Parent',this.Gui.FitVbox,... - 'style','pushbutton','Background','w','String','Accept fit','Callback',... - @(hObject, eventdata) acceptFitCallback(this, hObject, eventdata)); + 'style','pushbutton','Background','w','String','Accept fit', ... + 'Callback', @this.acceptFitCallback); %Checkbox for enabling cursors this.Gui.CursorsCheckbox=uicontrol('Parent',this.Gui.FitVbox,... 'style','checkbox','Background','w','String', ... 'Range selection cursors','Callback', @this.enableCursorsCallback); set(this.Gui.FitVbox,... - 'Heights',button_h*ones(1,length(this.Gui.FitVbox.Children))); - -this.Gui.TabPanel=uix.TabPanel('Parent',this.Gui.UserPanel,... - 'BackgroundColor',rgb_white); + 'Heights', button_h*ones(1,length(this.Gui.FitVbox.Children))); -%Creates the user gui. Made into its own function such that it can be -%overloaded for future customization -createUserGui(this, rgb_white, button_h); +%Fill the user panel with controls +createUserControls(this, 'field_hight', button_h, 'background_color', 'w'); if enable_save_panel %This creates the boxes for saving files and for specifying file saving %properties this.Gui.SavePanel=uix.BoxPanel( 'Parent', this.Gui.SaveHbox,... 'Padding',0,'BackgroundColor', rgb_white,... 'Title','Save Panel','TitleColor',rgb_blue); this.Gui.DirPanel=uix.BoxPanel('Parent',this.Gui.SaveHbox,... 'Padding',0,'BackgroundColor',rgb_white,... 'Title','Directory Panel','TitleColor',rgb_blue); set(this.Gui.SaveHbox,'Widths',[-1,-2],'MinimumWidths',[0,375]); %Here we create the buttons and edit boxes inside the save box this.Gui.SaveButtonBox=uix.VBox('Parent',this.Gui.SavePanel,... 'BackgroundColor',rgb_white); this.Gui.DirHbox=uix.HBox('Parent',this.Gui.DirPanel,... 'BackgroundColor',rgb_white); this.Gui.FileNameLabelBox=uix.VBox('Parent',this.Gui.DirHbox,... 'BackgroundColor',rgb_white); this.Gui.FileNameBox=uix.VBox('Parent',this.Gui.DirHbox,... 'BackgroundColor',rgb_white); set(this.Gui.DirHbox,'Widths',[-1,-2]); %Buttons for saving the fit and parameters this.Gui.SaveFitButton=uicontrol('Parent',this.Gui.SaveButtonBox,... 'style','pushbutton','Background','w','String','Save Fit',... 'Callback', @(hObject, eventdata) saveFitCallback(this, hObject, eventdata)); set(this.Gui.SaveButtonBox,'Heights',button_h* ... ones(1,length(this.Gui.SaveButtonBox.Children))); %Labels for the edit boxes this.Gui.BaseDirLabel=annotation(this.Gui.FileNameLabelBox,... 'textbox',[0.5,0.5,0.3,0.3],... 'String','Save Directory','Units','Normalized',... 'HorizontalAlignment','Left','VerticalAlignment','middle',... 'FontSize',10,'BackgroundColor',rgb_white); this.Gui.SessionNameLabel=annotation(this.Gui.FileNameLabelBox,... 'textbox',[0.5,0.5,0.3,0.3],... 'String','Session Name','Units','Normalized',... 'HorizontalAlignment','Left','VerticalAlignment','middle',... 'FontSize',10,'BackgroundColor',rgb_white); this.Gui.FileNameLabel=annotation(this.Gui.FileNameLabelBox,... 'textbox',[0.5,0.5,0.3,0.3],... 'String','File Name','Units','Normalized',... 'HorizontalAlignment','Left','VerticalAlignment','middle',... 'FontSize',10,'BackgroundColor',rgb_white); set(this.Gui.FileNameLabelBox,'Heights',button_h*ones(1,3)); %Boxes for editing the path and filename this.Gui.BaseDir=uicontrol('Parent',this.Gui.FileNameBox,... 'style','edit','HorizontalAlignment','Left',... 'FontSize',10); this.Gui.SessionName=uicontrol('Parent',this.Gui.FileNameBox,... 'style','edit','HorizontalAlignment','Left',... 'FontSize',10); this.Gui.FileName=uicontrol('Parent',this.Gui.FileNameBox,... 'style','edit','HorizontalAlignment','Left',... 'FontSize',10); set(this.Gui.FileNameBox,'Heights',button_h*ones(1,3)); end %We first make the BoxPanels to speed up the process. Otherwise everything %in the BoxPanel must be redrawn every time we make a new one. panel_str=cell(1,this.n_params); for i=1:this.n_params %Generates the string for the panel handle panel_str{i}=sprintf('Panel_%s',this.fit_params{i}); %Creates the panels this.Gui.(panel_str{i})=uix.BoxPanel( 'Parent', this.Gui.FitHbox ,... 'Padding',0,'BackgroundColor', 'w',... 'Title',sprintf('%s (%s)',this.fit_param_names{i},this.fit_params{i}),... 'TitleColor',rgb_blue,... 'Position',[1+edit_width*(i-1),1,edit_width,slider_h],... 'Visible','off'); end %Loops over number of parameters to create a fit panel for each one for i=1:this.n_params %Generates the string for the vbox handle vbox_str=sprintf('Vbox_%s',this.fit_params{i}); %Generates the string for the slider handle slider_str=sprintf('Slider_%s',this.fit_params{i}); %Generates string for edit panels edit_str=sprintf('Edit_%s',this.fit_params{i}); %Generates a string for the limit boxes lim_str=sprintf('Lim_%s',this.fit_params{i}); %Creates the vbox inside the panel that allows stacking this.Gui.(vbox_str) =uix.VBox( 'Parent', ... this.Gui.(panel_str{i}),'Padding',0,'BackgroundColor', 'w'); %Generates edit box for fit parameters this.Gui.(edit_str)=uicontrol('Parent',this.Gui.(vbox_str),... 'Style','edit','String',sprintf('%3.3e',this.param_vals(i)),... 'FontSize',14,'HorizontalAlignment','Right',... 'Position',[1,48,edit_width-4,30],'Units','Pixels', ... 'Callback', createParamFieldEditedCallback(this, i)); %Sets up HBox for the lower and upper limits this.Gui.(lim_str)=uix.HBox('Parent',this.Gui.(vbox_str),... 'Padding',0,'BackgroundColor','w'); %Generates edit box for limits this.Gui.([lim_str,'_lower'])=uicontrol('Parent',this.Gui.(lim_str),... 'Style','edit','String',sprintf('%3.3e',this.lim_lower(i)),... 'FontSize',10,'HorizontalAlignment','Right',... 'Position',[1,1,edit_width-4,30],'Units','Pixels', ... 'Callback', createLowerLimEditCallback(this, i)); this.Gui.([lim_str,'_upper'])=uicontrol('Parent',this.Gui.(lim_str),... 'Style','edit','String',sprintf('%3.3e',this.lim_upper(i)),... 'FontSize',10,'HorizontalAlignment','Right',... 'Position',[1,1,edit_width-4,30],'Units','Pixels', ... 'Callback', createLowerLimEditCallback(this, i)); %Generates java-based slider. Looks nicer than MATLAB slider this.Gui.(slider_str)=uicomponent('Parent',this.Gui.(vbox_str),... 'style','jslider','Value',50,'Orientation',0,... 'MajorTickSpacing',20,'MinorTickSpacing',5,'Paintlabels',0,... 'PaintTicks',1,'Background',java.awt.Color.white,... 'pos',[1,-7,edit_width-4,55]); %Sets up callbacks for the slider this.Gui.([slider_str,'_callback'])=handle(this.Gui.(slider_str),... 'CallbackProperties'); %Note that this is triggered whenever the state changes, even if it is %programatically this.Gui.([slider_str,'_callback']).StateChangedCallback = .... createSliderStateChangedCallback(this, i); this.Gui.([slider_str,'_callback']).MouseReleasedCallback = .... createSliderMouseReleasedCallback(this, i); %Sets heights and minimum heights for the elements in the fit vbox set(this.Gui.(vbox_str),'Heights',[30,30,55],'MinimumHeights',[30,30,55]) end %Makes all the panels at the bottom visible at the same time cellfun(@(x) set(this.Gui.(x),'Visible','on'),panel_str); end \ No newline at end of file diff --git a/@MyFit/createTab.m b/@MyFit/createTab.m deleted file mode 100644 index 0a553a6..0000000 --- a/@MyFit/createTab.m +++ /dev/null @@ -1,32 +0,0 @@ -function createTab(this,tab_tag,bg_color, button_h) -tab_field=sprintf('%sTab',tab_tag); -%Creates a tab inside the user panel -this.Gui.(tab_field)=uix.Panel('Parent', this.Gui.TabPanel,... - 'Padding', 0, 'BackgroundColor',bg_color); - -%Creates VBoxes for the quantities to be displayed -createUnitBox(this,bg_color,this.Gui.(tab_field),tab_tag); - -%Creates boxes to show numbers and labels for quality factor, frequency and -%linewidth -ind=structfun(@(x) strcmp(x.parent,tab_tag), this.UserGui.Fields); -names=fieldnames(this.UserGui.Fields); -names=names(ind); - -for i=1:length(names) - field=this.UserGui.Fields.(names{i}); - createUnitDisp(this,... - 'BackgroundColor',bg_color,... - 'Tag',names{i},... - 'Parent',tab_tag,... - 'Title',field.title,... - 'Enable',field.enable_flag,... - 'init_val',field.init_val/field.conv_factor); -end -%Sets the heights of the edit boxes -name_vbox=sprintf('%sNameVBox',tab_tag); -value_vbox=sprintf('%sEditVBox',tab_tag); - -set(this.Gui.(name_vbox),'Heights',button_h*ones(1,length(names))); -set(this.Gui.(value_vbox),'Heights',button_h*ones(1,length(names))); -end \ No newline at end of file diff --git a/@MyFit/createUnitBox.m b/@MyFit/createUnitBox.m deleted file mode 100644 index fb446bb..0000000 --- a/@MyFit/createUnitBox.m +++ /dev/null @@ -1,13 +0,0 @@ -function createUnitBox(this, bg_color, h_parent,name) -hbox_str=sprintf('%sDispHBox',name); -this.Gui.(hbox_str)=uix.HBox('Parent',h_parent,... - 'BackgroundColor',bg_color); -this.Gui.(sprintf('%sNameVBox',name))=... - uix.VBox('Parent',this.Gui.(hbox_str),... - 'BackgroundColor',bg_color); -this.Gui.(sprintf('%sEditVBox',name))=... - uix.VBox('Parent',this.Gui.(hbox_str),... - 'BackgroundColor',bg_color); -set(this.Gui.(hbox_str),'Widths',[-2,-1]); - -end \ No newline at end of file diff --git a/@MyFit/createUnitDisp.m b/@MyFit/createUnitDisp.m deleted file mode 100644 index 00b5cc0..0000000 --- a/@MyFit/createUnitDisp.m +++ /dev/null @@ -1,31 +0,0 @@ -function createUnitDisp(this,varargin) -p=inputParser; -addParameter(p,'BackgroundColor','w'); -addParameter(p,'Tag','Placeholder',@ischar); -addParameter(p,'Parent','Placeholder',@ischar); -addParameter(p,'Title','Placeholder',@ischar); -addParameter(p,'Enable','on',@ischar); -addParameter(p,'init_val',1,@isnumeric); -parse(p,varargin{:}); - -tag=p.Results.Tag; -vbox_name=sprintf('%sNameVBox',p.Results.Parent); -vbox_edit=sprintf('%sEditVBox',p.Results.Parent); -label_name=sprintf('%sLabel',tag); -value_name=sprintf('%sEdit',tag); - -this.Gui.(label_name)=annotation(this.Gui.(vbox_name),... - 'textbox',[0.5,0.5,0.3,0.3],... - 'String',p.Results.Title,'Units','Normalized',... - 'HorizontalAlignment','Left','VerticalAlignment','middle',... - 'FontSize',10,'BackgroundColor',p.Results.BackgroundColor); -this.Gui.(value_name)=uicontrol('Parent',this.Gui.(vbox_edit),... - 'Style','edit','String',num2str(p.Results.init_val),... - 'HorizontalAlignment','Right',... - 'FontSize',10,'Enable',p.Results.Enable); - -if ~isempty(this.UserGui.Fields.(tag).Callback) - this.Gui.(value_name).Callback=this.UserGui.Fields.(tag).Callback; -end - -end \ No newline at end of file diff --git a/@MyFit/createUserControls.m b/@MyFit/createUserControls.m new file mode 100644 index 0000000..d4c7dd1 --- /dev/null +++ b/@MyFit/createUserControls.m @@ -0,0 +1,79 @@ +% Fill the user panel with control elements using the information in +% UserParamList + +function createUserControls(this, varargin) + p = inputParser(); + addParameter(p, 'background_color', 'w'); + addParameter(p, 'field_hight', 0); + parse(p, varargin{:}); + + bg_color = p.Results.background_color; + field_h = p.Results.field_hight; + + % First, create the main hbox and two vboxes within it, for the display + % of parameter labels and values, respectively. + this.Gui.UserHbox = uix.HBox('Parent', this.Gui.UserPanel, ... + 'BackgroundColor', bg_color); + this.Gui.UserParamNameVBox = uix.VBox('Parent', this.Gui.UserHbox, ... + 'BackgroundColor', bg_color); + this.Gui.UserParamEditVBox = uix.VBox('Parent', this.Gui.UserHbox, ... + 'BackgroundColor', bg_color); + set(this.Gui.UserHbox, 'Widths', [-2,-1]); + + param_names = fieldnames(this.UserParamList); + + for i=1:length(param_names) + S = this.UserParamList.(param_names{i}); + + % Create names for the label and edit field gui elements + lcn = sprintf('%sLabel',param_names{i}); + vcn = sprintf('%sEdit',param_names{i}); + + this.Gui.(lcn) = annotation(this.Gui.UserParamNameVBox, ... + 'textbox', [0.5,0.5,0.3,0.3], ... + 'String', S.title, ... + 'Units', 'Normalized', ... + 'HorizontalAlignment', 'Left', ... + 'VerticalAlignment', 'middle', ... + 'FontSize', 10, ... + 'BackgroundColor', bg_color); + + this.Gui.(vcn) = uicontrol( ... + 'Parent', this.Gui.UserParamEditVBox, ... + 'Style', 'edit', ... + 'HorizontalAlignment', 'Right', ... + 'FontSize', 10, ... + 'Enable', S.editable); + + if S.editable + this.Gui.(vcn).Callback = ... + createUserParamCallback(this, param_names{i}); + end + + % Create a set method for the dynamic property corresponding to the + % user parameter, which will update the value displayed in GUI if + % property value is changed programmatically. + S.Metaprop.SetMethod = createSetUserParamFcn( ... + param_names{i}, this.Gui.(vcn)); + end + + % Sets the heights of the edit boxes + set(this.Gui.UserParamNameVBox, ... + 'Heights', field_h*ones(1, length(param_names))); + set(this.Gui.UserParamEditVBox, ... + 'Heights', field_h*ones(1, length(param_names))); +end + +% Subroutine for the creation of dynamic set methods +function f = createSetUserParamFcn(param_name, GuiElement) + function setUserParam(this, val) + this.(param_name) = val; + + conv_factor = ... + this.UserParamList.(param_name).gui_conv_factor; + GuiElement.String = num2str(val/conv_factor); + end + + f = @setUserParam; +end + diff --git a/Fit classes/@MyLorentzianFit/MyLorentzianFit.m b/Fit classes/@MyLorentzianFit/MyLorentzianFit.m index 93b6f6c..fa7f1b5 100644 --- a/Fit classes/@MyLorentzianFit/MyLorentzianFit.m +++ b/Fit classes/@MyLorentzianFit/MyLorentzianFit.m @@ -1,152 +1,117 @@ classdef MyLorentzianFit < MyFit properties (Access=public) - %Logical value that determines whether the data should be scaled or - %not + %Whether the data should be scaled before fitting or not scale_data=true; - %For calibration of optical frequencies using reference lines - tot_spacing=1; end - %Public methods methods (Access=public) - %Constructor function function this=MyLorentzianFit(varargin) this@MyFit(... 'fit_name','Lorentzian',... 'fit_function','1/pi*a*b/2/((x-c)^2+(b/2)^2)+d',... 'fit_tex','$$\frac{a}{\pi}\frac{b/2}{(x-c)^2+(b/2)^2}+d$$',... 'fit_params', {'a','b','c','d'},... 'fit_param_names',{'Amplitude','Width','Center','Offset'},... varargin{:}); end end - methods (Access=protected) + methods (Access = protected) + function createUserParamList(this) + addUserParam(this, 'Q', 'title', 'Qualify Factor', ... + 'editable', 'off'); + end + %Overload the doFit function to do scaled fits. %We here have the choice of whether to scale the data or not. function doFit(this) if this.scale_data ft=fittype(this.fit_function,'coefficients',... this.fit_params); opts=fitoptions('Method','NonLinearLeastSquares',... 'Lower',convRealToScaledCoeffs(this,this.lim_lower),... 'Upper',convRealToScaledCoeffs(this,this.lim_upper),... 'StartPoint',convRealToScaledCoeffs(this,this.param_vals),... 'MaxFunEvals',2000,... 'MaxIter',2000,... 'TolFun',1e-6,... 'TolX',1e-6); %Fits with the below properties. Chosen for maximum accuracy. [this.FitResult,this.Gof,this.FitInfo]=... fit(this.Data.scaled_x,this.Data.scaled_y,ft,opts); %Puts the coeffs into the class variable. this.param_vals=convScaledToRealCoeffs(this,... coeffvalues(this.FitResult)); else %Do the default fitting if we are not scaling. doFit@MyFit(this); end calcUserParams(this); end %Calculates the initial parameters using an external function. function [init_params,lim_lower,lim_upper]=calcInitParams(this) if this.scale_data [init_params,lim_lower,lim_upper]=... initParamLorentzian(this.Data.scaled_x,this.Data.scaled_y); %Convertion back to real values for display. init_params=convScaledToRealCoeffs(this,init_params); lim_lower=convScaledToRealCoeffs(this,lim_lower); lim_upper=convScaledToRealCoeffs(this,lim_upper); else [init_params,lim_lower,lim_upper]=... initParamLorentzian(this.Data.x,this.Data.y); end this.param_vals = init_params; this.lim_lower = lim_lower; this.lim_upper = lim_upper; end %Function for calculating the parameters shown in the user panel function calcUserParams(this) - this.mech_lw=this.param_vals(2); - this.mech_freq=this.param_vals(3); - this.Q=this.mech_freq/this.mech_lw; - this.opt_lw=convOptFreq(this,this.param_vals(2)); - this.Qf=this.mech_freq*this.Q; - end - - function createUserGuiStruct(this) - createUserGuiStruct@MyFit(this); - - %Parameters for the tab relating to mechanics - this.UserGui.Tabs.Mech.tab_title='Mech.'; - this.UserGui.Tabs.Mech.Children={}; - addUserField(this,'Mech','mech_lw','Linewidth (Hz)',1,... - 'enable_flag','off') - addUserField(this,'Mech','Q',... - 'Qualify Factor (x10^6)',1e6,... - 'enable_flag','off','conv_factor',1e6) - addUserField(this,'Mech','mech_freq','Frequency (MHz)',1e6,... - 'conv_factor',1e6, 'enable_flag','off') - addUserField(this,'Mech','Qf','Q\times f (10^{14} Hz)',1e14,... - 'conv_factor',1e14,'enable_flag','off'); - - %Parameters for the tab relating to optics - this.UserGui.Tabs.Opt.tab_title='Optical'; - this.UserGui.Tabs.Opt.Children={}; - addUserField(this,'Opt','line_spacing',... - 'Line Spacing (MHz)',1e6,'conv_factor',1e6,... - 'Callback', @(~,~) calcUserParams(this)); - addUserField(this,'Opt','line_no','Number of lines',1,... - 'Callback', @(~,~) calcUserParams(this)); - addUserField(this,'Opt','opt_lw','Linewidth (MHz)',1e6,... - 'enable_flag','off','conv_factor',1e6); + lw = this.param_vals(2); + freq = this.param_vals(3); + this.Q = freq/lw; end function genSliderVecs(this) genSliderVecs@MyFit(this); try %We choose to have the slider go over the range of %the x-values of the plot for the center of the %Lorentzian. this.slider_vecs{3}=... linspace(this.Fit.x(1),this.Fit.x(end),101); %Find the index closest to the init parameter [~,ind]=... min(abs(this.param_vals(3)-this.slider_vecs{3})); %Set to ind-1 as the slider goes from 0 to 100 set(this.Gui.(sprintf('Slider_%s',... this.fit_params{3})),'Value',ind-1); catch end end - - %This function is used to convert the x-axis to frequency. - function real_freq=convOptFreq(this,freq) - real_freq=freq*this.line_spacing*this.line_no/this.tot_spacing; - end end methods (Access=private) %Converts scaled coefficients to real coefficients function r_c=convScaledToRealCoeffs(this,s_c) [mean_x,std_x,mean_y,std_y]=calcZScore(this.Data); r_c(1)=s_c(1)*std_y*std_x; r_c(2)=s_c(2)*std_x; r_c(3)=s_c(3)*std_x+mean_x; r_c(4)=s_c(4)*std_y+mean_y; end function s_c=convRealToScaledCoeffs(this,r_c) [mean_x,std_x,mean_y,std_y]=calcZScore(this.Data); s_c(1)=r_c(1)/(std_y*std_x); s_c(2)=r_c(2)/std_x; s_c(3)=(r_c(3)-mean_x)/std_x; s_c(4)=(r_c(4)-mean_y)/std_y; end end end \ No newline at end of file