c@0
|
1 classdef MASSEF < handle
|
c@0
|
2 %MASSEF Multichannel audio source separation evaluation framework.
|
c@0
|
3 %
|
c@0
|
4 % The multichannel audio source separation evaluation framework is
|
c@0
|
5 % designed to faciliate the development and evaluation of audio
|
c@0
|
6 % source separation algorithms. Algorithms and estimates are evaluated
|
c@0
|
7 % using a range of metrics, including SNR, STOI, and PEASS. The choice of
|
c@0
|
8 % metrics is configurable. Furthermore, if the algorithm is intended to
|
c@0
|
9 % perform localisation, then this can also be evaluated.
|
c@0
|
10 %
|
c@0
|
11 % The framework can be run in two ways:
|
c@0
|
12 % 1) by providing iosr.bss.mixture objects and separation algorithms,
|
c@0
|
13 % or
|
c@0
|
14 % 2) providing estimate and true source wav files.
|
c@0
|
15 %
|
c@0
|
16 % If 1), the framework generates the mixture(s), calculates the ideal
|
c@0
|
17 % binary and ratio masks, provides the mixture(s) to the separation
|
c@0
|
18 % algorithm(s), and evaluates the outputs of the separation algorithm(s).
|
c@0
|
19 % The framework also evaluates: the ideal masks for the purposes of
|
c@0
|
20 % comparison, and any azimuth/elevation estimates returned by the
|
c@0
|
21 % algorithm. Use the EXECUTE() method to operate in this mode.
|
c@0
|
22 %
|
c@0
|
23 % If 2), the framework evaluates only the supplied estimate(s) using
|
c@0
|
24 % signal-based metrics. Use the EVALUATE() method to operate in this
|
c@0
|
25 % mode.
|
c@0
|
26 %
|
c@0
|
27 % Sources may have any number of channels; the framework evaluates each
|
c@0
|
28 % channel. The use of iosr.bss.mixture objects facilitate the evaluation
|
c@0
|
29 % of spatialised mixtures (e.g. binaural).
|
c@0
|
30 %
|
c@0
|
31 % Type
|
c@0
|
32 %
|
c@0
|
33 % MASSEF.start
|
c@12
|
34 % MASSEF.doc
|
c@0
|
35 %
|
c@12
|
36 % for more information.
|
c@0
|
37 %
|
c@0
|
38 % MASSEF can be used to evaluate and compare separation algorithms,
|
c@0
|
39 % provided that the algorithms conform to the required format. Consult
|
c@0
|
40 % the help documentation for more information.
|
c@0
|
41 %
|
c@0
|
42 % MASSEF properties:
|
c@0
|
43 % blocksize - When using the parallel computing toolbox,
|
c@0
|
44 % this parameter determines the maximum number
|
c@0
|
45 % of parallel.FevalFuture objects that are
|
c@0
|
46 % considered at any one time. The default is
|
c@0
|
47 % 128.
|
c@0
|
48 % creationDate - Date the object was created (read-only).
|
c@0
|
49 % dir - The MASSEF installation directory
|
c@0
|
50 % (read-only).
|
c@0
|
51 % evalPEASS - A logical value indicating whether PEASS
|
c@0
|
52 % evaluation should be executed. The default is
|
c@0
|
53 % false.
|
c@0
|
54 % evalSTOI - A logical value indicating whether STOI
|
c@0
|
55 % evaluation should be executed. The default is
|
c@0
|
56 % false.
|
c@0
|
57 % parpool - A parallel.Pool object on which to perform
|
c@0
|
58 % the parallel separations. If the parallel
|
c@0
|
59 % computing toolbox is available, MASSEF
|
c@0
|
60 % will use the current pool by default (as
|
c@0
|
61 % determined by gcp('nocreate')), if one is
|
c@0
|
62 % open. If the toolbox is not available, or no
|
c@0
|
63 % pool is open, separations will be performed
|
c@0
|
64 % serially.
|
c@0
|
65 % results - A MASSEFresults object containing results
|
c@0
|
66 % generated by the framework (read-only).
|
c@0
|
67 % results_filename - The name of the results file
|
c@0
|
68 % returned when MASSEF.EXECUTE finishes.
|
c@0
|
69 % The default is 'Results/results.mat'.
|
c@0
|
70 % saveDate - Date the object was last saved (read-only).
|
c@0
|
71 %
|
c@0
|
72 % MASSEF methods:
|
c@0
|
73 % MASSEF - Create an instance of MASSEF.
|
c@0
|
74 % evaluate - Run the framework using input audio files.
|
c@0
|
75 % execute - Run the framework using the input mixtures
|
c@0
|
76 % and separators.
|
c@0
|
77 % save - Save the framework's data and results.
|
c@0
|
78 % Static methods:
|
c@12
|
79 % doc - Display the framework documentation.
|
c@0
|
80 % start - Start the framework.
|
c@0
|
81 % install - Download and install MASSEF dependencies.
|
c@0
|
82 %
|
c@0
|
83 % See also IOSR.BSS.MIXTURE, MASSEFRESULTS.
|
c@0
|
84
|
c@0
|
85 % Copyright 2016 University of Surrey.
|
c@0
|
86
|
c@0
|
87 properties
|
c@0
|
88 blocksize % number of simultaneous parallel.FevalFuture objects
|
c@0
|
89 evalPEASS % logical flag determing evaluation using PEASS
|
c@0
|
90 evalSTOI % logical flag determing evaluation using STOI
|
c@0
|
91 parpool % parallel.pool object on which to perform separations
|
c@0
|
92 results_filename % the name of the results file
|
c@0
|
93 end
|
c@0
|
94
|
c@0
|
95 properties (Constant)
|
c@0
|
96 dir = fileparts(which(mfilename('fullpath'))) % directory in which this file is stored
|
c@0
|
97 end
|
c@0
|
98
|
c@0
|
99 properties (SetAccess = private)
|
c@0
|
100 results % the results data
|
c@0
|
101 creationDate % date this object was created
|
c@0
|
102 saveDate % date this object was last saved
|
c@0
|
103 end
|
c@0
|
104
|
c@0
|
105 properties (Access = private)
|
c@0
|
106 hWaitBar % handle to waitbar
|
c@0
|
107 iterations % total iterations
|
c@0
|
108 PEASSoptions % PEASS options
|
c@0
|
109 end
|
c@0
|
110
|
c@0
|
111 properties (Constant, Access = private)
|
c@0
|
112 IDEAL = -1 % algorithm number for ideal estimates
|
c@0
|
113 end
|
c@0
|
114
|
c@0
|
115 methods
|
c@0
|
116
|
c@0
|
117 % constructor
|
c@0
|
118
|
c@0
|
119 function obj = MASSEF(options)
|
c@0
|
120 %MASSEF Create an instance of MASSEF
|
c@0
|
121 %
|
c@0
|
122 % M = MASSEF instantiates MASSEF, returning an instance to M.
|
c@0
|
123 % Evaluations are performed using the EXECUTE method.
|
c@0
|
124 %
|
c@0
|
125 % M = MASSEF(OPTIONS) instantiates MASSEF using the options
|
c@0
|
126 % contained in the scalar structure OPTIONS. See MASSEF for a
|
c@0
|
127 % description of valid fields.
|
c@0
|
128
|
c@0
|
129 if nargin<1
|
c@0
|
130 options = struct;
|
c@0
|
131 end
|
c@0
|
132
|
c@0
|
133 options = obj.validate_options(options);
|
c@0
|
134
|
c@0
|
135 obj.evalPEASS = options.evalPEASS;
|
c@0
|
136 obj.evalSTOI = options.evalSTOI;
|
c@0
|
137 obj.parpool = options.parpool;
|
c@0
|
138 obj.results_filename = options.results_filename;
|
c@0
|
139 obj.blocksize = options.blocksize;
|
c@0
|
140
|
c@0
|
141 % create empty results object array
|
c@0
|
142 obj.results = MASSEFresults();
|
c@0
|
143
|
c@0
|
144 obj.creationDate = date;
|
c@0
|
145
|
c@0
|
146 end
|
c@0
|
147
|
c@0
|
148 % validate properties
|
c@0
|
149
|
c@0
|
150 function set.blocksize(obj,val)
|
c@23
|
151 assert(isnumeric(val) && isscalar(val),'MASSEF:blocksize:invalid','blocksize must be a numeric scalar.')
|
c@23
|
152 assert(round(val)==val,'MASSEF:invalidBlocksize','blocksize must be an integer.')
|
c@23
|
153 assert(val>=1,'MASSEF:invalidBlocksize','blocksize must be greater than or equal to 1.')
|
c@0
|
154 obj.blocksize = val;
|
c@0
|
155 end
|
c@0
|
156
|
c@0
|
157 function set.evalPEASS(obj,val)
|
c@23
|
158 assert(islogical(val) && isscalar(val),'MASSEF:evalPEASS:invalid','evalPEASS must be a logical scalar.')
|
c@0
|
159 obj.evalPEASS = val;
|
c@0
|
160 end
|
c@0
|
161
|
c@0
|
162 function set.evalSTOI(obj,val)
|
c@23
|
163 assert(islogical(val) && isscalar(val),'MASSEF:evalSTOI:invalid','evalSTOI must be a logical scalar.')
|
c@0
|
164 obj.evalSTOI = val;
|
c@0
|
165 end
|
c@0
|
166
|
c@0
|
167 function set.parpool(obj,val)
|
c@0
|
168 if ~isempty(val)
|
c@23
|
169 assert(isa(val,'parallel.Pool'),'MASSEF:parpool:invalid','parpool must be a parallel.pool object.')
|
c@0
|
170 end
|
c@0
|
171 obj.parpool = val;
|
c@0
|
172 end
|
c@0
|
173
|
c@0
|
174 function set.results_filename(obj,val)
|
c@23
|
175 assert(ischar(val),'MASSEF:results_filename:invalid','results_filename must be a char array')
|
c@0
|
176 obj.results_filename = val;
|
c@0
|
177 end
|
c@0
|
178
|
c@0
|
179 % other methods
|
c@0
|
180
|
c@0
|
181 function execute(obj,mixtures,separators)
|
c@0
|
182 %EXECUTE Run the framework using the input mixtures and separators
|
c@0
|
183 %
|
c@0
|
184 % OBJ.EXECUTE(MIXTURES) runs MASSEF using the array of
|
c@0
|
185 % iosr.bss.mixture objects MIXTURES and calculates performance
|
c@0
|
186 % metrics using only ideal estimates (IBM and IRM).
|
c@0
|
187 %
|
c@0
|
188 % OBJ.EXECUTE(MIXTURES,SEPARATORS) runs MASSEF using the array of
|
c@0
|
189 % iosr.bss.mixture objects MIXTURES and the array (or cell array)
|
c@0
|
190 % of instances of separation algorithms contained in SEPARATORS.
|
c@0
|
191 % Separation is performed for all combinations of these
|
c@0
|
192 % variables.
|
c@0
|
193 %
|
c@0
|
194 % MASSEF.EXECUTE(MIXTURES,SEPARATORS) runs MASSEF using the
|
c@0
|
195 % MASSEF instance MASSEF, the array of IOSR.BSS.MIXTURE objects
|
c@0
|
196 % MIXTURES and the array (or cell array) of instances of
|
c@0
|
197 % separation algorithms contained in SEPARATORS. Separation is
|
c@0
|
198 % performed for all combinations of separators and mixtures. The
|
c@0
|
199 % separation algorithm instances contained in SEPARATORS should
|
c@0
|
200 % conform to the required format. Type
|
c@0
|
201 %
|
c@12
|
202 % MASSEF.doc
|
c@0
|
203 %
|
c@12
|
204 % for more information.
|
c@0
|
205 %
|
c@0
|
206 % The EXECUTE method performs evaluations of the algorithm
|
c@0
|
207 % according to the data returned by the algorithm, and the
|
c@0
|
208 % options provided to MASSEF.
|
c@0
|
209 %
|
c@0
|
210 % If the separation algorithm returns a signal, then
|
c@0
|
211 % MASSEF.EXECUTE evaluates:
|
c@0
|
212 %
|
c@0
|
213 % - signal-to-noise ratio (SNR);
|
c@0
|
214 % - signal-to-ideal-noise ratio (SINR) (the SNR with respect
|
c@0
|
215 % to the signal reconstructed using the ideal binary and
|
c@0
|
216 % ratio masks).
|
c@0
|
217 %
|
c@0
|
218 % In addition, if 'evalPEASS' is true, then PEASS and BSSeval
|
c@0
|
219 % evaluation is performed. If 'evalSTOI' is true, then STOI
|
c@0
|
220 % evaluation is performed.
|
c@0
|
221 %
|
c@0
|
222 % If the separation algorithm returns a mask, and if the ideal
|
c@0
|
223 % mask dimensions match the estimated mask dimensions, then
|
c@0
|
224 % MASSEF.EXECUTE evaluates:
|
c@0
|
225 %
|
c@0
|
226 % - ideal mask ratio (IMR) with respect to the ideal binary
|
c@0
|
227 % and ratio masks.
|
c@0
|
228 %
|
c@0
|
229 % The EXECUTE method also calculates and evaluates the ideal
|
c@0
|
230 % binary and ratio masks using the signal-level metrics utilised
|
c@0
|
231 % for the algorithm evaluation. The masks are calculated using
|
c@0
|
232 % the settings that are local to each mixture object. The masks
|
c@0
|
233 % are then resynthesised using the inverse transform. For the
|
c@0
|
234 % gammatone filterbank, a windowed-sinc function is used for
|
c@0
|
235 % resynthesis in order to minimise the magnitude and phase
|
c@0
|
236 % distortion.
|
c@0
|
237 %
|
c@0
|
238 % Lastly, the framework captures the estimated azimuth and
|
c@0
|
239 % elevation of the targets and interferers, if they are returned.
|
c@0
|
240 %
|
c@0
|
241 % Once the evaluation is complete, the MASSEF object, which
|
c@0
|
242 % contains the results data in MASSEF.results as a MASSEFresults
|
c@0
|
243 % object, is saved to the file given by MASSEF.results_filename.
|
c@0
|
244
|
c@35
|
245 currDir = pwd;
|
c@35
|
246
|
c@0
|
247 % check the mixtures
|
c@23
|
248 assert(isa(mixtures,'iosr.bss.mixture'),'MASSEF:execute:invalidMixtures','The MIXTURES input must contain one or more objects of class ''iosr.bss.mixture''.')
|
c@0
|
249
|
c@0
|
250 if exist('separators','var')~=1
|
c@0
|
251 separators = {};
|
c@0
|
252 end
|
c@0
|
253
|
c@0
|
254 % check separators have required property and method
|
c@0
|
255 if ~isempty(separators)
|
c@0
|
256 if ~iscell(separators) % force to cell array
|
c@0
|
257 separators_old = separators;
|
c@0
|
258 separators = cell(size(separators_old));
|
c@0
|
259 for s = 1:numel(separators)
|
c@0
|
260 separators{s} = separators_old(s);
|
c@0
|
261 end
|
c@0
|
262 end
|
c@0
|
263 for s = 1:numel(separators) % check each separator
|
c@0
|
264 obj.check_separator(separators{s})
|
c@0
|
265 end
|
c@0
|
266 end
|
c@0
|
267
|
c@0
|
268 directory = fileparts(which([mfilename '.m']));
|
c@0
|
269 cd(directory)
|
c@35
|
270 tempdir = [cd filesep 'massef_temp'];
|
c@0
|
271 % ensure temp directory exists:
|
c@0
|
272 if exist(tempdir,'dir')~=7
|
c@0
|
273 success = mkdir(tempdir);
|
c@0
|
274 if ~success
|
c@23
|
275 error('MASSEF:execute:mkdir','Unable to create directory %s. Please create it.',tempdir)
|
c@0
|
276 end
|
c@0
|
277 end
|
c@0
|
278
|
c@0
|
279 %% IVs
|
c@0
|
280
|
c@0
|
281 % enumerate combinations of mixtures and separators
|
c@0
|
282 [IVs,obj.iterations] = obj.initialise_IVs(length(mixtures),length(separators));
|
c@0
|
283
|
c@0
|
284 %% PEASS options
|
c@0
|
285
|
c@0
|
286 obj.PEASSoptions.destDir = [tempdir filesep];
|
c@0
|
287 obj.PEASSoptions.segmentationFactor = 1;
|
c@0
|
288 if exist(obj.PEASSoptions.destDir,'dir')~=7
|
c@0
|
289 mkdir(obj.PEASSoptions.destDir)
|
c@0
|
290 end
|
c@0
|
291
|
c@0
|
292 %% Perform separations for each mixture and separator
|
c@0
|
293
|
c@0
|
294 % create a waitbar
|
c@0
|
295 obj.hWaitBar = obj.initWaitDisp();
|
c@0
|
296
|
c@0
|
297 if ~MASSEF.pctexists() || isempty(obj.parpool) % run in serial linear loop
|
c@0
|
298
|
c@0
|
299 fprintf('Performing %d separations serially.\n',obj.iterations)
|
c@0
|
300 nchars = obj.updateWaitDisp(0,0);
|
c@0
|
301
|
c@0
|
302 for M = 1:obj.iterations
|
c@0
|
303
|
c@0
|
304 % processing for this iteration
|
c@0
|
305 sepnum = IVs(M).algo_num;
|
c@0
|
306 mixnum = IVs(M).mixture_num;
|
c@0
|
307 if isempty(separators)
|
c@0
|
308 obj.process(mixtures(mixnum), ...
|
c@0
|
309 {},mixnum,sepnum,M);
|
c@0
|
310 else
|
c@0
|
311 obj.process(mixtures(mixnum), ...
|
c@0
|
312 separators{sepnum},mixnum,sepnum,M);
|
c@0
|
313 end
|
c@0
|
314
|
c@0
|
315 % Check to see if the cancel button was pressed
|
c@0
|
316 if obj.breakWaitDisp()
|
c@0
|
317 break;
|
c@0
|
318 end
|
c@0
|
319
|
c@0
|
320 % Update waitbar
|
c@0
|
321 nchars = obj.updateWaitDisp(M,nchars);
|
c@0
|
322 end
|
c@0
|
323
|
c@0
|
324 else % execute asynchronously on parallel workers
|
c@0
|
325
|
c@0
|
326 fprintf('Performing %d separations on %d workers.\n',obj.iterations,obj.parpool.NumWorkers)
|
c@0
|
327 nchars = obj.updateWaitDisp(0,0);
|
c@0
|
328
|
c@0
|
329 % work on BLOCKSIZE chunks of data at a time
|
c@0
|
330 totalNumCompleted = 0; % overall complete count for display
|
c@0
|
331 N = 1; % block start
|
c@0
|
332
|
c@0
|
333 while N <= obj.iterations % work through blocks
|
c@0
|
334
|
c@0
|
335 % do not exceed number of iterations
|
c@0
|
336 blockMax = min(obj.iterations,N+obj.blocksize-1);
|
c@0
|
337
|
c@0
|
338 % pass iterations to pool
|
c@0
|
339 for M = blockMax:-1:N % backwards prevents growing the array
|
c@0
|
340 sepnum = IVs(M).algo_num;
|
c@0
|
341 mixnum = IVs(M).mixture_num;
|
c@0
|
342 if isempty(separators)
|
c@0
|
343 futures(M-N+1) = parfeval(obj.parpool,@obj.process,1,mixtures(mixnum), ...
|
c@0
|
344 {},mixnum,sepnum,N+M-1);
|
c@0
|
345 else
|
c@0
|
346 futures(M-N+1) = parfeval(obj.parpool,@obj.process,1,mixtures(mixnum), ...
|
c@0
|
347 separators{sepnum},mixnum,sepnum,N+M-1);
|
c@0
|
348 end
|
c@0
|
349 end
|
c@0
|
350
|
c@0
|
351 % this cancels futures when cancelFutures is destroyed
|
c@0
|
352 cancelFutures = onCleanup(@() cancel(futures));
|
c@0
|
353
|
c@0
|
354 % capture data returned by workers
|
c@0
|
355 numCompleted = 0;
|
c@0
|
356 while numCompleted < blockMax-N+1
|
c@0
|
357
|
c@0
|
358 % return completed iteration
|
c@0
|
359 [~,data] = fetchNext(futures); % return data
|
c@0
|
360 obj.results = [obj.results; data];
|
c@0
|
361 obj.results = obj.results.merge();
|
c@0
|
362 numCompleted = numCompleted + 1; % increment local counter
|
c@0
|
363 totalNumCompleted = totalNumCompleted + 1; % increment total counter
|
c@0
|
364
|
c@0
|
365 % Check to see if the cancel button was pressed
|
c@0
|
366 br = obj.breakWaitDisp();
|
c@0
|
367 if br; break; end
|
c@0
|
368
|
c@0
|
369 % Update waitbar
|
c@0
|
370 nchars = obj.updateWaitDisp(totalNumCompleted,nchars);
|
c@0
|
371 end
|
c@0
|
372
|
c@0
|
373 % cancel the futures
|
c@0
|
374 cancel(futures);
|
c@0
|
375 clear futures;
|
c@0
|
376
|
c@0
|
377 % be sure to break out of this loop too
|
c@0
|
378 if br; break; end
|
c@0
|
379
|
c@0
|
380 % move to the next data block
|
c@0
|
381 N = N + obj.blocksize;
|
c@0
|
382
|
c@0
|
383 end
|
c@0
|
384
|
c@0
|
385 end
|
c@0
|
386
|
c@0
|
387 % clean up
|
c@0
|
388 delete(obj.hWaitBar);
|
c@0
|
389
|
c@0
|
390 %% Wrap up
|
c@0
|
391
|
c@0
|
392 obj.results.algorithmInfo(obj.IDEAL,'algorithmLabel','Ideal');
|
c@0
|
393 if ~isempty(separators)
|
c@0
|
394 for n = 1:length(separators)
|
c@0
|
395 obj.results.algorithmInfo(n,'algorithmLabel',separators{n}.label);
|
c@0
|
396 end
|
c@0
|
397 end
|
c@0
|
398 for n = 1:length(mixtures)
|
c@0
|
399 obj.results.mixtureInfo(n,'azi_sep',mixtures(n).azi_sep, ...
|
c@0
|
400 'elevation', mixtures(n).elevation, ...
|
c@0
|
401 'filename_t', mixtures(n).target.filename, ...
|
c@0
|
402 'filename_i', mixtures(n).int_fns, ...
|
c@0
|
403 'sofa_path', mixtures(n).sofa_path, ...
|
c@0
|
404 'target_azi', mixtures(n).target.azimuth, ...
|
c@0
|
405 'target_ele', mixtures(n).target.elevation, ...
|
c@0
|
406 'tir', mixtures(n).tir ...
|
c@0
|
407 );
|
c@26
|
408 mixtures(n).clearCache();
|
c@35
|
409 mixtures(n).deleteFiles();
|
c@0
|
410 end
|
c@0
|
411
|
c@0
|
412 obj.save();
|
c@0
|
413 save(obj.results_filename,'mixtures','separators','-append');
|
c@0
|
414
|
c@0
|
415 % delete temporary files
|
c@0
|
416 delete(sprintf('%s*',[obj.PEASSoptions.destDir filesep]))
|
c@0
|
417
|
c@0
|
418 fprintf('\n')
|
c@35
|
419 cd(currDir);
|
c@0
|
420 disp('MASSEF finished.');
|
c@0
|
421
|
c@0
|
422 end
|
c@0
|
423
|
c@0
|
424 function evaluate(obj,originalFiles,estimateFile,tag,mixnum,sepnum,estnum)
|
c@0
|
425 %EVALUATE Run the framework using input audio files
|
c@0
|
426 %
|
c@0
|
427 % OBJ.EVALUATE(ORIGINALFILES,ESTIMATEFILE) runs the framework
|
c@0
|
428 % using the true sources provided in the wav files whose
|
c@0
|
429 % filenames are contained in the cell array ORIGINALFILES (the
|
c@0
|
430 % target source is the first one) and the estimate provided in
|
c@0
|
431 % the wav file with filename ESTIMATEFILE.
|
c@0
|
432 %
|
c@0
|
433 % The method may be called as many times as desired. Use
|
c@0
|
434 % OBJ.SAVE() when finished to save the framework and its data to
|
c@0
|
435 % a file.
|
c@0
|
436 %
|
c@0
|
437 % OBJ.EVALUATE(ORIGINALFILES,ESTIMATEFILE,TAG) writes the char
|
c@0
|
438 % array TAG to the results data. Use the tag to identify
|
c@0
|
439 % different estimates in the results data.
|
c@0
|
440 %
|
c@0
|
441 % OBJ.EVALUATE(ORIGINALFILES,ESTIMATEFILE,TAG,MIXNUM) uses the
|
c@0
|
442 % mixture number MIXNUM to identify the separation of a
|
c@0
|
443 % particular mixture. MIXNUM is a key that can be used with
|
c@0
|
444 % MASSEFRESULTS.MIXTUREINFO() in order to add information about a
|
c@0
|
445 % particular mixture.
|
c@0
|
446 %
|
c@0
|
447 % OBJ.EVALUATE(ORIGINALFILES,ESTIMATEFILE,TAG,MIXNUM,SEPNUM) uses
|
c@0
|
448 % the separator number SEPNUM to identify the separation from a
|
c@0
|
449 % particular algorithm. SEPNUM is a key that can be used with
|
c@0
|
450 % MASSEFRESULTS.ALGORITHMINFO() in order to add information about a
|
c@0
|
451 % particular algorithm.
|
c@0
|
452 %
|
c@0
|
453 % OBJ.EVALUATE(ORIGINALFILES,ESTIMATEFILE,TAG,MIXNUM,SEPNUM,ESTNUM)
|
c@0
|
454 % uses the estimate number ESTNUM to identify different estimates
|
c@0
|
455 % from a given algorithm (e.g. a binary or soft mask output).
|
c@0
|
456 %
|
c@0
|
457 % See also IOSR.BSS.MASSEFRESULTS.
|
c@0
|
458
|
c@0
|
459 if exist('tag','var')~=1
|
c@0
|
460 tag = '';
|
c@0
|
461 else
|
c@23
|
462 assert(ischar(tag),'MASSEF:evaluate:invalidTag','TAG must be a char array')
|
c@0
|
463 end
|
c@0
|
464 if exist('mixnum','var')~=1
|
c@0
|
465 mixnum = 1;
|
c@0
|
466 else
|
c@23
|
467 assert(isscalar(mixnum),'MASSEF:evaluate:invalidMixnum','MIXNUM must be a scalar')
|
c@0
|
468 end
|
c@0
|
469 if exist('sepnum','var')~=1
|
c@0
|
470 sepnum = 1;
|
c@0
|
471 else
|
c@23
|
472 assert(isscalar(sepnum),'MASSEF:evaluate:invalidSepnum','SEPNUM must be a scalar')
|
c@0
|
473 end
|
c@0
|
474 if exist('estnum','var')~=1
|
c@0
|
475 estnum = 1;
|
c@0
|
476 else
|
c@23
|
477 assert(isscalar(estnum),'MASSEF:evaluate:invalidEstnum','ESTNUM must be a scalar')
|
c@0
|
478 end
|
c@0
|
479
|
c@0
|
480 [estimate,fs] = audioread(estimateFile);
|
c@0
|
481 target = audioread(originalFiles{1});
|
c@0
|
482 interferers = zeros(1,size(target,2));
|
c@0
|
483 for i = 2:numel(originalFiles)
|
c@0
|
484 int = audioread(originalFiles{i});
|
c@0
|
485 newlength = max(length(int),length(interferers));
|
c@0
|
486 int = obj.setlength(int,newlength);
|
c@0
|
487 interferers = obj.setlength(interferers,newlength);
|
c@0
|
488 interferers = interferers + int;
|
c@0
|
489 end
|
c@0
|
490
|
c@0
|
491 for C = 1:size(target,2) % iterate through each channel
|
c@0
|
492
|
c@0
|
493 % SNR
|
c@0
|
494 snr = iosr.bss.calcSnr(estimate(:,min(C,size(estimate,2))),target(:,C));
|
c@0
|
495 obj.results.input(mixnum,sepnum,estnum,'SNR',C,tag,snr);
|
c@0
|
496
|
c@0
|
497 % STOI
|
c@0
|
498 if obj.evalSTOI
|
c@0
|
499 stoi = taal2011(target(:,C), estimate(:,min(C,size(estimate,2))), fs);
|
c@0
|
500 obj.results.input(mixnum,sepnum,estnum,'STOI',C,tag,stoi);
|
c@0
|
501 end
|
c@0
|
502 end
|
c@0
|
503
|
c@0
|
504 % PEASS
|
c@0
|
505 if obj.evalPEASS
|
c@0
|
506 obj.evaluatePEASS(estimateFile,originalFiles, ...
|
c@0
|
507 mixnum,sepnum,estnum,1,tag);
|
c@0
|
508 end
|
c@0
|
509
|
c@0
|
510 end
|
c@0
|
511
|
c@0
|
512 function save(obj)
|
c@0
|
513 %SAVE Save the framework's data and results
|
c@0
|
514 %
|
c@0
|
515 % OBJ.SAVE() save the MASSEF framework object to the file
|
c@0
|
516 % determined by OBJ.RESULTS_FILENAME. The method is called
|
c@0
|
517 % automatically when using the EXECUTE() method; in this case,
|
c@0
|
518 % the mixtures and separators are also saved.
|
c@0
|
519
|
c@0
|
520 % Create results files
|
c@0
|
521 if exist(obj.results_filename,'file')==2
|
c@0
|
522 % do not overwrite
|
c@0
|
523 newfilename = obj.results_filename;
|
c@0
|
524 while exist(newfilename,'file')==2
|
c@0
|
525 fprintf('\n')
|
c@0
|
526 newfilename = input(...
|
c@0
|
527 ['The results file ''' newfilename ''' already exists.\nPlease enter a new filename:\n'],'s');
|
c@0
|
528 end
|
c@0
|
529 obj.results_filename = newfilename;
|
c@0
|
530 end
|
c@0
|
531 obj.saveDate = date;
|
c@0
|
532 save(obj.results_filename,'obj');
|
c@0
|
533 fprintf('\n')
|
c@0
|
534 disp(['File saved to: ' obj.results_filename]);
|
c@0
|
535
|
c@0
|
536 end
|
c@0
|
537
|
c@0
|
538 end % public methods
|
c@0
|
539
|
c@0
|
540 methods (Static)
|
c@0
|
541
|
c@12
|
542 function doc
|
c@12
|
543 %DOC Display the framework documentation.
|
c@12
|
544 try
|
c@12
|
545 web(fullfile(MASSEF.dir, 'help_html', 'help_Index.html'), '-helpbrowser')
|
c@12
|
546 catch
|
c@12
|
547 web(fullfile(MASSEF.dir, 'help_html', 'help_Index.html'))
|
c@12
|
548 end
|
c@12
|
549 end
|
c@12
|
550
|
c@0
|
551 function start
|
c@0
|
552 %START Start the framework.
|
c@0
|
553 %
|
c@0
|
554 % MASSEF.START starts the framework and its dependencies,
|
c@0
|
555 % adds the required folders to the Matlab path, and updates the
|
c@0
|
556 % HTML help documentation.
|
c@0
|
557
|
c@0
|
558 addpath(cd,...
|
c@0
|
559 [MASSEF.dir filesep 'Library'],...
|
c@0
|
560 [MASSEF.dir filesep 'Utilities'],...
|
c@0
|
561 [MASSEF.dir filesep 'Utilities' filesep 'AMT'],...
|
c@0
|
562 [MASSEF.dir filesep 'Utilities' filesep 'PEASS'],...
|
c@0
|
563 [MASSEF.dir filesep 'Utilities' filesep 'PEASS' filesep 'gammatone'],...
|
c@0
|
564 [MASSEF.dir filesep 'Stimuli']);
|
c@0
|
565
|
c@0
|
566 SOFAstart(0);
|
c@0
|
567 amtstart;
|
c@0
|
568
|
c@0
|
569 d = pwd;
|
c@0
|
570 cd([MASSEF.dir filesep 'help_html' filesep 'source'])
|
c@0
|
571 publishHelp;
|
c@0
|
572 cd(d);
|
c@0
|
573
|
c@0
|
574 end
|
c@0
|
575
|
c@0
|
576 function install
|
c@0
|
577 %INSTALL Download and install MASSEF dependencies.
|
c@0
|
578 %
|
c@0
|
579 % MASSEF.INSTALL downloads and installs the MASSEF
|
c@0
|
580 % dependencies. These dependencies are:
|
c@0
|
581 % - The Auditory Modelling Toolbox
|
c@0
|
582 % (http://amtoolbox.sourceforge.net);
|
c@0
|
583 % - The Large Time-Frequency Analysis Toolbox
|
c@0
|
584 % (http://ltfat.sourceforge.net);
|
c@0
|
585 % - Perceptual Evaluation methods for Audio Source Separation
|
c@0
|
586 % Toolkit (http://bass-db.gforge.inria.fr/peass/);
|
c@0
|
587 % - IoSR Matlab Toolbox
|
c@0
|
588 % (https://github.com/IoSR-Surrey/MatlabToolbox)
|
c@0
|
589 % - SOFA API
|
c@0
|
590 % (https://sourceforge.net/projects/sofacoustics/).
|
c@35
|
591
|
c@35
|
592 currDir = pwd;
|
c@0
|
593 directory = MASSEF.dir;
|
c@0
|
594 cd(directory);
|
c@0
|
595
|
c@0
|
596 %% download and install STOI
|
c@0
|
597
|
c@0
|
598 amt_folder = [directory filesep 'Utilities' filesep 'AMT'];
|
c@0
|
599 ltfat_folder = [directory filesep 'Utilities' filesep 'AMT' filesep 'thirdparty' filesep 'ltfat'];
|
c@0
|
600
|
c@0
|
601 if ~(exist(amt_folder, 'dir') == 7)
|
c@0
|
602 % AMT
|
c@0
|
603 amt_filename = 'amtoolbox-0.9.7.zip';
|
c@0
|
604 websave(amt_filename,'http://vorboss.dl.sourceforge.net/project/amtoolbox/amtoolbox-0.9.7.zip');
|
c@0
|
605 unzip(amt_filename,amt_folder);
|
c@0
|
606 amt_temp_folder = [amt_folder filesep 'release' filesep];
|
c@0
|
607 movefile([amt_temp_folder '*'],[amt_folder filesep]);
|
c@0
|
608 delete(amt_filename)
|
c@0
|
609 rmdir(amt_temp_folder,'s')
|
c@0
|
610
|
c@0
|
611 % LTFAT
|
c@0
|
612 ltfat_filename = 'ltfat-2.1.2.tgz';
|
c@0
|
613 websave(ltfat_filename,'http://netix.dl.sourceforge.net/project/ltfat/ltfat/2.0/ltfat-2.1.2.tgz');
|
c@0
|
614 untar(ltfat_filename,ltfat_folder);
|
c@0
|
615 ltfat_temp_folder = [ltfat_folder filesep 'ltfat' filesep];
|
c@0
|
616 movefile([ltfat_temp_folder '*'],[ltfat_folder filesep]);
|
c@0
|
617 delete(ltfat_filename)
|
c@0
|
618 rmdir(ltfat_temp_folder,'s')
|
c@0
|
619 else
|
c@0
|
620 display(strcat('Found existing AMT Toolbox directory: ', amt_folder))
|
c@0
|
621 end
|
c@0
|
622
|
c@0
|
623 %% download and install PEASS
|
c@0
|
624
|
c@0
|
625 % install dir
|
c@0
|
626 peass_folder = [directory filesep 'Utilities' filesep 'PEASS'];
|
c@0
|
627
|
c@0
|
628 % PEASS
|
c@0
|
629 if ~(exist(peass_folder, 'dir') == 7)
|
c@0
|
630 peass_filename = 'PEASS-Software-v2.0.zip';
|
c@0
|
631 websave(peass_filename,'http://bass-db.gforge.inria.fr/peass/PEASS-Software-v2.0.zip');
|
c@0
|
632 unzip(peass_filename,peass_folder);
|
c@0
|
633 peass_temp_folder = [peass_folder filesep 'PEASS-Software-v2.0' filesep];
|
c@0
|
634 movefile([peass_temp_folder '*'],[peass_folder filesep]);
|
c@0
|
635
|
c@0
|
636 % clean up
|
c@0
|
637 delete(peass_filename)
|
c@0
|
638 rmdir(peass_temp_folder,'s')
|
c@0
|
639 else
|
c@0
|
640 display(strcat('Found existing PEASS directory: ', peass_folder))
|
c@0
|
641 end
|
c@0
|
642 % Hair cell model
|
c@0
|
643 adapt_filename = 'adapt_loop.zip';
|
c@0
|
644 websave(adapt_filename,'http://medi.uni-oldenburg.de/download/demo/adaption-loops/adapt_loop.zip');
|
c@0
|
645 unzip(adapt_filename,peass_folder);
|
c@0
|
646 cd(peass_folder);
|
c@0
|
647 compile;
|
c@0
|
648 cd(directory);
|
c@0
|
649
|
c@0
|
650 %% download and install IoSR Matlab Toolbox
|
c@0
|
651
|
c@0
|
652 if ~MASSEF.gitExists
|
c@0
|
653 % IoSR Matlab Toolbox
|
c@0
|
654 if ~(exist([directory filesep 'Library' filesep '+iosr'], 'dir') == 7)
|
c@0
|
655 iosrMTB = 'iosr.zip';
|
c@0
|
656 iosr_dir = [directory filesep 'Library'];
|
c@0
|
657 websave(iosrMTB,'https://github.com/IoSR-Surrey/MatlabToolbox/archive/master.zip');
|
c@0
|
658 unzip(iosrMTB,iosr_dir);
|
c@0
|
659 movefile([iosr_dir filesep 'MatlabToolbox-master' filesep '*'],[iosr_dir filesep]);
|
c@0
|
660 delete(iosrMTB)
|
c@0
|
661 rmdir([iosr_dir filesep 'MatlabToolbox-master'],'s')
|
c@0
|
662 else
|
c@0
|
663 display('Found existing IoSR Matlab Toolbox directory')
|
c@0
|
664 end
|
c@0
|
665 end
|
c@23
|
666
|
c@23
|
667 iosr.install;
|
c@0
|
668
|
c@0
|
669 %% Remaining clean up
|
c@0
|
670
|
c@0
|
671 delete(adapt_filename)
|
c@35
|
672 cd(currDir);
|
c@0
|
673 disp('MASSEF successfully installed.')
|
c@0
|
674
|
c@0
|
675 end
|
c@0
|
676
|
c@0
|
677 end % Static public methods
|
c@0
|
678
|
c@0
|
679 methods (Static, Hidden)
|
c@0
|
680
|
c@0
|
681 function e = gitExists
|
c@0
|
682 %GITEXISTS Check if install is a git repo
|
c@0
|
683 e = exist([MASSEF.dir filesep '.git'],'dir')==7;
|
c@0
|
684 end
|
c@0
|
685
|
c@0
|
686 end
|
c@0
|
687
|
c@0
|
688 methods (Access = private)
|
c@0
|
689
|
c@0
|
690 function r = process(obj,mix,separator,mixnum,sepnum,iteration)
|
c@0
|
691 %PROCESS Main callback for performing the separation and analysis
|
c@0
|
692
|
c@0
|
693 %% Create mixture
|
c@0
|
694
|
c@0
|
695 % write mixture, target and interferers
|
c@0
|
696 mix.write(sprintf('%smix-%d.wav',obj.PEASSoptions.destDir,mixnum));
|
c@0
|
697 originalFiles = {mix.filename_t, mix.filename_i};
|
c@0
|
698
|
c@0
|
699 % create mixture signal
|
c@0
|
700 mixture = mix.signal;
|
c@0
|
701
|
c@0
|
702 %% Analyse separator
|
c@0
|
703
|
c@0
|
704 % calculate ideal outputs
|
c@0
|
705 ibm = mix.ibm;
|
c@0
|
706 irm = mix.irm;
|
c@0
|
707 output_ibm = mix.applyMask(ibm);
|
c@0
|
708 output_irm = mix.applyMask(irm);
|
c@0
|
709
|
c@0
|
710 if ~isempty(separator)
|
c@0
|
711
|
c@0
|
712 % calculate separator output
|
c@0
|
713 [signals,masks,est_azis,est_eles] = separator.separate(mixture);
|
c@0
|
714
|
c@0
|
715 % apply mask if signals are not calculated
|
c@0
|
716 if isempty(signals) && ~isempty(masks)
|
c@0
|
717 for E = 1:size(masks,4)
|
c@0
|
718 signals(:,:,E) = mix.applyMask(masks(:,:,:,E));
|
c@0
|
719 end
|
c@0
|
720 end
|
c@0
|
721
|
c@0
|
722 % Evaluate signals
|
c@0
|
723 if ~isempty(signals)
|
c@0
|
724 for E = 1:size(signals,3)
|
c@0
|
725
|
c@0
|
726 estimateFileBase = sprintf('%ssignal-%d',obj.PEASSoptions.destDir,iteration);
|
c@0
|
727 estimateFile = sprintf('%s.wav',estimateFileBase);
|
c@0
|
728 audiowrite(estimateFile,iosr.dsp.audio.normalize(signals(:,:,E)),mix.fs);
|
c@0
|
729
|
c@0
|
730 obj.evaluate(originalFiles,estimateFile,...
|
c@0
|
731 separator.estTag{E},mixnum,sepnum,E);
|
c@0
|
732
|
c@0
|
733 % SINR
|
c@0
|
734 for C = 1:size(mixture,2)
|
c@0
|
735 sinr = iosr.bss.calcSnr(signals(:,C,E),output_irm(:,C));
|
c@0
|
736 obj.results.input(mixnum,sepnum,E,'SINR',C,separator.estTag{E},sinr)
|
c@0
|
737 end
|
c@0
|
738
|
c@0
|
739 % delete temp files
|
c@0
|
740 delete(estimateFile);
|
c@0
|
741
|
c@0
|
742 end
|
c@0
|
743
|
c@0
|
744 end
|
c@0
|
745
|
c@0
|
746 % estimated azimuths
|
c@0
|
747 if ~isempty(est_azis)
|
c@0
|
748 for s = 1:length(est_azis)
|
c@0
|
749 if s==1
|
c@0
|
750 obj.results.input(mixnum,sepnum,1,'est_azi_target',1,[],est_azis(s))
|
c@0
|
751 else
|
c@0
|
752 obj.results.input(mixnum,sepnum,1,sprintf('est_azi_int_%02d',s-1),1,[],est_azis(s))
|
c@0
|
753 end
|
c@0
|
754 end
|
c@0
|
755 end
|
c@0
|
756
|
c@0
|
757 % estimated elevation
|
c@0
|
758 if ~isempty(est_eles)
|
c@0
|
759 for s = 1:length(est_eles)
|
c@0
|
760 if s==1
|
c@0
|
761 obj.results.input(mixnum,sepnum,1,'est_ele_target',1,[],est_eles(s))
|
c@0
|
762 else
|
c@0
|
763 obj.results.input(mixnum,sepnum,1,sprintf('est_ele_int_%02d',s-1),1,[],est_eles(s))
|
c@0
|
764 end
|
c@0
|
765 end
|
c@0
|
766 end
|
c@0
|
767
|
c@0
|
768 % only do mask comparisons when the masks are equal size
|
c@0
|
769 masks_size = size(masks);
|
c@0
|
770 ideal_size = size(ibm);
|
c@0
|
771 if isequal(masks_size(1:2),ideal_size(1:2))
|
c@0
|
772 for E = 1:size(masks,4) % iterature through each estimate/mask
|
c@0
|
773 for C = 1:size(mixture,2) % iterate through each channel
|
c@0
|
774 maskC = masks(:,:,min(C,size(masks,3)),E);
|
c@0
|
775
|
c@0
|
776 % IMR - binary
|
c@0
|
777 imrb = iosr.bss.calcImr(maskC,ibm(:,:,C));
|
c@0
|
778 obj.results.input(mixnum,sepnum,E,'IMR (IBM)',C,separator.estTag{E},imrb)
|
c@0
|
779
|
c@0
|
780 % IMR - ratio
|
c@0
|
781 imrr = iosr.bss.calcImr(maskC,irm(:,:,C));
|
c@0
|
782 obj.results.input(mixnum,sepnum,E,'IMR (IRM)',C,separator.estTag{E},imrr)
|
c@0
|
783 end
|
c@0
|
784
|
c@0
|
785 end
|
c@0
|
786
|
c@0
|
787 end
|
c@0
|
788
|
c@0
|
789 end
|
c@0
|
790
|
c@0
|
791 %% Analyse ideal masks
|
c@0
|
792
|
c@0
|
793 if sepnum==1 % evaluations of ideal masks
|
c@0
|
794
|
c@0
|
795 ibmFile = sprintf('%sibm-%d.wav',obj.PEASSoptions.destDir,iteration);
|
c@0
|
796 audiowrite(ibmFile,iosr.dsp.audio.normalize(output_ibm),mix.fs);
|
c@35
|
797 irmFile = sprintf('%sirm-%d.wav',obj.PEASSoptions.destDir,iteration);
|
c@0
|
798 audiowrite(irmFile,iosr.dsp.audio.normalize(output_irm),mix.fs);
|
c@0
|
799
|
c@0
|
800 obj.evaluate(originalFiles,ibmFile,...
|
c@0
|
801 'Binary',mixnum,obj.IDEAL,1);
|
c@0
|
802 obj.evaluate(originalFiles,irmFile,...
|
c@0
|
803 'Ratio',mixnum,obj.IDEAL,2);
|
c@0
|
804
|
c@0
|
805 % delete temp files
|
c@0
|
806 delete(ibmFile);
|
c@0
|
807 delete(irmFile);
|
c@0
|
808 end
|
c@0
|
809
|
c@0
|
810 r = obj.results;
|
c@0
|
811
|
c@0
|
812 end
|
c@0
|
813
|
c@0
|
814 function evaluatePEASS(obj,estimateFile,originalfiles, ...
|
c@0
|
815 mixnum,sepnum,estnum,channelnum,esttag)
|
c@0
|
816 %EVALUATE Evaluate resynthesised signals against ground truth
|
c@0
|
817
|
c@0
|
818 % calculate metrics
|
c@0
|
819 PEASS_output = PEASS_ObjectiveMeasure(originalfiles,estimateFile,obj.PEASSoptions);
|
c@0
|
820
|
c@0
|
821 % write data to outputs
|
c@0
|
822 obj.results.input(mixnum,sepnum,estnum,'OPS',channelnum,esttag,PEASS_output.OPS)
|
c@0
|
823 obj.results.input(mixnum,sepnum,estnum,'TPS',channelnum,esttag,PEASS_output.TPS)
|
c@0
|
824 obj.results.input(mixnum,sepnum,estnum,'IPS',channelnum,esttag,PEASS_output.IPS)
|
c@0
|
825 obj.results.input(mixnum,sepnum,estnum,'APS',channelnum,esttag,PEASS_output.APS)
|
c@0
|
826 obj.results.input(mixnum,sepnum,estnum,'SDR',channelnum,esttag,PEASS_output.SDR)
|
c@0
|
827 obj.results.input(mixnum,sepnum,estnum,'SAR',channelnum,esttag,PEASS_output.SAR)
|
c@0
|
828 obj.results.input(mixnum,sepnum,estnum,'SIR',channelnum,esttag,PEASS_output.SIR)
|
c@0
|
829 obj.results.input(mixnum,sepnum,estnum,'ISR',channelnum,esttag,PEASS_output.ISR)
|
c@0
|
830
|
c@0
|
831 [pathstr,name] = fileparts(estimateFile);
|
c@0
|
832 delete(fullfile(pathstr,[name '_true.wav']));
|
c@0
|
833 delete(fullfile(pathstr,[name '_eTarget.wav']));
|
c@0
|
834 delete(fullfile(pathstr,[name '_eInterf.wav']));
|
c@0
|
835 delete(fullfile(pathstr,[name '_eArtif.wav']));
|
c@0
|
836
|
c@0
|
837 end
|
c@0
|
838
|
c@0
|
839 function nchars = updateWaitDisp(obj,numComplete,nchars)
|
c@0
|
840 %UPDATEWAITDISP Update the wait display
|
c@0
|
841
|
c@0
|
842 fraction = numComplete/obj.iterations;
|
c@0
|
843
|
c@0
|
844 if ~isempty(obj.hWaitBar) % update waitbar if it exists
|
c@0
|
845 waitbar(fraction, obj.hWaitBar);
|
c@0
|
846 end
|
c@0
|
847 fprintf(1,repmat('\b',1,nchars)); % delete old message
|
c@0
|
848 if numComplete==obj.iterations
|
c@0
|
849 format = '%.0f'; % special format for 100%
|
c@0
|
850 else
|
c@0
|
851 format = '%.1f'; % normal format
|
c@0
|
852 end
|
c@0
|
853
|
c@0
|
854 % display message and return number of chars to delete for next message
|
c@0
|
855 nchars = fprintf(1,[format '%% complete.'],100*fraction);
|
c@0
|
856
|
c@0
|
857 end
|
c@0
|
858
|
c@0
|
859 function b = breakWaitDisp(obj)
|
c@0
|
860 %BREAKWAITDISP Determine whether to break the calculations
|
c@0
|
861
|
c@0
|
862 b = false; % don't by default
|
c@0
|
863 if ~isempty(obj.hWaitBar) % if waitbar exists
|
c@0
|
864 if getappdata(obj.hWaitBar, 'Cancelled') % determine if "Cancel" was clicked
|
c@0
|
865 b = true; % break if so
|
c@0
|
866 end
|
c@0
|
867 end
|
c@0
|
868 if b % display message if breaking
|
c@0
|
869 fprintf('\nMASSEF cancelled.\n');
|
c@0
|
870 end
|
c@0
|
871
|
c@0
|
872 end
|
c@0
|
873
|
c@0
|
874 end % private methods
|
c@0
|
875
|
c@0
|
876 methods (Static, Access = private)
|
c@0
|
877
|
c@0
|
878 function options = validate_options(options)
|
c@0
|
879 %VALIDATE_OPTIONS Validate the options structure input to this function
|
c@0
|
880
|
c@0
|
881 % check format
|
c@0
|
882 if numel(options)>1
|
c@23
|
883 error('MASSEF:validate_options:invalidInput','The input to MASSEF should be a 1x1 structure. Cell-array fields should be encapsulated within a single cell.')
|
c@0
|
884 end
|
c@0
|
885
|
c@0
|
886 fields_in = fieldnames(options);
|
c@0
|
887
|
c@0
|
888 % default values
|
c@0
|
889 if MASSEF.pctexists()
|
c@0
|
890 default_parpool = gcp('nocreate');
|
c@0
|
891 else
|
c@0
|
892 default_parpool = [];
|
c@0
|
893 end
|
c@0
|
894 default_values = struct( ...
|
c@0
|
895 'evalPEASS',false, ...
|
c@0
|
896 'evalSTOI',false, ...
|
c@0
|
897 'parpool',default_parpool, ...
|
c@0
|
898 'results_filename',[MASSEF.dir filesep 'Results' filesep 'results.mat'], ...
|
c@0
|
899 'blocksize',128 ...
|
c@0
|
900 );
|
c@0
|
901 def_fields = fieldnames(default_values);
|
c@0
|
902
|
c@0
|
903 % check for invalid options
|
c@0
|
904 for r = 1:length(fields_in)
|
c@23
|
905 assert(any(strcmpi(fields_in{r},def_fields)),'MASSEF:validate_options:invalidOption',['Invalid option ''' fields_in{r} ''' specified.'])
|
c@0
|
906 end
|
c@0
|
907
|
c@0
|
908 % write defaults
|
c@0
|
909 for r = 1:numel(def_fields)
|
c@0
|
910 field_name = def_fields{r};
|
c@0
|
911 if ~isfield(options,field_name)
|
c@0
|
912 options.(field_name) = default_values.(field_name);
|
c@0
|
913 end
|
c@0
|
914 end
|
c@0
|
915
|
c@0
|
916 end
|
c@0
|
917
|
c@0
|
918 function [IVs,iterations] = initialise_IVs(numMixtures,numSeparators)
|
c@0
|
919 %INITIALISE_IVs Initialise experiment results structure
|
c@0
|
920
|
c@0
|
921 % Independent variables space
|
c@0
|
922 IV_space = [numMixtures max(1,numSeparators)];
|
c@0
|
923
|
c@0
|
924 iterations = prod(IV_space); % total number of results
|
c@23
|
925 assert(numMixtures>0,'MASSEF:initialise_IVs:noMixtures','No mixtures have been specified')
|
c@0
|
926 %assert(numSeparators>0,'No separators have been specified')
|
c@0
|
927
|
c@0
|
928 IVs = struct('algo_num',[],'mixture_num',[]);
|
c@0
|
929
|
c@0
|
930 % fill IV cells with data
|
c@0
|
931 for N = 1:iterations
|
c@0
|
932 [n,p] = ind2sub(IV_space,N);
|
c@0
|
933 IVs(N).algo_num = p;
|
c@0
|
934 IVs(N).mixture_num = n;
|
c@0
|
935 end
|
c@0
|
936 end
|
c@0
|
937
|
c@0
|
938 function check_separator(obj)
|
c@0
|
939 %CHECK_SEPARATOR Check separator meets requirements
|
c@0
|
940
|
c@23
|
941 assert(isprop(obj,'label'),'MASSEF:check_separator:invalidSepNoLabel',['The object of class ''' class(obj) ''' does not have the required property ''label'''])
|
c@23
|
942 assert(ischar(obj.label),'MASSEF:check_separator:invalidSepLabelProp',['The ''label'' property of the ''' class(obj) ''' object should return a char array'])
|
c@23
|
943 assert(isprop(obj,'estTag'),'MASSEF:check_separator:invalidSepNoEstTag',['The object of class ''' class(obj) ''' does not have the required property ''estTag'''])
|
c@23
|
944 assert(iscellstr(obj.estTag),'MASSEF:check_separator:invalidSepEstTagProp',['The ''estTag'' property of the ''' class(obj) ''' object should return a cell array of strings'])
|
c@23
|
945 assert(ismethod(obj,'separate'),'MASSEF:check_separator:invalidSepNoSepMethod',['The object of class ''' class(obj) ''' does not have the required method ''separate'''])
|
c@0
|
946
|
c@0
|
947 end
|
c@0
|
948
|
c@0
|
949 function e = pctexists
|
c@0
|
950 %PCTEXISTS Determine whether the parallel computing toolbox exists
|
c@0
|
951
|
c@0
|
952 if isempty(ver('distcomp'))
|
c@0
|
953 e = false;
|
c@0
|
954 else
|
c@0
|
955 e = true;
|
c@0
|
956 end
|
c@0
|
957
|
c@0
|
958 end
|
c@0
|
959
|
c@0
|
960 function h = initWaitDisp
|
c@0
|
961 %INITWAITDISP Initialise the waiting display
|
c@0
|
962
|
c@0
|
963 if usejava('jvm') % only call waitbar if JVM is running
|
c@0
|
964 h = waitbar(0, 'MASSEF processing...', 'CreateCancelBtn', ...
|
c@0
|
965 @(src, event) setappdata(gcbf(), 'Cancelled', true));
|
c@0
|
966 setappdata(h, 'Cancelled', false);
|
c@0
|
967 else % do nothing
|
c@0
|
968 h = [];
|
c@0
|
969 end
|
c@0
|
970 end
|
c@0
|
971
|
c@0
|
972 function y = setlength(x,signal_length)
|
c@0
|
973 %SETLENGTH Crop or zero-pad signal to specified length
|
c@0
|
974
|
c@0
|
975 d = size(x);
|
c@0
|
976 if size(x,1)>signal_length % need to crop
|
c@0
|
977 subsidx = [{1:signal_length} repmat({':'},1,ndims(x)-1)];
|
c@0
|
978 y = x(subsidx{:});
|
c@0
|
979 elseif size(x,1)<signal_length % need to zero-pad
|
c@0
|
980 y = [x; zeros([signal_length-size(x,1),d(2:end)])];
|
c@0
|
981 else % do nothing
|
c@0
|
982 y = x;
|
c@0
|
983 end
|
c@0
|
984
|
c@0
|
985 end
|
c@0
|
986
|
c@0
|
987 end % static private methods
|
c@0
|
988
|
c@0
|
989 end
|