comparison 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
comparison
equal deleted inserted replaced
-1:000000000000 0:f0afece42f48
1 % QUASISWEPTSING computes a transfer function from swept-sine measurements
2 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
3 %
4 % DESCRIPTION: QUASISWEPTSING computes a transfer function from discrete
5 % swept-sine measurements.
6 %
7 % In order for the calculation to work, you need to give it an array of
8 % start and stop times (or durations), and (optionally) an array of
9 % amplitudes and frequencies of the injected sine-waves. If you don't
10 % specify the frequencies, you must give a time-series of the injected
11 % signal and the algorithm will try to determine the amplitudes and
12 % frequencies from the data.
13 %
14 %
15 % CALL: T = quasiSweptSine(out, pl);
16 %
17 % INPUTS: out - The measured output of the system
18 % PL - parameter list
19 %
20 % OUTPUT: T - the measured transfer function
21 %
22 % The procinfo of the output AOs contains the following fields:
23 %
24 % 'frequencies' - the frequencies used in the DFT estimation.
25 % 'timespans' - an array of timespan objects, one for each sine-wave
26 % segment
27 %
28 %
29 % <a href="matlab:utils.helper.displayMethodInfo('ao', 'quasiSweptSine')">Parameters Description</a>
30 %
31 % VERSION: $Id: quasiSweptSine.m,v 1.10 2011/04/08 08:56:16 hewitson Exp $
32 %
33 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
34
35
36 function varargout = quasiSweptSine(varargin)
37
38 % check if this is a call for parameters
39 if utils.helper.isinfocall(varargin{:})
40 varargout{1} = getInfo(varargin{3});
41 return
42 end
43
44 % tell the system we are runing
45 import utils.const.*
46 utils.helper.msg(msg.PROC3, 'running %s/%s', mfilename('class'), mfilename);
47
48 % collect input variable names
49 in_names = cell(size(varargin));
50 for ii = 1:nargin,in_names{ii} = inputname(ii);end
51
52 % collect all AOs and plists
53 [as, ao_invars] = utils.helper.collect_objects(varargin(:), 'ao', in_names);
54 pl = utils.helper.collect_objects(varargin(:), 'plist', in_names);
55
56 if nargout == 0
57 error('### quasiSweptSine can not be used as a modifier method. Please give at least one output');
58 end
59
60 % Make copies or handles to inputs
61 bs = copy(as, nargout);
62
63 % combine plists
64 pl = parse(pl, getDefaultPlist());
65
66 % Parameters
67 input = pl.find('input');
68 startTimes = pl.find('Start times');
69 stopTimes = pl.find('Stop times');
70 durations = pl.find('durations');
71 amplitudes = pl.find('amplitudes');
72 frequencies = pl.find('frequencies');
73 % phases = pl.find('phases');
74 inUnits = pl.find('Input Units');
75 win = pl.find('Win');
76 Nerror = pl.find('Nerror');
77
78 if isa(input, 'ao') && ~isa(input.data, 'tsdata')
79 utils.helper.err('quasiSweptSine requires time-series as input');
80 end
81
82
83 %---------------------------------------------------
84 %--------- Convert the times to timespans
85 if isempty(durations) && isempty(stopTimes)
86 utils.helper.err('You need to specify either an array of stop times, or an array of durations');
87 end
88
89
90 %---------------------------------------------------
91 %--------- if we have the input, we use it to determine amplitudes and
92 %--------- frequencies
93
94 if isempty(input) && isempty(amplitudes)
95 utils.helper.err('You need to specify either an input signal, or a full description of the signals including amplitudes and frequecies.');
96 end
97
98 computeFrequecies = false;
99 if isempty(frequencies)
100 computeFrequecies = true;
101 end
102
103
104 % Go through each ao
105 for jj=1:numel(bs)
106
107
108 if ~isa(bs.data, 'tsdata')
109 utils.helper.err('quasiSweptSine requires time-series as input');
110 end
111
112 [timespans, startTimes, stopTimes] = generateTimespans(bs(jj).t0, startTimes, stopTimes, durations);
113
114
115 % Go through each time-span
116 Txx = zeros(size(timespans));
117 dT = zeros(size(timespans));
118 for kk=1:numel(timespans)
119 if isa(input, 'ao')
120 inseg = input.split(plist('timespan', timespans(kk)));
121 else
122 % We don't have an input, so we need to create inputs from the
123 % signal specs
124
125 inseg = ao(plist('waveform', 'sine wave', ...
126 'nsecs', timespans(kk).interval, ...
127 'fs', bs(jj).fs, ...
128 'A', amplitudes(kk), ...
129 'f', frequencies(kk), ...
130 'toff', 0, ...
131 't0', startTimes(kk), ...
132 'yunits', inUnits));
133 end
134 if computeFrequecies
135 b = sineParams(inseg, plist('N', 1));
136 frequencies(kk) = b.y(2);
137 end
138
139 utils.helper.msg(msg.PROC1, 'Computing TF at %g Hz', frequencies(kk));
140
141 % Window
142 w = ao(plist('win', win, 'length', inseg.len));
143
144 % Output
145 outseg = bs(jj).split(plist('timespan', timespans(kk)));
146
147 % Compute DFT
148 fs = outseg.data.fs;
149 N = outseg.len;
150 J = -2*pi*1i.*(0:N-1)/fs;
151 outxx = exp(frequencies(kk)*J)*(w.y.*outseg.data.getY);
152 inxx = exp(frequencies(kk)*J)*(w.y.*inseg.data.getY);
153 Txx(kk) = outxx./inxx;
154
155 % Compute error
156 %
157 nout = noiseAroundLine(outseg, frequencies(kk), Nerror);
158 nin = noiseAroundLine(inseg, frequencies(kk), Nerror);
159 snr1 = abs(outxx)./nout;
160 snr2 = abs(inxx)./nin;
161 dT(kk) = abs(Txx(kk)) * sqrt( (1./snr1)^2 + (1./snr2)^2);
162
163
164 end % End loop over timespans (segments)
165
166 % Make output
167 bs(jj).data = fsdata(frequencies, Txx, bs(jj).data.fs);
168 bs(jj).data.dy = dT;
169 bs(jj).data.setXunits('Hz');
170 bs(jj).data.setYunits(outseg.yunits./inseg.yunits);
171 % Set name
172 bs(jj).name = sprintf('sweptsine(%s,%s)', input.name, ao_invars{jj});
173 % Set procinfo
174 bs(jj).procinfo = plist('frequencies', frequencies, ...
175 'timespan', timespans);
176 % Add history
177 bs(jj).addHistory(getInfo('None'), pl, ao_invars(jj), bs(jj).hist);
178
179 end % Loop over input aos
180
181 % set outputs
182 varargout{1} = bs;
183
184 end
185
186 function n = noiseAroundLine(sig, f0, N)
187
188 xx = abs(fft(sig));
189
190 % get the noise floor around the frequency of interest
191 % - we need the bin that is nearest the frequency
192 f = abs(xx.x - f0);
193 [m, mi] = min(f);
194 % xx.x(mi)
195 % iplot(xx)
196 % plot(xx.x(mi), xx.y(mi), 'x')
197
198 M = N-1;
199 % Measure below the line frequency if we can
200 nl = 0;
201 if (mi>1)
202 ls = max(1, mi-2*M);
203 le = max(1, mi-M);
204 nl = xx.y(ls:le);
205 end
206 % Measure above the line frequency if we can
207 nu = 0;
208 if mi<xx.len
209 us = min(xx.len, mi+M);
210 ue = min(xx.len, mi+2*M);
211 nu = xx.y(us:ue);
212 end
213 n = mean([nl;nu]);
214
215 end
216
217 function [ts, startTimes, stopTimes] = generateTimespans(t0, startTimes, stopTimes, durations)
218
219
220 if isempty(durations) && numel(startTimes) ~= numel(stopTimes)
221 utils.helper.err('You need to specify the same number of start and stop times.');
222 end
223 if isempty(stopTimes) && numel(startTimes) ~= numel(durations)
224 utils.helper.err('You need to specify the same number of durations and stop times.');
225 end
226
227
228 ts = timespan.initObjectWithSize(1,numel(startTimes));
229
230 % Convert to time objects
231 if iscell(startTimes)
232 startTimes = time(startTimes);
233 end
234 if isnumeric(startTimes)
235 newStarts = time.initObjectWithSize(1,numel(startTimes));
236 for kk=1:numel(startTimes)
237 newStarts(kk) = t0+startTimes(kk);
238 end
239 startTimes = newStarts;
240 end
241
242 if isempty(durations) && iscell(stopTimes)
243 stopTimes = time(stopTimes);
244 end
245
246 if isempty(durations) && isnumeric(stopTimes)
247 if isnumeric(stopTimes)
248 newStops = time.initObjectWithSize(1,numel(startTimes));
249 for kk=1:numel(stopTimes)
250 newStops(kk) = t0+stopTimes(kk);
251 end
252 stopTimes = newStops;
253 end
254 end
255
256 useDuration = isempty(stopTimes);
257 for kk=1:numel(startTimes)
258 if useDuration
259 ts(kk) = timespan(startTimes(kk), startTimes(kk)+durations(kk));
260 else
261 ts(kk) = timespan(startTimes(kk), stopTimes(kk));
262 end
263 end
264
265 end
266
267
268 % get info object
269 function ii = getInfo(varargin)
270 if nargin == 1 && strcmpi(varargin{1}, 'None')
271 sets = {};
272 pl = [];
273 else
274 sets = {'Default'};
275 pl = getDefaultPlist;
276 end
277 % build info object
278 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);
279 end
280
281 % get default plist
282 function plout = getDefaultPlist()
283 persistent pl;
284 if exist('pl', 'var')==0 || isempty(pl)
285 pl = buildplist();
286 end
287 plout = pl;
288 end
289
290 function pl = buildplist()
291
292 % default plist for linear fitting
293 pl = plist();
294
295 % Input
296 p = param({'input', 'The input data series.'}, paramValue.EMPTY_DOUBLE);
297 pl.append(p);
298
299 % Start times
300 p = param({'Start Times', 'A cell array of start times, or an array of time objects.'}, ...
301 paramValue.EMPTY_CELL);
302 pl.append(p);
303
304 % Stop times
305 p = param({'Stop Times', 'A cell array of stop times, or an array of time objects.'}, ...
306 paramValue.EMPTY_CELL);
307 pl.append(p);
308
309 % Durations
310 p = param({'Durations', 'An array of durations that can be used instead of the stop times.'}, ...
311 paramValue.EMPTY_DOUBLE);
312 pl.append(p);
313
314 % Amplitudes
315 p = param({'Amplitudes', 'An array of amplitudes.'}, ...
316 paramValue.EMPTY_DOUBLE);
317 pl.append(p);
318
319 % Frequencies
320 p = param({'Frequencies', 'An array of frequencies [Hz].'}, ...
321 paramValue.EMPTY_DOUBLE);
322 pl.append(p);
323
324 % Input units
325 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.'}, ...
326 {1, {'V'}, paramValue.OPTIONAL});
327 pl.append(p);
328
329 % Window
330 p = param({'Win', 'A window to apply to each segment when computing the DFT.'}, ...
331 paramValue.WINDOW);
332 pl.append(p);
333
334 % Error samples
335 p = param({'Nerror', ['The number of samples either side of the line frequency to use to estimate the noise floor.'...
336 '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 '...
337 'of the bin nearest to the frequency of the signal.']}, ...
338 paramValue.DOUBLE_VALUE(5));
339 pl.append(p);
340
341 % % Phases
342 % p = param({'Phases', 'An array of phases [degrees].'}, paramValue.DOUBLE_VALUE(0));
343 % pl.append(p);
344
345 end