diff --git a/@MyFit/MyFit.m b/@MyFit/MyFit.m index f05d11a..a47f998 100644 --- a/@MyFit/MyFit.m +++ b/@MyFit/MyFit.m @@ -1,807 +1,813 @@ classdef MyFit < dynamicprops %Note that dynamicprops classes are handle classes. properties (Access=public) Data; %MyTrace object contains the data to be fitted to init_params=[]; %Contains the initial parameters lim_lower; %Lower limits for fit parameters lim_upper; %Upper limits for fit parameters - enable_plot; %If enabled, plots initial parameters in the plot_handle - plot_handle; %The handle which fits and init params are plotted in + enable_plot; %If enabled, plots initial parameters in the Axes + Axes; %The handle which fits and init params are plotted in init_color='c'; %Color of plot of initial parameters end properties (GetAccess=public, SetAccess=protected) Fit; %MyTrace object containing the fit Gui; %Gui handles + %Output structures from fit: Fitdata; Gof; FitInfo; coeffs; %Parameters of the specific fit. fit_name; fit_function; fit_tex; fit_params; fit_param_names; anon_fit_fun; end properties (Access=protected) %Structure used for initializing GUI of userpanel UserGui; Parser; %Input parser for constructor enable_gui=1; hline_init; %Handle for the plotted init values %Private struct used for saving file information when there is no %gui SaveInfo slider_vecs; %Vectors for varying the range of the sliders for different fits end %Dependent variables with no set methods properties (Dependent=true, SetAccess=private) n_params; %Vector used for plotting, depends on the data trace x_vec; %Variables used for saving, linked to the GUI fullpath; save_path; end properties (Dependent=true, Access=protected) %These are used to create the usergui n_user_fields; user_field_tags; user_field_names; user_field_vals; end %Dependent variables with associated set methods properties (Dependent=true) filename; base_dir; session_name; end %Events for communicating with outside entities events NewFit; NewInitVal; end %Parser function methods (Access=private) %Creates parser for constructor function createParser(this) p=inputParser; addParameter(p,'fit_name','') - addParameter(p,'fit_function','') + addParameter(p,'fit_function','x') addParameter(p,'fit_tex','') addParameter(p,'fit_params',{}) addParameter(p,'fit_param_names',{}) addParameter(p,'Data',MyTrace()); addParameter(p,'Fit',MyTrace()); addParameter(p,'x',[]); addParameter(p,'y',[]); addParameter(p,'enable_gui',1); addParameter(p,'enable_plot',1); - addParameter(p,'plot_handle',[]); + addParameter(p,'Axes',[]); addParameter(p,'base_dir',this.SaveInfo.filename); addParameter(p,'session_name',this.SaveInfo.session_name); addParameter(p,'filename',this.SaveInfo.base_dir); this.Parser=p; end end %Public methods methods (Access=public) %Constructor function function this=MyFit(varargin) %Sets the default parameters for the save directory and %filename. this.SaveInfo.filename='placeholder'; this.SaveInfo.session_name='placeholder'; this.SaveInfo.base_dir=getLocalSettings('measurement_base_dir'); %We now create the parser for parsing the arguments to the %constructor, and parse the variables. createParser(this); parse(this.Parser,varargin{:}); parseInputs(this); %Generates the anonymous fit function from the input fit %function. This is used for fast plotting of the initial %values. - args=['@(x,', strjoin(this.fit_params,','),')']; + args=['@(', strjoin([{'x'}, this.fit_params], ','),')']; this.anon_fit_fun=... str2func(vectorize([args,this.fit_function])); %Sets dummy values for the GUI this.init_params=ones(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',this.Parser.UsingDefaults) &&... ~ismember('x',this.Parser.UsingDefaults) &&... ~ismember('y',this.Parser.UsingDefaults) this.Data.x=this.Parser.Results.x; this.Data.y=this.Parser.Results.y; end %Creates the structure that contains variables for calibration %of fit results createUserGuiStruct(this); %Creates the gui if the flag is enabled. This function is in a %separate file. if this.enable_gui createGui(this) %Generates the slider lookup table genSliderVecs(this); end %If the data is appropriate, generates initial %parameters if validateData(this); 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 ~isempty(this.hline_init); delete(this.hline_init); end if ~isempty(this.Fit.hlines); delete(this.Fit.hlines{:}); 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.coeffs) && ... length(this.coeffs)==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.coeffs),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.coeffs; 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 %We automatically append to the file if it already exists, %otherwise create a new file if exist(this.fullpath,'file') fileID=fopen(this.fullpath,'a'); fprintf('Appending data to %s \n',this.fullpath); else fileID=fopen(this.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.coeffs(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,coeffs) assert(length(coeffs)==this.n_params,... ['The length of the coefficient vector (currently %i) ',... 'must be equal to the number of parameters (%i)'],... length(this.coeffs),this.n_params) this.coeffs=coeffs; end %Fits the trace using currently set parameters, depending on the %model. function fitTrace(this) %Checks for valid data. assert(validateData(this),... ['The length of x is %d and the length of y is',... ' %d. The lengths must be equal and greater than ',... 'the number of fit parameters to perform a fit'],... length(this.Data.x),length(this.Data.y)) %Checks 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)})); %Perform the fit. doFit(this); %This function calculates the fit trace, using this.x_vec as %the x axis calcFit(this); %Sets the new initial parameters to be the fitted parameters this.init_params=this.coeffs; %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.hlines); clearInitFun(this); this.Fit.hlines={}; end %Clears the plot of the initial values function clearInitFun(this) delete(this.hline_init); this.hline_init=[]; end %Plots the trace contained in the Fit MyTrace object after %calculating the new values function plotFit(this,varargin) calcFit(this); - assert((isa(this.plot_handle,'matlab.graphics.axis.Axes')||... - isa(this.plot_handle,'matlab.ui.control.UIAxes')),... - 'plot_handle property must be defined to valid axis in order to plot') - this.Fit.plot(this.plot_handle,varargin{:}); + assert((isa(this.Axes,'matlab.graphics.axis.Axes')||... + isa(this.Axes,'matlab.ui.control.UIAxes')),... + 'Axes property must be defined to valid axis in order to plot') + this.Fit.plot(this.Axes,varargin{:}); clearInitFun(this); end %Function for plotting fit model with current initial parameters. function plotInitFun(this) %Substantially faster than any alternative - generating %anonymous functions is very cpu intensive. input_cell=num2cell(this.init_params); y_vec=feval(this.anon_fit_fun,... this.x_vec,input_cell{:}); if isempty(this.hline_init) - this.hline_init=plot(this.plot_handle,this.x_vec,y_vec,... + this.hline_init=plot(this.Axes,this.x_vec,y_vec,... 'Color',this.init_color); else set(this.hline_init,'XData',this.x_vec,'YData',y_vec); end end %Generates model-dependent initial parameters, lower and upper %boundaries. function genInitParams(this) assert(validateData(this), ['The data must be vectors of',... ' equal length greater than the number of fit parameters.',... ' Currently the number of fit parameters is %d, the',... ' length of x is %d and the length of y is %d'],... this.n_params,length(this.Data.x),length(this.Data.y)); %Cell for putting parameters in to be interpreted in the %parser. Element 1 contains the init params, Element 2 contains %the lower limits and Element 3 contains the upper limits. params=cell(1,3); [params{1},params{2},params{3}]=calcInitParams(this); %Validates the initial parameters p=createFitParser(this.n_params); parse(p,params{:}); %Loads the parsed results into the class variables this.init_params=p.Results.init_params; this.lim_lower=p.Results.lower; this.lim_upper=p.Results.upper; %Plots the fit function with the new initial parameters - if this.enable_plot; plotInitFun(this); end + if this.enable_plot + plotInitFun(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 end methods (Access=protected) %Creates the GUI of MyFit, in separate file. createGui(this); %Does the fit with the currently set parameters function doFit(this) - ft=fittype(this.fit_function,'coefficients',this.fit_params); - opts=fitoptions('Method','NonLinearLeastSquares',... + Ft=fittype(this.fit_function,'coefficients',this.fit_params); + Opts=fitoptions('Method','NonLinearLeastSquares',... 'Lower',this.lim_lower,... 'Upper',this.lim_upper,... 'StartPoint',this.init_params,... 'MaxFunEvals',2000,... 'MaxIter',2000,... 'TolFun',1e-6,... 'TolX',1e-6); %Fits with the below properties. Chosen for maximum accuracy. [this.Fitdata,this.Gof,this.FitInfo]=... - fit(this.Data.x,this.Data.y,ft,opts); + fit(this.Data.x,this.Data.y,Ft,Opts); %Puts the coeffs into the class variable. this.coeffs=coeffvalues(this.Fitdata); 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 %Default generation of initial parameters. This function 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); 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; 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 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.init_params(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 bool=validateData(this) bool=~isempty(this.Data.x) && ~isempty(this.Data.y) && ... length(this.Data.x)==length(this.Data.y) && ... length(this.Data.x)>=this.n_params; end %Creates an input parser for a fit function with n_arg arguments. Default %values are ones for initial parameters and plus and minus inf for upper %and lower limits respectively. Ensures that the length is equal to the %number of arguments. function p=createFitParser(n_arg) p=inputParser; validateStart=@(x) assert(isnumeric(x) && isvector(x) && length(x)==n_arg,... 'Starting points must be given as a vector of size %d',n_arg); validateLower=@(x) assert(isnumeric(x) && isvector(x) && length(x)==n_arg,... 'Lower limits must be given as a vector of size %d',n_arg); validateUpper=@(x) assert(isnumeric(x) && isvector(x) && length(x)==n_arg,... 'Upper limits must be given as a vector of size %d',n_arg); addOptional(p,'init_params',ones(1,n_arg),validateStart) addOptional(p,'lower',-Inf(1,n_arg),validateLower) addOptional(p,'upper',Inf(1,n_arg),validateUpper) end %Calculates the trace object for the fit function calcFit(this) this.Fit.x=this.x_vec; input_coeffs=num2cell(this.coeffs); this.Fit.y=this.anon_fit_fun(this.Fit.x,input_coeffs{:}); end end %Callbacks methods %Save fit function callback function saveFitCallback(this,~,~) assert(~isempty(this.base_dir),'Save directory is not specified'); assert(ischar(this.base_dir),... ['Save directory is not specified.',... ' Should be of type char but is %s.'], ... class(this.base_dir)) this.Fit.save('name',this.filename,... 'base_dir',this.save_path) end %Callback for saving parameters function saveParamCallback(this,~,~) saveParams(this); end %Callback functions for sliders in GUI. Uses param_ind to find out %which slider the call is coming from, this was implemented to %speed up the callback. Note that this gets triggered whenever the %value of the slider is changed. function sliderCallback(this, param_ind, hObject, ~) %Gets the value from the slider val=get(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 [~,ind]=... min(abs(this.init_params(param_ind)-this.slider_vecs{param_ind})); if ind~=(val+1) %Updates the scale with a new value from the lookup table this.init_params(param_ind)=... this.slider_vecs{param_ind}(val+1); %Updates the edit box with the new value from the slider set(this.Gui.(sprintf('Edit_%s',this.fit_params{param_ind})),... 'String',sprintf('%3.3e',this.init_params(param_ind))); - if this.enable_plot; plotInitFun(this); end + if this.enable_plot + plotInitFun(this); + end end end %Callback function for edit boxes in GUI function editCallback(this, hObject, ~) init_param=str2double(hObject.String); param_ind=str2double(hObject.Tag); %Centers the slider set(this.Gui.(sprintf('Slider_%s',this.fit_params{param_ind})),... 'Value',50); %Updates the correct initial parameter this.init_params(param_ind)=init_param; if this.enable_plot; plotInitFun(this); end %Triggers event for new init values triggerNewInitVal(this); %Generate the new slider vectors genSliderVecs(this); end %Callback function for editing limits in the GUI function limEditCallback(this, hObject,~) lim = str2double(hObject.String); %Regexp finds type (lower or upper bound) and index expr = '(?Upper|Lower)(?\d+)'; s=regexp(hObject.Tag,expr,'names'); ind=str2double(s.ind); switch s.type case 'Lower' this.lim_lower(ind)=lim; case 'Upper' this.lim_upper(ind)=lim; otherwise error('%s is not properly named for assignment of limits',... hObject.Tag); end end %Callback function for analyze button in GUI. Checks if the data is %ready for fitting. function analyzeCallback(this, ~, ~) fitTrace(this); end %Callback for clearing the fits on the axis. function clearFitCallback(this,~,~) clearFit(this); end %Callback function for generate init parameters button. function initParamCallback(this,~,~) genInitParams(this); end %Callback function for scaleData button function scaleDataCallback(this,hObject) if hObject.Value hObject.BackgroundColor=0.9*[1,1,1]; this.scale_data=true; else hObject.BackgroundColor=[1,1,1]; this.scale_data=false; end end end %Private methods 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); %Sets the class variables to the inputs from the inputParser. function parseInputs(this) for i=1:length(this.Parser.Parameters) %Takes the value from the inputParser to the appropriate %property. if isprop(this,this.Parser.Parameters{i}) this.(this.Parser.Parameters{i})=... this.Parser.Results.(this.Parser.Parameters{i}); end 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 %Triggers the NewInitVal event function triggerNewInitVal(this) notify(this,'NewInitVal'); end %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.init_params(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 % Get functions for dependent variables methods %Calculates the number of parameters in the fit function function n_params=get.n_params(this) n_params=length(this.fit_params); end %Generates a vector of x values for plotting function x_vec=get.x_vec(this) x_vec=linspace(min(this.Data.x),max(this.Data.x),1e3); 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 %Finds all the values of the user fields function user_field_vals=get.user_field_vals(this) user_field_vals=cellfun(@(x) this.(x), this.user_field_tags); end %Generates a full path for saving function fullpath=get.fullpath(this) fullpath=[this.save_path,this.filename,'.txt']; end %Generates a base path for saving function save_path=get.save_path(this) save_path=createSessionPath(this.base_dir,this.session_name); end end %Set and get functions for dependent variables with SetAccess methods %Gets the base dir from the gui function base_dir=get.base_dir(this) if this.enable_gui base_dir=this.Gui.BaseDir.String; else base_dir=this.SaveInfo.base_dir; end end function set.base_dir(this,base_dir) if this.enable_gui this.Gui.BaseDir.String=base_dir; else this.SaveInfo.base_dir=base_dir; end end function session_name=get.session_name(this) if this.enable_gui session_name=this.Gui.SessionName.String; else session_name=this.SaveInfo.session_name; end end function set.session_name(this,session_name) if this.enable_gui this.Gui.SessionName.String=session_name; else this.SaveInfo.session_name=session_name; end end function filename=get.filename(this) if this.enable_gui filename=this.Gui.FileName.String; else filename=this.SaveInfo.filename; end end function set.filename(this,filename) if this.enable_gui this.Gui.FileName.String=filename; else this.SaveInfo.filename=filename; end end end end \ No newline at end of file diff --git a/@MyFit/createGui.m b/@MyFit/createGui.m index a278534..c224ad4 100644 --- a/@MyFit/createGui.m +++ b/@MyFit/createGui.m @@ -1,266 +1,279 @@ function createGui(this) -%Makes the fit name have the first letter capitalized -fit_name=[upper(this.fit_name(1)),this.fit_name(2:end)]; +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; slider_h=130; min_fig_width=560; %Finds the minimum 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; else min_user_h=5; end userpanel_h=min_user_h*button_h; fig_h=title_h+equation_h+slider_h+savebox_h+userpanel_h; %Sets a minimum width -if this.n_params<4; edit_width=min_fig_width/this.n_params; end +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',[500,500,fig_width,fig_h]); %Sets the close function (runs when x is pressed) to be class function set(this.Gui.Window, 'CloseRequestFcn',... @(hObject,eventdata) closeFigure(this, hObject,eventdata)); %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); %Creates the HBox for saving parameters this.Gui.SaveHbox=uix.HBox('Parent',this.Gui.MainVbox,... 'BackgroundColor',rgb_white); %Creates the HBox for the fitting parameters this.Gui.FitHbox=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 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]); %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)); %Creates button for generating new initial parameters this.Gui.InitButton=uicontrol('Parent',this.Gui.FitVbox,... 'style','pushbutton','Background','w',... 'String','Generate Init. Params','Callback',... @(hObject, eventdata) initParamCallback(this, hObject, eventdata)); %Creates button for clearing fits this.Gui.ClearButton=uicontrol('Parent',this.Gui.FitVbox,... 'style','pushbutton','Background','w','String','Clear fits','Callback',... @(hObject, eventdata) clearFitCallback(this, hObject, eventdata)); %Creates button for toggling scaling of data if the class has the %scale_data property. if contains('scale_data',properties(this)) this.Gui.ScaleButton=uicontrol('Parent',this.Gui.FitVbox,... 'style','togglebutton','Background','w',... 'String','Scale data',... 'Callback',@(hObject, ~) scaleDataCallback(this,hObject)); end set(this.Gui.FitVbox,... 'Heights',button_h*ones(1,length(this.Gui.FitVbox.Heights))); this.Gui.TabPanel=uix.TabPanel('Parent',this.Gui.UserPanel,... 'BackgroundColor',rgb_white); %Creates the user gui. Made into its own function such that it can be %overloaded for future customization createUserGui(this, rgb_white, button_h); %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.SaveParamButton=uicontrol('Parent',this.Gui.SaveButtonBox,... 'style','pushbutton','Background','w','String','Save Parameters',... 'Callback', @(hObject, eventdata) saveParamCallback(this, hObject, eventdata)); 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,button_h]) %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','String',this.SaveInfo.base_dir,'HorizontalAlignment','Left',... 'FontSize',10); this.Gui.SessionName=uicontrol('Parent',this.Gui.FileNameBox,... 'style','edit','String',this.SaveInfo.session_name,'HorizontalAlignment','Left',... 'FontSize',10); this.Gui.FileName=uicontrol('Parent',this.Gui.FileNameBox,... 'style','edit','String',this.SaveInfo.filename,'HorizontalAlignment','Left',... 'FontSize',10); set(this.Gui.FileNameBox,'Heights',button_h*ones(1,3)); %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.init_params(i)),... 'FontSize',14,'Tag',num2str(i),'HorizontalAlignment','Right',... 'Position',[1,48,edit_width-4,30],'Units','Pixels','Callback',... @(hObject,eventdata) editCallback(this, hObject, eventdata)); %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,'Tag',sprintf('Lower%i',i),'HorizontalAlignment','Right',... 'Position',[1,1,edit_width-4,30],'Units','Pixels','Callback',... @(hObject,eventdata) limEditCallback(this, hObject, eventdata)); this.Gui.([lim_str,'_upper'])=uicontrol('Parent',this.Gui.(lim_str),... 'Style','edit','String',sprintf('%3.3e',this.lim_upper(i)),... 'FontSize',10,'Tag',sprintf('Upper%i',i),'HorizontalAlignment','Right',... 'Position',[1,1,edit_width-4,30],'Units','Pixels','Callback',... @(hObject,eventdata) limEditCallback(this, hObject, eventdata)); %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 = .... @(hObject, eventdata) sliderCallback(this,i,hObject,eventdata); this.Gui.([slider_str,'_callback']).MouseReleasedCallback = .... @(~, ~) triggerNewInitVal(this); %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/@MyGeneralPlot/MyGeneralPlot.m b/@MyGeneralPlot/MyGeneralPlot.m index 43b008d..40b2df8 100644 --- a/@MyGeneralPlot/MyGeneralPlot.m +++ b/@MyGeneralPlot/MyGeneralPlot.m @@ -1,1041 +1,1041 @@ % Acquisition and analysis program that receives data from Collector. Can % also be used for analysis of previously acquired traces. -classdef MyDaq < handle +classdef MyGeneralPlot < handle properties %Contains GUI handles Gui %Main figure Figure %main axes main_plot %Contains Reference trace (MyTrace object) Ref %Contains Data trace (MyTrace object) Data %Contains Background trace (MyTrace object) Background %List of all the programs with run files ProgramList= MyProgramDescriptor.empty() %Struct containing Cursor objects Cursors=struct() %Struct containing Cursor labels CrsLabels=struct() %Struct containing MyFit objects Fits=struct() %Input parser for class constructor ConstructionParser %Struct for listeners Listeners=struct() %Sets the colors of fits, data and reference fit_color='k' data_color='b' ref_color='r' bg_color='c' default_ext='.txt' % default data file extension end properties (Dependent=true) save_dir open_fits open_crs end properties (Dependent=true, SetAccess=private, GetAccess=public) %Properties for saving files base_dir session_name filename % File name is always returned with extension end methods (Access=public) %% Class functions %Constructor function function this=MyDaq(varargin) %We grab the guihandles from a GUI made in Guide. this.Gui=guihandles(eval('GuiDaq')); %Assign the handle of main figure to a property for %compatibility with Matalb apps this.Figure = this.Gui.figure1; this.main_plot = this.Gui.figure1.CurrentAxes; % Parse inputs p=inputParser; addParameter(p,'collector_handle',[]); this.ConstructionParser=p; parse(p, varargin{:}); %Sets a listener to the collector if ~isempty(p.Results.collector_handle) this.Listeners.Collector.NewDataWithHeaders=... addlistener(p.Results.collector_handle,... 'NewDataWithHeaders',... @(~,eventdata) acquireNewData(this,eventdata)); else errordlg(['No collector handle was supplied. ',... 'DAQ will be unable to acquire data'],... 'Error: No collector'); end % Initialize traces this.Ref=MyTrace(); this.Data=MyTrace(); this.Background=MyTrace(); %The list of instruments is automatically populated from the %run files. Select programs that are enabled and can produce %traces. FullProgList = getIcPrograms(); ind = [FullProgList.enabled] & [FullProgList.data_source]; this.ProgramList = FullProgList(ind); %This function sets all the callbacks for the GUI. If a new %button is made, the associated callback must be put in the %initGui function initGui(this); % Initialize the menu based on the available programs set(this.Gui.InstrMenu,'String',[{'Select the application'},... {this.ProgramList.title}]); % Add a property to the menu for storing the program file % names if ~isprop(this.Gui.InstrMenu, 'ItemsData') addprop(this.Gui.InstrMenu, 'ItemsData'); end set(this.Gui.InstrMenu,'ItemsData',0:length(this.ProgramList)); % Add Data, Ref and Bg traces in the proper order on the plot % as empty lines. hold(this.main_plot,'on'); this.Background.hlines{1}=line(this.main_plot, ... 'XData',[],'YData',[],'Color',this.bg_color); this.Ref.hlines{1}=line(this.main_plot, ... 'XData',[],'YData',[],'Color',this.ref_color); this.Data.hlines{1}=line(this.main_plot, ... 'XData',[],'YData',[],'Color',this.data_color); %Initializes saving locations this.base_dir=getLocalSettings('measurement_base_dir'); this.session_name='placeholder'; this.filename='placeholder'; end function delete(this) %Deletes the MyFit objects and their listeners cellfun(@(x) deleteListeners(this,x), this.open_fits); structfun(@(x) delete(x), this.Fits); %Deletes other listeners if ~isempty(fieldnames(this.Listeners)) cellfun(@(x) deleteListeners(this, x),... fieldnames(this.Listeners)); end %A class destructor should never through errors, so enclose the %attempt to close figure into try-catch structure try this.Gui.figure1.CloseRequestFcn=''; %Deletes the figure delete(this.Gui.figure1); %Removes the figure handle to prevent memory leaks this.Gui=[]; catch end end end methods (Access=private) %Sets callback functions for the GUI initGui(this) %Executes when the GUI is closed function closeFigure(this,~,~) delete(this); end %Updates fits function updateFits(this) %Pushes data into fits in the form of MyTrace objects, so that %units etc follow. Also updates user supplied parameters. for i=1:length(this.open_fits) switch this.open_fits{i} case {'Linear','Quadratic','Gaussian',... 'Exponential','Beta'} this.Fits.(this.open_fits{i}).Data=... getFitData(this,'VertData'); case {'Lorentzian','DoubleLorentzian'} this.Fits.(this.open_fits{i}).Data=... getFitData(this,'VertData'); %Here we push the information about line spacing %into the fit object if the reference cursors are %open. Only for Lorentzian fits. if isfield(this.Cursors,'VertRef') ind=findCursorData(this,'Data','VertRef'); this.Fits.(this.open_fits{i}).CalVals.line_spacing=... range(this.Data.x(ind)); end case {'G0'} this.Fits.G0.MechTrace=getFitData(this,'VertData'); this.Fits.G0.CalTrace=getFitData(this,'VertRef'); end end end % If vertical cursors are on, takes only data within cursors. If %the cursor is not open, it takes all the data from the selected %trace in the analysis trace selection dropdown function Trace=getFitData(this,varargin) %Parses varargin input p=inputParser; addOptional(p,'name','',@ischar); parse(p,varargin{:}) name=p.Results.name; %Finds out which trace the user wants to fit. trc_opts=this.Gui.SelTrace.String; trc_str=trc_opts{this.Gui.SelTrace.Value}; % Note the use of copy here! This is a handle %class, so if normal assignment is used, this.Fits.Data and %this.(trace_str) will refer to the same object, causing roblems. %Name input is the name of the cursor to be used to extract data. Trace=copy(this.(trc_str)); %If the cursor is open for the trace we are analyzing, we take %only the data enclosed by the cursor. if isfield(this.Cursors,name) ind=findCursorData(this, trc_str, name); Trace.x=this.(trc_str).x(ind); Trace.y=this.(trc_str).y(ind); end end %Finds data between named cursors in the given trace function ind=findCursorData(this, trc_str, name) crs_pos=sort([this.Cursors.(name){1}.Location,... this.Cursors.(name){2}.Location]); ind=(this.(trc_str).x>crs_pos(1) & this.(trc_str).x0 this.y = [this.y; zeros(diff,1)]; warning(['Zero padded y vector as the saved vectors ' ... 'are not of the same length']); end % Save data in the more compact of fixed point and scientific % notation with trailing zeros removed. % If save_prec=15, we get %.15g\t%.15g\r\n % Formatting without column padding may look ugly but it % signigicantly reduces the file size. data_format_str = ... sprintf(['%%.%ig', this.column_sep, '%%.%ig', ... this.line_sep], this.save_prec, this.save_prec); fprintf(fileID, data_format_str, [this.x, this.y]'); fclose(fileID); end function clearData(this) this.x = []; this.y = []; end %Plots the trace on the given axes, using the class variables to %define colors, markers, lines and labels. Takes all optional %parameters of the class as inputs. function plot(this, varargin) % Do nothing if there is no data in the trace if isDataEmpty(this) return end % Checks that x and y are the same size assert(validatePlot(this),... 'The length of x and y must be identical to make a plot') % Parses inputs p = inputParser(); p.KeepUnmatched = true; % Axes in which log should be plotted addOptional(p, 'Axes', [], @(x)assert( ... isa(x,'matlab.graphics.axis.Axes')||... isa(x,'matlab.ui.control.UIAxes'),... 'Argument must be axes or uiaxes.')); addParameter(p, 'make_labels', true, @islogical); interpreters = {'none', 'tex', 'latex'}; validateInterpreter = @(x) assert(contains(x,interpreters), ... 'Interpreter must be none, tex or latex'); addParameter(p, 'Interpreter', 'latex', validateInterpreter); parse(p, varargin{:}); line_opts = struct2namevalue(p.Unmatched); %If axes are not supplied get current if ~isempty(p.Results.Axes) Axes = p.Results.Axes; else Axes = gca(); end ind = findLineInd(this, Axes); if ~isempty(ind) && any(ind) set(this.hlines{ind}, 'XData', this.x, 'YData', this.y); else this.hlines{end+1} = plot(Axes, this.x, this.y); ind = length(this.hlines); end % Sets the correct color and label options if ~isempty(line_opts) set(this.hlines{ind}, line_opts{:}); end if p.Results.make_labels % Add labels to the axes interpreter = p.Results.Interpreter; xlabel(Axes, this.label_x, 'Interpreter', interpreter); ylabel(Axes, this.label_y, 'Interpreter', interpreter); set(Axes, 'TickLabelInterpreter', interpreter); end end %If there is a line object from the trace in the figure, this sets %it to the appropriate visible setting. function setVisible(this, Axes, bool) if bool vis='on'; else vis='off'; end ind=findLineInd(this, Axes); if ~isempty(ind) && any(ind) set(this.hlines{ind},'Visible',vis) end end %Defines addition of two MyTrace objects function sum=plus(this,b) checkArithmetic(this,b); sum=MyTrace('x',this.x,'y',this.y+b.y, ... 'unit_x',this.unit_x,'unit_y',this.unit_y, ... 'name_x',this.name_x,'name_y',this.name_y); end %Defines subtraction of two MyTrace objects function diff=minus(this,b) checkArithmetic(this,b); diff=MyTrace('x',this.x,'y',this.y-b.y, ... 'unit_x',this.unit_x,'unit_y',this.unit_y, ... 'name_x',this.name_x,'name_y',this.name_y); end function [max_val,max_x]=max(this) assert(validatePlot(this),['MyTrace object must contain',... ' nonempty data vectors of equal length to find the max']) [max_val,max_ind]=max(this.y); max_x=this.x(max_ind); end function fwhm=calcFwhm(this) assert(validatePlot(this),['MyTrace object must contain',... ' nonempty data vectors of equal length to find the fwhm']) [~,~,fwhm,~]=findpeaks(this.y,this.x,'NPeaks',1); end function [mean_x,std_x,mean_y,std_y]=calcZScore(this) mean_x=mean(this.x); std_x=std(this.x); mean_y=mean(this.y); std_y=std(this.y); end %Integrates the trace numerically function area=integrate(this,varargin) - assert(validatePlot(this),['MyTrace object must contain',... + assert(validatePlot(this), ['MyTrace object must contain',... ' nonempty data vectors of equal length to integrate']) %Input parser for optional inputs p=inputParser; %Default is to use all the data in the trace addOptional(p,'ind',true(1,length(this.x))); parse(p,varargin{:}); ind=p.Results.ind; %Integrates the data contained in the indexed part. area=trapz(this.x(ind),this.y(ind)); end % Picks every n-th element from the trace, % performing a running average first if opt=='avg' function downsample(this, n, opt) n0 = ceil(n/2); if exist('opt', 'var') && ... (strcmpi(opt,'average') || strcmpi(opt,'avg')) % Compute moving average with 'shrink' option so that the % total number of samples is preserved. Endpoints will be % discarded by starting the indexing from n0. tmpy = movmean(this.y, n, 'Endpoints', 'shrink'); this.x = this.x(n0:n:end); this.y = tmpy(n0:n:end); else % Downsample without averaging this.x = this.x(n0:n:end); this.y = this.y(n0:n:end); end end %Checks if the object is empty function bool = isDataEmpty(this) bool = isempty(this.x) && isempty(this.y); end %Checks if the data can be plotted function bool = validatePlot(this) bool =~isempty(this.x) && ~isempty(this.y)... && length(this.x)==length(this.y); end function hline = getLine(this, Ax) ind = findLineInd(this, Ax); if ~isempty(ind) hline = this.hlines{ind}; else hline = []; end end end methods (Access = public, Static = true) % Load trace from file function Trace = load(filename, varargin) assert(exist(filename, 'file') ~= 0, ['File does not ' ... 'exist, please choose a different load path.']) % Extract data formatting p = inputParser(); p.KeepUnmatched = true; addParameter(p, 'FormatSource', {}, @(x) isa(x,'MyTrace')); addParameter(p, 'metadata_opts', {}, @iscell); parse(p, varargin{:}); if ~ismember('FormatSource', p.UsingDefaults) Fs = p.Results.FormatSource; % Take formatting from the source object mdt_opts = Fs.metadata_opts; trace_opts = { ... 'column_sep', Fs.column_sep, ... 'line_sep', Fs.line_sep, ... 'data_sep', Fs.data_sep, ... 'save_prec', Fs.save_prec, ... 'metadata_opts', Fs.metadata_opts}; else % Formatting is either default or was suppled explicitly mdt_opts = p.Results.metadata_opts; trace_opts = varargin; end % Load metadata and convert from array to structure [Mdt, n_end_line] = MyMetadata.load(filename, mdt_opts{:}); Info = titleref(Mdt, 'Info'); if ~isempty(Info) && isparam(Info, 'Type') class_name = Info.ParamList.Type; else class_name = 'MyTrace'; end % Instantiate an appropriate type of Trace Trace = feval(class_name, trace_opts{:}); setMetadata(Trace, Mdt); % Reads x and y data data_array = dlmread(filename, Trace.column_sep, n_end_line,0); Trace.x = data_array(:,1); Trace.y = data_array(:,2); Trace.file_name = filename; end end methods (Access = protected) % Generate metadata that includes measurement headers and % information about trace. This function is used in place of 'get' % method so it can be overloaded in a subclass. function Mdt = getMetadata(this) % Make a field with the information about the trace Info = MyMetadata('title', 'Info'); addParam(Info, 'Type', class(this)); addParam(Info, 'Name1', this.name_x); addParam(Info, 'Name2', this.name_y); addParam(Info, 'Unit1', this.unit_x); addParam(Info, 'Unit2', this.unit_y); % Make a separator for the bulk of trace data DataSep = MyMetadata('title', this.data_sep); Mdt = [Info, this.MeasHeaders, DataSep]; % Ensure uniform formatting if ~isempty(this.metadata_opts) set(Mdt, this.metadata_opts{:}); end end % Load metadata into the trace function setMetadata(this, Mdt) Info = titleref(Mdt, 'Info'); if ~isempty(Info) if isparam(Info, 'Unit1') this.unit_x = Info.ParamList.Unit1; end if isparam(Info, 'Unit2') this.unit_y = Info.ParamList.Unit2; end if isparam(Info, 'Name1') this.name_x = Info.ParamList.Name1; end if isparam(Info, 'Name2') this.name_y = Info.ParamList.Name2; end % Remove the metadata containing trace properties Mdt = rmtitle(Mdt, 'Info'); else warning(['No trace metadata found. No units or labels ' ... 'assigned when loading trace from %s.'], filename); end % Remove the empty data separator field Mdt = rmtitle(Mdt, this.data_sep); % Store the remainder under measurement headers this.MeasHeaders = Mdt; end %Checks if arithmetic can be done with MyTrace objects. function checkArithmetic(this, b) assert(isa(this,'MyTrace') && isa(b,'MyTrace'),... ['Both objects must be of type MyTrace to add,',... 'here they are type %s and %s'],class(this),class(b)); assert(strcmp(this.unit_x, b.unit_x) && ... strcmp(this.unit_y,b.unit_y),... 'The MyTrace classes must have the same units for arithmetic') assert(length(this.x)==length(this.y)==... length(this.x)==length(this.y),... 'The length of x and y must be equal for arithmetic'); assert(all(this.x==b.x),... 'The MyTrace objects must have identical x-axis for arithmetic') end % Finds the hline handle that is plotted in the specified axes function ind = findLineInd(this, Axes) if ~isempty(this.hlines) ind = cellfun(@(x) ismember(x, findall(Axes, ... 'Type','Line')), this.hlines); else ind = []; end end % Overload the standard copy() method to create a deep copy, % i.e. when handle properties are copied recursively function Copy = copyElement(this) Copy = copyElement@matlab.mixin.Copyable(this); % Copy metadata Copy.MeasHeaders = copy(this.MeasHeaders); end end %% Set and get methods methods %Set function for MeasHeaders function set.MeasHeaders(this, Val) assert(isa(Val, 'MyMetadata'),... 'MeasHeaders must be an array of MyMetadata objects'); this.MeasHeaders = Val; end %Set function for x, checks if it is a vector of doubles and %reshapes into a column vector function set.x(this, x) assert(isnumeric(x),... 'Data must be of class double'); this.x=x(:); end %Set function for y, checks if it is a vector of doubles and %reshapes into a column vector function set.y(this, y) assert(isnumeric(y),... 'Data must be of class double'); this.y=y(:); end %Set function for unit_x, checks if input is a string. function set.unit_x(this, unit_x) assert(ischar(unit_x),'Unit must be a char, not a %s',... class(unit_x)); this.unit_x=unit_x; end %Set function for unit_y, checks if input is a string function set.unit_y(this, unit_y) assert(ischar(unit_y),'Unit must be a char, not a %s',... class(unit_y)); this.unit_y=unit_y; end %Set function for name_x, checks if input is a string function set.name_x(this, name_x) assert(ischar(name_x),'Name must be a char, not a %s',... class(name_x)); this.name_x=name_x; end %Set function for name_y, checks if input is a string function set.name_y(this, name_y) assert(ischar(name_y),'Name must be a char, not a %s',... class(name_y)); this.name_y=name_y; end function set.file_name(this, file_name) assert(ischar(file_name),'File path must be a char, not a %s',... class(file_name)); this.file_name=file_name; end %Get function for label_x, creates label from name_x and unit_x. function label_x=get.label_x(this) label_x=sprintf('%s (%s)', this.name_x, this.unit_x); end %Get function for label_y, creates label from name_y and unit_y. function label_y=get.label_y(this) label_y=sprintf('%s (%s)', this.name_y, this.unit_y); end %Get function for scaled_x, zscores x function scaled_x=get.scaled_x(this) scaled_x=zscore(this.x); end %Get function for scaled_x, zscores x function scaled_y=get.scaled_y(this) scaled_y=zscore(this.y); end end end diff --git a/Fit classes/@MyLorentzianFit/MyLorentzianFit.m b/Fit classes/@MyLorentzianFit/MyLorentzianFit.m index ca2376c..d29c3ed 100644 --- a/Fit classes/@MyLorentzianFit/MyLorentzianFit.m +++ b/Fit classes/@MyLorentzianFit/MyLorentzianFit.m @@ -1,146 +1,145 @@ classdef MyLorentzianFit < MyFit properties (Access=public) %Logical value that determines whether the data should be scaled or %not scale_data; %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) %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.init_params),... 'MaxFunEvals',2000,... 'MaxIter',2000,... 'TolFun',1e-6,... 'TolX',1e-6); %Fits with the below properties. Chosen for maximum accuracy. [this.Fitdata,this.Gof,this.FitInfo]=... fit(this.Data.scaled_x,this.Data.scaled_y,ft,opts); %Puts the coeffs into the class variable. this.coeffs=convScaledToRealCoeffs(this,... coeffvalues(this.Fitdata)); 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 end %Function for calculating the parameters shown in the user panel function calcUserParams(this) this.mech_lw=this.coeffs(2); this.mech_freq=this.coeffs(3); this.Q=this.mech_freq/this.mech_lw; this.opt_lw=convOptFreq(this,this.coeffs(2)); this.Qf=this.mech_freq*this.Q; end function createUserGuiStruct(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',10,... 'Callback', @(~,~) calcUserParams(this)); addUserField(this,'Opt','opt_lw','Linewidth (MHz)',1e6,... 'enable_flag','off','conv_factor',1e6); end function genSliderVecs(this) genSliderVecs@MyFit(this); if validateData(this) %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.x_vec(1),this.x_vec(end),101); %Find the index closest to the init parameter [~,ind]=... min(abs(this.init_params(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); 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 diff --git a/Test tools/GorodetskyFit test/testgorodetsky.m b/Test tools/GorodetskyFit test/testgorodetsky.m index d6bbf07..8e31c17 100644 --- a/Test tools/GorodetskyFit test/testgorodetsky.m +++ b/Test tools/GorodetskyFit test/testgorodetsky.m @@ -1,15 +1,15 @@ load('testmat') figure(124) ax=gca; -testfit=MyFit('fit_name','Gorodetsky2000','x',xf,'y',yf,'plot_handle',ax,... +testfit=MyFit('fit_name','Gorodetsky2000','x',xf,'y',yf,'Axes',ax,... 'enable_gui',true,'enable_plot',true); testfit.Data.plot(ax); hold on testfit.genInitParams; testfit.init_params(1) testfit.init_params(3:end) testfit.plotInitFun; testfit.fitTrace; testfit.plotFit('Color','r'); testfit.Gof testfit.FitInfo \ No newline at end of file diff --git a/Utility functions/testfit.m b/Utility functions/testfit.m index d01d8b9..69a200b 100644 --- a/Utility functions/testfit.m +++ b/Utility functions/testfit.m @@ -1,56 +1,56 @@ %Testing tool for MyFit clear close(figure(1)) x_vec=linspace(0,200,1000); % testFit=MyFit('fit_name','Linear','enable_gui',1); testFit=MyGorodetsky2000Fit(); params=cell(1,testFit.n_params); switch testFit.fit_name case 'Lorentzian' for i=1:testFit.n_params params{i}=5*rand; params{3}=200*rand; end case 'Exponential' params{1}=+5*rand; params{2}=-0.1*rand; params{3}=0.01*rand; case 'Linear' params{1}=10*rand; params{2}=-50*rand+25; case 'DoubleLorentzian' for i=1:testFit.n_params params{i}=5*rand; end params{3}=50+10*rand; params{2}=params{5}; params{6}=50+100*rand; case 'Gorodetsky2000' for i=1:testFit.n_params params{i}=5*rand; end params{3}=0.001*rand; params{2}=80+20*rand; params{4}=5+10*rand; otherwise for i=1:testFit.n_params params{i}=5*rand; end end params y_vec=testFit.anon_fit_fun(x_vec,params{:}).*normrnd(1,0.04,size(x_vec)); figure(1) ax=gca; plot(x_vec,y_vec,'x'); axis([min(x_vec),max(x_vec),0.5*min(y_vec),1.5*max(y_vec)]); hold on -testFit.plot_handle=ax; +testFit.Axes=ax; testFit.enable_plot=1; testFit.Data.x=x_vec; testFit.Data.y=y_vec; % testFit.genInitParams; % testFit.init_params % testFit.fitTrace; % testFit.init_params \ No newline at end of file