diff --git a/@MyDaq/MyDaq.m b/@MyDaq/MyDaq.m index 55ea12d..8173f0f 100644 --- a/@MyDaq/MyDaq.m +++ b/@MyDaq/MyDaq.m @@ -1,934 +1,934 @@ classdef MyDaq < handle properties %Contains GUI handles Gui; %Contains Reference trace (MyTrace object) Ref=MyTrace(); %Contains Data trace (MyTrace object) Data=MyTrace(); %Contains Background trace (MyTrace object) Background=MyTrace(); %Struct containing available instruments InstrList=struct(); %Struct containing MyInstrument objects Instruments=struct() %Struct containing Cursor objects Cursors=struct(); %Struct containing Cursor labels CrsLabels=struct(); %Struct containing MyFit objects Fits=struct(); %Input parser Parser; %Struct for listeners Listeners=struct(); %Sets the colors of fits, data and reference fit_color='k'; data_color='b'; ref_color='r'; %Properties for saving files base_dir; session_name; file_name; %Flag for enabling the GUI enable_gui; end properties (Dependent=true) save_dir; main_plot; open_fits; open_instrs; open_crs; instr_tags; instr_names; savefile; end methods (Access=public) %% Class functions %Constructor function function this=MyDaq(varargin) createParser(this); parse(this.Parser,varargin{:}); parseInputs(this); if this.enable_gui this.Gui=guihandles(eval('GuiDaq')); initGui(this); hold(this.main_plot,'on'); end initDaq(this) end function delete(this) %Deletes the MyFit objects and their listeners cellfun(@(x) delete(this.Fits.(x)), this.open_fits); cellfun(@(x) deleteListeners(this,x), this.open_fits); %Deletes the MyInstrument objects and their listeners cellfun(@(x) delete(this.Instruments.(x)), this.open_instrs); cellfun(@(x) deleteListeners(this,x), this.open_instrs); if this.enable_gui this.Gui.figure1.CloseRequestFcn=''; %Deletes the figure delete(this.Gui.figure1); %Removes the figure handle to prevent memory leaks this.Gui=[]; end end end methods (Access=private) %Creates parser for constructor function function createParser(this) p=inputParser; addParameter(p,'enable_gui',1); this.Parser=p; end %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 %Initializes the class depending on the computer name function initDaq(this) computer_name=getenv('computername'); switch computer_name case 'LPQM1PCLAB2' initRt(this); case 'LPQM1PC18' initUhq(this); case 'LPQM1PC2' %Test case for testing on Nils' computer. otherwise error('Please create an initialization function for this computer') end %Initializes empty trace objects this.Ref=MyTrace; this.Data=MyTrace; this.Background=MyTrace; end %Sets callback functions for the GUI initGui(this) %Executes when the GUI is closed function closeFigure(this,~,~) delete(this); end %Adds an instrument to InstrList. Used by initialization functions. function addInstr(this,tag,name,type,interface,address) %Usage: addInstr(this,tag,name,type,interface,address) if ~ismember(tag,this.instr_tags) this.InstrList.(tag).name=name; this.InstrList.(tag).type=type; this.InstrList.(tag).interface=interface; this.InstrList.(tag).address=address; else error(['%s is already defined as an instrument. ',... 'Please choose a different tag'],tag); end end %Gets the tag corresponding to an instrument name function tag=getTag(this,instr_name) ind=cellfun(@(x) strcmp(this.InstrList.(x).name,instr_name),... this.instr_tags); tag=this.instr_tags{ind}; end %Opens the correct instrument function openInstrument(this,tag) instr_type=this.InstrList.(tag).type; %Collects the correct inputs for creating the MyInstrument %class input_cell={this.InstrList.(tag).name,... this.InstrList.(tag).interface,... this.InstrList.(tag).address}; switch instr_type case 'RSA' this.Instruments.(tag)=MyRsa(input_cell{:},... 'gui','GuiRsa'); case 'Scope' this.Instruments.(tag)=MyScope(input_cell{:},... 'gui','GuiScope'); case 'NA' this.Instruments.(tag)=MyNa(input_cell{:},... 'gui','GuiNa'); end %Adds listeners for new data and deletion of the instrument. %These call plot functions and delete functions respectively. this.Listeners.(tag).NewData=... addlistener(this.Instruments.(tag),'NewData',... @(src, eventdata) acquireNewData(this, src, eventdata)); this.Listeners.(tag).Deletion=... addlistener(this.Instruments.(tag),'ObjectBeingDestroyed',... @(src, eventdata) deleteInstrument(this, src, eventdata)); end %Updates fits function updateFits(this) %Pushes data into fits in the form of MyTrace objects, so that %units etc follow. for i=1:length(this.open_fits) switch this.open_fits{i} case {'Linear','Quadratic','Gaussian','Lorentzian',... 'Exponential','Beta','DoubleLorentzian'} this.Fits.(this.open_fits{i}).Data=... getFitData(this,'VertData'); 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 ismember(name,fieldnames(this.Cursors)) ind=findCursorData(this, trc_str, name); Trace.x=this.(trc_str).x(ind); Trace.y=this.(trc_str).y(ind); end end %Finds data between named cursors in the given trace function ind=findCursorData(this, trc_str, name) crs_pos=sort([this.Cursors.(name){1}.Location,... this.Cursors.(name){2}.Location]); ind=(this.(trc_str).x>crs_pos(1) & this.(trc_str).x %Prints the figure to the clipboard print(newFig,'-clipboard','-dbitmap'); %Deletes the figure delete(newFig); end function updateAxis(this) axis(this.main_plot,'tight'); end end methods (Access=public) %% Callbacks %Callback for copying the plot to clipboard function copyPlotCallback(this,~,~) copyPlot(this); end %Callback for centering cursors function centerCursorsCallback(this, ~, ~) if ~this.Gui.LogX.Value x_pos=mean(this.main_plot.XLim); else x_pos=10^(mean(log10(this.main_plot.XLim))); end if ~this.Gui.LogX.Value y_pos=mean(this.main_plot.YLim); else y_pos=10^(mean(log10(this.main_plot.YLim))); end for i=1:length(this.open_crs) switch this.Cursors.(this.open_crs{i}){1}.Orientation case 'horizontal' pos=y_pos; case 'vertical' pos=x_pos; end %Centers the position cellfun(@(x) set(x,'Location',pos), ... this.Cursors.(this.open_crs{i})); %Triggers the UpdateCursorBar event, which triggers %listener callback to reposition text cellfun(@(x) notify(x,'UpdateCursorBar'),... this.Cursors.(this.open_crs{i})); cellfun(@(x) notify(x,'EndDrag'),... this.Cursors.(this.open_crs{i})); end end %Callback for creating vertical data cursors function cursorButtonCallback(this, hObject, ~) name=erase(hObject.Tag,'Button'); %Gets the first four characters of the tag (Vert or Horz) type=name(1:4); if hObject.Value hObject.BackgroundColor=[0,1,0.2]; createCursors(this,name,type); else hObject.BackgroundColor=[0.941,0.941,0.941]; deleteCursors(this,name); end end %Callback for the instrument menu function instrMenuCallback(this,hObject,~) val=hObject.Value; %Finds the correct instrument tag as long as an instrument is %selected if val~=1 names=hObject.String; tag=getTag(this,names(val)); else tag=''; end %If instrument is valid and not open, opens it. If it is valid %and open it changes focus to the instrument control window. if ismember(tag,this.instr_tags) && ... ~ismember(tag,this.open_instrs) openInstrument(this,tag); elseif ismember(tag,this.open_instrs) - figure(this.Instruments.(tag).figure1); + figure(this.Instruments.(tag).Gui.figure1); end end %Select trace callback function selTraceCallback(this, ~, ~) updateFits(this) end %Saves the data if the save data button is pressed. function saveDataCallback(this, ~, ~) if this.Data.validatePlot save(this.Data,'save_dir',this.save_dir,'name',... this.savefile) else error('Data trace was empty, could not save'); end end %Saves the reference if the save ref button is pressed. function saveRefCallback(this, ~, ~) if this.Data.validatePlot save(this.Ref,'save_dir',this.save_dir,'name',... this.savefile) else error('Reference trace was empty, could not save') end end %Toggle button callback for showing the data trace. function showDataCallback(this, hObject, ~) if hObject.Value hObject.BackgroundColor=[0,1,0.2]; setVisible(this.Data,this.main_plot,1); updateAxis(this); else hObject.BackgroundColor=[0.941,0.941,0.941]; setVisible(this.Data,this.main_plot,0); updateAxis(this); end end %Toggle button callback for showing the ref trace function showRefCallback(this, hObject, ~) if hObject.Value hObject.BackgroundColor=[0,1,0.2]; setVisible(this.Ref,this.main_plot,1); updateAxis(this); else hObject.BackgroundColor=[0.941,0.941,0.941]; setVisible(this.Ref,this.main_plot,0); updateAxis(this); end end %Callback for moving the data to reference. function dataToRefCallback(this, ~, ~) if this.Data.validatePlot this.Ref.x=this.Data.x; this.Ref.y=this.Data.y; this.Ref.plotTrace(this.main_plot,'Color',this.ref_color); this.Ref.setVisible(this.main_plot,1); updateFits(this); this.Gui.ShowRef.Value=1; this.Gui.ShowRef.BackgroundColor=[0,1,0.2]; else warning('Data trace was empty, could not move to reference') end end %Callback for ref to bg button. Sends the reference to background function refToBgCallback(this, ~, ~) if this.Ref.validatePlot this.Background.x=this.Ref.x; this.Background.y=this.Ref.y; this.Background.plotTrace(this.main_plot); this.Background.setVisible(this.main_plot,1); else warning('Reference trace was empty, could not move to background') end end %Callback for data to bg button. Sends the data to background function dataToBgCallback(this, ~, ~) if this.Data.validatePlot this.Background.x=this.Data.x; this.Background.y=this.Data.y; this.Background.plotTrace(this.main_plot); this.Background.setVisible(this.main_plot,1); else warning('Data trace was empty, could not move to background') end end %Callback for clear background button. Clears the background function clearBgCallback(this, ~, ~) this.Background.x=[]; this.Background.y=[]; this.Background.setVisible(this.main_plot,0); end %Callback for LogY button. Sets the YScale to log/lin function logYCallback(this, hObject, ~) if hObject.Value this.main_plot.YScale='Log'; hObject.BackgroundColor=[0,1,0.2]; else this.main_plot.YScale='Linear'; hObject.BackgroundColor=[0.941,0.941,0.941]; end updateAxis(this); updateCursors(this); end %Callback for LogX button. Sets the XScale to log/lin. Updates the %axis and cursors afterwards. function logXCallback(this, hObject, ~) if get(hObject,'Value') set(this.main_plot,'XScale','Log'); set(hObject, 'BackgroundColor',[0,1,0.2]); else set(this.main_plot,'XScale','Linear'); set(hObject, 'BackgroundColor',[0.941,0.941,0.941]); end updateAxis(this); updateCursors(this); end %Base directory callback. Sets the base directory. Also %updates fit objects with the new save directory. function baseDirCallback(this, hObject, ~) this.base_dir=hObject.String; for i=1:length(this.open_fits) this.Fits.(this.open_fits{i}).save_dir=this.save_dir; end end %Callback for session name edit box. Sets the session name. Also %updates fit objects with the new save directory. function sessionNameCallback(this, hObject, ~) this.session_name=hObject.String; for i=1:length(this.open_fits) this.Fits.(this.open_fits{i}).save_dir=this.save_dir; end end %Callback for filename edit box. Sets the file name. Also %updates fit objects with the new file name. function fileNameCallback(this, hObject,~) this.file_name=hObject.String; for i=1:length(this.open_fits) this.Fits.(this.open_fits{i}).save_name=this.file_name; end end %Callback for the analyze menu (popup menu for selecting fits). %Opens the correct MyFit object. function analyzeMenuCallback(this, hObject, ~) analyze_ind=hObject.Value; %Finds the correct fit name by erasing spaces and other %superfluous strings analyze_name=hObject.String{analyze_ind}; analyze_name=erase(analyze_name,'Fit'); analyze_name=erase(analyze_name,'Calibration'); analyze_name=erase(analyze_name,' '); switch analyze_name case {'Linear','Quadratic','Lorentzian','Gaussian',... 'DoubleLorentzian'} openMyFit(this,analyze_name); case 'g0' openMyG(this); case 'Beta' openMyBeta(this); end end function openMyFit(this,fit_name) %Sees if the MyFit object is already open, if it is, changes the %focus to it, if not, opens it. if ismember(fit_name,fieldnames(this.Fits)) %Changes focus to the relevant fit window figure(this.Fits.(fit_name).Gui.Window); else %Gets the data for the fit using the getFitData function %with the vertical cursors DataTrace=getFitData(this,'VertData'); %Makes an instance of MyFit with correct parameters. this.Fits.(fit_name)=MyFit(... 'fit_name',fit_name,... 'enable_plot',1,... 'plot_handle',this.main_plot,... 'Data',DataTrace,... 'save_dir',this.save_dir,... 'save_name',this.file_name); %Sets up a listener for the Deletion event, which %removes the MyFit object from the Fits structure if it is %deleted. this.Listeners.(fit_name).Deletion=... addlistener(this.Fits.(fit_name),'ObjectBeingDestroyed',... @(src, eventdata) deleteFit(this, src, eventdata)); %Sets up a listener for the NewFit. Callback plots the fit %on the main plot. this.Listeners.(fit_name).NewFit=... addlistener(this.Fits.(fit_name),'NewFit',... @(src, eventdata) plotNewFit(this, src, eventdata)); %Sets up a listener for NewInitVal this.Listeners.(fit_name).NewInitVal=... addlistener(this.Fits.(fit_name),'NewInitVal',... @(~,~) updateCursors(this)); end end %Opens MyG class if it is not open. function openMyG(this) if ismember('G0',this.open_fits) figure(this.Fits.G0.Gui.figure1); else MechTrace=getFitData(this,'VertData'); CalTrace=getFitData(this,'VertRef'); this.Fits.G0=MyG('MechTrace',MechTrace,'CalTrace',CalTrace,... 'name','G0'); %Adds listener for object being destroyed this.Listeners.G0.Deletion=addlistener(this.Fits.G0,... 'ObjectBeingDestroyed',... @(~,~) deleteObj(this,'G0')); end end %Opens MyBeta class if it is not open. function openMyBeta(this) if ismember('Beta', this.open_fits) figure(this.Fits.Beta.Gui.figure1); else DataTrace=getFitData(this); this.Fits.Beta=MyBeta('Data',DataTrace); %Adds listener for object being destroyed, to perform cleanup this.Listeners.Beta.Deletion=addlistener(this.Fits.Beta,... 'ObjectBeingDestroyed',... @(~,~) deleteObj(this,'Beta')); end end %Callback for load data button function loadDataCallback(this, ~, ~) if isempty(this.base_dir) warning('Please input a valid folder name for loading a trace'); this.base_dir=pwd; end try [load_name,path_name]=uigetfile('.txt','Select the trace',... this.base_dir); load_path=[path_name,load_name]; dest_trc=this.Gui.DestTrc.String{this.Gui.DestTrc.Value}; loadTrace(this.(dest_trc),load_path); this.(dest_trc).plotTrace(this.main_plot,... 'Color',this.(sprintf('%s_color',lower(dest_trc)))); updateAxis(this); updateCursors(this); catch error('Please select a valid file'); end end end methods (Access=public) %% Listener functions %Callback function for NewFit listener. Plots the fit in the %window using the plotFit function of the MyFit object function plotNewFit(this, src, ~) src.plotFit('Color',this.fit_color); updateAxis(this); updateCursors(this); end %Callback function for the NewData listener function acquireNewData(this, src, ~) hline=getLineHandle(this.Data,this.main_plot); this.Data=copy(src.Trace); if ~isempty(hline); this.Data.hlines{1}=hline; end clearData(src); this.Data.plotTrace(this.main_plot,'Color',this.data_color) updateAxis(this); updateCursors(this); updateFits(this); end %Callback function for MyInstrument ObjectBeingDestroyed listener. %Removes the relevant field from the Instruments struct and deletes %the listeners from the object function deleteInstrument(this, src, ~) %Deletes the object from the Instruments struct tag=getTag(this, src.name); if ismember(tag, this.open_instrs) this.Instruments=rmfield(this.Instruments,tag); end %Deletes the listeners from the Listeners struct deleteListeners(this, tag); end %Callback function for MyFit ObjectBeingDestroyed listener. %Removes the relevant field from the Fits struct and deletes the %listeners from the object. function deleteFit(this, src, ~) %Deletes the object from the Fits struct and deletes listeners deleteObj(this,src.fit_name); %Clears the fits src.clearFit; %Updates cursors since the fits are removed from the plot updateCursors(this); end %Callback function for other analysis method deletion listeners. %Does the same as above. function deleteObj(this,name) if ismember(name,this.open_fits) this.Fits=rmfield(this.Fits,name); end deleteListeners(this, name); end %Listener update function for vertical cursor function vertCursorUpdate(this, src) %Finds the index of the cursor. All cursors are tagged %(name)1, (name)2, e.g. VertData2, ind is the number. ind=str2double(src.Tag(end)); tag=src.Tag(1:(end-1)); %Moves the cursor labels set(this.CrsLabels.(tag){ind},'Position',[src.Location,... this.CrsLabels.(tag){ind}.Position(2),0]); if strcmp(tag,'VertData') %Sets the edit box displaying the location of the cursor this.Gui.(sprintf('EditV%d',ind)).String=... num2str(src.Location); %Sets the edit box displaying the difference in locations this.Gui.EditV2V1.String=... num2str(this.Cursors.VertData{2}.Location-... this.Cursors.VertData{1}.Location); end end %Listener update function for horizontal cursor function horzCursorUpdate(this, src) %Finds the index of the cursor. All cursors are tagged %(name)1, (name)2, e.g. VertData2, ind is the number. ind=str2double(src.Tag(end)); tag=src.Tag(1:(end-1)); %Moves the cursor labels set(this.CrsLabels.(tag){ind},'Position',... [this.CrsLabels.(tag){ind}.Position(1),... src.Location,0]); if strcmp(tag,'HorzData') %Sets the edit box displaying the location of the cursor this.Gui.(sprintf('EditH%d',ind)).String=... num2str(src.Location); %Sets the edit box displaying the difference in locations this.Gui.EditH2H1.String=... num2str(this.Cursors.HorzData{2}.Location-... this.Cursors.HorzData{1}.Location); end end %Function that deletes listeners from the listeners struct, %corresponding to an object of name obj_name function deleteListeners(this, obj_name) %Finds if the object has listeners in the listeners structure if ismember(obj_name, fieldnames(this.Listeners)) %Grabs the fieldnames of the object's listeners structure names=fieldnames(this.Listeners.(obj_name)); for i=1:length(names) %Deletes the listeners delete(this.Listeners.(obj_name).(names{i})); %Removes the field from the structure this.Listeners.(obj_name)=... rmfield(this.Listeners.(obj_name),names{i}); end %Removes the object's field from the structure this.Listeners=rmfield(this.Listeners, obj_name); end end end methods %% Set functions function set.base_dir(this,base_dir) if ~strcmp(base_dir(end),'\') base_dir(end+1)='\'; end this.base_dir=base_dir; end %% Get functions %Get function from save directory function save_dir=get.save_dir(this) save_dir=[this.base_dir,datestr(now,'yyyy-mm-dd '),... this.session_name,'\']; end %Get function for the plot handles function main_plot=get.main_plot(this) if this.enable_gui main_plot=this.Gui.figure1.CurrentAxes; else main_plot=[]; end end %Get function for available instrument tags function instr_tags=get.instr_tags(this) instr_tags=fieldnames(this.InstrList); end %Get function for open fits function open_fits=get.open_fits(this) open_fits=fieldnames(this.Fits); end %Get function for open instrument tags function open_instrs=get.open_instrs(this) open_instrs=fieldnames(this.Instruments); end %Get function for instrument names function instr_names=get.instr_names(this) %Cell of strings is output, so UniformOutput must be 0. instr_names=cellfun(@(x) this.InstrList.(x).name, ... this.instr_tags,'UniformOutput',0); end %Generates appropriate file name for the save file. function savefile=get.savefile(this) if get(this.Gui.AutoName,'Value') date_time = datestr(now,'yyyy-mm-dd_HHMMSS'); else date_time=''; end savefile=[this.file_name,date_time]; end %Get function that displays names of open cursors function open_crs=get.open_crs(this) open_crs=fieldnames(this.Cursors); end end end \ No newline at end of file diff --git a/@MyFit/MyFit.m b/@MyFit/MyFit.m index 931cba5..bc88712 100644 --- a/@MyFit/MyFit.m +++ b/@MyFit/MyFit.m @@ -1,498 +1,499 @@ -classdef MyFit < handle & dynamicprops +classdef MyFit < dynamicprops + %Note that dynamicprops classes are handle classes. properties (Access=public) Data; init_params=[]; scale_init=[]; lim_lower; lim_upper; enable_plot; plot_handle; save_name; save_dir; end properties (GetAccess=public, SetAccess=private) Fit; Gui; Fitdata; FitStruct; coeffs; fit_name='Linear' end properties (Access=private) %Structure used for initializing GUI of userpanel UserGuiStruct; Parser; enable_gui=1; hline_init; end properties (Dependent=true) fit_function; fit_tex; fit_params; fit_param_names; valid_fit_names; n_params; scaled_params; init_param_fun; x_vec; end events NewFit; NewInitVal; end %%Public methods methods (Access=public) %Constructor function function this=MyFit(varargin) createFitStruct(this); createParser(this); parse(this.Parser,varargin{:}); parseInputs(this); if ismember('Data',this.Parser.UsingDefaults) &&... ~ismember('x',this.Parser.UsingDefaults) &&... ~ismember('y',this.Parser.UsingDefaults) this.Data.x=this.Parser.Results.x; this.Data.y=this.Parser.Results.y; end %Sets the scale_init to 1, this is used for the GUI. this.scale_init=ones(1,this.n_params); this.init_params=ones(1,this.n_params); %Creates the structure that contains variables for calibration %of fit results createUserGuiStruct(this); if this.enable_gui; createGui(this); end %If the data is appropriate, generates initial %parameters if validateData(this); genInitParams(this); end end %Deletion function of object function delete(this) if this.enable_gui %Avoids loops set(this.Gui.Window,'CloseRequestFcn',''); %Deletes the figure delete(this.Gui.Window); %Removes the figure handle to prevent memory leaks this.Gui=[]; end if ~isempty(this.hline_init); delete(this.hline_init); end if ~isempty(this.Fit.hlines); delete(this.Fit.hlines{:}); end end %Close figure callback simply calls delete function for class function closeFigure(this,~,~) delete(this); end %Fits the trace using currently set parameters, depending on the %model. function fitTrace(this) this.Fit.x=this.x_vec; switch this.fit_name case 'Linear' %Fits polynomial of order 1 this.coeffs=polyfit(this.Data.x,this.Data.y,1); this.Fit.y=polyval(this.coeffs,this.Fit.x); 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 {'Exponential','Gaussian','Lorentzian',... 'DoubleLorentzian'} doFit(this); otherwise error('Selected fit is invalid'); end %Sets the new initial parameters to be the fitted parameters this.init_params=this.coeffs; %Resets the scale variables for the GUI this.scale_init=ones(1,this.n_params); %Updates the gui if it is enabled if this.enable_gui; 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 function createUserGuiStruct(this) switch this.fit_name case 'Lorentzian' addUserField(this,'Mech','MechLw','Linewidth (Hz)',1,'off') addUserField(this,'Mech','Q','Qualify Factor',1,'off') addUserField(this,'Mech','Freq','Frequency (MHz)',1,'on') this.UserGuiStruct.Mech.tab_title='Mech.'; addUserField(this,'Opt','Spacing','Line Spacing',1,'on'); addUserField(this,'Opt','LineNo','Number of lines',1,'on'); addUserField(this,'Opt','OptLw','Linewidth (MHz)',1,'off'); this.UserGuiStruct.Opt.tab_title='Optical'; otherwise this.UserGuiStruct=struct(); end end function addUserField(this, parent, tag, title, ... init_val,change_flag) this.UserGuiStruct.(parent).(tag).title=title; this.UserGuiStruct.(parent).(tag).init_val=init_val; this.UserGuiStruct.(parent).(tag).change_flag=change_flag; %Adds the new property to the class addUserProp(this, tag); end 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 function val=getUserVal(this, tag) val=str2double(this.Gui.([tag,'Edit']).String); end function setUserVal(this, val, tag) this.Gui.([tag,'Edit']).String=num2str(val); end %% Callbacks %Save function callback function saveCallback(this,~,~) assert(~isempty(this.save_dir),'Save directory is not specified'); assert(ischar(this.save_dir),... ['Save directory is not specified.',... ' Should be of type char but is %s.'], ... class(this.save_dir)) try this.Fit.save('name',this.save_name,... 'save_dir',this.save_dir) catch error(['Attempted to save to directory %s',... ' with file name %s, but failed'],this.save_dir,... this.save_name); 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. function sliderCallback(this, param_ind, hObject, ~) %Gets the value from the slider scale=get(hObject,'Value'); %Updates the scale with a new value this.scale_init(param_ind)=10^((scale-50)/50); %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.scaled_params(param_ind))); if this.enable_plot; plotInitFun(this); end end %Callback function for edit boxes in GUI function editCallback(this, hObject, ~) init_param=str2double(get(hObject,'String')); tag=get(hObject,'Tag'); %Finds the index where the fit_param name begins (convention is %after the underscore) fit_param=tag((strfind(tag,'_')+1):end); param_ind=strcmp(fit_param,this.fit_params); %Updates the slider to be such that the scaling is 1 set(this.Gui.(sprintf('Slider_%s',fit_param)),... '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); end %Callback function for analyze button in GUI. Checks if the data is %ready for fitting. function analyzeCallback(this, ~, ~) assert(validateData(this),... ['The length of x is %d and the length of y is',... ' %d. The lengths must be equal and greater than ',... 'the number of fit parameters to perform a fit'],... length(this.Data.x),length(this.Data.y)) fitTrace(this); end %Callback for clearing the fits on the axis. function clearFitCallback(this,~,~) clearFit(this); end %Callback function for generate init parameters button. Updates GUI %afterwards function initParamCallback(this,~,~) genInitParams(this); updateGui(this); 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 'DoubleLorentzian' [params{1},params{2},params{3}]=... initParamDblLorentzian(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_gui; plotInitFun(this); end end %Plots the trace contained in the Fit MyTrace object. function plotFit(this,varargin) this.Fit.plotTrace(this.plot_handle,varargin{:}); end %Clears the plots function clearFit(this) cellfun(@(x) delete(x), this.Fit.hlines); delete(this.hline_init); this.hline_init=[]; this.Fit.hlines={}; 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.scaled_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); else set(this.hline_init,'XData',this.x_vec,'YData',y_vec); end end end 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 edit box inside a UnitDisp for showing label and value of %a quantity. Used in conjunction with createUnitBox createUnitDisp(this,varargin); %Creates parser for constructor function createParser(this) p=inputParser; addParameter(p,'fit_name','Linear',@ischar) addParameter(p,'Data',MyTrace()); addParameter(p,'Fit',MyTrace()); addParameter(p,'x',[]); addParameter(p,'y',[]); addParameter(p,'enable_gui',1); addParameter(p,'enable_plot',0); addParameter(p,'plot_handle',[]); addParameter(p,'save_dir',[]); addParameter(p,'save_name',[]); this.Parser=p; end %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) %Fits with the below properties. Chosen for maximum accuracy. this.Fitdata=fit(this.Data.x,this.Data.y,this.fit_function,... 'Lower',this.lim_lower,'Upper',this.lim_upper,... 'StartPoint',this.init_params, .... 'MaxFunEvals',2000,'MaxIter',2000,'TolFun',1e-9); %Puts the y values of the fit into the struct. this.Fit.y=this.Fitdata(this.Fit.x); %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 function triggerNewInitVal(this) notify(this,'NewInitVal'); end %Creates the struct used to get all things relevant to the fit %model 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,'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'}); end %Updates the GUI if the edit or slider boxes are changed from %elsewhere. function updateGui(this) %Converts the scale variable to the value between 0 and 100 %necessary for the slider slider_vals=50*log10(this.scale_init)+50; for i=1:this.n_params set(this.Gui.(sprintf('Edit_%s',this.fit_params{i})),... 'String',sprintf('%3.3e',this.scaled_params(i))); set(this.Gui.(sprintf('Slider_%s',this.fit_params{i})),... 'Value',slider_vals(i)); end end %Adds a fit to the list of fits 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,','),')']; anon_fit_fun=str2func(vectorize([args,fit_function])); this.FitStruct.(fit_name).anon_fit_fun=anon_fit_fun; 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 %% Get and set functions methods %% Set functions %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 %% Get functions for dependent variables %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 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 scaled initial parameters function scaled_params=get.scaled_params(this) scaled_params=this.scale_init.*this.init_params; 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),1000); end end end \ No newline at end of file diff --git a/@MyInstrument/MyInstrument.m b/@MyInstrument/MyInstrument.m index 921f65d..99a2d32 100644 --- a/@MyInstrument/MyInstrument.m +++ b/@MyInstrument/MyInstrument.m @@ -1,236 +1,305 @@ -classdef MyInstrument < handle +classdef MyInstrument < dynamicprops properties (SetAccess=protected, GetAccess=public) name=''; interface=''; address=''; %Logical for whether gui is enabled enable_gui=false; %Contains the GUI handles Gui; %Contains the device object Device; %Input parser for class constructor Parser; %Contains a list of the commands available for the instrument as %well as the default values and input requirements CommandList; %Parses commands using an inputParser object CommandParser; %Trace object for storing data Trace=MyTrace(); end properties (Dependent=true) command_names; command_no; end events NewData; end methods (Access=public) function this=MyInstrument(name, interface, address, varargin) createParser(this); parse(this.Parser,name,interface,address,varargin{:}); %Loads parsed variables into class properties this.name=this.Parser.Results.name; this.interface=this.Parser.Results.interface; this.address=this.Parser.Results.address; this.enable_gui=~ismember('gui',this.Parser.UsingDefaults); %If a gui input is given, load the gui if this.enable_gui %Loads the gui from the input gui string this.Gui=guihandles(eval(this.Parser.Results.gui)); %Sets figure close function such that class will know when %figure is closed set(this.Gui.figure1, 'CloseRequestFcn',... @(hObject,eventdata) closeFigure(this, hObject, ... eventdata)); end end function delete(this) %Removes close function from figure, prevents infinite loop if this.enable_gui set(this.Gui.figure1,'CloseRequestFcn',''); %Deletes the figure handles structfun(@(x) delete(x), this.Gui); %Removes the figure handle to prevent memory leaks this.Gui=[]; end %Closes the connection to the device closeDevice(this); %Deletes the device object delete(this.Device); clear('this.Device'); end %Clears data from trace to save memory. function clearData(this) this.Trace.x=[]; this.Trace.y=[]; - end + end + %Writes properties to device. Can take multiple inputs. With the + %option init_device, the function writes default to all the + %available writeable parameters. function writeProperty(this, varargin) %Parses the inputs using the CommandParser parse(this.CommandParser, varargin{:}); - - %Finds the commands that are supplied by the user - ind=~ismember(this.CommandParser.Parameters,... - this.CommandParser.UsingDefaults); - %Creates a list of commands to be executed - exec=this.CommandParser.Parameters(ind); + + if ~this.CommandParser.Results.write_all_defaults + %Finds the writeable commands that are supplied by the user + ind=~ismember(this.CommandParser.Parameters,... + this.CommandParser.UsingDefaults); + %Creates a list of commands to be executed + exec=this.CommandParser.Parameters(ind); + else + exec=this.CommandParser.Parameters; + %Removes write_all_defaults from the list of commands to be + %executed + exec(strcmp(exec,'write_all_defaults'))=[]; + end for i=1:length(exec) - command=sprintf(this.CommandList.(exec{i}).command,... - this.CommandParser.Results.(exec{i})); + %Creates the write command using the right string spec + write_command=[this.CommandList.(exec{i}).command,... + ' %',this.CommandList.(exec{i}).str_spec]; + %Gets the value to write to the device + this.(exec{i})=this.CommandParser.Results.(exec{i}); + command=sprintf(write_command, this.(exec{i})); + %Sends command to device fprintf(this.Device, command); end end + %Wrapper for writeProperty that opens the device + function writePropertyHedged(this, varargin) + this.openDevice(); + try + this.writeProperty(varargin{:}); + catch + disp('Error while writing the properties:'); + disp(varargin); + end + this.readAll(); + closeDevice(this); + end + function result=readProperty(this, varargin) result=struct(); - for i=1:length(varargin) - %Finds the index of the % sign which indicates where the value - %to be written is supplied - ind=strfind(this.CommandList.(varargin{i}).command,'%'); - if ~any(ind) - error('%s is not a valid tag for a command in %s',... - varargin{i},class(this)); - end - + ind=cellfun(@(x) contains(this.CommandList.(x).access,'r'),... + varargin); + + exec=varargin(ind); + + if any(~ind) + disp('Some specified properties are write-only:') + non_exec=varargin(~ind); + disp(non_exec{:}); + end + + + for i=1:length(exec) %Creates the correct read command - read_command=... - [this.CommandList.(varargin{i}).command(1:(ind-2)),'?']; + read_command=[this.CommandList.(exec{i}).command,'?']; + %Reads the property from the device and stores it in the %correct place - result.(varargin{i})=... - str2double(query(this.Device,read_command)); + res_str = query(this.Device,read_command); + if strcmp(this.CommandList.(exec{i}).attributes{1},'string') + result.(exec{i})= res_str(1:(end-1)); + else + result.(exec{i})= str2double(res_str); + end + end + end + + function result = readPropertyHedged(this, varargin) + this.openDevice(); + try + result = this.readProperty(varargin{:}); + catch + disp('Error while reading the properties:'); + disp(varargin); + end + this.closeDevice(); + end + + % Execute all the read commands and update corresponding properties + function readAll(this) + ind=cellfun(@(x) contains(this.CommandList.(x).access,'r'),... + this.command_names); + result=readProperty(this, this.command_names{ind}); + res_names=fieldnames(result); + for i=1:length(res_names) + this.(res_names{i})=result.(res_names{i}); end end end methods (Access=private) function createParser(this) p=inputParser; addRequired(p,'name',@ischar); addRequired(p,'interface',@ischar); addRequired(p,'address',@ischar); addParameter(p,'gui','placeholder',@ischar); this.Parser=p; end end methods (Access=protected) %Triggers event for acquired data function triggerNewData(this) notify(this,'NewData') end %Checks if the connection to the device is open function bool=isopen(this) bool=strcmp(this.Device.Status, 'open'); end %Adds a command to the CommandList function addCommand(this, tag, command, varargin) p=inputParser; addRequired(p,'tag',@ischar); addRequired(p,'command',@ischar); addParameter(p,'default','placeholder'); - addParameter(p,'attributes','placeholder',@iscell) + addParameter(p,'attributes','placeholder',@iscell); + addParameter(p,'str_spec','d',@ischar); %If the write flag is on, it means this command can be used to %write a parameter to the device - addParameter(p,'write_flag',false,@islogical) - + addParameter(p,'access','rw',@ischar); + addParameter(p,'conv_factor',1,@isnumeric); parse(p,tag,command,varargin{:}); - if ~isprop(this, tag) && p.Results.write_flag - error('All commands must have a tag matching the property they modify') - end - + %Adds the command to be sent to the device this.CommandList.(tag).command=command; - this.CommandList.(tag).write_flag=p.Results.write_flag; + this.CommandList.(tag).access=p.Results.access; + + write_flag=contains(p.Results.access,'w'); +% read_flag=contains(p.Results.access,'r'); %Adds a default value and the attributes the inputs must have - if p.Results.write_flag + %and creates a new property in the class + if write_flag + %Adds the string specifier to the list + this.CommandList.(tag).str_spec=p.Results.str_spec; %Adds the default value this.CommandList.(tag).default=p.Results.default; %Adds the necessary attributes for the input to the command this.CommandList.(tag).attributes=p.Results.attributes; + %Adds a conversion factor for displaying the value + this.CommandList.(tag).conv_factor=p.Results.conv_factor; + %Adds a property to the class corresponding to the tag + addprop(this,tag); end end %Creates inputParser using the command list function createCommandParser(this) %Use input parser %Requires input of the appropriate class p=inputParser; p.StructExpand=0; + %Flag for whether the command should initialize the device with + %defaults + addParameter(p, 'write_all_defaults',false,@islogical); for i=1:this.command_no %Adds optional inputs for each command, with the %appropriate default value from the command list and the %required attributes for the command input. addParameter(p, this.command_names{i},... this.CommandList.(this.command_names{i}).default),... @(x) validateattributes(x,... this.CommandList.(this.command_names{i}).attributes{1:end}); end this.CommandParser=p; end %Connects to the device if it is not connected function openDevice(this) if ~isopen(this) try fopen(this.Device); catch try instr_list=instrfind('RemoteHost',this.address); fclose(instr_list); fopen(this.Device); warning('Multiple instrument objects of address %s exist',... this.address); catch error('Could not open device') end end end end %Closes the connection to the device function closeDevice(this) if isopen(this) try fclose(this.Device); catch error('Could not close device') end end end %Close figure callback simply calls delete function for class function closeFigure(this,~,~) delete(this); end end %% Get functions methods function command_names=get.command_names(this) command_names=fieldnames(this.CommandList); end function command_no=get.command_no(this) command_no=length(this.command_names); end end end \ No newline at end of file diff --git a/@MyRsa/MyRsa.m b/@MyRsa/MyRsa.m index 4cd415d..e3a1966 100644 --- a/@MyRsa/MyRsa.m +++ b/@MyRsa/MyRsa.m @@ -1,354 +1,370 @@ classdef MyRsa < MyInstrument properties (SetAccess=protected, GetAccess=public) - rbw; - start_freq; - stop_freq; - cent_freq; - span; - average_no; - point_no; - enable_avg; - read_cont; valid_points; end properties (Dependent=true) freq_vec; end %% Constructor and destructor methods (Access=public) function this=MyRsa(name,interface, address,varargin) this@MyInstrument(name, interface, address,varargin{:}); if this.enable_gui; initGui(this); end + createCommandList(this); + createCommandParser(this); %Valid point numbers for Tektronix 5103 and 5106. %Depends on the RSA. Remove this in the future. this.valid_points=[801,2401,4001,10401]; - - createCommandList(this); - createCommandParser(this); switch interface case 'TCPIP' connectTCPIP(this); end %Tests if device is working. try openDevice(this); closeDevice(this); catch error(['Failed to open communications with device.',... ' Check that the address and interface is correct.',... ' Currently the address is %s and the interface is ',... '%s.'],this.address,this.interface) end + + if this.enable_gui; setGuiProps(this); end %Opens communications openDevice(this); %Finds the current status of the device - readStatus(this); - %Initializes the device - initDevice(this); + readAll(this); + %Writes default parameters to the device + writeProperty(this,'write_all_defaults',true); closeDevice(this); end end %% Private functions methods (Access=private) function connectTCPIP(this) buffer = 1000 * 1024; visa_brand = 'ni'; visa_address_rsa = sprintf('TCPIP0::%s::inst0::INSTR',... this.address); this.Device=visa(visa_brand, visa_address_rsa,... 'InputBufferSize', buffer,... 'OutputBufferSize', buffer); set(this.Device,'InputBufferSize',1e6); set(this.Device,'Timeout',10); end + function setGuiProps(this) + prop_names=fieldnames(this.CommandList); + for i=1:length(prop_names) + tag=prop_names{i}; + h_prop=findprop(this,tag); + h_prop.GetMethod=@(this) getInstrProp(this, tag); + h_prop.SetMethod=@(this, val) setInstrProp(this, tag, val); + h_prop.Dependent=true; + end + end + + function setInstrProp(this, tag, val) + switch this.Gui.(tag).Style + case 'edit' + this.Gui.(tag).String=... + num2str(val/this.CommandList.(tag).conv_factor); + case 'checkbox' + this.Gui.(tag).Value=val; + case 'popupmenu' + ind=find(strcmp(this.Gui.(tag).String,num2str(val))); + this.Gui.(tag).Value=ind; + otherwise + error('No appropriate GUI field was found for %s',tag); + end + end + + function val=getInstrProp(this, tag) + switch this.Gui.(tag).Style + case 'edit' + val=str2double(this.Gui.(tag).String)*... + this.CommandList.(tag).conv_factor; + case 'checkbox' + val=this.Gui.(tag).Value; + case 'popupmenu' + val=str2double(this.Gui.(tag).String(this.Gui.(tag).Value)); + otherwise + error('No appropriate GUI field was found for %s',tag); + end + end + function initGui(this) this.Gui.Title.String{1}=sprintf('Real-Time Signal Analyzer %s',... this.name); this.Gui.reinit.Callback=@(hObject, eventdata)... reinitCallback(this, hObject,eventdata); this.Gui.point_no.Callback=@(hObject, eventdata)... point_noCallback(this, hObject,eventdata); this.Gui.start_freq.Callback=@(hObject, eventdata)... start_freqCallback(this, hObject, eventdata); this.Gui.stop_freq.Callback=@(hObject, eventdata)... stop_freqCallback(this, hObject,eventdata); this.Gui.cent_freq.Callback=@(hObject, eventdata)... cent_freqCallback(this, hObject, eventdata); this.Gui.span.Callback=@(hObject, eventdata)... spanCallback(this, hObject, eventdata); this.Gui.rbw.Callback=@(hObject, eventdata)... rbwCallback(this, hObject, eventdata); this.Gui.fetch_single.Callback=@(hObject, eventdata)... fetchCallback(this, hObject,eventdata); this.Gui.average_no.Callback=@(hObject, eventdata)... average_noCallback(this, hObject,eventdata); this.Gui.enable_avg.Callback=@(hObject, eventdata)... enable_avgCallback(this, hObject,eventdata); end - function initDevice(this) - for i=1:this.command_no - if this.CommandList.(this.command_names{i}).write_flag - fprintf(this.Device, ... - sprintf(this.CommandList.(this.command_names{i}).command,... - this.CommandList.(this.command_names{i}).default)); - this.(this.command_names{i})=... - this.CommandList.(this.command_names{i}).default; - end - end - end + function createCommandList(this) - addCommand(this,'average_no','TRAC3:DPSA:AVER:COUN %d',... - 'default',1,'attributes',{{'numeric'}},'write_flag',true); - addCommand(this, 'rbw','DPSA:BAND:RES %d Hz',... - 'default',1e3,'attributes',{{'numeric'}},'write_flag',true); - addCommand(this, 'span', 'DPSA:FREQ:SPAN %d Hz',... - 'default',1e6,'attributes',{{'numeric'}},'write_flag',true); - addCommand(this, 'start_freq','DPSA:FREQ:STAR %d Hz',... - 'default',1e6,'attributes',{{'numeric'}},'write_flag',true); - addCommand(this, 'stop_freq','DPSA:FREQ:STOP %d Hz',... - 'default',2e6,'attributes',{{'numeric'}},'write_flag',true); - addCommand(this, 'cent_freq','DPSA:FREQ:CENT %d Hz',... - 'default',1.5e6,'attributes',{{'numeric'}},'write_flag',true); - addCommand(this, 'point_no','DPSA:POIN:COUN P%i',... - 'default',10401,'attributes',{{'numeric'}},'write_flag',true); - addCommand(this,'enable_avg','TRAC3:DPSA:COUN:ENABLE %d',... - 'default',0,'attributes',{{'numeric'}},'write_flag',true); - addCommand(this,'read_cont','INIT:CONT %s','default','on',... - 'attributes',{{'char'}},'write_flag',true); + addCommand(this,'average_no','TRAC3:DPSA:AVER:COUN',... + 'default',1,'attributes',{{'numeric'}}); + addCommand(this, 'rbw','DPSA:BAND:RES',... + 'default',1e3,'attributes',{{'numeric'}},... + 'conv_factor',1e3); + addCommand(this, 'span', 'DPSA:FREQ:SPAN',... + 'default',1e6,'attributes',{{'numeric'}},... + 'conv_factor',1e6); + addCommand(this, 'start_freq','DPSA:FREQ:STAR',... + 'default',1e6,'attributes',{{'numeric'}},... + 'conv_factor',1e6); + addCommand(this, 'stop_freq','DPSA:FREQ:STOP',... + 'default',2e6,'attributes',{{'numeric'}},... + 'conv_factor',1e6); + addCommand(this, 'cent_freq','DPSA:FREQ:CENT',... + 'default',1.5e6,'attributes',{{'numeric'}},... + 'conv_factor',1e6); + addCommand(this, 'point_no','DPSA:POIN:COUN P',... + 'default',10401,'attributes',{{'numeric'}},'access','w'); + addCommand(this, 'enable_avg','TRAC3:DPSA:COUN:ENABLE',... + 'default',0,'attributes',{{'numeric'}}); + addCommand(this, 'read_cont','INIT:CONT','default',1,... + 'attributes',{{'numeric'}}); end end %% Public functions including callbacks methods (Access=public) - function readStatus(this) - result=readProperty(this,'rbw','cent_freq','span','start_freq',... - 'stop_freq','enable_avg'); - res_names=fieldnames(result); - for i=1:length(res_names) - this.(res_names{i})=result.(res_names{i}); - end - end function reinitDevice(this) openDevice(this); - readStatus(this); - initDevice(this); - writeProperty(this, 'read_cont','on') + readAll(this); + writeProperty(this, 'read_cont',1) closeDevice(this); end function readSingle(this) openDevice(this); fwrite(this.Device, 'fetch:dpsa:res:trace3?'); data = binblockread(this.Device,'float'); %Reads status at the end. - readStatus(this); + readAll(this); closeDevice(this); - x=this.freq_vec/1e6; - unit_x='MHz'; - name_x='Frequency'; + x_vec=this.freq_vec/1e6; %Calculates the power spectrum from the data, which is in dBm. %Output is in V^2/Hz power_spectrum = (10.^(data/10))/this.rbw*50*0.001; %Trace object is created containing the data and its units - setTrace(this.Trace,'name','RsaData','x',x,'y',power_spectrum,'unit_y',... - '$\mathrm{V}^2/\mathrm{Hz}$','name_y','Power','unit_x',... - unit_x,'name_x',name_x); + setTrace(this.Trace,'name','RsaData','x',x_vec,'y',power_spectrum,... + 'unit_y','$\mathrm{V}^2/\mathrm{Hz}$','name_y','Power',... + 'unit_x','MHz','name_x','Frequency'); %Trigger acquired data event (inherited from MyInstrument) triggerNewData(this); end function reinitCallback(this, hObject, ~) reinitDevice(this); %Turns off indicator set(hObject,'Value',0); end function point_noCallback(this, hObject, ~) value_list=get(hObject,'String'); this.point_no=str2double(value_list{get(hObject,'Value')}); openDevice(this); writeProperty(this,'point_no',this.point_no); - readStatus(this); + readAll(this); closeDevice(this); end function start_freqCallback(this, hObject, ~) this.start_freq=str2double(get(hObject,'String'))*1e6; openDevice(this); writeProperty(this,'start_freq',this.start_freq); - readStatus(this); + readAll(this); closeDevice(this); end function stop_freqCallback(this, hObject, ~) this.stop_freq=str2double(get(hObject,'String'))*1e6; openDevice(this); writeProperty(this,'stop_freq',this.stop_freq); - readStatus(this); + readAll(this); closeDevice(this); end function cent_freqCallback(this, hObject, ~) this.cent_freq=str2double(get(hObject,'String'))*1e6; openDevice(this); writeProperty(this,'cent_freq',this.cent_freq); - readStatus(this); + readAll(this); closeDevice(this); end function spanCallback(this, hObject, ~) this.span=str2double(get(hObject,'String'))*1e6; openDevice(this); writeProperty(this,'span',this.span); - readStatus(this) + readAll(this) closeDevice(this); end function rbwCallback(this, hObject, ~) this.rbw=str2double(get(hObject,'String'))*1e3; openDevice(this); writeProperty(this,'rbw',this.rbw); closeDevice(this); end function average_noCallback(this, hObject, ~) this.average_no=str2double(get(hObject,'String')); %Writes the average_no to the device only if averaging is %enabled openDevice(this); writeProperty(this,'average_no',this.average_no); closeDevice(this); end function fetchCallback(this, hObject, ~) %Fetches the data using the settings given. This function can %in principle be used in the future to add further fetch %functionality. switch get(hObject,'Tag') case 'fetch_single' readSingle(this) end set(this.Gui.fetch_single,'Value',0); end function enable_avgCallback(this, hObject, ~) this.enable_avg=get(hObject,'Value'); openDevice(this) writeProperty(this,'enable_avg',this.enable_avg); closeDevice(this); end end - %% Set functions - methods - %Set function for central frequency, changes gui to show central - %frequency in MHz - function set.cent_freq(this, cent_freq) - this.cent_freq=cent_freq; - if this.enable_gui - set(this.Gui.cent_freq,'String',this.cent_freq/1e6); - end - end - - %Set function for rbw, changes gui to show rbw in kHz - function set.rbw(this, rbw) - assert(isnumeric(rbw) && rbw>0,'RBW must be a positive double'); - this.rbw=rbw; - if this.enable_gui - set(this.Gui.rbw,'String',this.rbw/1e3); - end - end - - %Set function for enable_avg, changes gui - function set.enable_avg(this, enable_avg) - assert(isnumeric(enable_avg),... - 'Flag for averaging must be a number') - assert(enable_avg==1 || enable_avg==0,... - 'Flag for averaging must be 0 or 1') - this.enable_avg=enable_avg; - if this.enable_gui - set(this.Gui.enable_avg,'Value',this.enable_avg) - end - end - - %Set function for span, changes gui to show span in MHz - function set.span(this, span) - assert(isnumeric(span) && span>0,... - 'Span must be a positive number'); - this.span=span; - if this.enable_gui - set(this.Gui.span,'String',this.span/1e6); - end - end - - - %Set function for start frequency, changes gui to show start - %frequency in MHz - function set.start_freq(this, start_freq) - assert(isnumeric(start_freq),'Start frequency must be a number'); - this.start_freq=start_freq; - if this.enable_gui - set(this.Gui.start_freq,'String',this.start_freq/1e6); - end - end - - %Set function for stop frequency, changes gui to show stop - %frequency in MHz - function set.stop_freq(this, stop_freq) - assert(isnumeric(stop_freq),... - 'Stop frequency must be a number'); - this.stop_freq=stop_freq; - if this.enable_gui - set(this.Gui.stop_freq,'String',this.stop_freq/1e6) - end - end - - %Set function for average number, also changes GUI - function set.average_no(this, average_no) - assert(isnumeric(average_no),'Number of averages must be a number') - assert(logical(mod(average_no,1))==0 && average_no>0,... - 'Number of averages must be a positive integer') - this.average_no=average_no; - if this.enable_gui - set(this.Gui.average_no,'String',this.average_no); - end - end - - %Set function for point number, checks it is valid and changes GUI - function set.point_no(this, point_no) - if ismember(point_no,this.valid_points) - this.point_no=point_no; - if this.enable_gui - ind=strcmp(get(this.Gui.point_no,'String'),... - num2str(point_no)); - set(this.Gui.point_no,'Value',find(ind)); - end - else - error('Invalid number of points chosen for RSA') - end - end - end - +% %% Set functions +% methods +% %Set function for central frequency, changes gui to show central +% %frequency in MHz +% function set.cent_freq(this, cent_freq) +% this.cent_freq=cent_freq; +% if this.enable_gui +% set(this.Gui.cent_freq,'String',this.cent_freq/1e6); +% end +% end +% +% %Set function for rbw, changes gui to show rbw in kHz +% function set.rbw(this, rbw) +% assert(isnumeric(rbw) && rbw>0,'RBW must be a positive double'); +% this.rbw=rbw; +% if this.enable_gui +% set(this.Gui.rbw,'String',this.rbw/1e3); +% end +% end +% +% %Set function for enable_avg, changes gui +% function set.enable_avg(this, enable_avg) +% assert(isnumeric(enable_avg),... +% 'Flag for averaging must be a number') +% assert(enable_avg==1 || enable_avg==0,... +% 'Flag for averaging must be 0 or 1') +% this.enable_avg=enable_avg; +% if this.enable_gui +% set(this.Gui.enable_avg,'Value',this.enable_avg) +% end +% end +% +% %Set function for span, changes gui to show span in MHz +% function set.span(this, span) +% assert(isnumeric(span) && span>0,... +% 'Span must be a positive number'); +% this.span=span; +% if this.enable_gui +% set(this.Gui.span,'String',this.span/1e6); +% end +% end +% +% +% %Set function for start frequency, changes gui to show start +% %frequency in MHz +% function set.start_freq(this, start_freq) +% assert(isnumeric(start_freq),'Start frequency must be a number'); +% this.start_freq=start_freq; +% if this.enable_gui +% set(this.Gui.start_freq,'String',this.start_freq/1e6); +% end +% end +% +% %Set function for stop frequency, changes gui to show stop +% %frequency in MHz +% function set.stop_freq(this, stop_freq) +% assert(isnumeric(stop_freq),... +% 'Stop frequency must be a number'); +% this.stop_freq=stop_freq; +% if this.enable_gui +% set(this.Gui.stop_freq,'String',this.stop_freq/1e6) +% end +% end +% +% %Set function for average number, also changes GUI +% function set.average_no(this, average_no) +% assert(isnumeric(average_no),'Number of averages must be a number') +% assert(logical(mod(average_no,1))==0 && average_no>0,... +% 'Number of averages must be a positive integer') +% this.average_no=average_no; +% if this.enable_gui +% set(this.Gui.average_no,'String',this.average_no); +% end +% end +% +% %Set function for point number, checks it is valid and changes GUI +% function set.point_no(this, point_no) +% if ismember(point_no,this.valid_points) +% this.point_no=point_no; +% if this.enable_gui +% ind=strcmp(get(this.Gui.point_no,'String'),... +% num2str(point_no)); +% set(this.Gui.point_no,'Value',find(ind)); +% end +% else +% error('Invalid number of points chosen for RSA') +% end +% end +% end +% %% Get functions methods %Generates a vector of frequencies between the start and stop %frequency of length equal to the point number function freq_vec=get.freq_vec(this) freq_vec=linspace(this.start_freq,this.stop_freq,... this.point_no) ; end end end diff --git a/GUIs/GuiRsa.fig b/GUIs/GuiRsa.fig index 7e3356b..42d3421 100644 Binary files a/GUIs/GuiRsa.fig and b/GUIs/GuiRsa.fig differ diff --git a/GUIs/GuiRsa.m b/GUIs/GuiRsa.m index f262678..d720c36 100644 --- a/GUIs/GuiRsa.m +++ b/GUIs/GuiRsa.m @@ -1,101 +1,110 @@ function varargout = GuiRsa(varargin) % GuiRsa MATLAB code for GuiRsa.fig % GuiRsa, by itself, creates a new GuiRsa or raises the existing % singleton*. % % H = GuiRsa returns the handle to a new GuiRsa or the handle to % the existing singleton*. % % GuiRsa('CALLBACK',hObject,eventData,handles,...) calls the local % function named CALLBACK in GuiRsa.M with the given input arguments. % % GuiRsa('Property','Value',...) creates a new GuiRsa or raises the % existing singleton*. Starting from the left, property value pairs are % applied to the GUI before GuiRsa_OpeningFcn gets called. An % unrecognized property name or invalid value makes property application % stop. All inputs are passed to GuiRsa_OpeningFcn via varargin. % % *See GUI Options on GUIDE's Tools menu. Choose "GUI allows only one % instance to run (singleton)". % % See also: GUIDE, GUIDATA, GUIHANDLES % Edit the above text to modify the response to help GuiRsa -% Last Modified by GUIDE v2.5 10-Nov-2017 15:10:21 +% Last Modified by GUIDE v2.5 17-Nov-2017 13:20:09 % Begin initialization code - DO NOT EDIT gui_Singleton = 1; gui_State = struct('gui_Name', mfilename, ... 'gui_Singleton', gui_Singleton, ... 'gui_OpeningFcn', @GuiRsa_OpeningFcn, ... 'gui_OutputFcn', @GuiRsa_OutputFcn, ... 'gui_LayoutFcn', [] , ... 'gui_Callback', []); if nargin && ischar(varargin{1}) gui_State.gui_Callback = str2func(varargin{1}); end if nargout [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:}); else gui_mainfcn(gui_State, varargin{:}); end % End initialization code - DO NOT EDIT % --- Executes just before GuiRsa is made visible. function GuiRsa_OpeningFcn(hObject, eventdata, handles, varargin) handles.output=hObject; guidata(hObject, handles); % --- Outputs from this function are returned to the command line. function varargout = GuiRsa_OutputFcn(hObject, eventdata, handles) varargout{1}=handles.output; % --- Executes during object creation, after setting all properties. function cent_freq_CreateFcn(hObject, eventdata, handles) if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) set(hObject,'BackgroundColor','white'); end % --- Executes during object creation, after setting all properties. function span_CreateFcn(hObject, eventdata, handles) if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) set(hObject,'BackgroundColor','white'); end % --- Executes during object creation, after setting all properties. function start_freq_CreateFcn(hObject, eventdata, handles) if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) set(hObject,'BackgroundColor','white'); end % --- Executes during object creation, after setting all properties. function rbw_CreateFcn(hObject, eventdata, handles) if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) set(hObject,'BackgroundColor','white'); end % --- Executes during object creation, after setting all properties. function average_no_CreateFcn(hObject, eventdata, handles) if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) set(hObject,'BackgroundColor','white'); end % --- Executes during object creation, after setting all properties. function stop_freq_CreateFcn(hObject, eventdata, handles) if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) set(hObject,'BackgroundColor','white'); end % --- Executes during object creation, after setting all properties. function point_no_CreateFcn(hObject, eventdata, handles) if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor')) set(hObject,'BackgroundColor','white'); end % --- Executes when user attempts to close figure1. function figure1_CloseRequestFcn(hObject, eventdata, handles) + + +% --- Executes on button press in read_cont. +function read_cont_Callback(hObject, eventdata, handles) +% hObject handle to read_cont (see GCBO) +% eventdata reserved - to be defined in a future version of MATLAB +% handles structure with handles and user data (see GUIDATA) + +% Hint: get(hObject,'Value') returns toggle state of read_cont