annotate MASSEFresults.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 44863636466c
children
rev   line source
c@0 1 classdef MASSEFresults < handle
c@0 2 %MASSEFRESULTS A class for storing MASSEF results
c@0 3 %
c@0 4 % The multichannel audio source separation evaluation framework uses this
c@0 5 % class to store results. You can use its methods to recall and filter
c@0 6 % the data.
c@0 7 %
c@0 8 % MASSEFRESULTS properties:
c@0 9 % Read-only properties:
c@0 10 % data - The full results set returned as a Table object
c@0 11 % (read only). The Table has the following columns:
c@0 12 % - algorithmNum - the algorithm number;
c@0 13 % - channel - the audio channel
c@0 14 % - estimateNum - the estimate number;
c@0 15 % - estTag - a tag for each estimate from a
c@0 16 % given algorithm;
c@0 17 % - metric - the performance metric;
c@0 18 % - mixNum - the mixture number; and
c@0 19 % - value - the value of the performance metric
c@0 20 % Additional mixture and algorithm information is
c@0 21 % also returned in the results set.
c@0 22 % estTags - A list of the estTags, taken from the separators,
c@0 23 % that feature in the results set (read only).
c@0 24 % metrics - The metrics that feature in the results set (read
c@0 25 % only).
c@0 26 % numAlgorithms - The number of algorithms in the results set (read
c@0 27 % only).
c@0 28 % numMixtures - The number of mixtures in the results set (read
c@0 29 % only).
c@0 30 % numEstimates - The (maximum) number of estimates in the results
c@0 31 % set (read only).
c@0 32 % numChannels - The (maximum) number of channels in the results
c@0 33 % set (read only).
c@0 34 %
c@0 35 % MASSEFRESULTS methods:
c@0 36 % MASSEFresults - Create a MASSEFresults object.
c@0 37 % algorithmInfo - Add algorithm information.
c@27 38 % boxPlot - Analyse the results data by plotting notched
c@27 39 % box plots.
c@0 40 % filter - Filter the results set.
c@0 41 % input - Add performance data.
c@0 42 % merge - Combine results array into singular object.
c@0 43 % mixtureInfo - Add mixture information.
c@0 44 % removeDuplicates - Remove duplicate data from the results
c@0 45 % object.
c@0 46 %
c@0 47 % See also MASSEFFW.
c@0 48
c@0 49 % Copyright 2016 University of Surrey.
c@0 50
c@0 51 properties (SetAccess = private, Dependent)
c@0 52 data % The full results set.
c@0 53 estTags % A tag assigned to each estimate.
c@0 54 metrics % The metrics that feature in the results set.
c@0 55 numAlgorithms % The number of algorithms in the results set.
c@0 56 numMixtures % The number of mixtures in the results set.
c@0 57 numEstimates % The (maximum) number of estimates in the results set.
c@0 58 numChannels % The (maximum) number of channels in the results set.
c@0 59 end
c@0 60
c@0 61 properties (Access = private)
c@0 62 mixtures % mixtures table
c@0 63 algorithms % algorithms table
c@0 64 performance % main performance table
c@0 65 end
c@0 66
c@0 67 methods
c@0 68
c@0 69 function obj = MASSEFresults()
c@0 70 %MASSEFRESULTS Create the results set.
c@0 71 %
c@0 72 % R = MASSEFRESULTS creates an empty MASSEFRESULTS object. Use the
c@0 73 % MASSEFRESULTS.INPUT method to add data.
c@0 74
c@0 75 obj.mixtures = cell2table(cell(0,9), 'VariableNames',{'mixNum','azi_sep','elevation','filename_t','filename_i','sofa_path','target_azi','target_ele','tir'});
c@0 76 obj.algorithms = cell2table(cell(0,2), 'VariableNames',{'algorithmNum','algorithmLabel'});
c@0 77 obj.performance = cell2table(cell(0,7), 'VariableNames',{'mixNum','algorithmNum','estimateNum','channel','metric','estTag','value'});
c@0 78
c@0 79 end
c@0 80
c@0 81 function data = get.data(obj)
c@0 82 % GET.DATA get data set
c@0 83
c@0 84 data = obj.joinLookupTables(obj.performance);
c@0 85
c@0 86 end
c@0 87
c@0 88 function metrics = get.metrics(obj)
c@0 89 % GET.METRICS get metrics
c@0 90 metrics = unique(obj.performance.metric);
c@0 91 end
c@0 92
c@0 93 function estTags = get.estTags(obj)
c@0 94 % GET.METRICS get metrics
c@0 95 estTags = unique(obj.performance.estTag);
c@0 96 end
c@0 97
c@0 98 function numAlgorithms = get.numAlgorithms(obj)
c@0 99 % GET.NUMALGORITHMS get number of algorithms
c@0 100 numAlgorithms = length(unique(obj.performance.algorithmNum));
c@0 101 end
c@0 102
c@0 103 function numMixtures = get.numMixtures(obj)
c@0 104 % GET.NUMMIXTURES get number of mixtures
c@0 105 numMixtures = length(unique(obj.performance.mixNum));
c@0 106 end
c@0 107
c@0 108 function numEstimates = get.numEstimates(obj)
c@0 109 % GET.NUMESTIMATES get number of estimates
c@0 110 numEstimates = length(unique(obj.performance.estimateNum));
c@0 111 end
c@0 112
c@0 113 function numChannels = get.numChannels(obj)
c@0 114 % GET.NUMCHANNEL get number of channels
c@0 115 numChannels = length(unique(obj.performance.channel));
c@0 116 end
c@0 117
c@0 118 function newObj = merge(obj)
c@0 119 % MERGE Combine MASSEFresults array into singular object
c@0 120 %
c@0 121 % NEWOBJ = R.MERGE() combines the elements of the MASSEFRESULTS array
c@0 122 % R into a singular MASSEFRESULTS object NEWOBJ.
c@0 123
c@0 124 newObj = obj(1);
c@0 125 if length(obj) > 1
c@0 126 for n = 2:length(obj)
c@0 127 % combine lookup table and eliminate duplicate rows
c@0 128 newObj.mixtures = vertcat(newObj.mixtures,obj(n).mixtures);
c@0 129 newObj.algorithms = vertcat(newObj.algorithms,obj(n).algorithms);
c@0 130 % combine all rows of performance tables
c@0 131 newObj.performance = vertcat(newObj.performance,obj(n).performance);
c@0 132 end
c@0 133 end
c@0 134 newObj.removeDuplicates();
c@0 135
c@0 136 end
c@0 137
c@0 138 function removeDuplicates(obj)
c@0 139 % REMOVEDUPLICATES Remove duplicate data from MASSEFresults object
c@0 140 %
c@0 141 % R.REMOVEDUPLICATES() removes duplicate results from the
c@0 142 % MASSEFRESULTS object R.
c@0 143
c@0 144 obj.mixtures = unique(obj.mixtures);
c@0 145 obj.algorithms = unique(obj.algorithms);
c@0 146 obj.performance = unique(obj.performance);
c@0 147
c@0 148 end
c@0 149
c@0 150 function data = filter(obj,varargin)
c@0 151 %FILTER Filter the results data set
c@0 152 %
c@0 153 % R.FILTER(NAME,VALUE) filters the results set contained in the
c@0 154 % MASSEFRESULTS object R using the variable names and values
c@0 155 % contained in the NAME / VALUE pair arguments. The parameters
c@0 156 % are:
c@0 157 %
c@0 158 % - 'algorithmnum' - Filters the data according to the
c@0 159 % algorithm number. The parameter should be a function
c@0 160 % handle that takes the mixture number as its input, and
c@0 161 % returns a logical value.
c@0 162 % - 'channel' - Filters the data according to channel
c@0 163 % information. The parameter can be a function handle that
c@0 164 % takes the channel number as its input, and returns a
c@0 165 % logical value. Alternatively, the parameter can be 'max'
c@0 166 % or 'mean', which calculates the the maximum or mean
c@0 167 % respectively for every combination of the other
c@0 168 % variables.
c@0 169 % - 'estimate' - Filters the data according to estimate
c@0 170 % information. The specification is identical to 'channel'.
c@0 171 % - 'estTag' - Filters the data according to the estmate tag.
c@0 172 % The parameter should be a function handle that takes the
c@0 173 % tag string as its input, and returns a logical value.
c@0 174 % - 'metric' - Filters the data according to the metric. The
c@0 175 % parameter should be a function handle that takes the
c@0 176 % metric name as its input, and returns a logical value.
c@0 177 % - 'mixnum' - Filters the data according to the mixture
c@0 178 % number. The parameter should be a function handle that
c@0 179 % takes the mixture number as its input, and returns a
c@0 180 % logical value.
c@0 181 % - 'value' - Filters the data according to the value. The
c@0 182 % parameter should be a function handle that takes the
c@0 183 % value as its input, and returns a logical value.
c@33 184 %
c@33 185 % If using the 'mean' or 'max' option, the respective variable is
c@33 186 % removed from the output.
c@0 187
c@23 188 assert(mod(length(varargin),2)==0,'MASSEFresults:filter:invalidArgs','input must contain parameter/value pairs')
c@0 189 data = obj.data;
c@0 190
c@0 191 % work through varargin
c@0 192 for n = 1:2:length(varargin)
c@0 193 filtername = varargin{n}; % column to filter on
c@0 194 filterval = varargin{n+1}; % value used to filter
c@0 195 switch lower(filtername) % do filtering
c@0 196 case 'channel'
c@0 197 data = obj.filterRowOrAggregate(data,filterval,'channel',...
c@0 198 {'mixNum','algorithmNum','estimateNum','metric'},...
c@0 199 {'mixNum','algorithmNum','metric'});
c@0 200 case 'estimate'
c@0 201 data = obj.filterRowOrAggregate(data,filterval,'estimateNum',...
c@0 202 {'mixNum','algorithmNum','channel','metric'},...
c@0 203 {'mixNum','algorithmNum','metric'});
c@0 204 case 'mixnum'
c@0 205 data = obj.filterRows(data,filterval,'mixNum');
c@0 206 case 'algorithmnum'
c@0 207 data = obj.filterRows(data,filterval,'algorithmNum');
c@0 208 case 'metric'
c@0 209 data = obj.filterRows(data,filterval,'metric');
c@0 210 case 'esttag'
c@0 211 data = obj.filterRows(data,filterval,'estTag');
c@0 212 case 'value'
c@0 213 data = obj.filterRows(data,filterval,'value');
c@0 214 otherwise
c@0 215
c@0 216 end
c@0 217
c@0 218 end
c@0 219
c@0 220 end
c@0 221
c@0 222 function mixtureInfo(obj,mixtureNumber,varargin)
c@0 223 %MIXTUREINFO Add mixture information.
c@0 224 %
c@0 225 % R.MIXTUREINFO(MIXTURENUM,NAME,VALUE) adds algorithm information
c@0 226 % for the mixture with number MIXTURENUM to the results set
c@0 227 % contained in the MASSEFRESULTS object R using the variable names
c@0 228 % and values contained in the NAME / VALUE pair arguments. The
c@0 229 % following information can be stored about each mixture:
c@0 230 %
c@0 231 % - 'azi_sep' - azimuthal separation of widest sources
c@0 232 % (numeric);
c@0 233 % - 'elevation' - median elevation of sources (numeric);
c@0 234 % - 'filename_t' - target filename (char array);
c@0 235 % - 'filename_i' - interferer filename (char array);
c@0 236 % - 'sofa_path' - SOFA filename (char array);
c@0 237 % - 'target_azi' - the target azimuth (numeric);
c@0 238 % - 'target_ele' - the target elevation (numeric); and
c@0 239 % - 'tir' - target-to-interferer ratio (dB) (numeric).
c@0 240
c@0 241 % ensure some inputs are strings
c@0 242 varargin = obj.ensureKeyValParamStrs({'sofa_path','filename_t','filename_i'},varargin);
c@0 243
c@0 244 % add data
c@0 245 obj.mixtures = obj.addData(obj.mixtures,'mixNum',mixtureNumber,varargin{:});
c@0 246
c@0 247 end
c@0 248
c@0 249 function algorithmInfo(obj,algorithmNumber,varargin)
c@0 250 %ALGORITHMINFO Add algorithm information.
c@0 251 %
c@0 252 % R.ALGORITHMINFO(ALGORITHMNUM,NAME,VALUE)| adds algorithm
c@0 253 % information for the algorithm with number ALGORITHMNUM to the
c@0 254 % results set contained in the MASSEFRESULTS object R using the
c@0 255 % variable names and values contained in the NAME / VALUE pair
c@0 256 % arguments. The following information can be stored about each
c@0 257 % algorithm:
c@0 258 %
c@0 259 % - 'algorithmLabel' - a label for the algorithm (char
c@0 260 % array).
c@0 261
c@0 262 % ensure some inputs are strings
c@0 263 varargin = obj.ensureKeyValParamStrs('algorithmLabel',varargin);
c@0 264
c@0 265 obj.algorithms = obj.addData(obj.algorithms,'algorithmNum',algorithmNumber,varargin{:});
c@0 266
c@0 267 end
c@0 268
c@0 269 function input(obj,mixtureNum,algorithmNum,estimateNum,metric,channel,estTag,value)
c@0 270 %INPUT Input performance data.
c@0 271 %
c@0 272 % R.INPUT(MIXTURENUM,ALGORITHMNUM,...
c@0 273 % ESTIMATENUM,METRIC,CHANNEL,ESTTAG,VALUE)
c@0 274 % inputs the performance data for mixture number MIXTURENUM,
c@0 275 % algorithm number ALGORITHMNUM, estimate number ESTIMATENUM,
c@0 276 % metric METRIC, channel number CHANNEL, estimate tag ESTTAG, and
c@0 277 % value VALUE to the MASSEFRESULTS instance R.
c@0 278
c@0 279 if ~ischar(estTag)
c@0 280 estTag = char(estTag);
c@0 281 end
c@0 282
c@0 283 rownames = {'mixNum','algorithmNum','estimateNum','metric','channel','estTag','value'};
c@0 284 values = {mixtureNum,algorithmNum,estimateNum,metric,channel,estTag,value};
c@0 285 row = cell2table(values, 'VariableNames',rownames);
c@0 286
c@0 287 try
c@0 288 % find existing row
c@0 289 match = obj.performance.mixNum==mixtureNum && ...
c@0 290 obj.performance.algorithmNum==algorithmNum && ...
c@0 291 obj.performance.estimateNum==estimateNum && ...
c@0 292 strcmp(metric,obj.performance.metric) && ...
c@0 293 obj.performance.channel==channel && ...
c@0 294 strcmp(estTag,obj.performance.estTag);
c@0 295
c@0 296 if any(match)
c@0 297 % replace
c@0 298 obj.performance(find(match,1,'first'),:) = row;
c@0 299 else
c@0 300 % append
c@0 301 obj.performance = [obj.performance; row];
c@0 302 end
c@0 303 catch % there is no data
c@0 304 % append
c@0 305 obj.performance = [obj.performance; row];
c@0 306 end
c@0 307
c@0 308 end
c@0 309
c@27 310 function bph = boxPlot(obj)
c@27 311 %BOXPLOT Analyse the results data by plotting notched box plots
c@27 312 %
c@27 313 % R.BOXPLOT() produces a series of box plots, one for each
c@27 314 % metric, plotting the performance of each algorithms/estimate
c@27 315 % aggregated across all mixtures.
c@27 316 %
c@27 317 % BPH = R.BOXPLOT() returns an array of IOSR.STATISTICS.BOXPLOT
c@27 318 % objects BPH for the plots.
c@27 319
c@27 320 for m = numel(obj.metrics):-1:1
c@27 321
c@27 322 tabData = obj.filter('metric', @(x) strcmp(x, obj.metrics{m}), 'channel', 'max');
c@27 323 tabData.name = strcat(tabData.algorithmLabel, ':', {' '}, tabData.estTag);
c@27 324
c@27 325 [y, x] = iosr.statistics.tab2box(tabData.name, tabData.value);
c@27 326
c@27 327 figure
c@27 328 bph(m) = iosr.statistics.boxPlot(x, y, 'notch', true);
c@27 329 ylabel(obj.metrics{m})
c@27 330 xlabel('Algorithm')
c@27 331 box on
c@27 332
c@27 333 end
c@27 334
c@27 335 end
c@27 336
c@0 337 end % public methods
c@0 338
c@0 339 methods (Hidden)
c@0 340
c@0 341 function debug(obj) %#ok<MANU>
c@0 342 keyboard;
c@0 343 end
c@0 344
c@0 345 end % hidden methods
c@0 346
c@0 347 methods (Access = private)
c@0 348
c@0 349 function dataTable = filterRowOrAggregate(obj,dataTable,filterval,col,group,altgroup)
c@0 350 %FILTERROWORAGGREGATE filter data based on aggregate function.
c@0 351
c@0 352 if ischar(filterval)
c@0 353 % special aggregate function
c@0 354 switch lower(filterval)
c@0 355 case 'max'
c@0 356 fhandle = @max;
c@0 357 case 'mean'
c@0 358 fhandle = @mean;
c@0 359 otherwise
c@23 360 error('MASSEFresults:filterRowOrAggregate:unknownOption','Unknown filter parameter ''%s''.',filterval)
c@0 361 end
c@0 362 % do stats
c@0 363 try
c@27 364 filteredTable = varfun(fhandle,dataTable,'InputVariables','value',...
c@0 365 'GroupingVariables',group);
c@31 366 keys = group;
c@0 367 catch
c@27 368 filteredTable = varfun(fhandle,dataTable,'InputVariables','value',...
c@0 369 'GroupingVariables',altgroup);
c@31 370 keys = altgroup;
c@0 371 end
c@0 372 % rename value column and delete GroupCount column
c@27 373 filteredTable = obj.findRenameVar(filteredTable,'value','value');
c@27 374 filteredTable.GroupCount = [];
c@31 375 [~, ia, ib] = intersect(filteredTable(:,keys), dataTable(:,keys));
c@31 376 dataTable.value(ib) = filteredTable.value(ia);
c@31 377 dataTable = dataTable(ib, :);
c@33 378 dataTable(:, col) = [];
c@0 379 else
c@0 380 % normal filter function
c@0 381 dataTable = obj.filterRows(dataTable,filterval,col);
c@0 382 end
c@0 383
c@0 384 end
c@0 385
c@0 386 function dataTable = joinLookupTables(obj,dataTable)
c@0 387 %JOINLOOKUPTABLES join a table to the lookup tables
c@0 388
c@0 389 if ~isempty(obj.mixtures)
c@0 390 dataTable = outerjoin(dataTable,obj.mixtures,'Type','left','MergeKeys',true,'keys','mixNum');
c@0 391 end
c@0 392 if ~isempty(obj.algorithms)
c@0 393 dataTable = outerjoin(dataTable,obj.algorithms,'Type','left','MergeKeys',true);
c@0 394 end
c@0 395
c@0 396 end
c@0 397
c@0 398 end % private methods
c@0 399
c@0 400 methods (Static, Access = private)
c@0 401
c@0 402 function dataTable = addData(dataTable,key,keyVal,varargin)
c@0 403 %ADDDATA add data to lookup tables.
c@0 404
c@23 405 assert(mod(length(varargin),2)==0,'MASSEFresults:addData:invalidArgs','input must contain parameter/value pairs')
c@0 406 rI = find(ismember(dataTable.(key),keyVal),1,'first');
c@0 407 if isempty(rI) % add a new row
c@0 408 % get data from varargin
c@0 409 varnames = cell(0);
c@0 410 vals = cell(0);
c@0 411 for n = 1:2:length(varargin)
c@0 412 varnames{(n+1)/2} = varargin{n};
c@0 413 vals{(n+1)/2} = varargin{n+1};
c@0 414 end
c@0 415 % make new row
c@0 416 vars = dataTable.Properties.VariableNames;
c@0 417 newrow = cell(1,length(vars));
c@0 418 newrow{strcmp(key,vars)} = keyVal;
c@0 419 for r = 1:length(varnames)
c@0 420 if ismember(varnames{r},vars);
c@0 421 newrow{strcmp(varnames{r},vars)} = vals{r};
c@0 422 end
c@0 423 end
c@0 424 newrow = cell2table(newrow,'VariableNames',vars);
c@0 425 % append
c@0 426 dataTable = [dataTable; newrow];
c@0 427 else % update row
c@0 428 for n = 1:2:length(varargin)
c@0 429 try
c@0 430 dataTable(rI,varargin(n)) = varargin(n+1);
c@0 431 catch
c@0 432 dataTable.(varargin{n})(rI) = varargin(n+1);
c@0 433 end
c@0 434 end
c@0 435 end
c@0 436
c@0 437 end
c@0 438
c@0 439 function dataTable = findRenameVar(dataTable,old,new)
c@0 440 %FINDRENAMEVAR Find and rename a variable.
c@0 441
c@0 442 varnames = dataTable.Properties.VariableNames;
c@0 443 k = find(cellfun(@(x) ~isempty(strfind(x,old)),varnames),1,'first');
c@0 444 dataTable.Properties.VariableNames{varnames{k}} = new;
c@0 445
c@0 446 end
c@0 447
c@0 448 function dataTable = filterRows(dataTable,fhandle,col)
c@0 449 %FILTERROWS filter the rows in a table.
c@0 450
c@23 451 assert(isa(fhandle,'function_handle'),'MASSEFresults:filterRows:invalidFhandle','Parameter must be a function handle')
c@0 452 dataTable = dataTable(fhandle(dataTable.(col)),:);
c@0 453
c@0 454 end
c@0 455
c@0 456 function C = ensureKeyValParamStrs(keys,keyValArray)
c@0 457 keys = cellstr(keys);
c@0 458 C = keyValArray;
c@0 459 keyValArrayStr = cellfun(@char,keyValArray,'UniformOutput',false);
c@0 460 [~,~,ib] = intersect(keys,keyValArrayStr);
c@0 461 C(ib+1) = cellfun(@char,C(ib+1),'UniformOutput',false);
c@0 462 end
c@0 463
c@0 464 end % private static methods
c@0 465
c@0 466 end