annotate MASSEFresults.m @ 0:e34a535b9af0

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