diff --git a/@MyCeCryo/MyCeCryo.m b/@MyCeCryo/MyCeCryo.m new file mode 100644 index 0000000..5154cb4 --- /dev/null +++ b/@MyCeCryo/MyCeCryo.m @@ -0,0 +1,65 @@ +% Class for controlling the auto manifold of ColdEdge stinger cryostat. +% The manifold is managed by an Arduino board that communicates with +% computer via serial protocol. + +classdef MyCeCryo < MyScpiInstrument + + methods (Access = public) + function this = MyCeCryo(interface, address, varargin) + this@MyScpiInstrument(interface, address, varargin{:}); + + % Buffer size of 64 kB should be way an overkill. The labview + % program provided by ColdEdge use 256 Bytes. + this.Device.InputBufferSize=2^16; + this.Device.OutputBufferSize=2^16; + end + + function switchAllOff(this) + writePropertyHedged(this, ... + 'valve1', false, ... + 'valve2', false, ... + 'valve3', false, ... + 'valve4', false, ... + 'valve5', false, ... + 'valve7', false, ... + 'recirc', false, ... + 'cryocooler', false); + end + end + + methods (Access = protected) + function createCommandList(this) + + % Valve states + for i=1:7 + if i == 6 + continue % No valve 6 + end + + tag = sprintf('valve%i',i); + cmd = sprintf(':VALVE%i',i); + addCommand(this, tag, cmd,... + 'default', false, ... + 'fmt_spec', '%b', ... + 'info', 'Valve open(true)/clsed(false)'); + end + + addCommand(this, 'recirc', ':REC',... + 'default', false, ... + 'fmt_spec', '%b', ... + 'info', 'Recirculator on/off'); + + addCommand(this, 'cryocooler', ':COOL',... + 'default', false, ... + 'fmt_spec', '%b', ... + 'info', 'Cryocooler on/off'); + + addCommand(this, 'press', ':PRES',... + 'default', false, ... + 'fmt_spec', '%e', ... + 'access', 'r', ... + 'info', 'Supply pressure (PSI)'); + end + end +end + diff --git a/@MyDaq/MyDaq.m b/@MyDaq/MyDaq.m index 1513d8b..db5d5b8 100644 --- a/@MyDaq/MyDaq.m +++ b/@MyDaq/MyDaq.m @@ -1,1047 +1,1041 @@ % Acquisition and analysis program that receives data from Collector. Can % also be used for analysis of previously acquired traces. classdef MyDaq < 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).xthis.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','LorentzianGrad','Gaussian',... - 'DoubleLorentzian','DoubleLorentzianGrad',... - 'Exponential','Gorodetsky2000',... - 'Gorodetsky2000plus'} - doFit(this) - otherwise - error('Selected fit is invalid'); - end + %Perform the fit. + doFit(this); %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 + %Clears the plots + function clearFit(this) + cellfun(@(x) delete(x), this.Fit.hlines); + clearInitFun(this); + this.Fit.hlines={}; + end + + %Clears the plot of the initial values + function clearInitFun(this) + delete(this.hline_init); + this.hline_init=[]; + end + + %Plots the trace contained in the Fit MyTrace object after + %calculating the new values + function plotFit(this,varargin) + calcFit(this); + assert((isa(this.plot_handle,'matlab.graphics.axis.Axes')||... + isa(this.plot_handle,'matlab.ui.control.UIAxes')),... + 'plot_handle property must be defined to valid axis in order to plot') + this.Fit.plot(this.plot_handle,varargin{:}); + clearInitFun(this); + end + + %Function for plotting fit model with current initial parameters. + function plotInitFun(this) + %Substantially faster than any alternative - generating + %anonymous functions is very cpu intensive. + + input_cell=num2cell(this.init_params); + y_vec=feval(this.anon_fit_fun,... + this.x_vec,input_cell{:}); + if isempty(this.hline_init) + this.hline_init=plot(this.plot_handle,this.x_vec,y_vec,... + 'Color',this.init_color); + else + set(this.hline_init,'XData',this.x_vec,'YData',y_vec); end + end + + %Generates model-dependent initial parameters, lower and upper + %boundaries. + function genInitParams(this) + assert(validateData(this), ['The data must be vectors of',... + ' equal length greater than the number of fit parameters.',... + ' Currently the number of fit parameters is %d, the',... + ' length of x is %d and the length of y is %d'],... + this.n_params,length(this.Data.x),length(this.Data.y)); + + %Cell for putting parameters in to be interpreted in the + %parser. Element 1 contains the init params, Element 2 contains + %the lower limits and Element 3 contains the upper limits. + params=cell(1,3); + + [params{1},params{2},params{3}]=calcInitParams(this); + %Validates the initial parameters + p=createFitParser(this.n_params); + parse(p,params{:}); + %Loads the parsed results into the class variables + this.init_params=p.Results.init_params; + this.lim_lower=p.Results.lower; + this.lim_upper=p.Results.upper; + + %Plots the fit function with the new initial parameters + if this.enable_plot; plotInitFun(this); end + %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); - %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; + %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-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 %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'); - otherwise - %Do nothing if there is no defined user parameters - end + end + + %Default generation of initial parameters. This function should be + %overloaded in subclasses. + function [init_params,lim_lower,lim_upper]=calcInitParams(this) + init_params=ones(1,this.n_params); + lim_lower=-Inf(1,this.n_params); + lim_upper=Inf(1,this.n_params); end %Parent is the parent tab for the userfield, tag is the tag given %to the GUI element, title is the text written next to the field, %initial value is the initial value of the property and change_flag %determines whether the gui element is enabled for writing or not. %conv_factor is used to have different units in the field. In the %program, the value is always saved as the bare value. function addUserField(this, parent, tag, title, ... init_val,varargin) %Parsing inputs p=inputParser(); addRequired(p,'Parent'); addRequired(p,'Tag'); addRequired(p,'Title'); addRequired(p,'init_val'); addParameter(p,'enable_flag','on'); addParameter(p,'Callback',''); addParameter(p,'conv_factor',1); parse(p,parent,tag,title,init_val,varargin{:}); tag=p.Results.Tag; %Populates the UserGui struct this.UserGui.Fields.(tag).parent=p.Results.Parent; this.UserGui.Fields.(tag).title=p.Results.Title; this.UserGui.Fields.(tag).init_val=p.Results.init_val; this.UserGui.Fields.(tag).enable_flag=... p.Results.enable_flag; this.UserGui.Fields.(tag).conv_factor=p.Results.conv_factor; this.UserGui.Fields.(tag).Callback=... p.Results.Callback; this.UserGui.Tabs.(p.Results.Parent).Children{end+1}=tag; %Adds the new property to the class addUserProp(this, tag); end %Every user field has an associated property, which is added by %this function. The get and set functions are set to use the GUI %through the getUserVal and setUserVal functions if the GUI is %enabled. function addUserProp(this,tag) prop=addprop(this,tag); if this.enable_gui prop.GetMethod=@(this) getUserVal(this,tag); prop.SetMethod=@(this, val) setUserVal(this, val, tag); prop.Dependent=true; end end %This function gets the value of the userprop from the GUI. The GUI %is the single point of truth function val=getUserVal(this, tag) conv_factor=this.UserGui.Fields.(tag).conv_factor; val=str2double(this.Gui.([tag,'Edit']).String)*conv_factor; end %As above, but instead we set the GUI through setting the property function setUserVal(this, val, tag) conv_factor=this.UserGui.Fields.(tag).conv_factor; this.Gui.([tag,'Edit']).String=num2str(val/conv_factor); end - %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); + + + %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 - %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); + %Can be overloaded to have more convenient sliders + function genSliderVecs(this) + %Return values of the slider + slider_vals=1:101; + %Default scaling vector + def_vec=10.^((slider_vals-51)/50); + %Sets the cell to the default value + for i=1:this.n_params + this.slider_vecs{i}=def_vec*this.init_params(i); + set(this.Gui.(sprintf('Slider_%s',this.fit_params{i})),... + 'Value',50); + end end - %Clears the plots - function clearFit(this) - cellfun(@(x) delete(x), this.Fit.hlines); - clearInitFun(this); - this.Fit.hlines={}; + %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 - 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. + %Creates an input parser for a fit function with n_arg arguments. Default + %values are ones for initial parameters and plus and minus inf for upper + %and lower limits respectively. Ensures that the length is equal to the + %number of arguments. + function p=createFitParser(n_arg) + p=inputParser; + validateStart=@(x) assert(isnumeric(x) && isvector(x) && length(x)==n_arg,... + 'Starting points must be given as a vector of size %d',n_arg); + validateLower=@(x) assert(isnumeric(x) && isvector(x) && length(x)==n_arg,... + 'Lower limits must be given as a vector of size %d',n_arg); + validateUpper=@(x) assert(isnumeric(x) && isvector(x) && length(x)==n_arg,... + 'Upper limits must be given as a vector of size %d',n_arg); - 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 + addOptional(p,'init_params',ones(1,n_arg),validateStart) + addOptional(p,'lower',-Inf(1,n_arg),validateLower) + addOptional(p,'upper',Inf(1,n_arg),validateUpper) + end + + %Calculates the trace object for the fit + function calcFit(this) + this.Fit.x=this.x_vec; + input_coeffs=num2cell(this.coeffs); + this.Fit.y=this.anon_fit_fun(this.Fit.x,input_coeffs{:}); end end %Callbacks methods %Save fit function callback function saveFitCallback(this,~,~) assert(~isempty(this.base_dir),'Save directory is not specified'); assert(ischar(this.base_dir),... ['Save directory is not specified.',... ' Should be of type char but is %s.'], ... class(this.base_dir)) this.Fit.save('name',this.filename,... 'base_dir',this.save_path) end %Callback for saving parameters function saveParamCallback(this,~,~) saveParams(this); end - 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})); + 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 + %Callback function for edit boxes in GUI function editCallback(this, hObject, ~) init_param=str2double(hObject.String); param_ind=str2double(hObject.Tag); - - %Centers the slider + + %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. + %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 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); - %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/@MyFit/createGui.m b/@MyFit/createGui.m index dcb616d..a278534 100644 --- a/@MyFit/createGui.m +++ b/@MyFit/createGui.m @@ -1,265 +1,266 @@ function createGui(this) %Makes the fit name have the first letter capitalized fit_name=[upper(this.fit_name(1)),this.fit_name(2: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; +fig_h=title_h+equation_h+slider_h+savebox_h+userpanel_h; %Sets a minimum width if this.n_params<4; edit_width=min_fig_width/this.n_params; end fig_width=edit_width*this.n_params; %Name sets the title of the window, NumberTitle turns off the FigureN text %that would otherwise be before the title, MenuBar is the menu normally on %the figure, toolbar is the toolbar normally on the figure. %HandleVisibility refers to whether gcf, gca etc will grab this figure. this.Gui.Window = figure('Name', 'MyFit', 'NumberTitle', 'off', ... 'MenuBar', 'none', 'Toolbar', 'none', 'HandleVisibility', 'off',... 'Units','Pixels','Position',[500,500,fig_width,fig_h]); %Sets the close function (runs when x is pressed) to be class function set(this.Gui.Window, 'CloseRequestFcn',... @(hObject,eventdata) closeFigure(this, hObject,eventdata)); %The main vertical box. The four main panes of the GUI are stacked in the %box. We create these four boxes first so that we do not need to redraw %them later this.Gui.MainVbox=uix.VBox('Parent',this.Gui.Window,'BackgroundColor',rgb_white); %The title box this.Gui.Title=annotation(this.Gui.MainVbox,'textbox',[0.5,0.5,0.3,0.3],... 'String',fit_name,'Units','Normalized',... 'HorizontalAlignment','center','VerticalAlignment','middle',... 'FontSize',16,'BackgroundColor',rgb_white); %Displays the fitted equation this.Gui.Equation=annotation(this.Gui.MainVbox,'textbox',[0.5,0.5,0.3,0.3],... 'String',this.fit_tex,... 'Units','Normalized','Interpreter','LaTeX',... 'HorizontalAlignment','center','VerticalAlignment','middle',... 'FontSize',20,'BackgroundColor',rgb_white); %Creates an HBox for extracted parameters and user interactions with GUI this.Gui.UserHbox=uix.HBox('Parent',this.Gui.MainVbox,... 'BackgroundColor',rgb_white); %Creates the HBox for saving parameters this.Gui.SaveHbox=uix.HBox('Parent',this.Gui.MainVbox,... 'BackgroundColor',rgb_white); %Creates the HBox for the fitting parameters this.Gui.FitHbox=uix.HBox('Parent',this.Gui.MainVbox,'BackgroundColor',... rgb_white); %Sets the heights and minimum heights of the five vertical boxes. -1 means %it resizes with the window set(this.Gui.MainVbox,'Heights',[title_h,-1,userpanel_h,savebox_h,slider_h],... 'MinimumHeights',[title_h,equation_h,userpanel_h,savebox_h,slider_h]); %Here we create the fit panel in the GUI. this.Gui.FitPanel=uix.BoxPanel( 'Parent', this.Gui.UserHbox,... 'Padding',0,'BackgroundColor', rgb_white,... 'Title','Fit Panel','TitleColor',rgb_blue); %Here we create the panel for the useful parameters this.Gui.UserPanel=uix.BoxPanel( 'Parent', this.Gui.UserHbox,... 'Padding',0,'BackgroundColor', 'w',... 'Title','Calculated parameters','TitleColor',rgb_blue); %Sets the widths of the above set(this.Gui.UserHbox,'Widths',[-1,-2],'MinimumWidths',[0,375]); %This makes the buttons that go inside the FitPanel this.Gui.FitVbox=uix.VBox('Parent',this.Gui.FitPanel,'BackgroundColor',... rgb_white); %Creates the button for analysis inside the VBox this.Gui.AnalyzeButton=uicontrol('Parent',this.Gui.FitVbox,... 'style','pushbutton','Background','w','String','Analyze','Callback',... @(hObject, eventdata) analyzeCallback(this, hObject, eventdata)); %Creates button for generating new initial parameters this.Gui.InitButton=uicontrol('Parent',this.Gui.FitVbox,... 'style','pushbutton','Background','w',... 'String','Generate Init. Params','Callback',... @(hObject, eventdata) initParamCallback(this, hObject, eventdata)); %Creates button for clearing fits this.Gui.ClearButton=uicontrol('Parent',this.Gui.FitVbox,... 'style','pushbutton','Background','w','String','Clear fits','Callback',... @(hObject, eventdata) clearFitCallback(this, hObject, eventdata)); +%Creates button for toggling scaling of data if the class has the +%scale_data property. +if contains('scale_data',properties(this)) + this.Gui.ScaleButton=uicontrol('Parent',this.Gui.FitVbox,... + 'style','togglebutton','Background','w',... + 'String','Scale data',... + 'Callback',@(hObject, ~) scaleDataCallback(this,hObject)); +end - -set(this.Gui.FitVbox,'Heights',[button_h,button_h,button_h]); +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 values panel with associated tabs. The cellfun here -%creates the appropriately named tabs. To add a tab, add a new field to the -%UserGuiStruct. - -usertabs=fieldnames(this.UserGui.Tabs); - -if ~isempty(usertabs) - cellfun(@(x) createTab(this,x,rgb_white,button_h),usertabs); - this.Gui.TabPanel.TabTitles=... - cellfun(@(x) this.UserGui.Tabs.(x).tab_title, usertabs,... - 'UniformOutput',0); -end +%Creates the user gui. Made into its own function such that it can be +%overloaded for future customization +createUserGui(this, rgb_white, button_h); +%This creates the boxes for saving files and for specifying file saving +%properties this.Gui.SavePanel=uix.BoxPanel( 'Parent', this.Gui.SaveHbox,... 'Padding',0,'BackgroundColor', rgb_white,... 'Title','Save Panel','TitleColor',rgb_blue); this.Gui.DirPanel=uix.BoxPanel('Parent',this.Gui.SaveHbox,... 'Padding',0,'BackgroundColor',rgb_white,... 'Title','Directory Panel','TitleColor',rgb_blue); set(this.Gui.SaveHbox,'Widths',[-1,-2],'MinimumWidths',[0,375]); %Here we create the buttons and edit boxes inside the save box this.Gui.SaveButtonBox=uix.VBox('Parent',this.Gui.SavePanel,... 'BackgroundColor',rgb_white); this.Gui.DirHbox=uix.HBox('Parent',this.Gui.DirPanel,... 'BackgroundColor',rgb_white); this.Gui.FileNameLabelBox=uix.VBox('Parent',this.Gui.DirHbox,... 'BackgroundColor',rgb_white); this.Gui.FileNameBox=uix.VBox('Parent',this.Gui.DirHbox,... 'BackgroundColor',rgb_white); set(this.Gui.DirHbox,'Widths',[-1,-2]); %Buttons for saving the fit and parameters this.Gui.SaveParamButton=uicontrol('Parent',this.Gui.SaveButtonBox,... 'style','pushbutton','Background','w','String','Save Parameters',... 'Callback', @(hObject, eventdata) saveParamCallback(this, hObject, eventdata)); this.Gui.SaveFitButton=uicontrol('Parent',this.Gui.SaveButtonBox,... 'style','pushbutton','Background','w','String','Save Fit',... 'Callback', @(hObject, eventdata) saveFitCallback(this, hObject, eventdata)); set(this.Gui.SaveButtonBox,'Heights',[button_h,button_h]) %Labels for the edit boxes this.Gui.BaseDirLabel=annotation(this.Gui.FileNameLabelBox,... 'textbox',[0.5,0.5,0.3,0.3],... 'String','Save Directory','Units','Normalized',... 'HorizontalAlignment','Left','VerticalAlignment','middle',... 'FontSize',10,'BackgroundColor',rgb_white); this.Gui.SessionNameLabel=annotation(this.Gui.FileNameLabelBox,... 'textbox',[0.5,0.5,0.3,0.3],... 'String','Session Name','Units','Normalized',... 'HorizontalAlignment','Left','VerticalAlignment','middle',... 'FontSize',10,'BackgroundColor',rgb_white); this.Gui.FileNameLabel=annotation(this.Gui.FileNameLabelBox,... 'textbox',[0.5,0.5,0.3,0.3],... 'String','File Name','Units','Normalized',... 'HorizontalAlignment','Left','VerticalAlignment','middle',... 'FontSize',10,'BackgroundColor',rgb_white); set(this.Gui.FileNameLabelBox,'Heights',button_h*ones(1,3)); %Boxes for editing the path and filename this.Gui.BaseDir=uicontrol('Parent',this.Gui.FileNameBox,... 'style','edit','String',this.SaveInfo.base_dir,'HorizontalAlignment','Left',... 'FontSize',10); this.Gui.SessionName=uicontrol('Parent',this.Gui.FileNameBox,... 'style','edit','String',this.SaveInfo.session_name,'HorizontalAlignment','Left',... 'FontSize',10); this.Gui.FileName=uicontrol('Parent',this.Gui.FileNameBox,... 'style','edit','String',this.SaveInfo.filename,'HorizontalAlignment','Left',... 'FontSize',10); set(this.Gui.FileNameBox,'Heights',button_h*ones(1,3)); %We first make the BoxPanels to speed up the process. Otherwise everything %in the BoxPanel must be redrawn every time we make a new one. panel_str=cell(1,this.n_params); for i=1:this.n_params %Generates the string for the panel handle panel_str{i}=sprintf('Panel_%s',this.fit_params{i}); %Creates the panels this.Gui.(panel_str{i})=uix.BoxPanel( 'Parent', this.Gui.FitHbox ,... 'Padding',0,'BackgroundColor', 'w',... 'Title',sprintf('%s (%s)',this.fit_param_names{i},this.fit_params{i}),... 'TitleColor',rgb_blue,... 'Position',[1+edit_width*(i-1),1,edit_width,slider_h],... 'Visible','off'); end %Loops over number of parameters to create a fit panel for each one for i=1:this.n_params %Generates the string for the vbox handle vbox_str=sprintf('Vbox_%s',this.fit_params{i}); %Generates the string for the slider handle slider_str=sprintf('Slider_%s',this.fit_params{i}); %Generates string for edit panels edit_str=sprintf('Edit_%s',this.fit_params{i}); %Generates a string for the limit boxes lim_str=sprintf('Lim_%s',this.fit_params{i}); %Creates the vbox inside the panel that allows stacking this.Gui.(vbox_str) =uix.VBox( 'Parent', ... this.Gui.(panel_str{i}),'Padding',0,'BackgroundColor', 'w'); %Generates edit box for fit parameters this.Gui.(edit_str)=uicontrol('Parent',this.Gui.(vbox_str),... 'Style','edit','String',sprintf('%3.3e',this.init_params(i)),... 'FontSize',14,'Tag',num2str(i),'HorizontalAlignment','Right',... 'Position',[1,48,edit_width-4,30],'Units','Pixels','Callback',... @(hObject,eventdata) editCallback(this, hObject, eventdata)); %Sets up HBox for the lower and upper limits this.Gui.(lim_str)=uix.HBox('Parent',this.Gui.(vbox_str),... 'Padding',0,'BackgroundColor','w'); %Generates edit box for limits this.Gui.([lim_str,'_lower'])=uicontrol('Parent',this.Gui.(lim_str),... 'Style','edit','String',sprintf('%3.3e',this.lim_lower(i)),... 'FontSize',10,'Tag',sprintf('Lower%i',i),'HorizontalAlignment','Right',... 'Position',[1,1,edit_width-4,30],'Units','Pixels','Callback',... @(hObject,eventdata) limEditCallback(this, hObject, eventdata)); this.Gui.([lim_str,'_upper'])=uicontrol('Parent',this.Gui.(lim_str),... 'Style','edit','String',sprintf('%3.3e',this.lim_upper(i)),... 'FontSize',10,'Tag',sprintf('Upper%i',i),'HorizontalAlignment','Right',... 'Position',[1,1,edit_width-4,30],'Units','Pixels','Callback',... @(hObject,eventdata) limEditCallback(this, hObject, eventdata)); %Generates java-based slider. Looks nicer than MATLAB slider this.Gui.(slider_str)=uicomponent('Parent',this.Gui.(vbox_str),... 'style','jslider','Value',50,'Orientation',0,... 'MajorTickSpacing',20,'MinorTickSpacing',5,'Paintlabels',0,... 'PaintTicks',1,'Background',java.awt.Color.white,... 'pos',[1,-7,edit_width-4,55]); %Sets up callbacks for the slider this.Gui.([slider_str,'_callback'])=handle(this.Gui.(slider_str),... 'CallbackProperties'); %Note that this is triggered whenever the state changes, even if it is %programatically this.Gui.([slider_str,'_callback']).StateChangedCallback = .... @(hObject, eventdata) sliderCallback(this,i,hObject,eventdata); this.Gui.([slider_str,'_callback']).MouseReleasedCallback = .... @(~, ~) triggerNewInitVal(this); %Sets heights and minimum heights for the elements in the fit vbox set(this.Gui.(vbox_str),'Heights',[30,30,55],'MinimumHeights',[30,30,55]) end %Makes all the panels at the bottom visible at the same time cellfun(@(x) set(this.Gui.(x),'Visible','on'),panel_str); end \ No newline at end of file diff --git a/@MyGaussianFit/MyGaussianFit.m b/@MyGaussianFit/MyGaussianFit.m new file mode 100644 index 0000000..e3d612d --- /dev/null +++ b/@MyGaussianFit/MyGaussianFit.m @@ -0,0 +1,21 @@ +classdef MyGaussianFit < MyFit + %Public methods + methods (Access=public) + %Constructor function + function this=MyGaussianFit(varargin) + this@MyFit(... + 'fit_name','Gaussian',... + 'fit_function','a*exp(-((x-c)/b)^2/2)+d',... + 'fit_tex', '$$ae^{-\frac{(x-c)^2}{2b^2}}+d$$',... + 'fit_params',{'a','b','c','d'},... + 'fit_param_names',{'Amplitude','Width','Center','Offset'},... + varargin{:}); + end + end + methods (Access=protected) + function [init_params,lim_lower,lim_upper]=calcInitParams(this) + [init_params,lim_lower,lim_upper]=... + initParamGaussian(this.Data.x,this.Data.y); + end + end +end \ No newline at end of file diff --git a/@MyGorodetsky2000Fit/MyGorodetsky2000Fit.m b/@MyGorodetsky2000Fit/MyGorodetsky2000Fit.m new file mode 100644 index 0000000..ccb35d5 --- /dev/null +++ b/@MyGorodetsky2000Fit/MyGorodetsky2000Fit.m @@ -0,0 +1,44 @@ +classdef MyGorodetsky2000Fit < MyFit + %Public methods + methods (Access=public) + %Constructor function + function this=MyGorodetsky2000Fit(varargin) + this@MyFit(... + 'fit_name','Gorodetsky2000',... + 'fit_function',['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)'],... + 'fit_tex',['$$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)'],... + 'fit_params', { 'a','b','c','gamma','k0', 'kex'},... + 'fit_param_names',{'Background','Center','BG Slope','Mode splitting',... + 'Intrinsic','Extrinsic'},... + varargin{:}); + end + end + + methods (Access=protected) + %Calculates the initial parameters using an external function. + function [init_params,lim_lower,lim_upper]=calcInitParams(this) + [init_params,lim_lower,lim_upper]=... + initParamGorodetsky2000(this.Data.x,this.Data.y); + end + + function genSliderVecs(this) + genSliderVecs@MyFit(this); + + if validateData(this) + %We choose to have the slider go over the range of + %the x-values of the plot for the center of the + %Lorentzian. + this.slider_vecs{2}=... + linspace(this.x_vec(1),this.x_vec(end),101); + %Find the index closest to the init parameter + [~,ind]=... + min(abs(this.init_params(2)-this.slider_vecs{2})); + %Set to ind-1 as the slider goes from 0 to 100 + set(this.Gui.(sprintf('Slider_%s',... + this.fit_params{2})),'Value',ind-1); + end + end + end +end \ No newline at end of file diff --git a/@MyLinearFit/MyLinearFit.m b/@MyLinearFit/MyLinearFit.m new file mode 100644 index 0000000..3fb7928 --- /dev/null +++ b/@MyLinearFit/MyLinearFit.m @@ -0,0 +1,55 @@ +classdef MyLinearFit < MyFit + properties (Access=public) + %Logical value that determines whether the data should be scaled or + %not + scale_data; + end + + %Public methods + methods (Access=public) + %Constructor function + function this=MyLinearFit(varargin) + this@MyFit(... + 'fit_name','Linear',... + 'fit_function','a*x+b',... + 'fit_tex','$$ax+bx$$',... + 'fit_params',{'a','b'},... + 'fit_param_names',{'Gradient','Offset'},... + varargin{:}); + end + + end + + methods (Access=protected) + %Overload the doFit function to do polyFit instead of nonlinear + %fitting. We here have the choice of whether to scale the data or + %not. + + function doFit(this) + if this.scale_data + s_c=... + polyfit(this.Data.scaled_x,this.Data.scaled_y,1); + this.coeffs=convScaledToRealCoeffs(this,s_c); + else + %Fits polynomial of order 1 + this.coeffs=polyfit(this.Data.x,this.Data.y,1); + end + end + end + + methods (Access=private) + %Converts scaled coefficients to real coefficients + function r_c=convScaledToRealCoeffs(this,s_c) + [mean_x,std_x,mean_y,std_y]=calcZScore(this.Data); + r_c(1)=std_y/std_x*s_c(1); + r_c(2)=(s_c(2)-s_c(1)*mean_x/std_x)*std_y+mean_y; + end + + function s_c=convRealToScaledCoeffs(this,r_c) + [mean_x,std_x,mean_y,std_y]=calcZScore(this.Data); + s_c(1)=std_x/std_y*r_c(1); + s_c(2)=(r_c(2)-mean_y)/std_y+s_c(1)*mean_x/std_x; + end + end + +end \ No newline at end of file diff --git a/@MyLorentzianFit/MyLorentzianFit.m b/@MyLorentzianFit/MyLorentzianFit.m new file mode 100644 index 0000000..ca2376c --- /dev/null +++ b/@MyLorentzianFit/MyLorentzianFit.m @@ -0,0 +1,146 @@ +classdef MyLorentzianFit < MyFit + properties (Access=public) + %Logical value that determines whether the data should be scaled or + %not + scale_data; + %For calibration of optical frequencies using reference lines + tot_spacing=1; + + end + + %Public methods + methods (Access=public) + %Constructor function + function this=MyLorentzianFit(varargin) + this@MyFit(... + 'fit_name','Lorentzian',... + 'fit_function','1/pi*a*b/2/((x-c)^2+(b/2)^2)+d',... + 'fit_tex','$$\frac{a}{\pi}\frac{b/2}{(x-c)^2+(b/2)^2}+d$$',... + 'fit_params', {'a','b','c','d'},... + 'fit_param_names',{'Amplitude','Width','Center','Offset'},... + varargin{:}); + end + end + + methods (Access=protected) + %Overload the doFit function to do scaled fits. + %We here have the choice of whether to scale the data or not. + function doFit(this) + if this.scale_data + ft=fittype(this.fit_function,'coefficients',... + this.fit_params); + opts=fitoptions('Method','NonLinearLeastSquares',... + 'Lower',convRealToScaledCoeffs(this,this.lim_lower),... + 'Upper',convRealToScaledCoeffs(this,this.lim_upper),... + 'StartPoint',convRealToScaledCoeffs(this,this.init_params),... + 'MaxFunEvals',2000,... + 'MaxIter',2000,... + 'TolFun',1e-6,... + 'TolX',1e-6); + %Fits with the below properties. Chosen for maximum accuracy. + [this.Fitdata,this.Gof,this.FitInfo]=... + fit(this.Data.scaled_x,this.Data.scaled_y,ft,opts); + %Puts the coeffs into the class variable. + this.coeffs=convScaledToRealCoeffs(this,... + coeffvalues(this.Fitdata)); + else + %Do the default fitting if we are not scaling. + doFit@MyFit(this); + end + calcUserParams(this); + end + + %Calculates the initial parameters using an external function. + function [init_params,lim_lower,lim_upper]=calcInitParams(this) + if this.scale_data + [init_params,lim_lower,lim_upper]=... + initParamLorentzian(this.Data.scaled_x,this.Data.scaled_y); + %Convertion back to real values for display. + init_params=convScaledToRealCoeffs(this,init_params); + lim_lower=convScaledToRealCoeffs(this,lim_lower); + lim_upper=convScaledToRealCoeffs(this,lim_upper); + else + [init_params,lim_lower,lim_upper]=... + initParamLorentzian(this.Data.x,this.Data.y); + end + end + + %Function for calculating the parameters shown in the user panel + function calcUserParams(this) + this.mech_lw=this.coeffs(2); + this.mech_freq=this.coeffs(3); + this.Q=this.mech_freq/this.mech_lw; + this.opt_lw=convOptFreq(this,this.coeffs(2)); + this.Qf=this.mech_freq*this.Q; + end + + function createUserGuiStruct(this) + %Parameters for the tab relating to mechanics + this.UserGui.Tabs.Mech.tab_title='Mech.'; + this.UserGui.Tabs.Mech.Children={}; + addUserField(this,'Mech','mech_lw','Linewidth (Hz)',1,... + 'enable_flag','off') + addUserField(this,'Mech','Q',... + 'Qualify Factor (x10^6)',1e6,... + 'enable_flag','off','conv_factor',1e6) + addUserField(this,'Mech','mech_freq','Frequency (MHz)',1e6,... + 'conv_factor',1e6, 'enable_flag','off') + addUserField(this,'Mech','Qf','Q\times f (10^{14} Hz)',1e14,... + 'conv_factor',1e14,'enable_flag','off'); + + %Parameters for the tab relating to optics + this.UserGui.Tabs.Opt.tab_title='Optical'; + this.UserGui.Tabs.Opt.Children={}; + addUserField(this,'Opt','line_spacing',... + 'Line Spacing (MHz)',1e6,'conv_factor',1e6,... + 'Callback', @(~,~) calcUserParams(this)); + addUserField(this,'Opt','line_no','Number of lines',10,... + 'Callback', @(~,~) calcUserParams(this)); + addUserField(this,'Opt','opt_lw','Linewidth (MHz)',1e6,... + 'enable_flag','off','conv_factor',1e6); + end + + function genSliderVecs(this) + genSliderVecs@MyFit(this); + + if validateData(this) + %We choose to have the slider go over the range of + %the x-values of the plot for the center of the + %Lorentzian. + this.slider_vecs{3}=... + linspace(this.x_vec(1),this.x_vec(end),101); + %Find the index closest to the init parameter + [~,ind]=... + min(abs(this.init_params(3)-this.slider_vecs{3})); + %Set to ind-1 as the slider goes from 0 to 100 + set(this.Gui.(sprintf('Slider_%s',... + this.fit_params{3})),'Value',ind-1); + end + end + + %This function is used to convert the x-axis to frequency. + function real_freq=convOptFreq(this,freq) + real_freq=freq*this.line_spacing*this.line_no/this.tot_spacing; + end + end + + methods (Access=private) + %Converts scaled coefficients to real coefficients + function r_c=convScaledToRealCoeffs(this,s_c) + [mean_x,std_x,mean_y,std_y]=calcZScore(this.Data); + r_c(1)=s_c(1)*std_y*std_x; + r_c(2)=s_c(2)*std_x; + r_c(3)=s_c(3)*std_x+mean_x; + r_c(4)=s_c(4)*std_y+mean_y; + end + + function s_c=convRealToScaledCoeffs(this,r_c) + [mean_x,std_x,mean_y,std_y]=calcZScore(this.Data); + s_c(1)=r_c(1)/(std_y*std_x); + s_c(2)=r_c(2)/std_x; + s_c(3)=(r_c(3)-mean_x)/std_x; + s_c(4)=(r_c(4)-mean_y)/std_y; + end + end + +end \ No newline at end of file diff --git a/@MyLorentzianGradFit/MyLorentzianGradFit.m b/@MyLorentzianGradFit/MyLorentzianGradFit.m new file mode 100644 index 0000000..7dde846 --- /dev/null +++ b/@MyLorentzianGradFit/MyLorentzianGradFit.m @@ -0,0 +1,41 @@ +classdef MyLorentzianGradFit < MyFit + %Public methods + methods (Access=public) + %Constructor function + function this=MyLorentzianGradFit(varargin) + this@MyFit(... + 'fit_name','LorentzianGrad',... + 'fit_function','1/pi*a*b/2/((x-c)^2+(b/2)^2)+d*(x-c)+e',... + 'fit_tex','$$\frac{a}{\pi}\frac{b/2}{(x-c)^2+(b/2)^2}+d(x-c)+e$$',... + 'fit_params', {'a','b','c','d','e'},... + 'fit_param_names',{'Amplitude','Width','Center','Gradient','Offset'},... + varargin{:}); + end + end + + methods (Access=protected) + %Calculates the initial parameters using an external function. + function [init_params,lim_lower,lim_upper]=calcInitParams(this) + [init_params,lim_lower,lim_upper]=... + initParamLorentzianGrad(this.Data.x,this.Data.y); + end + + function genSliderVecs(this) + genSliderVecs@MyFit(this); + + if validateData(this) + %We choose to have the slider go over the range of + %the x-values of the plot for the center of the + %Lorentzian. + this.slider_vecs{3}=... + linspace(this.x_vec(1),this.x_vec(end),101); + %Find the index closest to the init parameter + [~,ind]=... + min(abs(this.init_params(3)-this.slider_vecs{3})); + %Set to ind-1 as the slider goes from 0 to 100 + set(this.Gui.(sprintf('Slider_%s',... + this.fit_params{3})),'Value',ind-1); + end + end + end +end \ No newline at end of file diff --git a/@MyPeakFinder/MyPeakFinder.m b/@MyPeakFinder/MyPeakFinder.m index 2058209..e9ff112 100644 --- a/@MyPeakFinder/MyPeakFinder.m +++ b/@MyPeakFinder/MyPeakFinder.m @@ -1,298 +1,300 @@ classdef MyPeakFinder < handle properties Trace; Peaks; end methods function this=MyPeakFinder(varargin) p=inputParser; addParameter(p,'Trace',MyTrace()); parse(p,varargin{:}) this.Trace=p.Results.Trace; this.Peaks=struct('Location',[],'Width',[],'Prominence',[],... 'Value',[]); end %Checks if a peak exists within the given limits function bool=checkPeakExist(this,x_min,x_max) assert(isnumeric(x_min) & isnumeric(x_max),... 'x_min and x_max must be numbers'); assert(x_max>x_min,['x_max must be greater than x_min,',... ' currently x_min is %e while x_max is %e'],x_min,x_max); bool=any([this.Peaks.Location]>x_min & [this.Peaks.Location] 0); isnumber=@(x) isnumeric(x) && isscalar(x); valid_sorts={'none','ascend','descend'}; sortstrcheck=@(x) any(validatestring(x,valid_sorts)); addParameter(p,'FindMinima',false); addParameter(p,'MinPeakDistance',0,ispositive); addParameter(p,'MinPeakHeight',-Inf,isnumber); addParameter(p,'MinPeakWidth',0,ispositive); addParameter(p,'MaxPeakWidth',Inf,ispositive); addParameter(p,'MinPeakProminence',0.1,isnumber); addParameter(p,'SortStr','none',sortstrcheck); addParameter(p,'Threshold',0); addParameter(p,'ClearPeaks',true); addParameter(p,'Limits',[min(this.Trace.x),max(this.Trace.x)]) addParameter(p,'NPeaks',0); addParameter(p,'WidthReference','halfprom') parse(p,varargin{:}); %Sets the indices to be searched x_lim=p.Results.Limits; ind=(this.Trace.xx_lim(1)); %Sets the minimum peak prominence, which is always normalized min_peak_prominence=p.Results.MinPeakProminence*... peak2peak(this.Trace.y(ind)); %We must condition the Trace such that x is increasing and has %no NaNs nan_ind=isnan(this.Trace.x) | isnan(this.Trace.y); this.Trace.x(nan_ind)=[]; this.Trace.y(nan_ind)=[]; ind(nan_ind)=[]; if ~issorted(this.Trace.x) this.Trace.x=flipud(this.Trace.x); this.Trace.y=flipud(this.Trace.y); %If it is still not sorted, we sort it if ~issorted(this.Trace.x) 'Sorting'; [this.Trace.x,sort_ind]=sort(this.Trace.x); this.Trace.y=this.Trace.y(sort_ind); end end %If we are looking for minima, we invert the trace if p.Results.FindMinima y=-this.Trace.y; else y=this.Trace.y; end %As there is no way to tell it to look for infinite numbers of %peaks when you specify NPeaks, we only specify this parameter %if we need to if p.Results.NPeaks extra_args={'NPeaks',p.Results.NPeaks}; else extra_args={}; end %We now search for the peaks using the specified parameters [pks,locs,wdth,prom]=findpeaks(y(ind),... this.Trace.x(ind),... 'MinPeakDistance',p.Results.MinPeakDistance,... 'MinPeakheight',p.Results.MinPeakHeight,... 'MinPeakWidth',p.Results.MinPeakWidth,... 'MaxPeakWidth',p.Results.MaxPeakWidth,... 'SortStr',p.Results.SortStr,... 'MinPeakProminence',min_peak_prominence,... 'Threshold',p.Results.Threshold,... 'WidthReference',p.Results.WidthReference,... extra_args{:}); %We invert the results back if we are looking for minima. if p.Results.FindMinima pks=-pks; prom=-prom; end PeakStruct=struct('Value',num2cell(pks),... 'Location',num2cell(locs),... 'Width',num2cell(wdth),... 'Prominence',num2cell(prom)); %If the clearpeaks flag is set, we delete the previous peaks. if p.Results.ClearPeaks clearPeaks(this); this.Peaks=PeakStruct; else this.Peaks=[this.Peaks;PeakStruct]; end end function loadTrace(this,fullfilename) %Finds type of file [~,~,ext]=fileparts(fullfilename); switch ext case '.txt' load(this.Trace,fullfilename); case '.mat' DataStruct=load(fullfilename); fields=fieldnames(DataStruct); %We try to find which vectors are x and y for the data, %first we find the two longest vectors in the .mat file %and use these vec_length=structfun(@(x) length(x), DataStruct); [~,sort_ind]=sort(vec_length,'descend'); vec_names=fields(sort_ind(1:2)); %Now we do some basic conditioning of these vectors: %Make column vectors and remove NaNs. vec{1}=DataStruct.(vec_names{1})(:); vec{2}=DataStruct.(vec_names{2})(:); %If there is a start and stopindex, cut down the %longest vector to size. if ismember('startIndex',fields) && ... ismember('stopIndex',fields) [~,ind]=max(cellfun(@(x) length(x), vec)); vec{ind}=vec{ind}(DataStruct.startIndex:... DataStruct.stopIndex); end nan_ind=isnan(vec{1}) | isnan(vec{2}); vec{1}(nan_ind)=[]; vec{2}(nan_ind)=[]; %We find what x is by looking for a sorted vector ind_x=cellfun(@(x) issorted(x,'monotonic'),vec); this.Trace.x=vec{ind_x}; this.Trace.y=vec{~ind_x}; if ismember('offsetFrequency',fields) this.Trace.x=this.Trace.x+DataStruct.offsetFrequency; end otherwise error('File type %s is not supported',ext) end end function fitAllPeaks(this,varargin) p=inputParser; addParameter(p,'FitNames',{'Gorodetsky2000'}); addParameter(p,'base_dir',pwd); addParameter(p,'session_name','placeholder'); addParameter(p,'filename','placeholder'); + addParameter(p,'fit_width',16) parse(p,varargin{:}); fit_names=p.Results.FitNames; %We instantiate the MyFit objects used for the fitting Fits=struct(); for i=1:length(fit_names) - Fits.(fit_names{i})=MyFit('fit_name',fit_names{i},... - 'enable_gui',0); + Fits.(fit_names{i})=launchFit(fit_names{i},... + 'enable_gui',0,'enable_plot',0); Fits.(fit_names{i}).base_dir=p.Results.base_dir; Fits.(fit_names{i}).session_name=p.Results.session_name; Fits.(fit_names{i}).filename=... [p.Results.filename,'_',fit_names{i}]; end %We fit the peaks for i=1:length(this.Peaks) %First extract the data around the peak - [x_fit,y_fit]=extractPeak(this,i); + [x_fit,y_fit]=extractPeak(this,i,p.Results.fit_width); for j=1:length(fit_names) Fits.(fit_names{j}).Data.x=x_fit; Fits.(fit_names{j}).Data.y=y_fit; genInitParams(Fits.(fit_names{j})); fitTrace(Fits.(fit_names{j})); saveParams(Fits.(fit_names{j}),... 'save_user_params',false,... 'save_gof',true); end end fprintf('Finished fitting peaks \n'); end - function [x_peak,y_peak]=extractPeak(this,peak_no) + function [x_peak,y_peak]=extractPeak(this,peak_no,ext_width) loc=this.Peaks(peak_no).Location; w=this.Peaks(peak_no).Width; - ind=(loc-8*wthis.Trace.x); + ind=(loc-ext_width/2*wthis.Trace.x); x_peak=this.Trace.x(ind)-loc; y_peak=this.Trace.y(ind); end function save(this,varargin) %Parse inputs for saving p=inputParser; addParameter(p,'filename','placeholder',@ischar); addParameter(p,'save_dir',pwd,@ischar); addParameter(p,'overwrite_flag',false); addParameter(p,'save_prec',15); parse(p,varargin{:}); %Assign shorter names filename=p.Results.filename; save_dir=p.Results.save_dir; overwrite_flag=p.Results.overwrite_flag; save_prec=p.Results.save_prec; %Puts together the full file name fullfilename=fullfile([save_dir,filename,'.txt']); %Creates the file in the given folder - write_flag=createFile(save_dir,fullfilename,overwrite_flag); + write_flag=createFile(fullfilename,'overwrite',overwrite_flag); %Returns if the file is not created for some reason if ~write_flag; return; end col_names={'Value','Location','Width','Prominence'}; n_columns=length(col_names); %Finds appropriate column width cw=max([cellfun(@(x) length(x),col_names), save_prec+7]); cw_vec=repmat(cw,1,4); pre_fmt_str=repmat('%%%is\\t',1,n_columns); fmt_str=sprintf([pre_fmt_str,'\r\n'],cw_vec); fileID=fopen(fullfilename,'w'); fprintf(fileID,fmt_str,col_names{:}); pre_fmt_str_nmb=repmat('%%%i.15e\\t',1,n_columns); %Removes the tab at the end pre_fmt_str_nmb((end-2):end)=[]; nmb_fmt_str=sprintf([pre_fmt_str_nmb,'\r\n'],cw_vec); fprintf(fileID,nmb_fmt_str,... [[this.Peaks.Value];[this.Peaks.Location];... [this.Peaks.Width];[this.Peaks.Prominence]]); fclose(fileID); end function loadPeaks(this,fullfilename) assert(ischar(fullfilename),... 'File name must be a char, currently it is a %s',... class(fullfilename)); if ~exist(fullfilename,'file') error('File named ''%s'' does not exist, choose a different file',... fullfilename); end LoadStruct=importdata(fullfilename); headers=regexp(LoadStruct.textdata{1},'\s*(\w*)\s*','Tokens'); this.Peaks=struct(headers{1}{1},num2cell(LoadStruct.data(:,1)),... headers{2}{1},num2cell(LoadStruct.data(:,2)),... headers{3}{1},num2cell(LoadStruct.data(:,3)),... headers{4}{1},num2cell(LoadStruct.data(:,4))); end function clearPeaks(this) this.Peaks=struct('Location',[],'Width',[],'Prominence',[],... 'Value',[]); end end end \ No newline at end of file diff --git a/@MyPeakFinderGui/MyPeakFinderGui.m b/@MyPeakFinderGui/MyPeakFinderGui.m index a4e961c..7b1db01 100644 --- a/@MyPeakFinderGui/MyPeakFinderGui.m +++ b/@MyPeakFinderGui/MyPeakFinderGui.m @@ -1,263 +1,264 @@ classdef MyPeakFinderGui < handle properties PeakFinder Gui; axis_handle; end properties (Access=private) peak_color='r'; data_color='b'; peak_handle; end properties (Dependent=true) trace_handle; filename; session_name; base_dir; save_dir; end methods function this=MyPeakFinderGui() this.PeakFinder=MyPeakFinder(); createGui(this); end function delete(this) %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 function closeFigure(this, ~, ~) delete(this); end function analyzeCallback(this, src, ~) src.BackgroundColor=[1,0,0]; src.String='Analyzing..'; searchPeaks(this.PeakFinder,... 'MinPeakProminence',str2double(this.Gui.PromEdit.String),... 'MinPeakDistance',str2double(this.Gui.SepEdit.String),... 'FindMinima',this.Gui.MinimaCheck.Value,... 'WidthReference','halfprom'); plotPeaks(this); src.BackgroundColor=[0.94,0.94,0.94]; src.String='Analyze'; end function plotPeaks(this) delete(this.peak_handle); this.peak_handle=plot(this.axis_handle,... [this.PeakFinder.Peaks.Location],... [this.PeakFinder.Peaks.Value],... 'Marker','o',... 'LineStyle','none',... 'Color',this.peak_color); end function fitPeakCallback(this,~,~) %Clean up fit names fit_names=cellfun(@(x) erase(x,' '), this.Gui.SelFitList.String,... 'UniformOutput',false); fitAllPeaks(this.PeakFinder,... 'FitNames',fit_names,... 'base_dir',this.base_dir,... 'session_name',this.session_name,... - 'filename',this.filename); + 'filename',this.filename,... + 'fit_width',str2double(this.Gui.FitWidthEdit.String)); end function clickCallback(this,~,~) switch this.Gui.Window.SelectionType case 'normal' %Left click addPeak(this); case 'alt' %Right click axis(this.axis_handle,'tight') case 'extend' %Shift click coords=this.axis_handle.CurrentPoint; removePeak(this, coords(1)); otherwise end end function windowScrollCallback(this, ~, event) coords=this.axis_handle.CurrentPoint; if event.VerticalScrollCount>0 %Zoom out zoomAxis(this,coords(1),0.1) else %Zoom in zoomAxis(this,coords(1),10); end end function removePeak(this, coord) [~,ind]=min(abs([this.PeakFinder.Peaks.Location]-coord)); this.PeakFinder.Peaks(ind)=[]; plotPeaks(this); end function addPeak(this) x_lim=[this.axis_handle.XLim(1),... this.axis_handle.XLim(2)]; if checkPeakExist(this.PeakFinder,x_lim(1),x_lim(2)) opts=struct('WindowStyle','modal','Interpreter','tex'); errordlg(['A peak already exists in the window. ',... 'To add a new peak, zoom such that there is ',... 'no peak in the window'],'Error',opts); return end searchPeaks(this.PeakFinder,... 'ClearPeaks',false,... 'Limits',x_lim,... 'MinPeakProminence',str2double(this.Gui.PromEdit.String),... 'FindMinima',this.Gui.MinimaCheck.Value,... 'NPeaks',1,... 'SortStr','descend'); plotPeaks(this); end function zoomAxis(this,coords,zoom_factor) curr_width=this.axis_handle.XLim(2)-this.axis_handle.XLim(1); new_width=curr_width/zoom_factor; this.axis_handle.XLim=... [coords(1)-new_width/2,coords(1)+new_width/2]; end function clearCallback(this, ~, ~) delete(getLineHandle(this.PeakFinder.Trace,this.axis_handle)); clearData(this.PeakFinder.Trace); cla(this.axis_handle); end function loadTraceCallback(this, src, ~) %Window can find all files [fname,path]=uigetfile('*.*'); if fname==0 warning('No file was selected'); return end src.BackgroundColor=[1,0,0]; src.String='Loading..'; loadTrace(this.PeakFinder,[path,fname]); plot(this.PeakFinder.Trace,this.axis_handle); this.trace_handle.ButtonDownFcn=... @(src, event) clickCallback(this, src, event); exitLoad(this); end function exitLoad(this) this.Gui.LoadTraceButton.BackgroundColor=[0.94,0.94,0.94]; this.Gui.LoadTraceButton.String='Load trace'; end function savePeaksCallback(this,~,~) save(this.PeakFinder,... 'save_dir',this.save_dir,... 'filename',this.filename); end function loadPeaksCallback(this,~,~) [fname,path]=uigetfile('*.*'); if fname==0 warning('No file was selected'); return end loadPeaks(this.PeakFinder,[path,fname]); plotPeaks(this); end function clearPeaksCallback(this,~,~) clearPeaks(this.PeakFinder); delete(this.peak_handle); end %Add a fit to the selected list function addFitCallback(this,~,~) val=this.Gui.FitList.Value; if val==0; return; end this.Gui.SelFitList.String{end+1}=this.Gui.FitList.String{val}; this.Gui.FitList.String(val)=[]; if val>length(this.Gui.FitList.String) this.Gui.FitList.Value=length(this.Gui.FitList.String); end if this.Gui.SelFitList.Value==0 this.Gui.SelFitList.Value=1; end end %Remove fits from selected fits function removeFitCallback(this,~,~) val=this.Gui.SelFitList.Value; if val==0; return; end this.Gui.FitList.String{end+1}=this.Gui.SelFitList.String{val}; this.Gui.SelFitList.String(val)=[]; if val>length(this.Gui.SelFitList.String) this.Gui.SelFitList.Value=length(this.Gui.SelFitList.String); end if this.Gui.FitList.Value==0 this.Gui.FitList.Value=1; end end end methods function trace_handle=get.trace_handle(this) trace_handle=getLineHandle(this.PeakFinder.Trace,this.axis_handle); end function base_dir=get.base_dir(this) try base_dir=this.Gui.BaseEdit.String; catch base_dir=pwd; end end function set.base_dir(this,base_dir) this.Gui.BaseEdit.String=base_dir; end function session_name=get.session_name(this) try session_name=this.Gui.SessionEdit.String; catch session_name=''; end end function set.session_name(this,session_name) this.Gui.SessionEdit.String=session_name; end function filename=get.filename(this) try filename=this.Gui.FileNameEdit.String; catch filename='placeholder'; end end function set.filename(this,filename) this.Gui.FileNameEdit.String=filename; end %Get function from save directory function save_dir=get.save_dir(this) save_dir=createSessionPath(this.base_dir,this.session_name); end end end \ No newline at end of file diff --git a/@MyPeakFinderGui/createGui.m b/@MyPeakFinderGui/createGui.m index fb14c83..5ef50dc 100644 --- a/@MyPeakFinderGui/createGui.m +++ b/@MyPeakFinderGui/createGui.m @@ -1,209 +1,225 @@ function createGui(this) fit_list={'Lorentzian','LorentzianGrad','Double Lorentzian',... 'Gorodetsky2000','Gorodetsky2000Plus'}; fig_width=1000; fig_h=800; row_height=50; col_width=120; x_first_col=50; button_size=[100,30]; edit_size=[100,30]; file_edit_size=[200,30]; h_first_row=fig_h-row_height; h_second_row=fig_h-2*row_height; h_third_row=fig_h-3*row_height; +h_fourth_row=fig_h-4*row_height; +h_fifth_row=fig_h-5*row_height; %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', 'PeakFinder',... 'NumberTitle', 'off', ... 'MenuBar','none',... 'Toolbar','figure',... 'HandleVisibility', 'off',... 'Units','Pixels',... 'Position',[400,0,fig_width,fig_h],... 'WindowScrollWheelFcn',@(src, event) windowScrollCallback(this, src, event)); %Sets the close function (runs when x is pressed) to be class function set(this.Gui.Window, 'CloseRequestFcn',... @(src,event) closeFigure(this, src, event)); %Creates axis this.axis_handle=axes(this.Gui.Window,... 'Box','on',... 'Units','Pixels',... 'Position',[50,50,fig_width-100,fig_h-6*row_height]); hold(this.axis_handle,'on'); this.axis_handle.ButtonDownFcn=@(src, event) clickCallback(this, src, event); %Button for doing the analysis this.Gui.AnalyzeButton=uicontrol(this.Gui.Window,... 'Style','pushbutton',... 'Units','Pixels',... 'Position',[x_first_col,h_first_row,button_size],... 'String','Analyze',... 'Callback',@(src, event) analyzeCallback(this, src, event)); %Checkbox for finding minima this.Gui.MinimaCheck=uicontrol(this.Gui.Window,... 'Style','checkbox',... 'Units','Pixels',... 'Position',[x_first_col+90,h_second_row,button_size],... 'Value',0); this.Gui.MinimaLabel=uicontrol(this.Gui.Window,... 'Style','edit',... 'Units','pixels',... 'Position',[x_first_col,h_second_row,[80,30]],... 'String','Find minima',... 'Enable','off'); %Button for clearing the data this.Gui.FitButton=uicontrol(this.Gui.Window,... 'Style','pushbutton',... 'Units','Pixels',... 'Position',[x_first_col,h_third_row,button_size],... 'String','Fit peaks',... 'Callback',@(src, event) fitPeakCallback(this, src, event)); %Button for clearing the data this.Gui.ClearButton=uicontrol(this.Gui.Window,... 'Style','pushbutton',... 'Units','Pixels',... 'Position',[x_first_col+col_width,h_first_row,button_size],... 'String','Clear',... 'Callback',@(src, event) clearCallback(this, src, event)); %Button for loading the trace this.Gui.LoadTraceButton=uicontrol(this.Gui.Window,... 'Style','pushbutton',... 'Units','Pixels',... 'Position',[x_first_col+4*col_width,h_first_row,button_size],... 'String','Load trace',... 'Callback',@(src, event) loadTraceCallback(this, src, event)); this.Gui.PromLabel=uicontrol(this.Gui.Window,... 'Style','edit',... 'Units','pixels',... 'Position',[x_first_col+2*col_width,h_first_row,edit_size],... 'String','Prominence',... 'Enable','off'); %Button for changing the peak threshold this.Gui.PromEdit=uicontrol(this.Gui.Window,... 'Style','edit',... 'Units','Pixels',... 'Position',[x_first_col+3*col_width,h_first_row,edit_size],... 'String','0.5'); this.Gui.SepLabel=uicontrol(this.Gui.Window,... 'Style','edit',... 'Units','pixels',... 'Position',[x_first_col+2*col_width,h_second_row,edit_size],... 'String','Res. Separation',... 'Enable','off'); %Button for changing the resonance separation this.Gui.SepEdit=uicontrol(this.Gui.Window,... 'Style','edit',... 'Units','Pixels',... 'Position',[x_first_col+3*col_width,h_second_row,edit_size],... 'String','1'); %Button for saving the peaks this.Gui.SavePeaksButton=uicontrol(this.Gui.Window,... 'Style','pushbutton',... 'Units','Pixels',... 'Position',[x_first_col+4*col_width,h_second_row,button_size],... 'String','Save Peaks',... 'Callback',@(src, event) savePeaksCallback(this, src, event)); %Button for loading peaks this.Gui.LoadPeaksButton=uicontrol(this.Gui.Window,... 'Style','pushbutton',... 'Units','Pixels',... 'Position',[x_first_col+4*col_width,h_third_row,button_size],... 'String','Load Peaks',... 'Callback',@(src, event) loadPeaksCallback(this, src, event)); %Button for clearing the peaks this.Gui.ClearPeaksButton=uicontrol(this.Gui.Window,... 'Style','pushbutton',... 'Units','Pixels',... 'Position',[x_first_col+col_width,h_second_row,button_size],... 'String','Clear peaks',... 'Callback',@(src, event) clearPeaksCallback(this, src, event)); this.Gui.BaseLabel=uicontrol(this.Gui.Window,... 'Style','edit',... 'Units','pixels',... 'Position',[x_first_col+5*col_width,h_first_row,edit_size],... 'String','Base directory',... 'Enable','off'); %Button for changing the peak threshold this.Gui.BaseEdit=uicontrol(this.Gui.Window,... 'Style','edit',... 'Units','Pixels',... 'Position',[x_first_col+6*col_width,h_first_row,file_edit_size],... 'String',pwd); this.Gui.SessionLabel=uicontrol(this.Gui.Window,... 'Style','edit',... 'Units','pixels',... 'Position',[x_first_col+5*col_width,h_second_row,edit_size],... 'String','Session name',... 'Enable','off'); %Editbox for changing the session name this.Gui.SessionEdit=uicontrol(this.Gui.Window,... 'Style','edit',... 'Units','Pixels',... 'Position',[x_first_col+6*col_width,h_second_row,file_edit_size],... 'String','placeholder'); this.Gui.FileNameLabel=uicontrol(this.Gui.Window,... 'Style','edit',... 'Units','pixels',... 'Position',[x_first_col+5*col_width,h_third_row,edit_size],... 'String','File name',... 'Enable','off'); %Editbox for changing the filename this.Gui.FileNameEdit=uicontrol(this.Gui.Window,... 'Style','edit',... 'Units','Pixels',... 'Position',[x_first_col+6*col_width,h_third_row,file_edit_size],... 'String','placeholder'); %For selecting fits this.Gui.FitList=uicontrol(this.Gui.Window,... 'Style','listbox',... 'Units','Pixels',... 'String',fit_list,... 'Position',[x_first_col+col_width,h_third_row-1.5*row_height,... col_width,2*row_height]); %For selecting fits this.Gui.SelFitList=uicontrol(this.Gui.Window,... 'Style','listbox',... 'Units','Pixels',... 'Position',[x_first_col+2.5*col_width,h_third_row-1.5*row_height,... col_width,2*row_height]); %Add fit this.Gui.RemoveFitButton=uicontrol(this.Gui.Window,... 'Style','pushbutton',... 'Units','Pixels',... 'Position',[x_first_col+2.05*col_width,h_third_row-0.9*row_height,50,20],... 'String','<<',... 'Callback',@(src, event) removeFitCallback(this, src, event)); %Remove fit this.Gui.RemoveFitButton=uicontrol(this.Gui.Window,... 'Style','pushbutton',... 'Units','Pixels',... 'Position',[x_first_col+2.05*col_width,h_third_row-0.4*row_height,50,20],... 'String','>>',... 'Callback',@(src, event) addFitCallback(this, src, event)); +this.Gui.FitWidthLabel=uicontrol(this.Gui.Window,... + 'Style','edit',... + 'Units','pixels',... + 'Position',[x_first_col,h_fourth_row+10,edit_size],... + 'String','Fit width',... + 'Enable','off'); +%Button for changing the resonance separation +this.Gui.FitWidthEdit=uicontrol(this.Gui.Window,... + 'Style','edit',... + 'Units','Pixels',... + 'Position',[x_first_col,h_fifth_row+30,edit_size],... + 'String','16'); + + end \ No newline at end of file diff --git a/@MyQuadraticFit/MyQuadraticFit.m b/@MyQuadraticFit/MyQuadraticFit.m new file mode 100644 index 0000000..a64c141 --- /dev/null +++ b/@MyQuadraticFit/MyQuadraticFit.m @@ -0,0 +1,26 @@ +classdef MyQuadraticFit < MyFit + %Public methods + methods (Access=public) + %Constructor function + function this=MyQuadraticFit(varargin) + this@MyFit(... + 'fit_name','Quadratic',... + 'fit_function','a*x^2+b*x+c',... + 'fit_tex','$$ax^2+bx+c$$',... + 'fit_params',{'a','b','c'},... + 'fit_param_names',{'Quadratic coeff.','Linear coeff.','Offset'},... + varargin{:}); + end + + end + + methods (Access=protected) + %Overload the doFit function to do polyFit instead of nonlinear + %fitting. We here have the choice of whether to scale the data or + %not. + + function doFit(this) + this.coeffs=polyfit(this.Data.x,this.Data.y,2); + end + end +end \ No newline at end of file diff --git a/@MyRsa/MyRsa.m b/@MyRsa/MyRsa.m index 4a0f4ea..014f2b3 100644 --- a/@MyRsa/MyRsa.m +++ b/@MyRsa/MyRsa.m @@ -1,189 +1,201 @@ % Class for controlling Tektronix RSA5103 and RSA5106 spectrum analyzers classdef MyRsa < MyScpiInstrument & MyDataSource & MyCommCont properties (SetAccess = protected, GetAccess = public) acq_trace = [] % The number of last read trace end methods (Access = public) function this = MyRsa(varargin) this@MyCommCont(varargin{:}); this.Trace.unit_x = 'Hz'; this.Trace.unit_y = '$\mathrm{V}^2/\mathrm{Hz}$'; this.Trace.name_y = 'Power'; this.Trace.name_x = 'Frequency'; createCommandList(this); end end methods (Access = protected) function createCommandList(this) % We define commands for both the nominal and actual resolution % bandwidths as these two are useful in different % circumstances. The nominal one unlike the actual one takes % effect immediately after it is set to a new value, whereas % the actual one is the true rbw if the device does not follow % the nominal one (usually if the nominal rbw is is too small). addCommand(this, 'rbw', ':DPX:BANDwidth:RESolution', ... 'format', '%e', ... 'info', 'Nominal resolution bandwidth (Hz)'); addCommand(this, 'rbw_act', ':DPX:BANDwidth:ACTual', ... 'format', '%e', ... 'access', 'r', ... 'info', 'Actual resolution bandwidth (Hz)'); addCommand(this, 'auto_rbw', ':DPX:BAND:RES:AUTO', ... 'format', '%b'); addCommand(this, 'span', ':DPX:FREQ:SPAN', ... 'format', '%e', ... 'info', '(Hz)'); addCommand(this, 'start_freq', ':DPX:FREQ:STAR',... 'format', '%e', ... 'info', '(Hz)'); addCommand(this, 'stop_freq', ':DPX:FREQ:STOP',... 'format', '%e', ... 'info', '(Hz)'); addCommand(this, 'cent_freq', ':DPX:FREQ:CENT',... 'format', '%e', ... 'info', '(Hz)'); % Continuous triggering addCommand(this, 'init_cont', ':INIT:CONT', ... 'format', '%b',... 'info', 'Continuous triggering on/off'); % Number of points in trace addCommand(this, 'point_no', ':DPSA:POIN:COUN', ... 'format', 'P%i', ... 'value_list', {801, 2401, 4001, 10401}); % Reference level (dB) addCommand(this, 'ref_level',':INPut:RLEVel', ... 'format', '%e',... 'info', '(dB)'); % Display scale per division (dBm/div) addCommand(this, 'disp_y_scale', ':DISPlay:DPX:Y:PDIVision',... 'format', '%e', ... 'info', '(dBm/div)'); % Display vertical offset (dBm) addCommand(this, 'disp_y_offset', ':DISPLAY:DPX:Y:OFFSET', ... 'format', '%e', ... 'info', '(dBm)'); % Parametric commands for i = 1:3 i_str = num2str(i); % Display trace addCommand(this, ['disp_trace',i_str], ... [':TRAC',i_str,':DPX'], ... 'format', '%b', ... 'info', 'on/off'); % Trace Detection addCommand(this, ['det_trace',i_str],... [':TRAC',i_str,':DPX:DETection'],... 'format', '%s', ... 'value_list', {'AVERage', 'NEGative', 'POSitive'}); % Trace Function addCommand(this, ['func_trace',i_str], ... [':TRAC',i_str,':DPX:FUNCtion'], ... 'format', '%s', ... 'value_list', {'AVERage', 'HOLD', 'NORMal'}); % Number of averages addCommand(this, ['average_no',i_str], ... [':TRAC',i_str,':DPX:AVER:COUN'], ... 'format', '%i'); % Count completed averages addCommand(this, ['cnt_trace',i_str], ... [':TRACe',i_str,':DPX:COUNt:ENABle'], ... 'format', '%b', ... 'info', 'Count completed averages'); end end end methods (Access = public) function readTrace(this, varargin) if ~isempty(varargin) n_trace = varargin{1}; else n_trace = this.acq_trace; end % Ensure that device parameters, especially those that will be % later used for the calculation of frequency axis, are up to % date sync(this); writeString(this, sprintf('fetch:dpsa:res:trace%i?', n_trace)); data = binblockread(this.Comm, 'float'); % Calculate the frequency axis this.Trace.x = linspace(this.start_freq, this.stop_freq,... this.point_no); +<<<<<<< HEAD + %Calculates the power spectrum from the data, which is in dBm. + %Output is in V^2/Hz + readProperty(this,'rbw'); + power_spectrum = (10.^(data/10))/this.rbw*50*0.001; + %Trace object is created containing the data and its units + this.Trace.x = x_vec; + this.Trace.y = power_spectrum; + + this.acq_trace=n_trace; +======= % Calculates the power spectrum from the data, which is in dBm. % Output is in V^2/Hz this.Trace.y = (10.^(data/10))/this.rbw_act*50*0.001; this.acq_trace = n_trace; +>>>>>>> NewDaq % Trigger acquired data event triggerNewData(this); end % Abort data acquisition function abortAcq(this) writeString(this, ':ABORt'); end % Initiate data acquisition function initAcq(this) writeString(this, ':INIT'); end % Wait for the current operation to be completed function val = opc(this) val = queryString(this, '*OPC?'); end % Extend readHeader function function Hdr = readHeader(this) %Call parent class method and then append parameters Hdr = readHeader@MyScpiInstrument(this); %Hdr should contain single field addParam(Hdr, Hdr.field_names{1}, ... 'acq_trace', this.acq_trace, ... 'comment', 'The number of last read trace'); end end methods function set.acq_trace(this, val) assert((val==1 || val==2 || val==3), ... 'Acquisition trace number must be 1, 2 or 3.'); this.acq_trace = val; end end end diff --git a/@MyTrace/MyTrace.m b/@MyTrace/MyTrace.m index c2628e8..2b81f0c 100644 --- a/@MyTrace/MyTrace.m +++ b/@MyTrace/MyTrace.m @@ -1,513 +1,541 @@ % Class for XY data representation with labelling, plotting and % saving/loading functionality % If instantiated as MyTrace(load_path) then % the content is loaded from file classdef MyTrace < handle & matlab.mixin.Copyable & matlab.mixin.SetGet properties (Access = public) x = [] y = [] name_x = 'x' name_y = 'y' unit_x = '' unit_y = '' file_name = '' % Array of MyMetadata objects with information about the trace MeasHeaders % Formatting options for the metadata metadata_opts = {} % Data formatting options column_sep = '\t' % Data column separator line_sep = '\r\n' % Data line separator data_sep = 'Data' % Separator between metadata and data save_prec = 15 % Maximum digits of precision in saved data end properties (Access = public, NonCopyable = true) % Cell that contains handles the trace is plotted in hlines = {} end +<<<<<<< HEAD + properties (Dependent=true) + label_x; + label_y; + %Z-scored variables (scaled to have mean of 0 and std of 1) + scaled_x; + scaled_y; + +======= properties (Dependent = true) label_x label_y +>>>>>>> NewDaq end methods (Access = public) function this = MyTrace(varargin) P = MyClassParser(this); processInputs(P, this, varargin{:}); this.MeasHeaders = MyMetadata.empty(); end %Defines the save function for the class. function save(this, filename, varargin) % Parse inputs for saving p = inputParser; addParameter(p, 'overwrite', false); parse(p, varargin{:}); assert(ischar(filename) && isvector(filename), ... '''filename'' must be a character vector.') this.file_name = filename; % Create the file in the given folder stat = createFile(filename, 'overwrite', p.Results.overwrite); % Returns if the file is not created for some reason if ~stat warning('File not created, returned write_flag %i.', stat); return end % Create metadata header Mdt = getMetadata(this); save(Mdt, filename); % Write the data fileID = fopen(filename,'a'); % Pads the vectors if they are not equal length diff = length(this.x)-length(this.y); if diff<0 this.x = [this.x; zeros(-diff,1)]; warning(['Zero padded x vector as the saved vectors ' ... 'are not of the same length']); elseif diff>0 this.y = [this.y; zeros(diff,1)]; warning(['Zero padded y vector as the saved vectors ' ... 'are not of the same length']); end % Save data in the more compact of fixed point and scientific % notation with trailing zeros removed. % If save_prec=15, we get %.15g\t%.15g\r\n % Formatting without column padding may look ugly but it % signigicantly reduces the file size. data_format_str = ... sprintf(['%%.%ig', this.column_sep, '%%.%ig', ... this.line_sep], this.save_prec, this.save_prec); fprintf(fileID, data_format_str, [this.x, this.y]'); fclose(fileID); end function clearData(this) this.x = []; this.y = []; end %Plots the trace on the given axes, using the class variables to %define colors, markers, lines and labels. Takes all optional %parameters of the class as inputs. function plot(this, varargin) % Do nothing if there is no data in the trace if isempty(this) return end %Checks that x and y are the same size assert(validatePlot(this),... 'The length of x and y must be identical to make a plot') %Parses inputs p=inputParser(); % Axes in which log should be plotted addOptional(p, 'plot_axes', [], @(x)assert( ... isa(x,'matlab.graphics.axis.Axes')||... isa(x,'matlab.ui.control.UIAxes'),... 'Argument must be axes or uiaxes.')); validateColor=@(x) assert(iscolor(x),... 'Input must be a valid color. See iscolor function'); addParameter(p,'Color','b',validateColor); validateMarker=@(x) assert(ismarker(x),... 'Input must be a valid marker. See ismarker function'); addParameter(p,'Marker','none',validateMarker); validateLine=@(x) assert(isline(x),... 'Input must be a valid linestyle. See isline function'); addParameter(p,'LineStyle','-',validateLine); addParameter(p,'MarkerSize',6,... @(x) validateattributes(x,{'numeric'},{'positive'})); addParameter(p,'make_labels',false,@islogical); interpreters={'none','tex','latex'}; validateInterpreter=@(x) assert(contains(x,interpreters),... 'Interpreter must be none, tex or latex'); addParameter(p,'Interpreter','latex',validateInterpreter); parse(p,varargin{:}); %If axes are not supplied get current if ~isempty(p.Results.plot_axes) plot_axes=p.Results.plot_axes; else plot_axes=gca(); end ind=findLineInd(this, plot_axes); if ~isempty(ind) && any(ind) set(this.hlines{ind},'XData',this.x,'YData',this.y); else this.hlines{end+1}=plot(plot_axes,this.x,this.y); ind=length(this.hlines); end %Sets the correct color and label options set(this.hlines{ind},'Color',p.Results.Color,'LineStyle',... p.Results.LineStyle,'Marker',p.Results.Marker,... 'MarkerSize',p.Results.MarkerSize); if p.Results.make_labels interpreter=p.Results.Interpreter; xlabel(plot_axes,this.label_x,'Interpreter',interpreter); ylabel(plot_axes,this.label_y,'Interpreter',interpreter); set(plot_axes,'TickLabelInterpreter',interpreter); end end %If there is a line object from the trace in the figure, this sets %it to the appropriate visible setting. function setVisible(this, Axes, bool) if bool vis='on'; else vis='off'; end ind=findLineInd(this, Axes); if ~isempty(ind) && any(ind) set(this.hlines{ind},'Visible',vis) end end %Defines addition of two MyTrace objects function sum=plus(this,b) checkArithmetic(this,b); sum=MyTrace('x',this.x,'y',this.y+b.y, ... 'unit_x',this.unit_x,'unit_y',this.unit_y, ... 'name_x',this.name_x,'name_y',this.name_y); end %Defines subtraction of two MyTrace objects function diff=minus(this,b) checkArithmetic(this,b); diff=MyTrace('x',this.x,'y',this.y-b.y, ... 'unit_x',this.unit_x,'unit_y',this.unit_y, ... 'name_x',this.name_x,'name_y',this.name_y); end function [max_val,max_x]=max(this) assert(validatePlot(this),['MyTrace object must contain',... ' nonempty data vectors of equal length to find the max']) [max_val,max_ind]=max(this.y); max_x=this.x(max_ind); end function fwhm=calcFwhm(this) assert(validatePlot(this),['MyTrace object must contain',... ' nonempty data vectors of equal length to find the fwhm']) [~,~,fwhm,~]=findpeaks(this.y,this.x,'NPeaks',1); end + function [mean_x,std_x,mean_y,std_y]=calcZScore(this) + mean_x=mean(this.x); + std_x=std(this.x); + mean_y=mean(this.y); + std_y=std(this.y); + end + %Integrates the trace numerically function area=integrate(this,varargin) assert(validatePlot(this),['MyTrace object must contain',... ' nonempty data vectors of equal length to integrate']) %Input parser for optional inputs p=inputParser; %Default is to use all the data in the trace addOptional(p,'ind',true(1,length(this.x))); parse(p,varargin{:}); ind=p.Results.ind; %Integrates the data contained in the indexed part. area=trapz(this.x(ind),this.y(ind)); end % Picks every n-th element from the trace, % performing a running average first if opt=='avg' function downsample(this, n, opt) n0 = ceil(n/2); if exist('opt', 'var') && ... (strcmpi(opt,'average') || strcmpi(opt,'avg')) % Compute moving average with 'shrink' option so that the % total number of samples is preserved. Endpoints will be % discarded by starting the indexing from n0. tmpy = movmean(this.y, n, 'Endpoints', 'shrink'); this.x = this.x(n0:n:end); this.y = tmpy(n0:n:end); else % Downsample without averaging this.x = this.x(n0:n:end); this.y = this.y(n0:n:end); end end %Checks if the object is empty function bool = isDataEmpty(this) bool = isempty(this.x) && isempty(this.y); end %Checks if the data can be plotted function bool = validatePlot(this) bool =~isempty(this.x) && ~isempty(this.y)... && length(this.x)==length(this.y); end function hline = getLineHandle(this,ax) ind=findLineInd(this,ax); if ~isempty(ind) hline=this.hlines{ind}; else hline=[]; end end end methods (Access = public, Static = true) % Load trace from file function Trace = load(filename, varargin) assert(exist(filename, 'file') ~= 0, ['File does not ' ... 'exist, please choose a different load path.']) % Extract data formatting p = inputParser(); p.KeepUnmatched = true; addParameter(p, 'FormatSource', {}, @(x) isa(x,'MyTrace')); addParameter(p, 'metadata_opts', {}, @iscell); parse(p, varargin{:}); if ~ismember('FormatSource', p.UsingDefaults) Fs = p.Results.FormatSource; % Take formatting from the source object mdt_opts = Fs.metadata_opts; trace_opts = { ... 'column_sep', Fs.column_sep, ... 'line_sep', Fs.line_sep, ... 'data_sep', Fs.data_sep, ... 'save_prec', Fs.save_prec, ... 'metadata_opts', Fs.metadata_opts}; else % Formatting is either default or was suppled explicitly mdt_opts = p.Results.metadata_opts; trace_opts = varargin; end % Load metadata and convert from array to structure [Mdt, n_end_line] = MyMetadata.load(filename, mdt_opts{:}); Info = titleref(Mdt, 'Info'); if ~isempty(Info) && isparam(Info, 'Type') class_name = Info.ParamList.Type; else class_name = 'MyTrace'; end % Instantiate an appropriate type of Trace Trace = feval(class_name, trace_opts{:}); setMetadata(Trace, Mdt); % Reads x and y data data_array = dlmread(filename, Trace.column_sep, n_end_line,0); Trace.x = data_array(:,1); Trace.y = data_array(:,2); Trace.file_name = filename; end end methods (Access = protected) % Generate metadata that includes measurement headers and % information about trace. This function is used in place of 'get' % method so it can be overloaded in a subclass. function Mdt = getMetadata(this) % Make a field with the information about the trace Info = MyMetadata('title', 'Info'); addParam(Info, 'Type', class(this)); addParam(Info, 'Name1', this.name_x); addParam(Info, 'Name2', this.name_y); addParam(Info, 'Unit1', this.unit_x); addParam(Info, 'Unit2', this.unit_y); % Make a separator for the bulk of trace data DataSep = MyMetadata('title', this.data_sep); Mdt = [Info, this.MeasHeaders, DataSep]; % Ensure uniform formatting if ~isempty(this.metadata_opts) set(Mdt, this.metadata_opts{:}); end end % Load metadata into the trace function setMetadata(this, Mdt) Info = titleref(Mdt, 'Info'); if ~isempty(Info) if isparam(Info, 'Unit1') this.unit_x = Info.ParamList.Unit1; end if isparam(Info, 'Unit2') this.unit_y = Info.ParamList.Unit2; end if isparam(Info, 'Name1') this.name_x = Info.ParamList.Name1; end if isparam(Info, 'Name2') this.name_y = Info.ParamList.Name2; end % Remove the metadata containing trace properties Mdt = rmtitle(Mdt, 'Info'); else warning(['No trace metadata found. No units or labels ' ... 'assigned when loading trace from %s.'], filename); end % Remove the empty data separator field Mdt = rmtitle(Mdt, this.data_sep); % Store the remainder under measurement headers this.MeasHeaders = Mdt; end %Checks if arithmetic can be done with MyTrace objects. function checkArithmetic(this, b) assert(isa(this,'MyTrace') && isa(b,'MyTrace'),... ['Both objects must be of type MyTrace to add,',... 'here they are type %s and %s'],class(this),class(b)); assert(strcmp(this.unit_x, b.unit_x) && ... strcmp(this.unit_y,b.unit_y),... 'The MyTrace classes must have the same units for arithmetic') assert(length(this.x)==length(this.y)==... length(this.x)==length(this.y),... 'The length of x and y must be equal for arithmetic'); assert(all(this.x==b.x),... 'The MyTrace objects must have identical x-axis for arithmetic') end % Finds the hline handle that is plotted in the specified axes function ind = findLineInd(this, Axes) if ~isempty(this.hlines) ind = cellfun(@(x) ismember(x, findall(Axes, ... 'Type','Line')), this.hlines); else ind = []; end end % Overload the standard copy() method to create a deep copy, % i.e. when handle properties are copied recursively function Copy = copyElement(this) Copy = copyElement@matlab.mixin.Copyable(this); % Copy metadata Copy.MeasHeaders = copy(this.MeasHeaders); end end %% Set and get methods methods %Set function for MeasHeaders function set.MeasHeaders(this, Val) assert(isa(Val, 'MyMetadata'),... 'MeasHeaders must be an array of MyMetadata objects'); this.MeasHeaders = Val; end %Set function for x, checks if it is a vector of doubles and %reshapes into a column vector function set.x(this, x) assert(isnumeric(x),... 'Data must be of class double'); this.x=x(:); end %Set function for y, checks if it is a vector of doubles and %reshapes into a column vector function set.y(this, y) assert(isnumeric(y),... 'Data must be of class double'); this.y=y(:); end %Set function for unit_x, checks if input is a string. function set.unit_x(this, unit_x) assert(ischar(unit_x),'Unit must be a char, not a %s',... class(unit_x)); this.unit_x=unit_x; end %Set function for unit_y, checks if input is a string function set.unit_y(this, unit_y) assert(ischar(unit_y),'Unit must be a char, not a %s',... class(unit_y)); this.unit_y=unit_y; end %Set function for name_x, checks if input is a string function set.name_x(this, name_x) assert(ischar(name_x),'Name must be a char, not a %s',... class(name_x)); this.name_x=name_x; end %Set function for name_y, checks if input is a string function set.name_y(this, name_y) assert(ischar(name_y),'Name must be a char, not a %s',... class(name_y)); this.name_y=name_y; end function set.file_name(this, file_name) assert(ischar(file_name),'File path must be a char, not a %s',... class(file_name)); this.file_name=file_name; end %Get function for label_x, creates label from name_x and unit_x. function label_x=get.label_x(this) label_x=sprintf('%s (%s)', this.name_x, this.unit_x); end %Get function for label_y, creates label from name_y and unit_y. function label_y=get.label_y(this) label_y=sprintf('%s (%s)', this.name_y, this.unit_y); end + + %Get function for scaled_x, zscores x + function scaled_x=get.scaled_x(this) + scaled_x=zscore(this.x); + end + + %Get function for scaled_x, zscores x + function scaled_y=get.scaled_y(this) + scaled_y=zscore(this.y); + end + end end diff --git a/@MyZiRingdown/MyZiRingdown.m b/@MyZiRingdown/MyZiRingdown.m index ca1d0ca..f5f36da 100644 --- a/@MyZiRingdown/MyZiRingdown.m +++ b/@MyZiRingdown/MyZiRingdown.m @@ -1,934 +1,1039 @@ % Class for performing ringdown measurements of mechanical oscillators % using Zurich Instruments UHF or MF lock-in. % % Operation: sweep the driving tone (drive_osc) using the sweep module % in LabOne web user interface, when the magnitude of the demodulator % signal exceeds trig_threshold the driving tone is switched off and % the recording of demodulated signal is started, the signal is recorded % for the duration of record_time. % % Features: % % Adaptive measurement oscillator frequency % % Averaging % % Auto saving % % Auxiliary output signal: If enable_aux_out=true % then after a ringdown is started a sequence of pulses is applied % to the output consisting of intermittent on and off periods % starting from on. classdef MyZiRingdown < MyZiLockIn & MyDataSource properties (Access = public, SetObservable = true) % Ringdown is recorded if the signal in the triggering demodulation % channel exceeds this value trig_threshold = 1e-3 % V % Duration of the recorded ringdown record_time = 1 % (s) % If enable_acq is true, then the drive is on and the acquisition % of record is triggered when signal exceeds trig_threshold enable_acq = false % Auxiliary output signal during ringdown. enable_aux_out = false % If auxiliary output is applied % time during which the output is in aux_out_on_lev state aux_out_on_t = 1 % (s) % time during which the output is in aux_out_off_lev state aux_out_off_t = 1 % (s) aux_out_on_lev = 1 % (V), output trigger on level aux_out_off_lev = 0 % (V), output trigger off level % Average the trace over n points to reduce amount of stored data % while keeping the demodulator bandwidth large downsample_n = 1 fft_length = 128 auto_save = false % if all ringdowns should be automatically saved % In adaptive measurement oscillator mode the oscillator frequency % is continuously changed to follow the signal frequency during % ringdown acquisition. This helps against the oscillator frequency % drift. adaptive_meas_osc = false end % The properties which are read or set only once during the class % initialization properties (GetAccess = public, SetAccess = {?MyClassParser}, ... SetObservable = true) % enumeration for demodulators, oscillators and output starts from 1 demod = 1 % demodulator used for both triggering and measurement % Enumeration in the node structure starts from 0, so, for example, % the default path to the trigger demodulator refers to the % demodulator #1 demod_path = '/dev4090/demods/0' drive_osc = 1 meas_osc = 2 % Signal input, integers above 1 correspond to main inputs, aux % input etc. (see the user interface for device-specific details) signal_in = 1 drive_out = 1 % signal output used for driving % Number of an auxiliary channel used for the output of triggering % signal, primarily intended to switch the measurement apparatus % off during a part of the ringdown and thus allow for free % evolution of the oscillator during that period. aux_out = 1 % Poll duration of 1 ms practically means that ziDAQ('poll', ... % returns immediately with the data accumulated since the % previous function call. poll_duration = 0.001 % s poll_timeout = 50 % ms % Margin for adaptive oscillator frequency adjustment - oscillator % follows the signal if the dispersion of frequency in the % demodulator band is below ad_osc_margin times the demodulation % bandwidth (under the condition that adaptive_meas_osc=true) ad_osc_margin = 0.1 end % Internal variables properties (GetAccess = public, SetAccess = protected, ... SetObservable = true) recording = false % true if a ringdown is being recorded % true if adaptive measurement oscillator mode is on and if the % measurement oscillator is actually actively following. ad_osc_following = false % Reference timestamp at the beginning of measurement record. % Stored as uint64. t0 elapsed_t = 0 % Time elapsed since the last recording was started DemodSpectrum % MyTrace object to store FFT of the demodulator data end % Other dependent variables that are not device properties properties (Dependent = true) % Downsample the measurement record to reduce the amount of data % while keeping the large demodulation bandwidth. % (samples/s), sampling rate of the trace after avraging downsampled_rate % Provides public access to properties of private AvgTrace n_avg % number of ringdowns to be averaged avg_count % the average counter fft_rbw % resolution bandwidth of fft poll_period % (s) end % Keeping handle objects fully private is the only way to restrict set % access to their properties properties (Access = private) PollTimer AuxOutOffTimer % Timer responsible for switching the aux out off AuxOutOnTimer % Timer responsible for switching the aux out on % Demodulator samples z(t) stored to continuously calculate % spectrum, the values of z are complex here, z=x+iy. % osc_freq is the demodulation frequency DemodRecord = struct('t',[],'z',[],'osc_freq',[]) AvgTrace % MyAvgTrace object used for averaging ringdowns end events NewDemodSample % New demodulator samples received RecordingStarted % Acquisition of a new trace triggered end methods (Access = public) %% Constructor and destructor function this = MyZiRingdown(varargin) % Extract poll period from varargin p = inputParser(); p.KeepUnmatched = true; addParameter(p, 'poll_period', 0.1, @isnumeric); parse(p, varargin{:}); varargin = struct2namevalue(p.Unmatched); this = this@MyZiLockIn(varargin{:}); % Create and configure trace objects % Trace is inherited from the superclass this.Trace = MyTrace(... 'name_x','Time',... 'unit_x','s',... 'name_y','Magnitude r',... 'unit_y','V'); this.DemodSpectrum = MyTrace(... 'name_x','Frequency',... 'unit_x','Hz',... 'name_y','PSD',... 'unit_y','V^2/Hz'); this.AvgTrace = MyAvgTrace(); % Set up the poll timer. Using a timer for anyncronous % data readout allows to use the wait time for execution % of other programs. % Fixed spacing is preferred as it is the most robust mode of % operation when keeping the intervals between callbacks % precisely defined is not the biggest concern. % Busy mode is 'drop' - there is no need to accumulate timer % callbacks as the data is stored in the buffer of zi data % server since the previous poll. this.PollTimer = timer(... 'BusyMode', 'drop',... 'ExecutionMode', 'fixedSpacing',... 'Period', p.Results.poll_period,... 'TimerFcn', @this.pollTimerCallback); % Aux out timers use fixedRate mode for more precise timing. % The two timers are executed periodically with a time lag. % The first timer switches the auxiliary output off this.AuxOutOffTimer = timer(... 'ExecutionMode', 'fixedRate',... 'TimerFcn', @this.auxOutOffTimerCallback); % The second timer switches the auxiliary output on this.AuxOutOnTimer = timer(... 'ExecutionMode', 'fixedRate',... 'TimerFcn', @this.auxOutOnTimerCallback); this.demod_path = sprintf('/%s/demods/%i', this.dev_id, ... this.demod-1); createCommandList(this); end function delete(this) % delete function should never throw errors, so protect % statements with try-catch try stopPoll(this) catch warning(['Could not usubscribe from the demodulator ', ... 'or stop the poll timer.']) end % Delete timers to prevent them from running indefinitely in % the case of program crash try delete(this.PollTimer) catch warning('Could not delete the poll timer.') end try stop(this.AuxOutOffTimer); delete(this.AuxOutOffTimer); catch warning('Could not stop and delete AuxOutOff timer.') end try stop(this.AuxOutOnTimer); delete(this.AuxOutOnTimer); catch warning('Could not stop and delete AuxOutOn timer.') end end %% Other methods function startPoll(this) sync(this); % Configure the oscillators, demodulator and driving output % -1 accounts for the difference in enumeration conventions % in the software names (starting from 1) and node numbers % (starting from 0). % First, update the demodulator path this.demod_path = sprintf('/%s/demods/%i', ... this.dev_id, this.demod-1); % Set the data transfer rate so that it satisfies the Nyquist % criterion (>x2 the bandwidth of interest) this.demod_rate = 4*this.lowpass_bw; % Configure the demodulator. Signal input: ziDAQ('setInt', ... [this.demod_path,'/adcselect'], this.signal_in-1); % Oscillator: ziDAQ('setInt', ... [this.demod_path,'/oscselect'], this.drive_osc-1); % Enable data transfer from the demodulator to the computer ziDAQ('setInt', [this.demod_path,'/enable'], 1); % Configure the signal output - disable all the oscillator % contributions excluding the driving tone path = sprintf('/%s/sigouts/%i/enables/*', ... this.dev_id, this.drive_out-1); ziDAQ('setInt', path, 0); path = sprintf('/%s/sigouts/%i/enables/%i', ... this.dev_id, this.drive_out-1, this.drive_osc-1); ziDAQ('setInt', path, 1); % By convention, we start form 'enable_acq=false' state this.enable_acq = false; this.drive_on = false; % Configure the auxiliary trigger output - put it in the manual % mode so it does not output demodulator readings path = sprintf('/%s/auxouts/%i/outputselect', ... this.dev_id, this.aux_out-1); ziDAQ('setInt', path, -1); % The convention is that aux out is on by default this.aux_out_on = true; % Subscribe to continuously receive samples from the % demodulator. Samples accumulated between timer callbacks % will be read out using ziDAQ('poll', ... ziDAQ('subscribe', [this.demod_path,'/sample']); % Start continuous polling start(this.PollTimer) end function stopPoll(this) stop(this.PollTimer) ziDAQ('unsubscribe', [this.demod_path,'/sample']); end % Main function that polls data from the device demodulator function pollTimerCallback(this, ~, ~) % Switch off the hedged mode to reduce latency this.auto_sync = false; % ziDAQ('poll', ... with short poll_duration returns % immediately with the data accumulated since the last timer % callback Data = ziDAQ('poll', this.poll_duration, this.poll_timeout); try % Get the new demodulator data DemodSample = Data.(this.dev_id).demods(this.demod).sample; catch this.auto_sync = true; return end % Append new samples to the record and recalculate spectrum appendSamplesToBuff(this, DemodSample); calcfft(this); if this.recording % If the recording has just started, save the start time if isempty(this.Trace.x) this.t0 = DemodSample.timestamp(1); end +<<<<<<< HEAD + if this.recording + % If recording is under way, append the new samples to + % the trace + rec_finished = appendSamplesToTrace(this, DemodSample); + + % Recording can be manually stopped by setting + % enable_acq=false + if ~this.enable_acq + rec_finished=true; + end + + % Update elapsed time + this.elapsed_t=this.Trace.x(end); + + % If the adaptive measurement frequency mode is on, + % update the measurement oscillator frequency. + % Make sure that the demodulator record actually + % contains signal by comparing the dispersion of + % frequency to demodulator bandwidth. + if this.adaptive_meas_osc + [df_avg, df_dev]=calcfreq(this); + if df_dev < this.ad_osc_margin*this.lowpass_bw + this.meas_osc_freq=df_avg; + % Change indicator + this.ad_osc_following=true; + else + this.ad_osc_following=false; + end +======= % If recording is under way, append the new samples to % the trace rec_finished = appendSamplesToTrace(this, DemodSample); % Update elapsed time if ~isempty(this.Trace.x) this.elapsed_t = this.Trace.x(end); else this.elapsed_t = 0; end % If the adaptive measurement frequency mode is on, % update the measurement oscillator frequency. % Make sure that the demodulator record actually % contains a signal by comparing the dispersion of % frequency to the demodulator bandwidth. if this.adaptive_meas_osc [df_avg, df_dev] = calcfreq(this); if df_dev < this.ad_osc_margin*this.lowpass_bw this.meas_osc_freq = df_avg; % Change indicator this.ad_osc_following = true; +>>>>>>> NewDaq else this.ad_osc_following = false; end else +<<<<<<< HEAD + r=sqrt(DemodSample.x.^2+DemodSample.y.^2); + if this.enable_acq && max(r)>this.trig_threshold + % Start acquisition of a new trace if the maximum + % of the signal exceeds threshold + this.recording=true; + + % Find index at which the threshold was + % exceeded + ind0=find(r>this.trig_threshold,1,'first'); + + this.t0=DemodSample.timestamp(ind0); + this.elapsed_t=0; + + % Switch the drive off + this.drive_on=false; + + % Set the measurement oscillator frequency to be + % the frequency at which triggering occurred + this.meas_osc_freq=this.drive_osc_freq; + + % Switch the oscillator + this.current_osc=this.meas_osc; + + % Optionally start the auxiliary output timers + if this.enable_aux_out + % Configure measurement periods and delays + T=this.aux_out_on_t+this.aux_out_off_t; + this.AuxOutOffTimer.Period=T; + this.AuxOutOnTimer.Period=T; + + this.AuxOutOffTimer.startDelay=... + this.aux_out_on_t; + this.AuxOutOnTimer.startDelay=T; + + % Start timers + start(this.AuxOutOffTimer) + start(this.AuxOutOnTimer) + end + + % Clear trace and append new data starting from the + % index, at which triggering occurred. + % Theoretically, a record can be finished with + % this one portion if the record time is set small. + clearData(this.Trace); + + rec_finished = ... + appendSamplesToTrace(this, DemodSample, ind0); + + notify(this, 'RecordingStarted'); + else + rec_finished=false; + end + + % Indicator for adaptive measurement is off, since + % recording is not under way + this.ad_osc_following=false; +======= this.ad_osc_following = false; +>>>>>>> NewDaq end else r = sqrt(DemodSample.x.^2+DemodSample.y.^2); if this.enable_acq && max(r)>this.trig_threshold % Start acquisition of a new trace if the maximum % of the signal exceeds threshold this.recording = true; this.elapsed_t = 0; % Switch the drive off this.drive_on = false; % Set the measurement oscillator frequency to be % the frequency at which triggering occurred this.meas_osc_freq = this.drive_osc_freq; % Switch the oscillator this.current_osc = this.meas_osc; % Clear the buffer on ZI data server from existing % demodulator samples, as these samples were % recorded with drive on ziDAQ('poll', this.poll_duration, this.poll_timeout); % Optionally start the auxiliary output timers if this.enable_aux_out % Configure measurement periods and delays T = this.aux_out_on_t + this.aux_out_off_t; this.AuxOutOffTimer.Period = T; this.AuxOutOnTimer.Period = T; this.AuxOutOffTimer.startDelay =... this.aux_out_on_t; this.AuxOutOnTimer.startDelay = T; % Start timers start(this.AuxOutOffTimer) start(this.AuxOutOnTimer) end % Clear trace clearData(this.Trace); notify(this, 'RecordingStarted'); end rec_finished = false; % Indicator for adaptive measurement is off, since % recording is not under way this.ad_osc_following = false; end notify(this,'NewDemodSample'); % Stop recording if a ringdown record was completed if rec_finished % stop recording this.recording = false; this.ad_osc_following = false; % Stop auxiliary timers stop(this.AuxOutOffTimer); stop(this.AuxOutOnTimer); % Return the drive and aux out to the default state this.aux_out_on = true; this.current_osc = this.drive_osc; % Do trace averaging. If the new data length is not of % the same size as the length of the existing data % (which should happen only when the record period was % changed during recording or when recording was % manually stopped), truncate to the minimum length if ~isempty(this.AvgTrace.x) && ... (length(this.AvgTrace.y)~=length(this.Trace.y)) l = min(length(this.AvgTrace.y), ... length(this.Trace.y)); this.AvgTrace.y = this.AvgTrace.y(1:l); this.AvgTrace.x = this.AvgTrace.x(1:l); this.Trace.y = this.Trace.y(1:l); this.Trace.x = this.Trace.x(1:l); disp('Ringdown record was truncated') end avg_compl = addAverage(this.AvgTrace, this.Trace); % Trigger NewData if this.n_avg>1 end_str = sprintf('_%i', this.AvgTrace.avg_count); else end_str = ''; end triggerNewData(this, 'save', this.auto_save, ... 'filename_ending', end_str); % If the ringdown averaging is complete, disable % further triggering to exclude data overwriting if avg_compl this.enable_acq = false; this.drive_on = false; if this.n_avg>1 end_str = '_avg'; % Trigger one more time to transfer the average % trace. % A new measurement header is not necessary % as the delay since the last triggering is % minimum. triggerNewData(this, ... 'Trace', copy(this.AvgTrace), ... 'save', this.auto_save, ... 'filename_ending', end_str); end else % Continue trying to acquire new ringdowns this.enable_acq = true; this.drive_on = true; end end this.auto_sync = true; end % Append timestamps vs r=sqrt(x^2+y^2) to the measurement record. % Starting index can be supplied as varargin. % The output variable tells if the record is finished. +<<<<<<< HEAD + function isfin = appendSamplesToTrace(this, DemodSample, varargin) + if isempty(varargin) + startind=1; + else + startind=varargin{1}; + end + + r=sqrt(DemodSample.x(startind:end).^2 + ... + DemodSample.y(startind:end).^2); + % Subtract the reference time, convert timestamps to seconds + ts=double(DemodSample.timestamp(startind:end) -... + this.t0)/this.clockbase; +======= function isfin = appendSamplesToTrace(this, DemodSample) persistent ts_buff r_sq_buff r_sq = DemodSample.x.^2 + DemodSample.y.^2; % Subtract the reference time, convert timestamps to seconds ts = double(DemodSample.timestamp - this.t0)/this.clockbase; +>>>>>>> NewDaq % Check if recording should be stopped isfin = (ts(end) >= this.record_time); if isfin % Remove excess data points from the new data ind = (tsflen this.DemodRecord.t = this.DemodRecord.t(end-flen+1:end); this.DemodRecord.z = this.DemodRecord.z(end-flen+1:end); this.DemodRecord.osc_freq = ... this.DemodRecord.osc_freq(end-flen+1:end); end end function calcfft(this) flen = min(this.fft_length, length(this.DemodRecord.t)); [freq, spectr] = xyFourier( ... this.DemodRecord.t(end-flen+1:end), ... this.DemodRecord.z(end-flen+1:end)); this.DemodSpectrum.x = freq; this.DemodSpectrum.y = abs(spectr).^2; end % Calculate the average frequency and dispersion of the demodulator % signal function [f_avg, f_dev] = calcfreq(this) if ~isempty(this.DemodSpectrum.x) norm = sum(this.DemodSpectrum.y); % Calculate the center frequency of the spectrum f_avg = dot(this.DemodSpectrum.x, ... this.DemodSpectrum.y)/norm; f_dev = sqrt(dot(this.DemodSpectrum.x.^2, ... this.DemodSpectrum.y)/norm-f_avg^2); % Shift the FFT center by the demodulation frequency to % output absolute value f_avg = f_avg + mean(this.DemodRecord.osc_freq); else f_avg = []; f_dev = []; end end % Provide restricted access to private AvgTrace function resetAveraging(this) % Clear data and reset the counter clearData(this.AvgTrace); end function auxOutOffTimerCallback(this, ~, ~) this.aux_out_on = false; end function auxOutOnTimerCallback(this, ~, ~) this.aux_out_on = true; end end methods (Access = protected) function createCommandList(this) addCommand(this, 'drive_osc_freq', ... 'readFcn', @this.readDriveOscFreq, ... 'writeFcn', @this.writeDriveOscFreq, ... 'info', '(Hz)'); addCommand(this, 'meas_osc_freq', ... 'readFcn', @this.readMeasOscFreq, ... 'writeFcn', @this.writeMeasOscFreq, ... 'info', '(Hz)'); addCommand(this, 'drive_on', ... 'readFcn', @this.readDriveOn, ... 'writeFcn', @this.writeDriveOn); addCommand(this, 'current_osc', ... 'readFcn', @this.readCurrentOsc, ... 'writeFcn', @this.writeCurrentOsc, ... 'info', 'measurement or driving'); addCommand(this, 'drive_amp', ... 'readFcn', @this.readDriveAmp, ... 'writeFcn', @this.writeDriveAmp, ... 'info', '(Vpk)'); addCommand(this, 'lowpass_order', ... 'readFcn', @this.readLowpassOrder, ... 'writeFcn', @this.writeLowpassOrder, ... 'default', 1); addCommand(this, 'lowpass_bw', ... 'readFcn', @this.readLowpassBw, ... 'writeFcn', @this.writeLowpassBw, ... 'info', '3 db bandwidth of lowpass filter (Hz)'); addCommand(this, 'demod_rate', ... 'readFcn', @this.readDemodRate, ... 'writeFcn', @this.writeDemodRate, ... 'info', ['Rate at which demodulator data is ' ... 'transferred to computer']); addCommand(this, 'aux_out_on', ... 'readFcn', @this.readAuxOutOn, ... 'writeFcn', @this.writeAuxOutOn, ... 'info', 'If aux out in ''on'' state, true/false'); end function val = readDriveOscFreq(this) path = sprintf('/%s/oscs/%i/freq', this.dev_id, ... this.drive_osc-1); val = ziDAQ('getDouble', path); end function writeDriveOscFreq(this, val) path = sprintf('/%s/oscs/%i/freq', this.dev_id, ... this.drive_osc-1); ziDAQ('setDouble', path, val); end function val = readMeasOscFreq(this) path = sprintf('/%s/oscs/%i/freq', this.dev_id, ... this.meas_osc-1); val = ziDAQ('getDouble', path); end function writeMeasOscFreq(this, val) path = sprintf('/%s/oscs/%i/freq', this.dev_id, ... this.meas_osc-1); ziDAQ('setDouble', path, val); end function val = readDriveOn(this) path = sprintf('/%s/sigouts/%i/on', this.dev_id, ... this.drive_out-1); val = logical(ziDAQ('getInt', path)); end function writeDriveOn(this, val) path = sprintf('/%s/sigouts/%i/on', this.dev_id, ... this.drive_out-1); % Use double() to convert from logical ziDAQ('setInt', path, double(val)); end function val = readCurrentOsc(this) val = double(ziDAQ('getInt', ... [this.demod_path,'/oscselect']))+1; end function writeCurrentOsc(this, val) assert((val==this.drive_osc) || (val==this.meas_osc), ... ['The number of current oscillator must be that of ', ... 'the drive or measurement oscillator, not ', num2str(val)]) ziDAQ('setInt', [this.demod_path,'/oscselect'], val-1); end function val = readDriveAmp(this) path = sprintf('/%s/sigouts/%i/amplitudes/%i', ... this.dev_id, this.drive_out-1, this.drive_osc-1); val = ziDAQ('getDouble', path); end function writeDriveAmp(this, val) path=sprintf('/%s/sigouts/%i/amplitudes/%i', ... this.dev_id, this.drive_out-1, this.drive_osc-1); ziDAQ('setDouble', path, val); end function n = readLowpassOrder(this) n = ziDAQ('getInt', [this.demod_path,'/order']); end function writeLowpassOrder(this, val) assert(any(val==[1,2,3,4,5,6,7,8]), ['Low-pass filter ', ... 'order must be an integer between 1 and 8']) ziDAQ('setInt', [this.demod_path,'/order'], val); end function bw = readLowpassBw(this) tc = ziDAQ('getDouble', [this.demod_path,'/timeconstant']); bw = ziTC2BW(tc, this.lowpass_order); end function writeLowpassBw(this, val) tc = ziBW2TC(val, this.lowpass_order); ziDAQ('setDouble', [this.demod_path,'/timeconstant'], tc); end function val = readDemodRate(this) val = ziDAQ('getDouble', [this.demod_path,'/rate']); end function writeDemodRate(this, val) ziDAQ('setDouble', [this.demod_path,'/rate'], val); end function bool = readAuxOutOn(this) path = sprintf('/%s/auxouts/%i/offset', ... this.dev_id, this.aux_out-1); val = ziDAQ('getDouble', path); % Signal from the auxiliary output is continuous, we make the % binary decision about the output state depending on if % the signal is closer to the ON or OFF level bool = (abs(val-this.aux_out_on_lev) < ... abs(val-this.aux_out_off_lev)); end function writeAuxOutOn(this, bool) path = sprintf('/%s/auxouts/%i/offset', ... this.dev_id, this.aux_out-1); if bool out_offset = this.aux_out_on_lev; else out_offset = this.aux_out_off_lev; end ziDAQ('setDouble', path, out_offset); end function createMetadata(this) createMetadata@MyZiLockIn(this); % Demodulator parameters addObjProp(this.Metadata, this, 'demod', 'comment', ... 'Number of the demodulator in use (starting from 1)'); addObjProp(this.Metadata, this, 'meas_osc', 'comment', ... 'Measurement oscillator number'); % Signal input addObjProp(this.Metadata, this, 'signal_in', 'comment', ... 'Singnal input number'); % Drive parameters addObjProp(this.Metadata, this, 'drive_out', 'comment', ... 'Driving output number'); addObjProp(this.Metadata, this, 'drive_osc', 'comment', ... 'Swept oscillator number'); % Parameters of the auxiliary output addObjProp(this.Metadata, this, 'aux_out', 'comment', ... 'Auxiliary output number'); addObjProp(this.Metadata, this, 'enable_aux_out', 'comment',... 'Auxiliary output is applied during ringdown'); addObjProp(this.Metadata, this, 'aux_out_on_lev', ... 'comment', '(V)'); addObjProp(this.Metadata, this, 'aux_out_off_lev', ... 'comment', '(V)'); addObjProp(this.Metadata, this, 'aux_out_on_t', ... 'comment', '(s)'); addObjProp(this.Metadata, this, 'aux_out_off_t', ... 'comment', '(s)'); % Software parameters addObjProp(this.Metadata, this, 'trig_threshold', 'comment',... '(V), threshold for starting a ringdown record'); addObjProp(this.Metadata, this, 'record_time', ... 'comment', '(s)'); addObjProp(this.Metadata, this, 'downsampled_rate', ... 'comment', ['(samples/s), rate to which a ringown ', ... 'trace is downsampled with averaging after acquisition']); addObjProp(this.Metadata, this, 'auto_save', 'comment', '(s)'); % Adaptive measurement oscillator addObjProp(this.Metadata, this, 'adaptive_meas_osc', ... 'comment', ['If true the measurement oscillator ', ... 'frequency is adjusted during ringdown']); addObjProp(this.Metadata, this, 'ad_osc_margin'); addObjProp(this.Metadata, this, 'fft_length', ... 'comment', '(points)'); % Timer poll parameters addParam(this.Metadata, 'poll_period', [],... 'comment', '(s)'); addObjProp(this.Metadata, this, 'poll_duration', ... 'comment', '(s)'); addObjProp(this.Metadata, this, 'poll_timeout', ... 'comment', '(ms)'); end end %% Set and get methods. methods function set.downsample_n(this, val) n = round(val); assert(n>=1, ['Number of points for trace averaging must ', ... 'be greater than 1']) this.downsample_n = n; end function set.downsampled_rate(this, val) dr = this.demod_rate; % Downsampled rate should not exceed the data transfer rate val = min(val, dr); % Round so that the averaging is done over an integer number of % points this.downsample_n = round(dr/val); end function val = get.downsampled_rate(this) val = this.demod_rate/this.downsample_n; end function set.fft_length(this, val) % Round val to the nearest 2^n to make the calculation of % Fourier transform efficient n = round(log2(max(val, 1))); this.fft_length = 2^n; end function val = get.fft_rbw(this) val = this.demod_rate/this.fft_length; end function set.fft_rbw(this, val) assert(val>0,'FFT resolution bandwidth must be greater than 0') % Rounding of fft_length to the nearest integer is handled by % its own set method this.fft_length = this.demod_rate/val; end function set.n_avg(this, val) this.AvgTrace.n_avg = val; end function val = get.n_avg(this) val = this.AvgTrace.n_avg; end function val = get.avg_count(this) val = this.AvgTrace.avg_count; end function set.aux_out_on_t(this, val) assert(val>0.001, ... 'Aux out on time must be greater than 0.001 s.') this.aux_out_on_t = val; end function set.aux_out_off_t(this, val) assert(val>0.001, ... 'Aux out off time must be greater than 0.001 s.') this.aux_out_off_t = val; end function set.enable_acq(this, val) this.enable_acq = logical(val); end function val = get.poll_period(this) val = this.PollTimer.Period; end end end diff --git a/GUIs/GuiCeCryo.mlapp b/GUIs/GuiCeCryo.mlapp new file mode 100644 index 0000000..c5cd6f1 Binary files /dev/null and b/GUIs/GuiCeCryo.mlapp differ 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 diff --git a/Measurement and analysis routines/MyPeakInspector.mlapp b/Measurement and analysis routines/MyPeakInspector.mlapp index b896158..aec3709 100644 Binary files a/Measurement and analysis routines/MyPeakInspector.mlapp and b/Measurement and analysis routines/MyPeakInspector.mlapp differ diff --git a/Utility functions/Initial parameter estimation/initParamExponential.m b/Utility functions/Initial parameter estimation/initParamExponential.m index 2dc9d41..4a713c8 100644 --- a/Utility functions/Initial parameter estimation/initParamExponential.m +++ b/Utility functions/Initial parameter estimation/initParamExponential.m @@ -1,62 +1,62 @@ function [p_in,lim_lower,lim_upper]=initParamExponential(x,y) %Assumes form a*exp(-bx)+c %Setting upper and lower limits [amp_max,ind_max]=max(y); [amp_min,ind_min]=min(y); lim_upper=[Inf,Inf,Inf]; lim_lower=-lim_upper; %Fix to avoid unphysical offsets on data where all y values exceed 0. if all(y>0) lim_lower(3)=0; end if abs(amp_max)>abs(amp_min) lim_upper(1)=Inf; lim_lower(1)=0; else lim_upper(1)=0; lim_lower(1)=-Inf; end if (ind_max>ind_min && abs(amp_max)>abs(amp_min))... || (ind_max0) - y=y-amp_min+eps; - n=length(x); - y2=log(y); - j=sum(x); - k=sum(y2); - l=sum(x.^2); - r2=sum(x .* y2); - p_in(2)=(n * r2 - k * j)/(n * l - j^2); - p_in(1)=exp((k-p_in(2)*j)/n); - y=y+amp_min-eps; -elseif abs(amp_max)>abs(amp_min) && amp_max>0 - p_in(1)=amp_max; - p_in(2)=-1; -else - p_in(1)=amp_min; - p_in(2)=-1; -end + +y=y-amp_min+eps; +n=length(x); +y2=log(y); +j=sum(x); +k=sum(y2); +l=sum(x.^2); +r2=sum(x .* y2); +p_in(2)=(n * r2 - k * j)/(n * l - j^2); +p_in(1)=exp((k-p_in(2)*j)/n); +y=y+amp_min-eps; +% if abs(amp_max)>abs(amp_min) && amp_max>0 +% p_in(1)=amp_max; +% p_in(2)=-1; +% else +% p_in(1)=amp_min; +% p_in(2)=-1; +% end if abs(amp_max)>abs(amp_min) p_in(3)=amp_min; else p_in(3)=amp_max; end end \ No newline at end of file diff --git a/Utility functions/launchFit.m b/Utility functions/launchFit.m new file mode 100644 index 0000000..337de23 --- /dev/null +++ b/Utility functions/launchFit.m @@ -0,0 +1,24 @@ +%Launcher for MyFit objects +function MyFitObj=launchFit(fit_name,varargin) +assert(ischar(fit_name),'fit_name input must be a char, currently it is a %s',... + class(fit_name)); + + switch lower(fit_name) + case 'linear' + MyFitObj=MyLinearFit(varargin{:}); + case 'quadratic' + MyFitObj=MyQuadraticFit(varargin{:}); + case 'gaussian' + MyFitObj=MyGaussianFit(varargin{:}); + case 'exponential' + MyFitObj=MyExponentialFit(varargin{:}); + case 'lorentzian' + MyFitObj=MyLorentzianFit(varargin{:}); + case 'doublelorentzian' + MyFitObj=MyDoubleLorentzianFit(varargin{:}); + case 'gorodetsky2000' + MyFitObj=MyGorodetksy2000Fit(varargin{:}); + otherwise + error('%s is not a valid fit',fit_name); + end +end \ No newline at end of file diff --git a/Utility functions/testfit.m b/Utility functions/testfit.m index 8719a73..d01d8b9 100644 --- a/Utility functions/testfit.m +++ b/Utility functions/testfit.m @@ -1,37 +1,56 @@ %Testing tool for MyFit clear +close(figure(1)) x_vec=linspace(0,200,1000); -testFit=MyFit('fit_name','Lorentzian','enable_gui',1); +% testFit=MyFit('fit_name','Linear','enable_gui',1); +testFit=MyGorodetsky2000Fit(); params=cell(1,testFit.n_params); switch testFit.fit_name case 'Lorentzian' for i=1:testFit.n_params params{i}=5*rand; params{3}=200*rand; end case 'Exponential' - params{1}=-5*rand; + params{1}=+5*rand; params{2}=-0.1*rand; params{3}=0.01*rand; + case 'Linear' + params{1}=10*rand; + params{2}=-50*rand+25; + case 'DoubleLorentzian' + for i=1:testFit.n_params + params{i}=5*rand; + end + params{3}=50+10*rand; + params{2}=params{5}; + params{6}=50+100*rand; + case 'Gorodetsky2000' + for i=1:testFit.n_params + params{i}=5*rand; + end + params{3}=0.001*rand; + params{2}=80+20*rand; + params{4}=5+10*rand; otherwise for i=1:testFit.n_params params{i}=5*rand; end end params -y_vec=testFit.FitStruct.(testFit.fit_name).anon_fit_fun(x_vec,params{:}).*normrnd(1,0.04,size(x_vec)); +y_vec=testFit.anon_fit_fun(x_vec,params{:}).*normrnd(1,0.04,size(x_vec)); figure(1) ax=gca; plot(x_vec,y_vec,'x'); axis([min(x_vec),max(x_vec),0.5*min(y_vec),1.5*max(y_vec)]); hold on testFit.plot_handle=ax; testFit.enable_plot=1; testFit.Data.x=x_vec; testFit.Data.y=y_vec; % testFit.genInitParams; % testFit.init_params % testFit.fitTrace; % testFit.init_params \ No newline at end of file