Mercurial > hg > ltpda
diff m-toolbox/classes/@ao/quasiSweptSine.m @ 0:f0afece42f48
Import.
author | Daniele Nicolodi <nicolodi@science.unitn.it> |
---|---|
date | Wed, 23 Nov 2011 19:22:13 +0100 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/m-toolbox/classes/@ao/quasiSweptSine.m Wed Nov 23 19:22:13 2011 +0100 @@ -0,0 +1,345 @@ +% QUASISWEPTSING computes a transfer function from swept-sine measurements +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% +% DESCRIPTION: QUASISWEPTSING computes a transfer function from discrete +% swept-sine measurements. +% +% In order for the calculation to work, you need to give it an array of +% start and stop times (or durations), and (optionally) an array of +% amplitudes and frequencies of the injected sine-waves. If you don't +% specify the frequencies, you must give a time-series of the injected +% signal and the algorithm will try to determine the amplitudes and +% frequencies from the data. +% +% +% CALL: T = quasiSweptSine(out, pl); +% +% INPUTS: out - The measured output of the system +% PL - parameter list +% +% OUTPUT: T - the measured transfer function +% +% The procinfo of the output AOs contains the following fields: +% +% 'frequencies' - the frequencies used in the DFT estimation. +% 'timespans' - an array of timespan objects, one for each sine-wave +% segment +% +% +% <a href="matlab:utils.helper.displayMethodInfo('ao', 'quasiSweptSine')">Parameters Description</a> +% +% VERSION: $Id: quasiSweptSine.m,v 1.10 2011/04/08 08:56:16 hewitson Exp $ +% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% + + +function varargout = quasiSweptSine(varargin) + + % check if this is a call for parameters + if utils.helper.isinfocall(varargin{:}) + varargout{1} = getInfo(varargin{3}); + return + end + + % tell the system we are runing + import utils.const.* + utils.helper.msg(msg.PROC3, 'running %s/%s', mfilename('class'), mfilename); + + % collect input variable names + in_names = cell(size(varargin)); + for ii = 1:nargin,in_names{ii} = inputname(ii);end + + % collect all AOs and plists + [as, ao_invars] = utils.helper.collect_objects(varargin(:), 'ao', in_names); + pl = utils.helper.collect_objects(varargin(:), 'plist', in_names); + + if nargout == 0 + error('### quasiSweptSine can not be used as a modifier method. Please give at least one output'); + end + + % Make copies or handles to inputs + bs = copy(as, nargout); + + % combine plists + pl = parse(pl, getDefaultPlist()); + + % Parameters + input = pl.find('input'); + startTimes = pl.find('Start times'); + stopTimes = pl.find('Stop times'); + durations = pl.find('durations'); + amplitudes = pl.find('amplitudes'); + frequencies = pl.find('frequencies'); +% phases = pl.find('phases'); + inUnits = pl.find('Input Units'); + win = pl.find('Win'); + Nerror = pl.find('Nerror'); + + if isa(input, 'ao') && ~isa(input.data, 'tsdata') + utils.helper.err('quasiSweptSine requires time-series as input'); + end + + + %--------------------------------------------------- + %--------- Convert the times to timespans + if isempty(durations) && isempty(stopTimes) + utils.helper.err('You need to specify either an array of stop times, or an array of durations'); + end + + + %--------------------------------------------------- + %--------- if we have the input, we use it to determine amplitudes and + %--------- frequencies + + if isempty(input) && isempty(amplitudes) + utils.helper.err('You need to specify either an input signal, or a full description of the signals including amplitudes and frequecies.'); + end + + computeFrequecies = false; + if isempty(frequencies) + computeFrequecies = true; + end + + + % Go through each ao + for jj=1:numel(bs) + + + if ~isa(bs.data, 'tsdata') + utils.helper.err('quasiSweptSine requires time-series as input'); + end + + [timespans, startTimes, stopTimes] = generateTimespans(bs(jj).t0, startTimes, stopTimes, durations); + + + % Go through each time-span + Txx = zeros(size(timespans)); + dT = zeros(size(timespans)); + for kk=1:numel(timespans) + if isa(input, 'ao') + inseg = input.split(plist('timespan', timespans(kk))); + else + % We don't have an input, so we need to create inputs from the + % signal specs + + inseg = ao(plist('waveform', 'sine wave', ... + 'nsecs', timespans(kk).interval, ... + 'fs', bs(jj).fs, ... + 'A', amplitudes(kk), ... + 'f', frequencies(kk), ... + 'toff', 0, ... + 't0', startTimes(kk), ... + 'yunits', inUnits)); + end + if computeFrequecies + b = sineParams(inseg, plist('N', 1)); + frequencies(kk) = b.y(2); + end + + utils.helper.msg(msg.PROC1, 'Computing TF at %g Hz', frequencies(kk)); + + % Window + w = ao(plist('win', win, 'length', inseg.len)); + + % Output + outseg = bs(jj).split(plist('timespan', timespans(kk))); + + % Compute DFT + fs = outseg.data.fs; + N = outseg.len; + J = -2*pi*1i.*(0:N-1)/fs; + outxx = exp(frequencies(kk)*J)*(w.y.*outseg.data.getY); + inxx = exp(frequencies(kk)*J)*(w.y.*inseg.data.getY); + Txx(kk) = outxx./inxx; + + % Compute error + % + nout = noiseAroundLine(outseg, frequencies(kk), Nerror); + nin = noiseAroundLine(inseg, frequencies(kk), Nerror); + snr1 = abs(outxx)./nout; + snr2 = abs(inxx)./nin; + dT(kk) = abs(Txx(kk)) * sqrt( (1./snr1)^2 + (1./snr2)^2); + + + end % End loop over timespans (segments) + + % Make output + bs(jj).data = fsdata(frequencies, Txx, bs(jj).data.fs); + bs(jj).data.dy = dT; + bs(jj).data.setXunits('Hz'); + bs(jj).data.setYunits(outseg.yunits./inseg.yunits); + % Set name + bs(jj).name = sprintf('sweptsine(%s,%s)', input.name, ao_invars{jj}); + % Set procinfo + bs(jj).procinfo = plist('frequencies', frequencies, ... + 'timespan', timespans); + % Add history + bs(jj).addHistory(getInfo('None'), pl, ao_invars(jj), bs(jj).hist); + + end % Loop over input aos + + % set outputs + varargout{1} = bs; + +end + +function n = noiseAroundLine(sig, f0, N) + + xx = abs(fft(sig)); + + % get the noise floor around the frequency of interest + % - we need the bin that is nearest the frequency + f = abs(xx.x - f0); + [m, mi] = min(f); +% xx.x(mi) +% iplot(xx) +% plot(xx.x(mi), xx.y(mi), 'x') + + M = N-1; + % Measure below the line frequency if we can + nl = 0; + if (mi>1) + ls = max(1, mi-2*M); + le = max(1, mi-M); + nl = xx.y(ls:le); + end + % Measure above the line frequency if we can + nu = 0; + if mi<xx.len + us = min(xx.len, mi+M); + ue = min(xx.len, mi+2*M); + nu = xx.y(us:ue); + end + n = mean([nl;nu]); + +end + +function [ts, startTimes, stopTimes] = generateTimespans(t0, startTimes, stopTimes, durations) + + + if isempty(durations) && numel(startTimes) ~= numel(stopTimes) + utils.helper.err('You need to specify the same number of start and stop times.'); + end + if isempty(stopTimes) && numel(startTimes) ~= numel(durations) + utils.helper.err('You need to specify the same number of durations and stop times.'); + end + + + ts = timespan.initObjectWithSize(1,numel(startTimes)); + + % Convert to time objects + if iscell(startTimes) + startTimes = time(startTimes); + end + if isnumeric(startTimes) + newStarts = time.initObjectWithSize(1,numel(startTimes)); + for kk=1:numel(startTimes) + newStarts(kk) = t0+startTimes(kk); + end + startTimes = newStarts; + end + + if isempty(durations) && iscell(stopTimes) + stopTimes = time(stopTimes); + end + + if isempty(durations) && isnumeric(stopTimes) + if isnumeric(stopTimes) + newStops = time.initObjectWithSize(1,numel(startTimes)); + for kk=1:numel(stopTimes) + newStops(kk) = t0+stopTimes(kk); + end + stopTimes = newStops; + end + end + + useDuration = isempty(stopTimes); + for kk=1:numel(startTimes) + if useDuration + ts(kk) = timespan(startTimes(kk), startTimes(kk)+durations(kk)); + else + ts(kk) = timespan(startTimes(kk), stopTimes(kk)); + end + end + +end + + +% get info object +function ii = getInfo(varargin) + if nargin == 1 && strcmpi(varargin{1}, 'None') + sets = {}; + pl = []; + else + sets = {'Default'}; + pl = getDefaultPlist; + end + % build info object + ii = minfo(mfilename, 'ao', 'ltpda', utils.const.categories.op, '$Id: quasiSweptSine.m,v 1.10 2011/04/08 08:56:16 hewitson Exp $', sets, pl); +end + +% get default plist +function plout = getDefaultPlist() + persistent pl; + if exist('pl', 'var')==0 || isempty(pl) + pl = buildplist(); + end + plout = pl; +end + +function pl = buildplist() + + % default plist for linear fitting + pl = plist(); + + % Input + p = param({'input', 'The input data series.'}, paramValue.EMPTY_DOUBLE); + pl.append(p); + + % Start times + p = param({'Start Times', 'A cell array of start times, or an array of time objects.'}, ... + paramValue.EMPTY_CELL); + pl.append(p); + + % Stop times + p = param({'Stop Times', 'A cell array of stop times, or an array of time objects.'}, ... + paramValue.EMPTY_CELL); + pl.append(p); + + % Durations + p = param({'Durations', 'An array of durations that can be used instead of the stop times.'}, ... + paramValue.EMPTY_DOUBLE); + pl.append(p); + + % Amplitudes + p = param({'Amplitudes', 'An array of amplitudes.'}, ... + paramValue.EMPTY_DOUBLE); + pl.append(p); + + % Frequencies + p = param({'Frequencies', 'An array of frequencies [Hz].'}, ... + paramValue.EMPTY_DOUBLE); + pl.append(p); + + % Input units + p = param({'Input Units', 'If you don''t give an input signal AO, you can specify the units of the signal that will be constructed internally.'}, ... + {1, {'V'}, paramValue.OPTIONAL}); + pl.append(p); + + % Window + p = param({'Win', 'A window to apply to each segment when computing the DFT.'}, ... + paramValue.WINDOW); + pl.append(p); + + % Error samples + p = param({'Nerror', ['The number of samples either side of the line frequency to use to estimate the noise floor.'... + 'The noise is estimated from <br><br><tt>mean([y(idx-2*M:idx-M);y(idx+M:idx+2M)])</tt><br><br> where <tt>M=N-1</tt> and <tt>idx</tt> is the index '... + 'of the bin nearest to the frequency of the signal.']}, ... + paramValue.DOUBLE_VALUE(5)); + pl.append(p); + +% % Phases +% p = param({'Phases', 'An array of phases [degrees].'}, paramValue.DOUBLE_VALUE(0)); +% pl.append(p); + +end