diff --git a/@MyDaq/MyDaq.m b/@MyDaq/MyDaq.m index 40764fd..54807f4 100644 --- a/@MyDaq/MyDaq.m +++ b/@MyDaq/MyDaq.m @@ -1,1055 +1,1047 @@ % Acquisition and analysis program that receives data from Collector. Can % also be used for analysis of previously acquired traces. classdef MyDaq < handle properties %Global variable with Daq name is cleared on exit. global_name %Contains GUI handles Gui %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=struct() %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 main_plot 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) % Parse inputs p=inputParser; addParameter(p,'global_name','',@ischar); addParameter(p,'collector_handle',[]); this.ConstructionParser=p; parse(p, varargin{:}); this.global_name = p.Results.global_name; %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 this.ProgramList = readRunFiles(); %We grab the guihandles from a GUI made in Guide. this.Gui=guihandles(eval('GuiDaq')); %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 run files content = menuFromRunFiles(this.ProgramList,... 'show_in_daq',true); set(this.Gui.InstrMenu,'String',[{'Select the application'};... content.titles]); % 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',[{''};... content.tags]); % 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 % clear global variable, to which Daq handle is assigned evalin('base', sprintf('clear(''%s'')', this.global_name)); %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 %Prints the figure to the clipboard print(newFig,'-clipboard','-dbitmap'); %Deletes the figure delete(newFig); end %Resets the axis to be tight around the plots. function updateAxis(this) axis(this.main_plot,'tight'); end end methods (Access=public) %% Callbacks %Callback for copying the plot to clipboard function copyPlotCallback(this,~,~) copyPlot(this); end %Callback for centering cursors function centerCursorsCallback(this, ~, ~) if ~this.Gui.LogX.Value x_pos=mean(this.main_plot.XLim); else x_pos=10^(mean(log10(this.main_plot.XLim))); end if ~this.Gui.LogY.Value y_pos=mean(this.main_plot.YLim); else y_pos=10^(mean(log10(this.main_plot.YLim))); end for i=1:length(this.open_crs) switch this.Cursors.(this.open_crs{i}){1}.Orientation case 'horizontal' pos=y_pos; case 'vertical' pos=x_pos; end %Centers the position cellfun(@(x) set(x,'Location',pos), ... this.Cursors.(this.open_crs{i})); %Triggers the UpdateCursorBar event, which triggers %listener callback to reposition text cellfun(@(x) notify(x,'UpdateCursorBar'),... this.Cursors.(this.open_crs{i})); %Triggers the EndDrag event, updating the data in the fit %objects. cellfun(@(x) notify(x,'EndDrag'),... this.Cursors.(this.open_crs{i})); end end %Callback for creating vertical data cursors function cursorButtonCallback(this, hObject, ~) name=erase(hObject.Tag,'Button'); %Gets the first four characters of the tag (Vert or Horz) type=name(1:4); %Changes the color of the button and appropriately creates or %deletes the cursors. if hObject.Value hObject.BackgroundColor=[0,1,0.2]; createCursors(this,name,type); else hObject.BackgroundColor=[0.941,0.941,0.941]; deleteCursors(this,name); end end %Callback for the instrument menu function instrMenuCallback(this,hObject,~) val=hObject.Value; if val==1 %Returns if we are on the dummy option ('Select instrument') return else tag = hObject.ItemsData{val}; end try eval(this.ProgramList.(tag).run_expr); catch errordlg(sprintf('An error occured while running %s',... this.ProgramList.(tag).name)) end end %Select trace callback. If we change the trace being analyzed, the %fit objects are updated. function selTraceCallback(this, ~, ~) updateFits(this) end %Saves the data if the save data button is pressed. function saveCallback(this, src, ~) switch src.Tag case 'SaveData' saveTrace(this,'Data'); case 'SaveRef' saveTrace(this,'Ref'); end end function saveTrace(this, trace_tag, varargin) p=inputParser(); % If file already exists, generate a uinique name rather than % show user the overwrite dialog. addParameter(p, 'make_unique_name', false, @islogical); parse(p,varargin{:}); %Check if the trace is valid (i.e. x and y are equal length) %before saving if ~this.(trace_tag).validatePlot errordlg(sprintf('%s trace was empty, could not save',... trace_tag)); return end fullfilename=fullfile(this.save_dir,this.filename); if p.Results.make_unique_name && exist(fullfilename, 'file')~=0 fullfilename=makeUniqueFileName(this); end %Save in readable format using the method of MyTrace save(this.(trace_tag), fullfilename) end % Make the filename unique within the measurement folder % by appending _n. This function does not make sure that the % filename is valid - i.e. does not contain symbols forbidden by % the file system. function fullfilename=makeUniqueFileName(this) [~, fn, ext]=fileparts(this.filename); % List all the existing files in the measurement directory % that have the same extension as our filename DirCont=dir(fullfile(this.save_dir,['*', ext])); file_ind=~[DirCont.isdir]; existing_fns={DirCont(file_ind).name}; % Remove extensions [~,existing_fns,~]=cellfun(@fileparts, existing_fns, ... 'UniformOutput',false); % Generate a new file name if ~isempty(fn) fn=matlab.lang.makeUniqueStrings(fn, existing_fns); else fn=matlab.lang.makeUniqueStrings('placeholder', ... existing_fns); end fullfilename=fullfile(this.save_dir,[fn, ext]); end %Toggle button callback for showing the data trace. function showDataCallback(this, hObject, ~) if hObject.Value hObject.BackgroundColor=[0,1,0.2]; setVisible(this.Data,this.main_plot,1); updateAxis(this); else hObject.BackgroundColor=[0.941,0.941,0.941]; setVisible(this.Data,this.main_plot,0); updateAxis(this); end end %Toggle button callback for showing the ref trace function showRefCallback(this, hObject, ~) if hObject.Value hObject.BackgroundColor=[0,1,0.2]; setVisible(this.Ref,this.main_plot,1); updateAxis(this); else hObject.BackgroundColor=[0.941,0.941,0.941]; setVisible(this.Ref,this.main_plot,0); updateAxis(this); end end %Callback for moving the data to reference. function dataToRefCallback(this, ~, ~) if this.Data.validatePlot % Copy Data to Reference and pass Reference the handle to % the line in the main plot hline=getLineHandle(this.Ref, this.main_plot); this.Ref=copy(this.Data); if ~isempty(hline) this.Ref.hlines{1}=hline; end %Plot the reference trace and make it visible plot(this.Ref, this.main_plot, 'Color',this.ref_color,... 'make_labels',true); setVisible(this.Ref, this.main_plot,1); %Update the fit objects updateFits(this); %Change button color this.Gui.ShowRef.Value=1; this.Gui.ShowRef.BackgroundColor=[0,1,0.2]; else warning('Data trace was empty, could not move to reference') end end %Callback for ref to bg button. Sends the reference to background function refToBgCallback(this, ~, ~) if this.Ref.validatePlot hline=getLineHandle(this.Background, this.main_plot); this.Background=copy(this.Ref); if ~isempty(hline) this.Background.hlines{1}=hline; end this.Background.plot(this.main_plot,... 'Color',this.bg_color,'make_labels',true); this.Background.setVisible(this.main_plot,1); else warning('Reference trace was empty, could not move to background') end end %Callback for data to bg button. Sends the data to background function dataToBgCallback(this, ~, ~) if this.Data.validatePlot hline=getLineHandle(this.Background, this.main_plot); this.Background=copy(this.Data); if ~isempty(hline) this.Background.hlines{1}=hline; end this.Background.plot(this.main_plot,... 'Color',this.bg_color,'make_labels',true); this.Background.setVisible(this.main_plot,1); else warning('Data trace was empty, could not move to background') end end %Callback for clear background button. Clears the background function clearBgCallback(this, ~, ~) this.Background.x=[]; this.Background.y=[]; this.Background.setVisible(this.main_plot,0); end %Callback for LogY button. Sets the YScale to log/lin function logYCallback(this, hObject, ~) if hObject.Value this.main_plot.YScale='Log'; hObject.BackgroundColor=[0,1,0.2]; else this.main_plot.YScale='Linear'; hObject.BackgroundColor=[0.941,0.941,0.941]; end updateAxis(this); updateCursors(this); end %Callback for LogX button. Sets the XScale to log/lin. Updates the %axis and cursors afterwards. function logXCallback(this, hObject, ~) if get(hObject,'Value') set(this.main_plot,'XScale','Log'); set(hObject, 'BackgroundColor',[0,1,0.2]); else set(this.main_plot,'XScale','Linear'); set(hObject, 'BackgroundColor',[0.941,0.941,0.941]); end updateAxis(this); updateCursors(this); end %Base directory callback. Sets the base directory. Also %updates fit objects with the new save directory. function baseDirCallback(this, ~, ~) for i=1:length(this.open_fits) this.Fits.(this.open_fits{i}).base_dir=this.base_dir; end end %Callback for session name edit box. Sets the session name. Also %updates fit objects with the new save directory. function sessionNameCallback(this, ~, ~) for i=1:length(this.open_fits) this.Fits.(this.open_fits{i}).session_name=this.session_name; end end - %Callback for filename edit box. Sets the file name. Also - %updates fit objects with the new file name. - function fileNameCallback(this, ~,~) - for i=1:length(this.open_fits) - this.Fits.(this.open_fits{i}).filename=this.filename; - end - end - %Callback for the analyze menu (popup menu for selecting fits). %Opens the correct MyFit object. function analyzeMenuCallback(this, hObject, ~) analyze_ind=hObject.Value; %Finds the correct fit name by erasing spaces and other %superfluous strings analyze_name=hObject.String{analyze_ind}; analyze_name=erase(analyze_name,'Fit'); analyze_name=erase(analyze_name,'Calibration'); analyze_name=erase(analyze_name,' '); %Sets the correct tooltip hObject.TooltipString=sprintf(this.Gui.AnalyzeTip{analyze_ind}) ; %Opens the correct analysis tool switch analyze_name case {'Linear','Quadratic','Exponential',... 'Lorentzian','Gaussian',... 'DoubleLorentzian'} openMyFit(this,analyze_name); case 'g0' openMyG(this); case 'Beta' openMyBeta(this); end end function openMyFit(this,fit_name) %Sees if the MyFit object is already open, if it is, changes the %focus to it, if not, opens it. if ismember(fit_name,fieldnames(this.Fits)) %Changes focus to the relevant fit window figure(this.Fits.(fit_name).Gui.Window); else %Gets the data for the fit using the getFitData function %with the vertical cursors DataTrace=getFitData(this,'VertData'); %Makes an instance of MyFit with correct parameters. this.Fits.(fit_name)=MyFit(... 'fit_name',fit_name,... 'enable_plot',1,... 'plot_handle',this.main_plot,... 'Data',DataTrace,... 'base_dir',this.base_dir,... 'session_name',this.session_name,... 'filename',this.filename); updateFits(this); %Sets up a listener for the Deletion event, which %removes the MyFit object from the Fits structure if it is %deleted. this.Listeners.(fit_name).Deletion=... addlistener(this.Fits.(fit_name),'ObjectBeingDestroyed',... @(src, eventdata) deleteFit(this, src, eventdata)); %Sets up a listener for the NewFit. Callback plots the fit %on the main plot. this.Listeners.(fit_name).NewFit=... addlistener(this.Fits.(fit_name),'NewFit',... @(src, eventdata) plotNewFit(this, src, eventdata)); %Sets up a listener for NewInitVal this.Listeners.(fit_name).NewInitVal=... addlistener(this.Fits.(fit_name),'NewInitVal',... @(~,~) updateCursors(this)); end end %Opens MyG class if it is not open. function openMyG(this) if ismember('G0',this.open_fits) figure(this.Fits.G0.Gui.figure1); else %Populate the MyG class with the right data. We assume the %mechanics is in the Data trace. MechTrace=getFitData(this,'VertData'); CalTrace=getFitData(this,'VertRef'); this.Fits.G0=MyG('MechTrace',MechTrace,'CalTrace',CalTrace,... 'name','G0'); %Adds listener for object being destroyed this.Listeners.G0.Deletion=addlistener(this.Fits.G0,... 'ObjectBeingDestroyed',... @(~,~) deleteObj(this,'G0')); end end %Opens MyBeta class if it is not open. function openMyBeta(this) if ismember('Beta', this.open_fits) figure(this.Fits.Beta.Gui.figure1); else DataTrace=getFitData(this); this.Fits.Beta=MyBeta('Data',DataTrace); %Adds listener for object being destroyed, to perform cleanup this.Listeners.Beta.Deletion=addlistener(this.Fits.Beta,... 'ObjectBeingDestroyed',... @(~,~) deleteObj(this,'Beta')); end end %Callback for load data button function loadDataCallback(this, ~, ~) if isempty(this.base_dir) warning('Please input a valid folder name for loading a trace'); this.base_dir=pwd; end [load_name,path_name]=uigetfile(this.default_ext, ... 'Select the trace', this.base_dir); if load_name==0 warning('No file was selected'); return end load_path=[path_name,load_name]; %Finds the destination trace from the GUI dest_trc=this.Gui.DestTrc.String{this.Gui.DestTrc.Value}; %Get the line handle from the trace to not create a new line hline=getLineHandle(this.(dest_trc), this.main_plot); %Reset and load the destination trace this.(dest_trc)=MyTrace(); load(this.(dest_trc), load_path); % Assign existing line handle to the trace if ~isempty(hline) this.(dest_trc).hlines{1}=hline; end %Color and plot the right trace. plot(this.(dest_trc), this.main_plot,... 'Color',this.(sprintf('%s_color',lower(dest_trc))),... 'make_labels',true); %Update axis and cursors updateAxis(this); updateCursors(this); end % Callback for open folder button function openFolderCallback(this, hObject, eventdata) dir=uigetdir(this.Gui.BaseDir.String); if ~isempty(dir) this.Gui.BaseDir.String=dir; end % Execute the same callback as if the base directory edit % field was manually updated baseDirCallback(this, hObject, eventdata); end end methods (Access=public) %% Listener functions %Callback function for NewFit listener. Plots the fit in the %window using the plotFit function of the MyFit object function plotNewFit(this, src, ~) src.plotFit('Color',this.fit_color); updateAxis(this); updateCursors(this); end %Callback function for the NewData listener function acquireNewData(this, EventData) %Get the currently selected instrument val=this.Gui.InstrMenu.Value; curr_instr_name=this.Gui.InstrMenu.ItemsData{val}; %Check if the data originates from the currently selected %instrument if strcmp(EventData.src_name, curr_instr_name) hline=getLineHandle(this.Data,this.main_plot); %Copy the data from the source instrument this.Data=copy(EventData.Trace); %We give the new trace object the right line handle to plot in if ~isempty(hline) this.Data.hlines{1}=hline; end plot(this.Data, this.main_plot,... 'Color',this.data_color,... 'make_labels',true) updateAxis(this); updateCursors(this); updateFits(this); % If the save flag is on in EventData, save the new trace % making sure that a unique filename is generated to not % prompt the owerwrite dialog if isprop(EventData, 'save') && EventData.save if isprop(EventData, 'filename_ending') % If present, use the file name ending supplied % externally. By default filename_ending is empty. [~,fn,ext]=fileparts(this.filename); this.filename=[fn, EventData.filename_ending, ext]; saveTrace(this, 'Data', 'make_unique_name', true); % Return the file name to its original value this.filename=[fn, ext]; else saveTrace(this, 'Data', 'make_unique_name', true); end end end end %Callback function for MyFit ObjectBeingDestroyed listener. %Removes the relevant field from the Fits struct and deletes the %listeners from the object. function deleteFit(this, src, ~) %Deletes the object from the Fits struct and deletes listeners deleteObj(this,src.fit_name); %Clears the fits src.clearFit; %Updates cursors since the fits are removed from the plot updateCursors(this); end %Callback function for other analysis method deletion listeners. %Does the same as above. function deleteObj(this,name) if ismember(name,this.open_fits) this.Fits=rmfield(this.Fits,name); end deleteListeners(this, name); end %Listener update function for vertical cursor function vertCursorUpdate(this, src) %Finds the index of the cursor. All cursors are tagged %(name)1, (name)2, e.g. VertData2, ind is the number. ind=str2double(src.Tag(end)); tag=src.Tag(1:(end-1)); %Moves the cursor labels set(this.CrsLabels.(tag){ind},'Position',[src.Location,... this.CrsLabels.(tag){ind}.Position(2),0]); if strcmp(tag,'VertData') %Sets the edit box displaying the location of the cursor this.Gui.(sprintf('EditV%d',ind)).String=... num2str(src.Location); %Sets the edit box displaying the difference in locations this.Gui.EditV2V1.String=... num2str(this.Cursors.VertData{2}.Location-... this.Cursors.VertData{1}.Location); end end %Listener update function for horizontal cursor function horzCursorUpdate(this, src) %Finds the index of the cursor. All cursors are tagged %(name)1, (name)2, e.g. VertData2, ind is the number. ind=str2double(src.Tag(end)); tag=src.Tag(1:(end-1)); %Moves the cursor labels set(this.CrsLabels.(tag){ind},'Position',... [this.CrsLabels.(tag){ind}.Position(1),... src.Location,0]); if strcmp(tag,'HorzData') %Sets the edit box displaying the location of the cursor this.Gui.(sprintf('EditH%d',ind)).String=... num2str(src.Location); %Sets the edit box displaying the difference in locations this.Gui.EditH2H1.String=... num2str(this.Cursors.HorzData{2}.Location-... this.Cursors.HorzData{1}.Location); end end %Function that deletes listeners from the listeners struct, %corresponding to an object of name obj_name deleteListeners(this, obj_name); end %Get functions for dependent variables without set functions methods %Get function from save directory function save_dir=get.save_dir(this) save_dir=createSessionPath(this.base_dir,this.session_name); end %Get function for the plot handles function main_plot=get.main_plot(this) main_plot=this.Gui.figure1.CurrentAxes; end %Get function for open fits function open_fits=get.open_fits(this) open_fits=fieldnames(this.Fits); end %Get function that displays names of open cursors function open_crs=get.open_crs(this) open_crs=fieldnames(this.Cursors); end end %Get and set functions for dependent properties with SetAccess methods function base_dir=get.base_dir(this) try base_dir=this.Gui.BaseDir.String; catch base_dir=pwd; end end function set.base_dir(this,base_dir) this.Gui.BaseDir.String=base_dir; end function session_name=get.session_name(this) try session_name=this.Gui.SessionName.String; catch session_name=''; end end function set.session_name(this,session_name) this.Gui.SessionName.String=session_name; end % Always return filename with extension function filename=get.filename(this) try filename=this.Gui.FileName.String; [~,~,ext]=fileparts(filename); if isempty(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 diff --git a/@MyDaq/initGui.m b/@MyDaq/initGui.m index 08e89f7..f9380f3 100644 --- a/@MyDaq/initGui.m +++ b/@MyDaq/initGui.m @@ -1,119 +1,116 @@ %Initializes MyDaq Gui. Needs no inputs, but should be modified if you wish %to change the callbacks etc. function initGui(this) %Close request function is set to delete function of the class this.Gui.figure1.CloseRequestFcn=@(hObject,eventdata)... closeFigure(this, hObject, eventdata); %Sets callback for the edit box of the base directory this.Gui.BaseDir.Callback=@(hObject, eventdata)... baseDirCallback(this, hObject, eventdata); %Sets callback for the session name edit box this.Gui.SessionName.Callback=@(hObject, eventdata)... sessionNameCallback(this, hObject, eventdata); -%Sets callback for the file name edit box -this.Gui.FileName.Callback=@(hObject, eventdata) ... - fileNameCallback(this, hObject,eventdata); %Sets callback for the save data button this.Gui.SaveData.Callback=@(hObject, eventdata) ... saveCallback(this, hObject,eventdata); %Sets callback for the save ref button this.Gui.SaveRef.Callback=@(hObject, eventdata)... saveCallback(this, hObject, eventdata); %Sets callback for the show data button this.Gui.ShowData.Callback=@(hObject, eventdata)... showDataCallback(this, hObject, eventdata); %Sets callback for the show reference button this.Gui.ShowRef.Callback=@(hObject, eventdata)... showRefCallback(this, hObject, eventdata); %Sets callback for the data to reference button this.Gui.DataToRef.Callback=@(hObject, eventdata)... dataToRefCallback(this, hObject, eventdata); %Sets callback for the LogY button this.Gui.LogY.Callback=@(hObject, eventdata) ... logYCallback(this, hObject, eventdata); %Sets callback for the LogX button this.Gui.LogX.Callback=@(hObject, eventdata)... logXCallback(this, hObject, eventdata); %Sets callback for the data to background button this.Gui.DataToBg.Callback=@(hObject, eventdata) ... dataToBgCallback(this, hObject,eventdata); %Sets callback for the ref to background button this.Gui.RefToBg.Callback=@(hObject, eventdata) ... refToBgCallback(this, hObject,eventdata); %Sets callback for the clear background button this.Gui.ClearBg.Callback=@(hObject, eventdata)... clearBgCallback(this, hObject,eventdata); %Sets callback for the select trace popup menu this.Gui.SelTrace.Callback=@(hObject,eventdata)... selTraceCallback(this, hObject,eventdata); %Sets callback for the vertical cursor button this.Gui.VertDataButton.Callback=@(hObject, eventdata)... cursorButtonCallback(this, hObject,eventdata); %Sets callback for the horizontal cursors button this.Gui.HorzDataButton.Callback=@(hObject, eventdata)... cursorButtonCallback(this, hObject,eventdata); %Sets callback for the reference cursors button this.Gui.VertRefButton.Callback=@(hObject, eventdata)... cursorButtonCallback(this, hObject,eventdata); %Sets callback for the center cursors button this.Gui.CenterCursors.Callback=@(hObject,eventdata)... centerCursorsCallback(this,hObject,eventdata); %Sets callback for the center cursors button this.Gui.CopyPlot.Callback=@(hObject,eventdata)... copyPlotCallback(this,hObject,eventdata); %Sets callback for load trace button this.Gui.LoadButton.Callback=@(hObject,eventdata)... loadDataCallback(this,hObject,eventdata); %Sets callback for open directory button this.Gui.OpenFolderButton.Callback=@(hObject,eventdata)... openFolderCallback(this,hObject,eventdata); %Initializes the AnalyzeMenu this.Gui.AnalyzeMenu.Callback=@(hObject, eventdata)... analyzeMenuCallback(this, hObject,eventdata); %List of available analysis routines this.Gui.AnalyzeMenu.String={'Select a routine...',... 'Linear Fit','Quadratic Fit','Exponential Fit',... 'Gaussian Fit','Lorentzian Fit','Double Lorentzian Fit',... 'g0 Calibration','Beta Calibration'}; %Tooltips for AnalyzeMenu standard_tip=['Generate initial parameters with gen. init. param. \n',... 'Press Analyze to fit. Move the sliders or enter numbers to',... ' change initial parameters.\n']; this.Gui.AnalyzeTip{1}='Select a routine to open it.'; this.Gui.AnalyzeTip{2}=standard_tip; this.Gui.AnalyzeTip{3}=standard_tip; this.Gui.AnalyzeTip{4}=[standard_tip,... 'Calculation of the quality factor (assuming amplitude ringdown) ',... 'is included']; this.Gui.AnalyzeTip{5}=standard_tip; this.Gui.AnalyzeTip{6}=... [standard_tip,... 'To calibrate optical linewidths, move the reference cursors to',... ' have the correct number of resonances between them \n',... 'The number of resonances can be changed by changing the number of ',... 'lines in the GUI. \n The FSR is entered in the line spacing field \n',... 'The tab for mechanics is automatically populated and calculated.']; this.Gui.AnalyzeTip{7}= [standard_tip,... 'To calibrate optical linewidths, move the reference cursors to',... ' have the correct number of resonances between them \n',... 'The number of resonances can be changed by changing the number of ',... 'lines in the GUI. \n The FSR is entered in the line spacing field \n',... 'The modal spacing is automatically calculated']; this.Gui.AnalyzeTip{8}=['Put calibration tone between reference cursors.\n',... 'Put mechanical mode signal between vertical cursors.\n',... 'Enter the correct temperature and beta, then press analyze to find g0.']; this.Gui.AnalyzeTip{9}=['Automatically calculates beta based on first ',... 'and second order phase modulation sidebands']; %Initializes the InstrMenu this.Gui.InstrMenu.Callback=@(hObject,eventdata) ... instrMenuCallback(this,hObject,eventdata); %Initializes the axis this.main_plot.Box='on'; this.main_plot.XLabel.String='x'; this.main_plot.YLabel.String='y'; end \ No newline at end of file diff --git a/@MyFit/MyFit.m b/@MyFit/MyFit.m index f39d8ab..41ec74d 100644 --- a/@MyFit/MyFit.m +++ b/@MyFit/MyFit.m @@ -1,1032 +1,1043 @@ 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 %Calibration values supplied externally CalVals=struct(); init_color='c'; end properties (GetAccess=public, SetAccess=private) Fit; %MyTrace object containing the fit Gui; %Gui handles %Output structures from fit: Fitdata; Gof; FitInfo; %Contains information about all the fits and their parameters FitStruct; coeffs; %The name of the fit used. fit_name='Linear' end properties (Access=private) %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) %These grab and set the appropriate field of the FitStruct fit_function; anon_fit_fun; fit_tex; fit_params; fit_param_names; valid_fit_names; n_params; %These are used to create the usergui n_user_fields; user_field_tags; user_field_names; user_field_vals; %Vector used for plotting, depends on the data trace x_vec; %Variables used for saving fullpath; save_path; 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','Linear',@ischar) addParameter(p,'Data',MyTrace()); addParameter(p,'Fit',MyTrace()); addParameter(p,'x',[]); addParameter(p,'y',[]); addParameter(p,'enable_gui',1); addParameter(p,'enable_plot',0); addParameter(p,'plot_handle',[]); 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 first create the FitStruct, which contains all the %information about the available fits. createFitStruct(this); %We now create the parser for parsing the arguments to the %constructor, and parse the variables. createParser(this); parse(this.Parser,varargin{:}); parseInputs(this); %Loads values into the CalVals struct depending on the type of %fit initCalVals(this); %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 %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); %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 %Initializes the CalVals structure. function initCalVals(this) switch this.fit_name case 'Lorentzian' %Line spacing is the spacing between all the lines, %i.e. number of lines times the spacing between each %one this.CalVals.line_spacing=1; case 'DoubleLorentzian' this.CalVals.line_spacing=1; end 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)})); switch this.fit_name case 'Linear' %Fits polynomial of order 1 this.coeffs=polyfit(this.Data.x,this.Data.y,1); case 'Quadratic' %Fits polynomial of order 2 this.coeffs=polyfit(this.Data.x,this.Data.y,2); this.Fit.y=polyval(this.coeffs,this.Fit.x); case 'Lorentzian' scale_factor=max(this.Data.y); this.Data.y=this.Data.y/scale_factor; this.init_params(1)=this.init_params(1)/scale_factor; this.init_params(4)=this.init_params(4)/scale_factor; doFit(this); this.coeffs(1)=this.coeffs(1)*scale_factor; this.coeffs(4)=this.coeffs(4)*scale_factor; this.Data.y=this.Data.y*scale_factor; + case 'Exponential' + x_tr=min(this.Data.x); + this.Data.x=this.Data.x-x_tr; + this.init_params(1)=... + this.init_params(1)*exp(this.init_params(2)*x_tr); + doFit(this); + this.Data.x=this.Data.x+x_tr; + this.coeffs(1)=... + this.coeffs(1)*exp(-this.coeffs(2)*x_tr); case {'LorentzianGrad','Gaussian',... 'DoubleLorentzian','DoubleLorentzianGrad',... 'Exponential','Gorodetsky2000',... 'Gorodetsky2000plus'} doFit(this) otherwise error('Selected fit is invalid'); end %This function calculates the fit trace, using this.x_vec as %the x axis calcFit(this); %This function calculates user-defined parameters calcUserParams(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 %This function calculates all the user-defined parameters shown in %the GUI. To add more parameters, add them in createUserGuiStruct, %then add them here when you wish to calculate them. function calcUserParams(this) switch this.fit_name case 'Lorentzian' this.mech_lw=this.coeffs(2); %#ok this.mech_freq=this.coeffs(3); %#ok this.Q=this.mech_freq/this.mech_lw; %#ok this.opt_lw=convOptFreq(this,this.coeffs(2)); %#ok this.Qf=this.mech_freq*this.Q; %#ok case 'DoubleLorentzian' this.opt_lw1=convOptFreq(this,this.coeffs(2)); %#ok this.opt_lw2=convOptFreq(this,this.coeffs(5)); %#ok splitting=abs(this.coeffs(6)-this.coeffs(3)); this.mode_split=convOptFreq(this,splitting); %#ok case 'Exponential' if ~isempty(this.coeffs) this.tau=abs(1/this.coeffs(2)); %#ok this.lw=abs(this.coeffs(2)/pi); %#ok end this.Q=pi*this.freq*this.tau; %#ok this.Qf=this.Q*this.freq; %#ok otherwise %If fit_name is not listed, do nothing end end %This function is used to convert the x-axis to frequency. function real_freq=convOptFreq(this,freq) real_freq=freq*this.spacing*this.line_no/this.CalVals.line_spacing; 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()); switch this.fit_name case 'Lorentzian' %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','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); case 'DoubleLorentzian' this.UserGui.Tabs.Opt.tab_title='Optical'; this.UserGui.Tabs.Opt.Children={}; addUserField(this,'Opt','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_lw1','Linewidth 1 (MHz)',1e6,... 'enable_flag','off','conv_factor',1e6); addUserField(this,'Opt','opt_lw2','Linewidth 2 (MHz)',1e6,... 'enable_flag','off','conv_factor',1e6); addUserField(this,'Opt','mode_split',... 'Modal splitting (MHz)',1e6,... 'enable_flag','off','conv_factor',1e6); case 'Exponential' this.UserGui.Tabs.Q.tab_title='Q'; this.UserGui.Tabs.Q.Children={}; addUserField(this,'Q','tau','\tau (s)',1,... 'enable_flag','off') addUserField(this,'Q','lw','Linewidth (Hz)',1,... 'enable_flag','off') addUserField(this,'Q','Q',... 'Qualify Factor (x10^6)',1e6,... 'enable_flag','off','conv_factor',1e6) addUserField(this,'Q','freq','Frequency (MHz)',1e6,... 'conv_factor',1e6, 'enable_flag','on',... 'Callback',@(~,~) calcUserParams(this)); addUserField(this,'Q','Qf','Q\times f (10^{14} Hz)',1e14,... 'conv_factor',1e14,'enable_flag','off'); + addUserField(this,'Q','tag','Tag (number)','',... + 'enable_flag','on') otherwise %Do nothing if there is no defined user parameters end 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 %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={}; switch this.fit_name case 'Exponential' [params{1},params{2},params{3}]=... initParamExponential(this.Data.x,this.Data.y); case 'Gaussian' [params{1},params{2},params{3}]=... initParamGaussian(this.Data.x,this.Data.y); case 'Lorentzian' [params{1},params{2},params{3}]=... initParamLorentzian(this.Data.x,this.Data.y); case 'LorentzianGrad' [params{1},params{2},params{3}]=... initParamLorentzianGrad(this.Data.x,this.Data.y); case 'DoubleLorentzian' [params{1},params{2},params{3}]=... initParamDblLorentzian(this.Data.x,this.Data.y); case 'DoubleLorentzianGrad' [params{1},params{2},params{3}]=... initParamDblLorentzianGrad(this.Data.x,this.Data.y); case 'Gorodetsky2000' [params{1},params{2},params{3}]=... initParamGorodetsky2000(this.Data.x,this.Data.y); case 'Gorodetsky2000plus' [params{1},params{2},params{3}]=... initParamGorodetsky2000Plus(this.Data.x,this.Data.y); end %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 %Updates the GUI and creates new lookup tables for the init %param sliders if this.enable_gui genSliderVecs(this); updateGui(this); end 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 %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{:}); clearInitFun(this); end %Clears the plots function clearFit(this) cellfun(@(x) delete(x), this.Fit.hlines); clearInitFun(this); this.Fit.hlines={}; end function clearInitFun(this) delete(this.hline_init); this.hline_init=[]; 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.FitStruct.(this.fit_name).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,... 'Color',this.init_color); else set(this.hline_init,'XData',this.x_vec,'YData',y_vec); end 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 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 %Here we can put special cases such that the sliders can behave %differently for different fits. if validateData(this) switch this.fit_name case 'Lorentzian' %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 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 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 end %Private methods methods(Access=private) %Creates the GUI of MyFit, in separate file. createGui(this); %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 %Does the fit with the currently set parameters function doFit(this) 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-9,... - 'TolX',1e-9); + '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 coeffs into the class variable. this.coeffs=coeffvalues(this.Fitdata); 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 %Creates the struct used to get all things relevant to the fit %model. Ensure that fit parameters are listed alphabetically, as %otherwise the anon_fit_fun will not work properly. function createFitStruct(this) %Adds fits addFit(this,'Linear','a*x+b','$$ax+b$$',{'a','b'},... {'Gradient','Offset'}) addFit(this,'Quadratic','a*x^2+b*x+c','$$ax^2+bx+c$$',... {'a','b','c'},{'Quadratic coeff.','Linear coeff.','Offset'}); addFit(this,'Gaussian','a*exp(-((x-c)/b)^2/2)+d',... '$$ae^{-\frac{(x-c)^2}{2b^2}}+d$$',{'a','b','c','d'},... {'Amplitude','Width','Center','Offset'}); addFit(this,'Lorentzian','1/pi*a*b/2/((x-c)^2+(b/2)^2)+d',... '$$\frac{a}{\pi}\frac{b/2}{(x-c)^2+(b/2)^2}+d$$',... {'a','b','c','d'},... {'Amplitude','Width','Center','Offset'}); addFit(this,'LorentzianGrad','1/pi*a*b/2/((x-c)^2+(b/2)^2)+d*(x-c)+e',... '$$\frac{a}{\pi}\frac{b/2}{(x-c)^2+(b/2)^2}+d*(x-b)+e$$',... {'a','b','c','d','e'},... {'Amplitude','Width','Center','Gradient','Offset'}); addFit(this,'Exponential','a*exp(b*x)+c',... '$$ae^{bx}+c$$',{'a','b','c'},... {'Amplitude','Rate','Offset'}); addFit(this,'DoubleLorentzian',... '1/pi*b/2*a/((x-c)^2+(b/2)^2)+1/pi*e/2*d/((x-f)^2+(e/2)^2)+g',... '$$\frac{a}{\pi}\frac{b/2}{(x-c)^2+(b/2)^2}+\frac{d}{\pi}\frac{e/2}{(x-f)^2+(e/2)^2}+g$$',... {'a','b','c','d','e','f','g'},... {'Amplitude 1','Width 1','Center 1','Amplitude 2',... 'Width 2','Center 2','Offset'}); addFit(this,'DoubleLorentzianGrad',... '1/pi*b/2*a/((x-c)^2+(b/2)^2)+1/pi*e/2*d/((x-f)^2+(e/2)^2)+g*(x-c)+h',... '$$\frac{a}{\pi}\frac{b/2}{(x-c)^2+(b/2)^2}+\frac{d}{\pi}\frac{e/2}{(x-f)^2+(e/2)^2}+g*x+h$$',... {'a','b','c','d','e','f','g','h'},... {'Amplitude 1','Width 1','Center 1','Amplitude 2',... 'Width 2','Center 2','Gradient','Offset'}); addFit(this,'Gorodetsky2000',... ['a*abs( (k0^2/4 - kex^2/4 + gamma^2/4 - (x-b).^2 + i*k0.*(x-b))',... './( (k0 + kex)^2/4 + gamma^2/4 - (x-b).^2 + i.*(x-b)*(k0 + kex) )).^2+c*(x-b)'],... ['$$a\left|\frac{\kappa_0^2/4-\kappa_{ex}^2/4+\gamma^2/4-(x-b)^2+i\kappa_0(x-b)/2}',... '{(\kappa_0+\kappa_{ex})^2/4+\gamma^2/4-(x-b)^2+i(x-b)(\kappa_0+\kappa_{ex})}\right|^2$$+c(x-b)'],... { 'a','b','c','gamma','k0', 'kex'},... {'Background','Center','BG Slope','Mode splitting',... 'Intrinsic','Extrinsic'}); addFit(this,'Gorodetsky2000plus',... ['(a+c*x+d*x.^2+e*x.^2)*abs( (k0^2/4 - kex^2/4 + gamma^2/4 - (x-b).^2 + i*k0.*(x-b))',... './( (k0 + kex)^2/4 + gamma^2/4 - (x-b).^2 + i.*(x-b)*(k0 + kex) )).^2+f'],... ['$$\left[a+cx+dx^2\right]\left|\frac{\kappa_0^2/4-\kappa_{ex}^2/4+\gamma^2/4-(x-b)^2+i\kappa_0(x-b)/2}',... '{(\kappa_0+\kappa_{ex})^2/4+\gamma^2/4-(x-b)^2+i(x-b)(\kappa_0+\kappa_{ex})}\right|^2$$'],... { 'a','b','c','d','e','f','gamma','k0', 'kex'},... {'Baseline','Center','Lin. Coeff','Quad. Coeff','Cubic Coeff.',... 'Background','Mode splitting','Intrinsic','Extrinsic'}); end %Adds a fit to the list of fits. See above for real examples %fit_function: the function used to fit to in MATLAB form %fit_tex: the fit function written in tex for display in the GUI %fit_params: the fit parameters %fit_param_names: longer names of fit parameters for GUI function addFit(this,fit_name,fit_function,fit_tex,fit_params,... fit_param_names) this.FitStruct.(fit_name).fit_function=fit_function; this.FitStruct.(fit_name).fit_tex=fit_tex; this.FitStruct.(fit_name).fit_params=fit_params; this.FitStruct.(fit_name).fit_param_names=fit_param_names; %Generates the anonymous fit function from the above args=['@(x,', strjoin(fit_params,','),')']; this.FitStruct.(fit_name).anon_fit_fun=... str2func(vectorize([args,fit_function])); 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 %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 end % Set function for nondependent variable methods %Set function for fit_name. function set.fit_name(this,fit_name) assert(ischar(fit_name),'The fit name must be a string'); %Capitalizes the first letter fit_name=[upper(fit_name(1)),lower(fit_name(2:end))]; %Checks it is a valid fit name ind=strcmpi(fit_name,this.valid_fit_names);%#ok assert(any(ind),'%s is not a supported fit name',fit_name); this.fit_name=this.valid_fit_names{ind}; %#ok end end % Get functions for dependent variables methods %Generates the valid fit names function valid_fit_names=get.valid_fit_names(this) valid_fit_names=fieldnames(this.FitStruct); end %Grabs the correct fit function from FitStruct function fit_function=get.fit_function(this) fit_function=this.FitStruct.(this.fit_name).fit_function; end %Grabs the correct tex string from FitStruct function fit_tex=get.fit_tex(this) fit_tex=this.FitStruct.(this.fit_name).fit_tex; end %Grabs the correct anon fit function from FitStruct function anon_fit_fun=get.anon_fit_fun(this) anon_fit_fun=this.FitStruct.(this.fit_name).anon_fit_fun; end %Grabs the correct fit parameters from FitStruct function fit_params=get.fit_params(this) fit_params=this.FitStruct.(this.fit_name).fit_params; end %Grabs the correct fit parameter names from FitStruct function fit_param_names=get.fit_param_names(this) fit_param_names=this.FitStruct.(this.fit_name).fit_param_names; end %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/GUIs/GuiDaq.fig b/GUIs/GuiDaq.fig index fec7ffe..a1e337a 100644 Binary files a/GUIs/GuiDaq.fig and b/GUIs/GuiDaq.fig differ