diff --git a/@MyFit/MyFit.m b/@MyFit/MyFit.m index 1561e25..ec2acd4 100644 --- a/@MyFit/MyFit.m +++ b/@MyFit/MyFit.m @@ -1,874 +1,815 @@ % Class that implements fitting routines with interactive capabilities. classdef MyFit < dynamicprops & matlab.mixin.CustomDisplay %Note that dynamicprops classes are handle classes. properties (Access=public) Data; %MyTrace object contains the data to be fitted to lim_lower; %Lower limits for fit parameters lim_upper; %Upper limits for fit parameters enable_plot; %If enabled, plots initial parameters in the Axes Axes; %The handle which the fit is plotted in fit_color='black'; %Color of the fit line fit_length=1e3; %Number of points in the fit trace end properties (GetAccess=public, SetAccess=protected) Fit; %MyTrace object containing the fit Gui; %Gui handles %Output structures from fit: Fitdata; Gof; FitInfo; param_vals; %Values of fit parameters %Parameters of the specific fit. fit_name; fit_function; fit_tex; fit_params; fit_param_names; anon_fit_fun; end + + %Dependent variables with no set methods + properties (Dependent=true, GetAccess=public, SetAccess=private) + n_params; + end properties (Access=protected) %Structure used for initializing GUI of userpanel UserGui; enable_gui=1; - %Private struct used for saving file information when there is no - %gui - SaveInfo - %Vectors for varying the range of the sliders for different fits slider_vecs; end - %Dependent variables with no set methods - properties (Dependent=true, SetAccess=private) - n_params; - - %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; end methods (Access=public) 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. + %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,'enable_gui',1); addParameter(p,'enable_plot',1); addParameter(p,'Axes',[]); + addParameter(p,'standalone_mode',true,@islogical); - addParameter(p,'base_dir',this.SaveInfo.filename); - addParameter(p,'session_name',this.SaveInfo.session_name); - addParameter(p,'filename',this.SaveInfo.base_dir); + % These parameters are only active when GUI is enabled + 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); %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); + + 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 data was supplied, generates initial fit parameters if ~ismember('Data', p.UsingDefaults) || ... ~ismember('x', p.UsingDefaults) || ... ~ismember('y', p.UsingDefaults) 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, inrder to remove the % fit curve from the axes delete(this.Fit); 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']; + 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(this.fullpath,'file') - fileID=fopen(this.fullpath,'a'); - fprintf('Appending data to %s \n',this.fullpath); + if exist(fullpath,'file') + fileID=fopen(fullpath,'a'); + fprintf('Appending data to %s \n',fullpath); else - fileID=fopen(this.fullpath,'w'); + 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; + 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); updateFitMetadata(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 after %calculating the new values function plotFit(this, 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') 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); %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 end methods (Access=protected) %Creates the GUI of MyFit, in separate file. createGui(this); %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. [this.Fitdata,this.Gof,this.FitInfo]=... fit(this.Data.x,this.Data.y,Ft,Opts); %Puts the coefficients into the class variable. this.param_vals=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 %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. 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; 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.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) this.Fit.x=linspace(min(this.Data.x), max(this.Data.x), ... this.fit_length); input_coeffs=num2cell(this.param_vals); this.Fit.y=this.anon_fit_fun(this.Fit.x, input_coeffs{:}); end % Create metadata with all the fitting and user-defined parameters function createMetadata(this) FitMdt = MyMetadata('title', 'FittingParameters'); addObjProp(FitMdt, this, 'fit_name'); addObjProp(FitMdt, this, 'fit_function'); for i=1:length(this.fit_params) addParam(FitMdt, this.fit_params{i}, this.param_vals(i),... 'comment', this.fit_param_names{i}); end UserParMdt = MyMetadata('title', 'UserParameters'); - user_params = fieldnames(this.UserGui.Fields); + user_params = this.user_field_tags; for i=1:length(user_params) tag = user_params{i}; addParam(UserParMdt, tag, this.(tag), ... 'comment', this.UserGui.Fields.(tag).title); end this.Fit.UserMetadata = [FitMdt, UserParMdt]; end function updateFitMetadata(this) if isempty(this.Fit.UserMetadata) createMetadata(this); end FitMdt = this.Fit.UserMetadata(1); UserParMdt = this.Fit.UserMetadata(2); % Update metadata parameters for i=1:length(this.fit_params) FitMdt.ParamList.(this.fit_params{i}) = this.param_vals(i); end - user_params = fieldnames(this.UserGui.Fields); + user_params = this.user_field_tags; for i=1:length(user_params) tag = user_params{i}; UserParMdt.ParamList.(tag) = this.(tag); end 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 = fieldnames(this.UserGui.Fields); + user_params = this.user_field_tags; + static_props = setdiff(properties(this), user_params); - props = setdiff(properties(this), user_params); - - PrGroups = [matlab.mixin.util.PropertyGroup(props), ... + PrGroups = [matlab.mixin.util.PropertyGroup(static_props), ... matlab.mixin.util.PropertyGroup(user_params)]; end end %Callbacks methods (Access=protected) %Save fit function callback function saveFitCallback(this,~,~) - assert(~isempty(this.base_dir),'Save directory is not specified'); - assert(ischar(this.base_dir),... + 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 + + save_path=createSessionPath(base_dir, session_name); + + assert(~isempty(base_dir),'Save directory is not specified'); + assert(ischar(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) + class(base_dir)) + + save(this.Fit, fullfile(save_path, file_name)); end %Callback for saving parameters function saveParamCallback(this,~,~) saveParams(this); 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); updateParamVal(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); updateParamVal(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 updateParamVal(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); %Calculate user parameters calcUserParams(this); updateFitMetadata(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 %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); %Triggers the NewFit event such that other objects can use this to %e.g. plot new fits function triggerNewFit(this) notify(this,'NewFit'); 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.param_vals(i))); set(this.Gui.(sprintf('Lim_%s_upper',str)),... - 'String',sprintf('%3.3e',this.lim_upper(i))) + 'String',sprintf('%3.3e',this.lim_upper(i))); set(this.Gui.(sprintf('Lim_%s_lower',str)),... - 'String',sprintf('%3.3e',this.lim_lower(i))) + '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 %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 7c3c7e9..b07d84b 100644 --- a/@MyFit/createGui.m +++ b/@MyFit/createGui.m @@ -1,286 +1,286 @@ function createGui(this) 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 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)); %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',... + 'style','edit','HorizontalAlignment','Left',... 'FontSize',10); this.Gui.SessionName=uicontrol('Parent',this.Gui.FileNameBox,... - 'style','edit','String',this.SaveInfo.session_name,'HorizontalAlignment','Left',... + 'style','edit','HorizontalAlignment','Left',... 'FontSize',10); this.Gui.FileName=uicontrol('Parent',this.Gui.FileNameBox,... - 'style','edit','String',this.SaveInfo.filename,'HorizontalAlignment','Left',... + 'style','edit','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.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/@MyGeneralPlot/MyGeneralPlot.m b/@MyGeneralPlot/MyGeneralPlot.m index f3a2b79..1a88243 100644 --- a/@MyGeneralPlot/MyGeneralPlot.m +++ b/@MyGeneralPlot/MyGeneralPlot.m @@ -1,1041 +1,1043 @@ % Acquisition and analysis program that receives data from Collector. Can % also be used for analysis of previously acquired traces. 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).x 5) || any(isspace(ext)) + % Add default file extension filename=[filename, this.default_ext]; end catch filename=['placeholder', this.default_ext]; end end function set.filename(this, str) [~,fn,ext]=fileparts(str); if strcmpi(ext, this.default_ext) % By default display filename without extension this.Gui.FileName.String=fn; else this.Gui.FileName.String=[fn,ext]; end end function set.default_ext(this, str) assert(ischar(str)&&(str(1)=='.'), ['Default file ' ... 'extension must be a character string startign ' ... 'with ''.''']) this.default_ext=str; end end end \ No newline at end of file