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
|