annotate MASSEF.m @ 38:c7d11a428a0d tip master

Merge branch 'develop' * develop: Updated copyright year.
author Christopher Hummersone <c.hummersone@surrey.ac.uk>
date Tue, 16 May 2017 12:15:34 +0100
parents e14df3f8139e
children
rev   line source
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