diff --git a/@MyDaq/MyDaq.m b/@MyDaq/MyDaq.m index d6c8730..489f3d6 100644 --- a/@MyDaq/MyDaq.m +++ b/@MyDaq/MyDaq.m @@ -1,935 +1,937 @@ +% Acquisition and analysis program that receives data from Collector. Can +% also be used for analysis of previously acquired traces. classdef MyDaq < handle properties %Global variable with Daq name is cleared on exit. global_name; %Contains GUI handles Gui; %Contains Reference trace (MyTrace object) Ref=MyTrace(); %Contains Data trace (MyTrace object) Data=MyTrace(); %Contains Background trace (MyTrace object) Background=MyTrace(); %List of all the programs with run files ProgramList=struct(); %Struct containing Cursor objects Cursors=struct(); %Struct containing Cursor labels CrsLabels=struct(); %Struct containing MyFit objects Fits=struct(); %Input parser for class constructor ConstructionParser; %Struct for listeners Listeners=struct(); %Sets the colors of fits, data and reference fit_color='k'; data_color='b'; ref_color='r'; bg_color='c'; end properties (Dependent=true) save_dir; main_plot; open_fits; open_crs; end properties (Dependent=true, SetAccess=private, GetAccess=public) %Properties for saving files base_dir; session_name; filename; end methods (Access=public) %% Class functions %Constructor function function this=MyDaq(varargin) p=inputParser; addParameter(p,'global_name','',@ischar); addParameter(p,'collector_handle',[]); this.ConstructionParser=p; parse(p, varargin{:}); this.global_name = p.Results.global_name; %Sets a listener to the collector if ~isempty(p.Results.collector_handle) this.Listeners.Collector.NewDataWithHeaders=... addlistener(p.Results.collector_handle,... 'NewDataWithHeaders',... @(~,eventdata) acquireNewData(this,eventdata)); else errordlg(['No collector handle was supplied. ',... 'DAQ will be unable to acquire data'],... 'Error: No collector'); end %The list of instruments is automatically populated from the %run files this.ProgramList = readRunFiles(); %We grab the guihandles from a GUI made in Guide. this.Gui=guihandles(eval('GuiDaq')); %This function sets all the callbacks for the GUI. If a new %button is made, the associated callback must be put in the %initGui function initGui(this); % Initialize the menu based on the available run files content = menuFromRunFiles(this.ProgramList,... 'show_in_daq',true); set(this.Gui.InstrMenu,'String',[{'Select the application'};... content.titles]); % Add a property to the menu for storing the program file % names if ~isprop(this.Gui.InstrMenu, 'ItemsData') addprop(this.Gui.InstrMenu, 'ItemsData'); end set(this.Gui.InstrMenu,'ItemsData',[{''};... content.tags]); hold(this.main_plot,'on'); %Initializes saving locations this.base_dir=getLocalSettings('measurement_base_dir'); this.session_name='placeholder'; this.filename='placeholder'; end function delete(this) %Deletes the MyFit objects and their listeners cellfun(@(x) deleteListeners(this,x), this.open_fits); structfun(@(x) delete(x), this.Fits); %Deletes other listeners if ~isempty(fieldnames(this.Listeners)) cellfun(@(x) deleteListeners(this, x),... fieldnames(this.Listeners)); end % clear global variable, to which Daq handle is assigned evalin('base', sprintf('clear(''%s'')', this.global_name)); this.Gui.figure1.CloseRequestFcn=''; %Deletes the figure delete(this.Gui.figure1); %Removes the figure handle to prevent memory leaks this.Gui=[]; end end methods (Access=private) %Sets callback functions for the GUI initGui(this) %Executes when the GUI is closed function closeFigure(this,~,~) delete(this); end %Updates fits function updateFits(this) %Pushes data into fits in the form of MyTrace objects, so that %units etc follow. Also updates user supplied parameters. for i=1:length(this.open_fits) switch this.open_fits{i} case {'Linear','Quadratic','Gaussian',... 'Exponential','Beta'} this.Fits.(this.open_fits{i}).Data=... getFitData(this,'VertData'); case {'Lorentzian','DoubleLorentzian'} this.Fits.(this.open_fits{i}).Data=... getFitData(this,'VertData'); %Here we push the information about line spacing %into the fit object if the reference cursors are %open. Only for Lorentzian fits. if isfield(this.Cursors,'VertRef') ind=findCursorData(this,'Data','VertRef'); this.Fits.(this.open_fits{i}).CalVals.line_spacing=... range(this.Data.x(ind)); end case {'G0'} this.Fits.G0.MechTrace=getFitData(this,'VertData'); this.Fits.G0.CalTrace=getFitData(this,'VertRef'); end end end % If vertical cursors are on, takes only data within cursors. If %the cursor is not open, it takes all the data from the selected %trace in the analysis trace selection dropdown function Trace=getFitData(this,varargin) %Parses varargin input p=inputParser; addOptional(p,'name','',@ischar); parse(p,varargin{:}) name=p.Results.name; %Finds out which trace the user wants to fit. trc_opts=this.Gui.SelTrace.String; trc_str=trc_opts{this.Gui.SelTrace.Value}; % Note the use of copy here! This is a handle %class, so if normal assignment is used, this.Fits.Data and %this.(trace_str) will refer to the same object, causing roblems. %Name input is the name of the cursor to be used to extract data. Trace=copy(this.(trc_str)); %If the cursor is open for the trace we are analyzing, we take %only the data enclosed by the cursor. if isfield(this.Cursors,name) ind=findCursorData(this, trc_str, name); Trace.x=this.(trc_str).x(ind); Trace.y=this.(trc_str).y(ind); end end %Finds data between named cursors in the given trace function ind=findCursorData(this, trc_str, name) crs_pos=sort([this.Cursors.(name){1}.Location,... this.Cursors.(name){2}.Location]); ind=(this.(trc_str).x>crs_pos(1) & this.(trc_str).x %Prints the figure to the clipboard print(newFig,'-clipboard','-dbitmap'); %Deletes the figure delete(newFig); end %Resets the axis to be tight around the plots. function updateAxis(this) axis(this.main_plot,'tight'); end end methods (Access=public) %% Callbacks %Callback for copying the plot to clipboard function copyPlotCallback(this,~,~) copyPlot(this); end %Callback for centering cursors function centerCursorsCallback(this, ~, ~) if ~this.Gui.LogX.Value x_pos=mean(this.main_plot.XLim); else x_pos=10^(mean(log10(this.main_plot.XLim))); end if ~this.Gui.LogY.Value y_pos=mean(this.main_plot.YLim); else y_pos=10^(mean(log10(this.main_plot.YLim))); end for i=1:length(this.open_crs) switch this.Cursors.(this.open_crs{i}){1}.Orientation case 'horizontal' pos=y_pos; case 'vertical' pos=x_pos; end %Centers the position cellfun(@(x) set(x,'Location',pos), ... this.Cursors.(this.open_crs{i})); %Triggers the UpdateCursorBar event, which triggers %listener callback to reposition text cellfun(@(x) notify(x,'UpdateCursorBar'),... this.Cursors.(this.open_crs{i})); %Triggers the EndDrag event, updating the data in the fit %objects. cellfun(@(x) notify(x,'EndDrag'),... this.Cursors.(this.open_crs{i})); end end %Callback for creating vertical data cursors function cursorButtonCallback(this, hObject, ~) name=erase(hObject.Tag,'Button'); %Gets the first four characters of the tag (Vert or Horz) type=name(1:4); %Changes the color of the button and appropriately creates or %deletes the cursors. if hObject.Value hObject.BackgroundColor=[0,1,0.2]; createCursors(this,name,type); else hObject.BackgroundColor=[0.941,0.941,0.941]; deleteCursors(this,name); end end %Callback for the instrument menu function instrMenuCallback(this,hObject,~) val=hObject.Value; if val==1 %Returns if we are on the dummy option ('Select instrument') return else tag = hObject.ItemsData{val}; end try eval(this.ProgramList.(tag).run_expr); catch errordlg(sprintf('An error occured while running %s',... this.ProgramList.(tag).name)) end end %Select trace callback. If we change the trace being analyzed, the %fit objects are updated. function selTraceCallback(this, ~, ~) updateFits(this) end %Saves the data if the save data button is pressed. function saveCallback(this, src, ~) switch src.Tag case 'SaveData' saveTrace(this,'Data'); case 'SaveRef' saveTrace(this,'Ref'); end end function saveTrace(this, trace_tag) %Check if the trace is valid (i.e. x and y are equal length) %before saving if ~this.(trace_tag).validatePlot errordlg(sprintf('%s trace was empty, could not save',trace_tag)); return end %Uses the protected save function of MyTrace save(this.(trace_tag),... 'save_dir',this.save_dir,... 'filename',this.filename) 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 setTrace(this.Ref,... 'x',this.Data.x,... 'y',this.Data.y,... 'name_x',this.Data.name_x,... 'name_y',this.Data.name_y,... 'unit_x',this.Data.unit_x,... 'unit_y',this.Data.unit_y) %Since UID is automatically reset when y is changed, we now %change it back to be the same as the Data. this.Ref.uid=this.Data.uid; %Plot the reference trace and make it visible this.Ref.plotTrace(this.main_plot,'Color',this.ref_color,... 'make_labels',true); this.Ref.setVisible(this.main_plot,1); %Update the fit objects updateFits(this); %Change button color this.Gui.ShowRef.Value=1; this.Gui.ShowRef.BackgroundColor=[0,1,0.2]; else warning('Data trace was empty, could not move to reference') end end %Callback for ref to bg button. Sends the reference to background function refToBgCallback(this, ~, ~) if this.Ref.validatePlot this.Background.x=this.Ref.x; this.Background.y=this.Ref.y; this.Background.plotTrace(this.main_plot,... 'Color',this.bg_color,'make_labels',true); this.Background.setVisible(this.main_plot,1); else warning('Reference trace was empty, could not move to background') end end %Callback for data to bg button. Sends the data to background function dataToBgCallback(this, ~, ~) if this.Data.validatePlot this.Background.x=this.Data.x; this.Background.y=this.Data.y; this.Background.plotTrace(this.main_plot,... 'Color',this.bg_color,'make_labels',true); this.Background.setVisible(this.main_plot,1); else warning('Data trace was empty, could not move to background') end end %Callback for clear background button. Clears the background function clearBgCallback(this, ~, ~) this.Background.x=[]; this.Background.y=[]; this.Background.setVisible(this.main_plot,0); end %Callback for LogY button. Sets the YScale to log/lin function logYCallback(this, hObject, ~) if hObject.Value this.main_plot.YScale='Log'; hObject.BackgroundColor=[0,1,0.2]; else this.main_plot.YScale='Linear'; hObject.BackgroundColor=[0.941,0.941,0.941]; end updateAxis(this); updateCursors(this); end %Callback for LogX button. Sets the XScale to log/lin. Updates the %axis and cursors afterwards. function logXCallback(this, hObject, ~) if get(hObject,'Value') set(this.main_plot,'XScale','Log'); set(hObject, 'BackgroundColor',[0,1,0.2]); else set(this.main_plot,'XScale','Linear'); set(hObject, 'BackgroundColor',[0.941,0.941,0.941]); end updateAxis(this); updateCursors(this); end %Base directory callback. Sets the base directory. Also %updates fit objects with the new save directory. function baseDirCallback(this, ~, ~) for i=1:length(this.open_fits) this.Fits.(this.open_fits{i}).base_dir=this.base_dir; end end %Callback for session name edit box. Sets the session name. Also %updates fit objects with the new save directory. function sessionNameCallback(this, ~, ~) for i=1:length(this.open_fits) this.Fits.(this.open_fits{i}).session_name=this.session_name; end end %Callback for filename edit box. Sets the file name. Also %updates fit objects with the new file name. function fileNameCallback(this, ~,~) for i=1:length(this.open_fits) this.Fits.(this.open_fits{i}).filename=this.filename; end end %Callback for the analyze menu (popup menu for selecting fits). %Opens the correct MyFit object. function analyzeMenuCallback(this, hObject, ~) analyze_ind=hObject.Value; %Finds the correct fit name by erasing spaces and other %superfluous strings analyze_name=hObject.String{analyze_ind}; analyze_name=erase(analyze_name,'Fit'); analyze_name=erase(analyze_name,'Calibration'); analyze_name=erase(analyze_name,' '); %Sets the correct tooltip hObject.TooltipString=sprintf(this.Gui.AnalyzeTip{analyze_ind}) ; %Opens the correct analysis tool switch analyze_name case {'Linear','Quadratic','Exponential',... 'Lorentzian','Gaussian',... 'DoubleLorentzian'} openMyFit(this,analyze_name); case 'g0' openMyG(this); case 'Beta' openMyBeta(this); end end function openMyFit(this,fit_name) %Sees if the MyFit object is already open, if it is, changes the %focus to it, if not, opens it. if ismember(fit_name,fieldnames(this.Fits)) %Changes focus to the relevant fit window figure(this.Fits.(fit_name).Gui.Window); else %Gets the data for the fit using the getFitData function %with the vertical cursors DataTrace=getFitData(this,'VertData'); %Makes an instance of MyFit with correct parameters. this.Fits.(fit_name)=MyFit(... 'fit_name',fit_name,... 'enable_plot',1,... 'plot_handle',this.main_plot,... 'Data',DataTrace,... 'base_dir',this.base_dir,... 'session_name',this.session_name,... 'filename',this.filename); updateFits(this); %Sets up a listener for the Deletion event, which %removes the MyFit object from the Fits structure if it is %deleted. this.Listeners.(fit_name).Deletion=... addlistener(this.Fits.(fit_name),'ObjectBeingDestroyed',... @(src, eventdata) deleteFit(this, src, eventdata)); %Sets up a listener for the NewFit. Callback plots the fit %on the main plot. this.Listeners.(fit_name).NewFit=... addlistener(this.Fits.(fit_name),'NewFit',... @(src, eventdata) plotNewFit(this, src, eventdata)); %Sets up a listener for NewInitVal this.Listeners.(fit_name).NewInitVal=... addlistener(this.Fits.(fit_name),'NewInitVal',... @(~,~) updateCursors(this)); end end %Opens MyG class if it is not open. function openMyG(this) if ismember('G0',this.open_fits) figure(this.Fits.G0.Gui.figure1); else %Populate the MyG class with the right data. We assume the %mechanics is in the Data trace. MechTrace=getFitData(this,'VertData'); CalTrace=getFitData(this,'VertRef'); this.Fits.G0=MyG('MechTrace',MechTrace,'CalTrace',CalTrace,... 'name','G0'); %Adds listener for object being destroyed this.Listeners.G0.Deletion=addlistener(this.Fits.G0,... 'ObjectBeingDestroyed',... @(~,~) deleteObj(this,'G0')); end end %Opens MyBeta class if it is not open. function openMyBeta(this) if ismember('Beta', this.open_fits) figure(this.Fits.Beta.Gui.figure1); else DataTrace=getFitData(this); this.Fits.Beta=MyBeta('Data',DataTrace); %Adds listener for object being destroyed, to perform cleanup this.Listeners.Beta.Deletion=addlistener(this.Fits.Beta,... 'ObjectBeingDestroyed',... @(~,~) deleteObj(this,'Beta')); end end %Callback for load data button function loadDataCallback(this, ~, ~) if isempty(this.base_dir) warning('Please input a valid folder name for loading a trace'); this.base_dir=pwd; end [load_name,path_name]=uigetfile('.txt','Select the trace',... this.base_dir); if load_name==0 warning('No file was selected'); return end load_path=[path_name,load_name]; %Finds the destination trace from the GUI dest_trc=this.Gui.DestTrc.String{this.Gui.DestTrc.Value}; %Call the load trace function on the right trace loadTrace(this.(dest_trc),load_path); %Color and plot the right trace. this.(dest_trc).plotTrace(this.main_plot,... 'Color',this.(sprintf('%s_color',lower(dest_trc))),... 'make_labels',true); %Update axis and cursors updateAxis(this); updateCursors(this); end end methods (Access=public) %% Listener functions %Callback function for NewFit listener. Plots the fit in the %window using the plotFit function of the MyFit object function plotNewFit(this, src, ~) src.plotFit('Color',this.fit_color); updateAxis(this); updateCursors(this); end %Callback function for the NewData listener function acquireNewData(this, EventData) %Get the currently selected instrument val=this.Gui.InstrMenu.Value; curr_instr_name=this.Gui.InstrMenu.ItemsData{val}; %Get the name of instrument that generated new data SourceInstr = EventData.InstrEventData.Source; source_name = SourceInstr.name; %Check if the data originates from the currently selected %instrument if strcmp(source_name, curr_instr_name) hline=getLineHandle(this.Data,this.main_plot); %Copy the data from the source instrument this.Data=copy(SourceInstr.Trace); %We give the new trace object the right line handle to plot in if ~isempty(hline); this.Data.hlines{1}=hline; end this.Data.plotTrace(this.main_plot,... 'Color',this.data_color,... 'make_labels',true) updateAxis(this); updateCursors(this); updateFits(this); end end %Callback function for MyFit ObjectBeingDestroyed listener. %Removes the relevant field from the Fits struct and deletes the %listeners from the object. function deleteFit(this, src, ~) %Deletes the object from the Fits struct and deletes listeners deleteObj(this,src.fit_name); %Clears the fits src.clearFit; %Updates cursors since the fits are removed from the plot updateCursors(this); end %Callback function for other analysis method deletion listeners. %Does the same as above. function deleteObj(this,name) if ismember(name,this.open_fits) this.Fits=rmfield(this.Fits,name); end deleteListeners(this, name); end %Listener update function for vertical cursor function vertCursorUpdate(this, src) %Finds the index of the cursor. All cursors are tagged %(name)1, (name)2, e.g. VertData2, ind is the number. ind=str2double(src.Tag(end)); tag=src.Tag(1:(end-1)); %Moves the cursor labels set(this.CrsLabels.(tag){ind},'Position',[src.Location,... this.CrsLabels.(tag){ind}.Position(2),0]); if strcmp(tag,'VertData') %Sets the edit box displaying the location of the cursor this.Gui.(sprintf('EditV%d',ind)).String=... num2str(src.Location); %Sets the edit box displaying the difference in locations this.Gui.EditV2V1.String=... num2str(this.Cursors.VertData{2}.Location-... this.Cursors.VertData{1}.Location); end end %Listener update function for horizontal cursor function horzCursorUpdate(this, src) %Finds the index of the cursor. All cursors are tagged %(name)1, (name)2, e.g. VertData2, ind is the number. ind=str2double(src.Tag(end)); tag=src.Tag(1:(end-1)); %Moves the cursor labels set(this.CrsLabels.(tag){ind},'Position',... [this.CrsLabels.(tag){ind}.Position(1),... src.Location,0]); if strcmp(tag,'HorzData') %Sets the edit box displaying the location of the cursor this.Gui.(sprintf('EditH%d',ind)).String=... num2str(src.Location); %Sets the edit box displaying the difference in locations this.Gui.EditH2H1.String=... num2str(this.Cursors.HorzData{2}.Location-... this.Cursors.HorzData{1}.Location); end end %Function that deletes listeners from the listeners struct, %corresponding to an object of name obj_name deleteListeners(this, obj_name); end %Get functions for dependent variables without set functions methods %Get function from save directory function save_dir=get.save_dir(this) save_dir=createSessionPath(this.base_dir,this.session_name); end %Get function for the plot handles function main_plot=get.main_plot(this) main_plot=this.Gui.figure1.CurrentAxes; end %Get function for open fits function open_fits=get.open_fits(this) open_fits=fieldnames(this.Fits); end %Get function that displays names of open cursors function open_crs=get.open_crs(this) open_crs=fieldnames(this.Cursors); end end %Get and set functions for dependent properties with SetAccess methods function base_dir=get.base_dir(this) try base_dir=this.Gui.BaseDir.String; catch base_dir=pwd; end end function set.base_dir(this,base_dir) this.Gui.BaseDir.String=base_dir; end function session_name=get.session_name(this) try session_name=this.Gui.SessionName.String; catch session_name=''; end end function set.session_name(this,session_name) this.Gui.SessionName.String=session_name; end function filename=get.filename(this) try filename=this.Gui.FileName.String; catch filename='placeholder'; end end function set.filename(this,filename) this.Gui.FileName.String=filename; end end end \ No newline at end of file diff --git a/@MyScpiInstrument/MyScpiInstrument.m b/@MyScpiInstrument/MyScpiInstrument.m index 22b8f4a..15696ff 100644 --- a/@MyScpiInstrument/MyScpiInstrument.m +++ b/@MyScpiInstrument/MyScpiInstrument.m @@ -1,334 +1,334 @@ -% Class for instruments supporting SCPI, features epecialized framework for +% Class for instruments supporting SCPI, features specialized framework for % read/write commands classdef MyScpiInstrument < MyInstrument properties (SetAccess=protected, GetAccess=public) %Contains a list of the commands available for the instrument as %well as the default values and input requirements CommandList=struct(); %Parses commands using an inputParser object CommandParser; end properties (Dependent=true) command_names; command_no; write_commands; read_commands; end methods (Access=public) %% Read and write commands %Writes properties to device. Can take multiple inputs. With the %option all, the function writes default to all the %available writeable parameters. function writeProperty(this, varargin) %Parses the inputs using the CommandParser parse(this.CommandParser, varargin{:}); if this.CommandParser.Results.all % If the 'all' is true, write all the commands exec=this.write_commands; else % Check which commands were passed values ind_val=cellfun(@(x)... (~ismember(x, this.CommandParser.UsingDefaults)),... this.write_commands); exec=this.write_commands(ind_val); end for i=1:length(exec) %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 and closes the device function writePropertyHedged(this, varargin) openDevice(this); try writeProperty(this, varargin{:}); catch warning('Error while writing the properties:'); disp(varargin); end readProperty(this, 'all'); closeDevice(this); end function result=readProperty(this, varargin) result = struct(); read_all_flag = any(strcmp('all',varargin)); if read_all_flag % Read all the commands with read access exec=this.read_commands; else ind_r=ismember(varargin,this.read_commands); exec=varargin(ind_r); if any(~ind_r) % Issue warnings for commands not in the command_names warning('The following are not valid read commands:'); disp(varargin(~ind_r)); end end % concatenate all commands in one string read_command=join(cellfun(... @(cmd)this.CommandList.(cmd).command,exec,... 'UniformOutput',false),'?;:'); read_command=[read_command{1},'?;']; res_str = query(this.Device,read_command); % drop the end-of-the-string symbol and split res_str = split(res_str(1:end-1),';'); if length(exec)==length(res_str) for i=1:length(exec) result.(exec{i})=sscanf(res_str{i},... this.CommandList.(exec{i}).str_spec); %Assign the values to the MyInstrument properties this.(exec{i})=result.(exec{i}); end else warning(['Not all the properties could be read, ',... 'no instrument class values are not updated']); end end % Wrapper for readProperty that opens and closes the device function result=readPropertyHedged(this, varargin) openDevice(this); try result = readProperty(this, varargin{:}); catch warning('Error while reading the properties:'); disp(varargin); end closeDevice(this); end % Re-define readHeader function function HdrStruct=readHeader(this) Values=readPropertyHedged(this,'all'); for i=1:length(this.read_commands) HdrStruct.(this.read_commands{i}).value=... Values.(this.read_commands{i}); HdrStruct.(this.read_commands{i}).str_spec=... this.CommandList.(this.read_commands{i}).str_spec; end end %% Processing of the class variable values % Extend the property value based on val_list function std_val = standardizeValue(this, cmd, varargin) if ~ismember(cmd,this.command_names) warning('%s is not a valid command',cmd); std_val = ''; return end vlist = this.CommandList.(cmd).val_list; % The value to normalize can be explicitly passed as % varargin{1}, otherwise use this.cmd as value if isempty(varargin) val = this.(cmd); else val = varargin{1}; end % find matching commands ismatch = false(1,length(vlist)); for i=1:length(vlist) n = min([length(val), length(vlist{i})]); % compare first n symbols disregarding case ismatch(i) = strncmpi(val, vlist{i},n); end % out of matching names pick the longest if any(ismatch) mvlist = vlist(ismatch); %Finds the length of each element of mvlist n_el=cellfun(@(x) length(x), mvlist); %Sets std_val to the longest element std_val=mvlist{n_el==max(n_el)}; % sets the property if value was not given explicitly if isempty(varargin) this.(cmd) = std_val; end else warning(['The value %s is not in the val_list ',... 'of %s command'], val, cmd) std_val = val; end end % Return the list of long command values excluding abbreviations function std_val_list = stdValueList(this, cmd) if ~ismember(cmd,this.command_names) warning('%s is not a valid command',cmd); std_val_list = {}; return end vlist = this.CommandList.(cmd).val_list; % Select the commands, which appear only once in the beginnings % of the strings in val_list long_val_ind = cellfun(... @(x)(sum(startsWith(vlist,x,'IgnoreCase',true))==1),vlist); std_val_list = vlist(long_val_ind); end %% addCommand %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,'classes',{},@iscell); addParameter(p,'attributes',{},@iscell); addParameter(p,'str_spec','%e',@ischar); % list of the values the variable can take, {} means no % restriction addParameter(p,'val_list',{},@iscell); addParameter(p,'access','rw',@ischar); parse(p,tag,command,varargin{:}); %Adds the command to be sent to the device this.CommandList.(tag).command=command; this.CommandList.(tag).access=p.Results.access; this.CommandList.(tag).write_flag=contains(p.Results.access,'w'); this.CommandList.(tag).read_flag=contains(p.Results.access,'r'); this.CommandList.(tag).default=p.Results.default; this.CommandList.(tag).val_list=p.Results.val_list; % Adds the string specifier to the list. if the format % specifier is not given explicitly, try to infer if ismember('str_spec', p.UsingDefaults) this.CommandList.(tag).str_spec=... formatSpecFromAttributes(this,p.Results.classes... ,p.Results.attributes); elseif strcmp(p.Results.str_spec,'%b') % b is a non-system specifier to represent the % logical type this.CommandList.(tag).str_spec='%i'; else this.CommandList.(tag).str_spec=p.Results.str_spec; end % Adds the attributes for the input to the command. If not % given explicitly, infer from the format specifier if ismember('classes',p.UsingDefaults) [this.CommandList.(tag).classes,... this.CommandList.(tag).attributes]=... attributesFromFormatSpec(this, p.Results.str_spec); else this.CommandList.(tag).classes=p.Results.classes; this.CommandList.(tag).attributes=p.Results.attributes; end % Adds a property to the class corresponding to the tag if ~isprop(this,tag) addprop(this,tag); end this.(tag)=p.Results.default; end %Creates inputParser using the command list function p = 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, 'all',false,@islogical); for i=1:length(this.write_commands) %Adds optional inputs for each command, with the %appropriate default value from the command list and the %required attributes for the command input. tag=this.write_commands{i}; % Create validation function based on properties: % class, attributes and list of values if ~isempty(this.CommandList.(tag).val_list) if all(cellfun(@ischar, this.CommandList.(tag).val_list)) % for textual values use case insentice string comparison v_func = @(x) any(cellfun(@(y) strcmpi(y, x),... this.CommandList.(tag).val_list)); else % for everything else compare as it is v_func = @(x) any(cellfun(@(y) isequal(y, x),... this.CommandList.(tag).val_list)); end else v_func = @(x) validateattributes(x,... this.CommandList.(tag).classes,... this.CommandList.(tag).attributes); end addParameter(p, tag,... this.CommandList.(tag).default, v_func); end this.CommandParser=p; end %% Auxiliary functions for auto format assignment to commands function str_spec=formatSpecFromAttributes(~,classes,attributes) if ismember('char',classes) str_spec='%s'; elseif ismember('logical',classes)||... (ismember('numeric',classes)&&... ismember('integer',attributes)) str_spec='%i'; else %assign default value, i.e. double str_spec='%e'; end end function [class,attribute]=attributesFromFormatSpec(~, str_spec) % find index of the first letter after the % sign ind_p=strfind(str_spec,'%'); ind=ind_p+find(isletter(str_spec(ind_p:end)),1)-1; str_spec_letter=str_spec(ind); switch str_spec_letter case {'d','f','e','g'} class={'numeric'}; attribute={}; case 'i' class={'numeric'}; attribute={'integer'}; case 's' class={'char'}; attribute={}; case 'b' class={'logical'}; attribute={}; otherwise % Any of the above classes will pass class={'numeric','char','logical'}; attribute={}; end end end %% Get functions methods function command_names=get.command_names(this) command_names=fieldnames(this.CommandList); end function write_commands=get.write_commands(this) ind_w=structfun(@(x) x.write_flag, this.CommandList); write_commands=this.command_names(ind_w); end function read_commands=get.read_commands(this) ind_r=structfun(@(x) x.read_flag, this.CommandList); read_commands=this.command_names(ind_r); 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/GUIs/GuiLakeshore.mlapp b/GUIs/GuiLakeshore.mlapp index fd9984b..ed53fd5 100644 Binary files a/GUIs/GuiLakeshore.mlapp and b/GUIs/GuiLakeshore.mlapp differ diff --git a/GUIs/GuiLogger.mlapp b/GUIs/GuiLogger.mlapp index c383c6e..c59ffd5 100644 Binary files a/GUIs/GuiLogger.mlapp and b/GUIs/GuiLogger.mlapp differ diff --git a/GUIs/GuiTpg.mlapp b/GUIs/GuiTpg.mlapp index 2689071..5a483dd 100644 Binary files a/GUIs/GuiTpg.mlapp and b/GUIs/GuiTpg.mlapp differ diff --git a/Utility functions/@MyInputHandler/MyInputHandler.m b/Utility functions/@MyInputHandler/MyInputHandler.m index 12c6bcd..c70a0dd 100644 --- a/Utility functions/@MyInputHandler/MyInputHandler.m +++ b/Utility functions/@MyInputHandler/MyInputHandler.m @@ -1,62 +1,64 @@ +% Class containing ConstructionParser that can be used to automatically +% assign public properties during construction of subclasses classdef MyInputHandler < handle properties (SetAccess=protected, GetAccess=public) %Input parser for class constructor ConstructionParser; end methods (Access=protected) % Create parser. Can contain parameter additions % if overloaded in a subclass function p = createConstructionParser(this) p=inputParser(); this.ConstructionParser=p; end % Add all the properties the class which are not present in the % scheme of ConstructionParser and which have public set acces % to the scheme of ConstructionParser function addClassProperties(this) thisMetaclass = metaclass(this); for i=1:length(thisMetaclass.PropertyList) Tmp = thisMetaclass.PropertyList(i); % Constant, Dependent and Abstract propeties cannot be set if (~Tmp.Constant)&&(~Tmp.Abstract)&&(~Tmp.Dependent)&&... strcmpi(Tmp.SetAccess,'public')&&... (~ismember(Tmp.Name,... this.ConstructionParser.Parameters)) if Tmp.HasDefault def = Tmp.DefaultValue; else def = []; end addParameter(this.ConstructionParser, Tmp.Name, def); end end end % parse varargin and assign results to class properties % with the same names as parameters function parseClassInputs(this, varargin) parse(this.ConstructionParser, varargin{:}); % assign results that have associated class properties with % public set access for i=1:length(this.ConstructionParser.Parameters) par = this.ConstructionParser.Parameters{i}; metaprop=findprop(this, par); if ~ismember(par, this.ConstructionParser.UsingDefaults)&&... isprop(this, par)&&... strcmpi(metaprop.SetAccess,'public') try this.(par) = this.ConstructionParser.Results.(par); catch warning(['Value of the input parameter ''',... par,''' could not be assigned to property']) end end end end end end diff --git a/Utility functions/DataAcquisitionMenu.mlapp b/Utility functions/DataAcquisitionMenu.mlapp index b5b8efa..8f87382 100644 Binary files a/Utility functions/DataAcquisitionMenu.mlapp and b/Utility functions/DataAcquisitionMenu.mlapp differ