diff --git a/@MyAvgTrace/MyAvgTrace.m b/@MyAvgTrace/MyAvgTrace.m index 2d4b866..371b21e 100644 --- a/@MyAvgTrace/MyAvgTrace.m +++ b/@MyAvgTrace/MyAvgTrace.m @@ -1,164 +1,164 @@ % Adds averaging capabilities to MyTrace % % The averaging type is 'lin' (or 'linear')/ 'exp' (or 'exponential'). % Linear averaging is a simple mean % x=\sum_{n=0}^N x_n, % exponential is an unlimited weighted sum % x=(1-exp(-1/n_avg))*\sum_{n=0}^\inf x_n exp(-n/n_avg). classdef MyAvgTrace < MyTrace properties (Access = public) % Target number of averages, when it is reached or exceeded % AveragingDone event is triggered n_avg = 1 avg_type = 'lin' end properties (GetAccess = public, SetAccess = protected) % Counter for the averaging function, can be reset by clearData avg_count = 0 end methods (Access = public) % Adds data to the average accumulator. When the averaging counter % reaches n_avg (or exceeds it in the exponential case), completed % is set to 'true', otherwise 'false'. % In exponential regime the averaging proceeds indefinitely so that % avg_count can exceed n_avg. % In linear regime when the averaging counter exceeds n_avg, new % data is discarded. function completed = addAverage(this, b) assert(isa(b,'MyTrace'), ['Second argument must be a ' ... 'MyTrace object']); if isempty(this) || length(this.x)~=length(b.x) || ... any(this.x~=b.x) % Initialize new data and return this.x=b.x; this.y=b.y; this.name_x=b.name_x; this.unit_x=b.unit_x; this.name_y=b.name_y; this.unit_y=b.unit_y; this.avg_count=1; completed=(this.avg_count>=this.n_avg); return end assert(length(b.y)==length(this.y), ... ['New vector of y values must be of the same', ... 'length as the exisiting y data of MyTrace in ', ... 'order to perform averanging']) switch this.avg_type case 'lin' if this.avg_count=this.n_avg); otherwise error('Averaging type %s is not supported', ... this.avg_type) end end % Provide restricted access to the trace averaging counter function resetCounter(this) this.avg_count = 0; end % Overload clearData so that it reset the averaging counter in % addition to clearing the x and y values function clearData(this) this.x = []; this.y = []; resetCounter(this); end end methods (Access = protected) % Extend the info stored in trace metadata compare to MyTrace function MdtS = getMetadata(this) MdtS = getMetadata@MyTrace(this); addParam(MdtS.Info, 'avg_type', this.avg_type, ... 'comment', 'Averaging type, linear or exponential'); addParam(MdtS.Info, 'avg_count', this.avg_count, 'comment', ... 'Number of accomplished averages'); addParam(MdtS.Info, 'n_avg', this.n_avg, 'comment', ... ['Target number of averages (lin) or exponential ' ... 'averaging constant (exp)']); end function setMetadata(this, MdtS) if isfield(MdtS, 'Info') if isparam(MdtS.Info, 'avg_type') - this.avg_type = getParam(MdtS.Info, 'avg_type'); + this.avg_type = MdtS.Info.avg_type; end if isparam(MdtS.Info, 'n_avg') - this.n_avg = getParam(MdtS.Info, 'n_avg'); + this.n_avg = MdtS.Info.n_avg; end if isparam(MdtS.Info, 'avg_count') - this.avg_count = getParam(MdtS.Info, 'avg_count'); + this.avg_count = MdtS.Info.avg_count; end end setMetadata@MyTrace(this, MdtS); end end %% Set and get methods methods % Ensure the supplied value for averaging mode is assigned in its % standard form - lowercase and abbreviated function set.avg_type(this, val) old_val=this.avg_type; switch lower(val) case {'lin', 'linear'} this.avg_type='lin'; case {'exp', 'exponential'} this.avg_type='exp'; otherwise error(['Averaging type must be ''lin'' ' ... '(''linear'') or ''exp'' (''exponential'')']) end % Clear data if the averaging type was changed if this.avg_type~=old_val clearData(this); end end function set.n_avg(this, val) % The number of averages should be integer not smaller than one this.n_avg=max(1, round(val)); end end end diff --git a/@MyMetadata/MyMetadata.m b/@MyMetadata/MyMetadata.m index 74a09fa..2f65d7d 100644 --- a/@MyMetadata/MyMetadata.m +++ b/@MyMetadata/MyMetadata.m @@ -1,437 +1,439 @@ % MyMetadata stores parameter-value pairs and can be saved in a readable % format. % % Metadata parameters can be strings, numerical values or cells, as well as % any arrays and structures of such with arbitrary nesting. Sub-indices are % automatically expanded when saving. -classdef MyMetadata < handle & matlab.mixin.CustomDisplay & matlab.mixin.SetGet +classdef MyMetadata < dynamicprops & matlab.mixin.CustomDisplay & matlab.mixin.SetGet properties (Access = public) % Header sections are separated by [hdr_spec, title, hdr_spec] title = '' hdr_spec = '==' % Columns are separated by this symbol (space-tab by default) column_sep = ' \t' % Comments start from this symbol comment_sep = '%' line_sep = '\r\n' % Limit for column padding. Variables which take more space than % this limit are ignored when calculating the padding length. pad_lim = 15 end properties (GetAccess = public, SetAccess = protected) ParamList = struct() end methods (Access = public) function this = MyMetadata(varargin) P = MyClassParser(this); processInputs(P, this, varargin{:}); end % Adds a new metadata parameter. function addParam(this, param_name, value, varargin) assert(isvarname(param_name), ['Parameter name must be a ' ... 'valid variable name.']); p = inputParser(); % Format specifier for printing the value addParameter(p, 'fmt_spec', '', @ischar); % Comment to be added to the line addParameter(p, 'comment', '', @ischar); addParameter(p, 'SubStruct', struct('type',{},'subs',{}),... @isstruct) parse(p, varargin{:}); - % Store the comment and format specifier. This is done only for - % a newly added parameter. if ~isparam(this, param_name) + % Add a dynamic property for referencing the parameter + H = addprop(this, param_name); + H.GetAccess = 'public'; + H.SetAccess = 'private'; + H.GetMethod = @(x)x.ParamList.(param_name).value; + + % Hide the dynamic property from the output of display() + % for beauty + H.Hidden = true; + + % Store the comment and format specifier. % Make sure that the comment does not contain new line or % carriage return characters, which would mess up formating % when saving the metadata [comment, is_mod] = toSingleLine(p.Results.comment); this.ParamList.(param_name).comment = comment; if is_mod warning(['Comment string for ''%s'' has been ' ... 'converted to single line.'], param_name); end this.ParamList.(param_name).fmt_spec = p.Results.fmt_spec; end S = p.Results.SubStruct; if isempty(S) % Assign value directly this.ParamList.(param_name).value = value; else % Assign using subref structure if isfield(this.ParamList.(param_name), 'value') % Adding a new subscript to existing parameter tmp = this.ParamList.(param_name).value; else % Creating the value for a new parameter tmp = feval([class(value),'.empty']); end this.ParamList.(param_name).value = subsasgn(tmp,S,value); end end - % Get the value of an existing parameter - function value = getParam(this, param_name) - assert(isparam(this, param_name), [param_name ... - ' must correspond to one of the metadata parameters.']); - value = this.ParamList.(param_name).value; - end - function bool = isparam(this, param_name) bool = isfield(param_name, this.ParamList); end % Alias for addParam that is useful to ensure the correspondence % between metadata parameter names and object property names. function addObjProp(this, Obj, tag, varargin) addParam(this, tag, Obj.(tag), varargin{:}); end % Print metadata in a readable form function str = mdt2str(this) % Make the function spannable over arrays if isempty(this) str = ''; return elseif length(this) > 1 str_arr = arrayfun(@(x)mdt2str(x), this, ... 'UniformOutput', false); str = [str_arr{:}]; return end % Compose the list of parameter names expanded over subscripts % except for those which are already character arrays par_names = fieldnames(this.ParamList); % Expand parameters over subscripts, except for the character % arrays exp_par_names = cell(1, length(par_names)); maxnmarr = zeros(1, length(par_names)); for i=1:length(par_names) tmpval = this.ParamList.(par_names{i}).value; exp_par_names{i} = printSubs(tmpval, ... 'own_name', par_names{i}, ... 'expansion_test',@(y) ~ischar(y)); % Max name length for this parameter including subscripts maxnmarr(i)=max(cellfun(@(x) length(x), exp_par_names{i})); end % Calculate width of the name column name_pad_length = min(max(maxnmarr), this.pad_lim); % Compose list of parameter values converted to char strings par_strs = cell(1, length(par_names)); % Width of the values column will be the maximum parameter % string width val_pad_length = 0; for i=1:length(par_names) TmpPar = this.ParamList.(par_names{i}); for j=1:length(exp_par_names{i}) tmpnm = exp_par_names{i}{j}; TmpS = str2substruct(tmpnm); if isempty(TmpS) tmpval = TmpPar.value; else tmpval = subsref(TmpPar.value, TmpS); end %Do the check to detect unsupported data type if ischar(tmpval)&&~isvector(tmpval)&&~isempty(tmpval) warning(['Argument ''%s'' is a multi-dimensional ',... 'character array. It will be converted to ',... 'single string during saving. Use cell',... 'arrays to save data as a set of separate ',... 'strings.'],tmpnm) % Flatten tmpval = tmpval(:); end % Check for new line symbols in strings if (ischar(tmpval)||isstring(tmpval)) && ... any(ismember({newline,sprintf('\r')},tmpval)) warning(['String value must not contain ',... '''\\n'' and ''\\r'' symbols, replacing ',... 'them with '' ''.']); tmpval=replace(tmpval,{newline,sprintf('\r')},' '); end if isempty(TmpPar.fmt_spec) % Convert to string with format specifier % extracted from the varaible calss par_strs{i}{j} = var2str(tmpval); else par_strs{i}{j} = sprintf(TmpPar.fmt_spec, tmpval); end % Find maximum length to determine the colum width, % but, for beauty, do not account for variables with % excessively long value strings tmplen = length(par_strs{i}{j}); if (val_pad_length else str = [str, sprintf([par_fmt_spec, ls],... exp_par_names{i}{j}, par_strs{i}{j})]; %#ok end end end % Prints an extra line separator at the end str = [str, sprintf(ls)]; end % Save metadata to a file function save(this, filename) fileID = fopen(filename,'a'); fprintf(fileID, mdt2str(this)); fclose(fileID); end % Create a structure from metadata array function MdtList = arrToStruct(this) MdtList = struct(); for i = 1:length(this) fn = matlab.lang.makeValidName(this(i).title); MdtList.(fn) = this(i); end end end methods (Access = public, Static = true) % Create metadata indicating the present moment of time function TimeMdt = time(title) if nargin()>0 assert(ischar(title)&&isvector(title),... 'Time field name must be a character vector') else title = 'Time'; end TimeMdt = MyMetadata(); TimeMdt.title = title; dv = datevec(datetime('now')); addParam(TimeMdt, 'Year', dv(1), 'fmt_spec','%i'); addParam(TimeMdt, 'Month', dv(2), 'fmt_spec','%i'); addParam(TimeMdt, 'Day', dv(3), 'fmt_spec','%i'); addParam(TimeMdt, 'Hour', dv(4), 'fmt_spec','%i'); addParam(TimeMdt, 'Minute', dv(5), 'fmt_spec','%i'); addParam(TimeMdt, 'Second', floor(dv(6)), 'fmt_spec','%i'); addParam(TimeMdt, 'Millisecond',... round(1000*(dv(6)-floor(dv(6)))),'fmt_spec','%i'); end % Load metadata from file. Return all the entries found and % the number of the last line read. function [MdtArr, n_end_line] = load(filename, varargin) fileID = fopen(filename,'r'); MasterMdt = MyMetadata(varargin{:}); % Loop initialization MdtArr = MyMetadata.empty(); line_no = 0; % Loop continues until we reach the next header or we reach % the end of the file while ~feof(fileID) % Grabs the current line curr_line = fgetl(fileID); % Give a warning if the file is empty, i.e. if fgetl % returns -1 if curr_line == -1 disp(['Read empty file ', filename, '.']); break end % Skips if the current line is empty if isempty(deblank(curr_line)) continue end S = parseLine(MasterMdt, curr_line); switch S.type case 'title' % Generate a valid identifier and add new metadata % to the output list TmpMdt = MyMetadata(varargin{:}, 'title', S.match); MdtArr = [MdtArr, TmpMdt]; %#ok case 'paramval' % Add a new parameter-value pair to the current % metadata [name, val, comment, Subs] = S.match{:}; addParam(TmpMdt, name, val, 'SubStruct', Subs, ... 'comment', comment); otherwise % Exit break end % Increment the counter line_no = line_no+1; end n_end_line = line_no; fclose(fileID); end end methods (Access = protected) % Parse string and determine the type of string function S = parseLine(this, str) S = struct( ... 'type', '', ... % title, paramval, other 'match', []); % parsed output % Check if the line contains a parameter - value pair. % First separate the comment if present pv_token = regexp(str, this.comment_sep, 'split', 'once'); if length(pv_token)>1 comment = pv_token{2}; % There is a comment else comment = ''; % There is no comment end % Then process name-value pair. Regard everything after % the first column separator as value. pv_token = regexp(pv_token{1}, this.column_sep, 'split', ... 'once'); % Remove leading and trailing blanks pv_token = strtrim(pv_token); if length(pv_token)>=2 % A candidate for parameter-value pair is found. Infer % the variable name and subscript reference. [Subs, name] = str2substruct(pv_token{1}); if isvarname(name) % Attempt converting the value to a number value = str2doubleHedged(pv_token{2}); S.type = 'paramval'; S.match = {name, value, comment, Subs}; return end end % Check if the line contains a title title_exp = [this.hdr_spec, '(\w.*)', this.hdr_spec]; title_token = regexp(str, title_exp, 'once', 'tokens'); if ~isempty(title_token) % Title expression found S.type = 'title'; S.match = title_token{1}; return end % No match found S.type = 'other'; S.match = {}; end % Make custom footer for command line display % (see matlab.mixin.CustomDisplay) function str = getFooter(this) if length(this) == 1 % For a single object display its properties str = ['Content:', newline, newline, ... replace(mdt2str(this), sprintf(this.line_sep), newline)]; elseif length(this) > 1 % For a non-empty array of objects display titles str = sprintf('\tTitles:\n\n'); for i = 1:length(this) str = [str, sprintf('%i\t%s\n', i, this(i).title)]; %#ok end str = [str, newline]; else % For an empty array display nothing str = ''; end end end end diff --git a/@MyTrace/MyTrace.m b/@MyTrace/MyTrace.m index bfb45b3..e126999 100644 --- a/@MyTrace/MyTrace.m +++ b/@MyTrace/MyTrace.m @@ -1,502 +1,502 @@ % Class for XY data representation with labelling, plotting and % saving/loading functionality % If instantiated as MyTrace(load_path) then % the content is loaded from file classdef MyTrace < handle & matlab.mixin.Copyable & matlab.mixin.SetGet properties (Access = public) x = [] y = [] name_x = 'x' name_y = 'y' unit_x = '' unit_y = '' file_name = '' % Structure storing MyMetadata objects with information about the % trace was taken MeasHeaders = struct() % Formatting options for the metadata metadata_opts = {} % Data formatting options column_sep = '\t' % Data column separator line_sep = '\r\n' % Data line separator data_sep = 'Data' % Separator between metadata and data save_prec = 15 % Maximum digits of precision in saved data % Cell that contains handles the trace is plotted in hlines = {} end properties (Dependent = true) label_x label_y end methods (Access = public) function this = MyTrace(varargin) P = MyClassParser(this); processInputs(P, this, varargin{:}); end %Defines the save function for the class. function save(this, filename, varargin) % Parse inputs for saving p = inputParser; addParameter(p, 'overwrite', false); parse(p, varargin{:}); assert(ischar(filename) && isvector(filename), ... '''filename'' must be a character vector.') this.file_name = filename; % Create the file in the given folder stat = createFile(filename, 'overwrite', p.Results.overwrite); % Returns if the file is not created for some reason if ~stat warning('File not created, returned write_flag %i.', stat); return end % Create metadata header MdtS = getMetadata(this); % Convert to array, set unified formatting and save Mdt = structfun(@(x)x, MdtS); if ~isempty(this.metadata_fmt) set(Mdt, this.metadata_fmt{:}); end save(Mdt, filename); % Write the data fileID = fopen(filename,'a'); % Pads the vectors if they are not equal length diff = length(this.x)-length(this.y); if diff<0 this.x = [this.x; zeros(-diff,1)]; warning(['Zero padded x vector as the saved vectors ' ... 'are not of the same length']); elseif diff>0 this.y = [this.y; zeros(diff,1)]; warning(['Zero padded y vector as the saved vectors ' ... 'are not of the same length']); end % Save data in the more compact of fixed point and scientific % notation with trailing zeros removed. % If save_prec=15, we get %.15g\t%.15g\r\n % Formatting without column padding may look ugly but it % signigicantly reduces the file size. data_format_str = ... sprintf(['%%.%ig', this.column_sep, '%%.%ig', ... this.line_sep], this.save_prec, this.save_prec); fprintf(fileID, data_format_str, [this.x, this.y]'); fclose(fileID); end function clearData(this) this.x = []; this.y = []; end %Plots the trace on the given axes, using the class variables to %define colors, markers, lines and labels. Takes all optional %parameters of the class as inputs. function plot(this, varargin) % Do nothing if there is no data in the trace if isempty(this) return end %Checks that x and y are the same size assert(validatePlot(this),... 'The length of x and y must be identical to make a plot') %Parses inputs p=inputParser(); % Axes in which log should be plotted addOptional(p, 'plot_axes', [], @(x)assert( ... isa(x,'matlab.graphics.axis.Axes')||... isa(x,'matlab.ui.control.UIAxes'),... 'Argument must be axes or uiaxes.')); validateColor=@(x) assert(iscolor(x),... 'Input must be a valid color. See iscolor function'); addParameter(p,'Color','b',validateColor); validateMarker=@(x) assert(ismarker(x),... 'Input must be a valid marker. See ismarker function'); addParameter(p,'Marker','none',validateMarker); validateLine=@(x) assert(isline(x),... 'Input must be a valid linestyle. See isline function'); addParameter(p,'LineStyle','-',validateLine); addParameter(p,'MarkerSize',6,... @(x) validateattributes(x,{'numeric'},{'positive'})); addParameter(p,'make_labels',false,@islogical); interpreters={'none','tex','latex'}; validateInterpreter=@(x) assert(contains(x,interpreters),... 'Interpreter must be none, tex or latex'); addParameter(p,'Interpreter','latex',validateInterpreter); parse(p,varargin{:}); %If axes are not supplied get current if ~isempty(p.Results.plot_axes) plot_axes=p.Results.plot_axes; else plot_axes=gca(); end ind=findLineInd(this, plot_axes); if ~isempty(ind) && any(ind) set(this.hlines{ind},'XData',this.x,'YData',this.y); else this.hlines{end+1}=plot(plot_axes,this.x,this.y); ind=length(this.hlines); end %Sets the correct color and label options set(this.hlines{ind},'Color',p.Results.Color,'LineStyle',... p.Results.LineStyle,'Marker',p.Results.Marker,... 'MarkerSize',p.Results.MarkerSize); if p.Results.make_labels interpreter=p.Results.Interpreter; xlabel(plot_axes,this.label_x,'Interpreter',interpreter); ylabel(plot_axes,this.label_y,'Interpreter',interpreter); set(plot_axes,'TickLabelInterpreter',interpreter); end end %If there is a line object from the trace in the figure, this sets %it to the appropriate visible setting. function setVisible(this, Axes, bool) if bool vis='on'; else vis='off'; end ind=findLineInd(this, Axes); if ~isempty(ind) && any(ind) set(this.hlines{ind},'Visible',vis) end end %Defines addition of two MyTrace objects function sum=plus(this,b) checkArithmetic(this,b); sum=MyTrace('x',this.x,'y',this.y+b.y, ... 'unit_x',this.unit_x,'unit_y',this.unit_y, ... 'name_x',this.name_x,'name_y',this.name_y); end %Defines subtraction of two MyTrace objects function diff=minus(this,b) checkArithmetic(this,b); diff=MyTrace('x',this.x,'y',this.y-b.y, ... 'unit_x',this.unit_x,'unit_y',this.unit_y, ... 'name_x',this.name_x,'name_y',this.name_y); end function [max_val,max_x]=max(this) assert(validatePlot(this),['MyTrace object must contain',... ' nonempty data vectors of equal length to find the max']) [max_val,max_ind]=max(this.y); max_x=this.x(max_ind); end function fwhm=calcFwhm(this) assert(validatePlot(this),['MyTrace object must contain',... ' nonempty data vectors of equal length to find the fwhm']) [~,~,fwhm,~]=findpeaks(this.y,this.x,'NPeaks',1); end %Integrates the trace numerically function area=integrate(this,varargin) assert(validatePlot(this),['MyTrace object must contain',... ' nonempty data vectors of equal length to integrate']) %Input parser for optional inputs p=inputParser; %Default is to use all the data in the trace addOptional(p,'ind',true(1,length(this.x))); parse(p,varargin{:}); ind=p.Results.ind; %Integrates the data contained in the indexed part. area=trapz(this.x(ind),this.y(ind)); end % Picks every n-th element from the trace, % performing a running average first if opt=='avg' function downsample(this, n, opt) n0 = ceil(n/2); if nargin()==3 && (strcmpi(opt,'average')||strcmpi(opt,'avg')) % Compute moving average with 'shrink' option so that the % total number of samples is preserved. Endpoints will be % discarded by starting the indexing from n0. tmpy = movmean(this.y, 'Endpoints', 'shrink'); this.x = this.x(n0:n:end); this.y = tmpy(n0:n:end); else % Downsample without averaging this.x = this.x(n0:n:end); this.y = this.y(n0:n:end); end end %Checks if the object is empty function bool = isDataEmpty(this) bool = isempty(this.x) && isempty(this.y); end %Checks if the data can be plotted function bool = validatePlot(this) bool =~isempty(this.x) && ~isempty(this.y)... && length(this.x)==length(this.y); end function hline = getLineHandle(this,ax) ind=findLineInd(this,ax); if ~isempty(ind) hline=this.hlines{ind}; else hline=[]; end end end methods (Access = public, Static = true) % Load trace from file function Trace = load(filename, varargin) assert(exist(filename, 'file'), ['File does not exist, ' ... 'please choose a different load path.']) % Extract data formatting p = inputParser(); p.KeepUnmatched = true; addParameter(p, 'FormatSource', {}, @(x) isa(x,'MyTrace')); addParameter(p, 'metadata_opts', {}, @iscell); parse(p, varargin{:}); if ~ismember('FormatSource', p.UsingDefaults) Fs = p.Results.FormatSource; % Take formatting from the source object mdt_opts = Fs.metadata_opts; trace_opts = { ... 'column_sep', Fs.column_sep, ... 'line_sep', Fs.line_sep, ... 'data_sep', Fs.data_sep, ... 'save_prec', Fs.save_prec, ... 'metadata_opts', Fs.metadata_opts}; else % Formatting is either default or was suppled explicitly mdt_opts = p.Results.metadata_opts; trace_opts = varargin; end % Load metadata and convert from array to structure [Mdt, n_end_line] = MyMetadata.load(filename, mdt_opts{:}); MdtS = arrToStruct(Mdt); if isfield(MdtS, 'Info') && isparam(MdtS.Info, 'Type') - class_name = getParam(MdtS.Info, 'Type'); + class_name = MdtS.Info.Type; else class_name = 'MyTrace'; end % Instantiate an appropriate type of Trace Trace = feval(class_name, trace_opts{:}); setMetadata(Trace, MdtS); % Reads x and y data data_array = dlmread(filename, Trace.column_sep, n_end_line,0); Trace.x = data_array(:,1); Trace.y = data_array(:,2); Trace.file_name = filename; end end methods (Access = protected) % Generate metadata that includes measurement headers and % information about trace. This function is used in place of 'get' % method so it can be overloaded in a subclass. function MdtS = getMetadata(this) MdtS = this.MeasHeaders; % Add a field with the information about the trace Info = MyMetadata('title', 'Info'); addParam(Info, 'Type', class(this)); addParam(Info, 'Name1', this.name_x); addParam(Info, 'Name2', this.name_y); addParam(Info, 'Unit1', this.unit_x); addParam(Info, 'Unit2', this.unit_y); MdtS.Info = Info; % Add a separator for the bulk of trace data DataSep = MyMetadata('title', this.data_sep); MdtS.DataSep = DataSep; end % Load metadata into the trace function setMetadata(this, MdtS) if isfield(MdtS, 'Info') if isparam(MdtS.Info, 'Unit1') - this.unit_x = getParam(MdtS.Info, 'Unit1'); + this.unit_x = MdtS.Info.Unit1; end if isparam(MdtS.Info, 'Unit2') - this.unit_y = getParam(MdtS.Info, 'Unit2'); + this.unit_y = MdtS.Info.Unit2; end if isparam(MdtS.Info, 'Name1') - this.name_x = getParam(MdtS.Info, 'Name1'); + this.name_x = MdtS.Info.Name1; end if isparam(MdtS.Info, 'Name2') - this.name_y = getParam(MdtS.Info, 'Name2'); + this.name_y = MdtS.Info.Name2; end % Remove the metadata containing trace properties MdtS = rmfield(MdtS, 'Info'); else warning(['No trace metadata found. No units or labels ' ... 'assigned when loading trace from %s.'], filename); end if isfield(MdtS, this.data_sep) % Remove the empty data separator field MdtS = rmfield(MdtS, this.data_sep); end % Store the remainder under measurement headers this.MeasHeaders = MdtS; end %Checks if arithmetic can be done with MyTrace objects. function checkArithmetic(this, b) assert(isa(this,'MyTrace') && isa(b,'MyTrace'),... ['Both objects must be of type MyTrace to add,',... 'here they are type %s and %s'],class(this),class(b)); assert(strcmp(this.unit_x, b.unit_x) && ... strcmp(this.unit_y,b.unit_y),... 'The MyTrace classes must have the same units for arithmetic') assert(length(this.x)==length(this.y)==... length(this.x)==length(this.y),... 'The length of x and y must be equal for arithmetic'); assert(all(this.x==b.x),... 'The MyTrace objects must have identical x-axis for arithmetic') end %Finds the hline handle that is plotted in the specified axes function ind = findLineInd(this, Axes) if ~isempty(this.hlines) ind = cellfun(@(x) ismember(x, findall(Axes, ... 'Type','Line')), this.hlines); else ind = []; end end end %Set and get methods methods %Set function for MeasHeaders function set.MeasHeaders(this, Val) assert(isstruct(Val),... 'MeasHeaders must be a structure of MyMetadata objects'); this.MeasHeaders = Val; end %Set function for x, checks if it is a vector of doubles and %reshapes into a column vector function set.x(this, x) assert(isnumeric(x),... 'Data must be of class double'); this.x=x(:); end %Set function for y, checks if it is a vector of doubles and %reshapes into a column vector function set.y(this, y) assert(isnumeric(y),... 'Data must be of class double'); this.y=y(:); end %Set function for unit_x, checks if input is a string. function set.unit_x(this, unit_x) assert(ischar(unit_x),'Unit must be a char, not a %s',... class(unit_x)); this.unit_x=unit_x; end %Set function for unit_y, checks if input is a string function set.unit_y(this, unit_y) assert(ischar(unit_y),'Unit must be a char, not a %s',... class(unit_y)); this.unit_y=unit_y; end %Set function for name_x, checks if input is a string function set.name_x(this, name_x) assert(ischar(name_x),'Name must be a char, not a %s',... class(name_x)); this.name_x=name_x; end %Set function for name_y, checks if input is a string function set.name_y(this, name_y) assert(ischar(name_y),'Name must be a char, not a %s',... class(name_y)); this.name_y=name_y; end function set.file_name(this, file_name) assert(ischar(file_name),'File path must be a char, not a %s',... class(file_name)); this.file_name=file_name; end %Get function for label_x, creates label from name_x and unit_x. function label_x=get.label_x(this) label_x=sprintf('%s (%s)', this.name_x, this.unit_x); end %Get function for label_y, creates label from name_y and unit_y. function label_y=get.label_y(this) label_y=sprintf('%s (%s)', this.name_y, this.unit_y); end end end diff --git a/testClass.m b/testClass.m index 727e285..72bb0aa 100644 --- a/testClass.m +++ b/testClass.m @@ -1,84 +1,84 @@ classdef testClass < MyInstrument & MyCommCont properties Listeners PropList end properties (GetAccess = public, SetAccess=public, SetObservable=true) prop1 = 1 end - properties (GetAccess = public, SetAccess=immutable) - prop2 =0 + properties (GetAccess = public, Hidden = true) + prop2 = 0.3 end properties (GetAccess = public, SetAccess=public) prop3 end methods (Access = public) function this = testClass(varargin) P = MyClassParser(this); processInputs(P, this, varargin{:}); end function delete(this) try delete(this.prop1); catch end end function preSetCallback(this) %isequal(this.prop1, 2); %disp('Pre Set'); %disp(this.prop1); end function postSetCallback(this) %isequal(this.prop1, 2); %disp('Post Set'); %disp(this.prop1); end end methods (Access = protected) function createCommandList(this) addCommand(this, 'cmd1', ... 'readFcn', @()rand()); addCommand(this, 'cmd2', ... 'readFcn', @()rand(), ... 'writeFcn', @(x)(disp('write cmd2'))); addprop(this,'dynprop1'); mp=addprop(this,'ddp'); %mp.Dependent = true; %mp.GetMethod = @get_ddp; mp.SetMethod = @set_ddp; this.PropList.ddp.value=1; end function set_ddp(this, val) this.ddp = val; %this.PropList.ddp.value = val; end function val = get_ddp(this) val = this.PropList.ddp.value; end end methods function set.prop1(this, val) if ~isequal(val, this.prop1) this.prop1 = val; disp('Set method'); end end end end