1function plotBenchmark(fileNames, export)
2%PLOTBENCHMARK Plots and exports video codec benchmarking results.
3%   PLOTBENCHMARK(FILENAMES, EXPORT) parses the video codec benchmarking result
4%   files given by the cell array of strings FILENAME. It plots the results and
5%   optionally exports each plot to an appropriately named file.
6%
7%   EXPORT parameter:
8%       'none'  No file exports.
9%       'eps'   Exports to eps files (default).
10%       'pdf'   Exports to eps files and uses the command-line utility
11%               epstopdf to obtain pdf files.
12%
13%   Example:
14%       plotBenchmark({'H264Benchmark.txt' 'LSVXBenchmark.txt'}, 'pdf')
15
16if (nargin < 1)
17    error('Too few input arguments');
18elseif (nargin < 2)
19    export = 'eps';
20end
21
22if ~iscell(fileNames)
23    if ischar(fileNames)
24        % one single file name as a string is ok
25        if size(fileNames,1) > 1
26            % this is a char matrix, not ok
27            error('First argument must not be a char matrix');
28        end
29        % wrap in a cell array
30        fileNames = {fileNames};
31    else
32        error('First argument must be a cell array of strings');
33    end
34end
35
36if ~ischar(export)
37    error('Second argument must be a string');
38end
39
40outpath = 'BenchmarkPlots';
41[status, errMsg] = mkdir(outpath);
42if status == 0
43    error(errMsg);
44end
45
46nCases = 0;
47testCases = [];
48% Read each test result file
49for fileIdx = 1:length(fileNames)
50    if ~isstr(fileNames{fileIdx})
51        error('First argument must be a cell array of strings');
52    end
53
54    fid = fopen(fileNames{fileIdx}, 'rt');
55    if fid == -1
56        error(['Unable to open ' fileNames{fileIdx}]);
57    end
58
59    version = '1.0';
60    if ~strcmp(fgetl(fid), ['#!benchmark' version])
61        fclose(fid);
62        error(['Requires benchmark file format version ' version]);
63    end
64
65    % Parse results file into testCases struct
66    codec = fgetl(fid);
67    tline = fgetl(fid);
68    while(tline ~= -1)
69        nCases = nCases + 1;
70
71        delim = strfind(tline, ',');
72        name = tline(1:delim(1)-1);
73        % Drop underscored suffix from name
74        underscore = strfind(name, '_'); 
75        if ~isempty(underscore)
76            name = name(1:underscore(1)-1);
77        end
78
79        resolution = tline(delim(1)+1:delim(2)-1);
80        frameRate = tline(delim(2)+1:end);
81
82        tline = fgetl(fid);
83        delim = strfind(tline, ',');
84        bitrateLabel = tline(1:delim(1)-1); 
85        bitrate = sscanf(tline(delim(1):end),',%f');
86
87        tline = fgetl(fid);
88        delim = strfind(tline, ',');
89        psnrLabel = tline(1:delim(1)-1); 
90        psnr = sscanf(tline(delim(1):end),',%f'); 
91
92
93        % Default data for the optional lines
94        speedLabel = 'Default';
95        speed = 0;
96        ssimLabel = 'Default';
97        ssim = 0;
98        
99        tline = fgetl(fid);
100        delim = strfind(tline, ',');
101        
102        while ~isempty(delim)
103            % More data
104            % Check type of data
105            if strncmp(lower(tline), 'speed', 5)
106                % Speed data included
107                speedLabel = tline(1:delim(1)-1);
108                speed = sscanf(tline(delim(1):end), ',%f');
109
110                tline = fgetl(fid);
111                
112            elseif strncmp(lower(tline), 'encode time', 11)
113                % Encode and decode times included
114                % TODO: take care of the data
115                
116                % pop two lines from file
117                tline = fgetl(fid);
118                tline = fgetl(fid);
119                
120            elseif strncmp(tline, 'SSIM', 4)
121                % SSIM data included
122                ssimLabel = tline(1:delim(1)-1);
123                ssim = sscanf(tline(delim(1):end), ',%f');
124
125                tline = fgetl(fid);
126            end
127            delim = strfind(tline, ',');
128        end
129
130        testCases = [testCases struct('codec', codec, 'name', name, 'resolution', ...
131            resolution, 'frameRate', frameRate, 'bitrate', bitrate, 'psnr', psnr, ...
132            'speed', speed, 'bitrateLabel', bitrateLabel, 'psnrLabel', psnrLabel, ...
133            'speedLabel', speedLabel, ...
134            'ssim', ssim, 'ssimLabel', ssimLabel)];
135
136        tline = fgetl(fid);
137    end
138
139    fclose(fid);
140end
141
142i = 0;
143casesPsnr = testCases;
144while ~isempty(casesPsnr)
145    i = i + 1;
146    casesPsnr = plotOnePsnr(casesPsnr, i, export, outpath);
147end
148
149casesSSIM = testCases;
150while ~isempty(casesSSIM)
151    i = i + 1;
152    casesSSIM = plotOneSSIM(casesSSIM, i, export, outpath);
153end
154
155casesSpeed = testCases;
156while ~isempty(casesSpeed)
157    if casesSpeed(1).speed == 0
158        casesSpeed = casesSpeed(2:end);
159    else
160        i = i + 1;
161        casesSpeed = plotOneSpeed(casesSpeed, i, export, outpath);
162    end
163end
164
165
166
167%%%%%%%%%%%%%%%%%%
168%% SUBFUNCTIONS %%
169%%%%%%%%%%%%%%%%%%
170
171function casesOut = plotOnePsnr(cases, num, export, outpath)
172% Find matching specs
173plotIdx = 1;
174for i = 2:length(cases)
175    if strcmp(cases(1).resolution, cases(i).resolution) & ...
176        strcmp(cases(1).frameRate, cases(i).frameRate)
177        plotIdx = [plotIdx i];
178    end
179end
180
181% Return unplotted cases
182casesOut = cases(setdiff(1:length(cases), plotIdx));
183cases = cases(plotIdx);
184
185% Prune similar results
186for i = 1:length(cases)
187    simIndx = find(abs(cases(i).bitrate - [cases(i).bitrate(2:end) ; 0]) < 10);
188    while ~isempty(simIndx)
189        diffIndx = setdiff(1:length(cases(i).bitrate), simIndx);
190        cases(i).psnr = cases(i).psnr(diffIndx);
191        cases(i).bitrate = cases(i).bitrate(diffIndx);
192        simIndx = find(abs(cases(i).bitrate - [cases(i).bitrate(2:end) ; 0]) < 10);
193    end
194end
195
196% Prepare figure with axis labels and so on
197hFig = figure(num);
198clf;
199hold on;
200grid on;
201axis([0 1100 20 50]);
202set(gca, 'XTick', 0:200:1000);
203set(gca, 'YTick', 20:10:60);
204xlabel(cases(1).bitrateLabel);
205ylabel(cases(1).psnrLabel);
206res = cases(1).resolution;
207frRate = cases(1).frameRate;
208title([res ', ' frRate]);
209
210hLines = [];
211codecs = {};
212sequences = {};
213i = 0;
214while ~isempty(cases)
215    i = i + 1;
216    [cases, hLine, codec, sequences] = plotOneCodec(cases, 'bitrate', 'psnr', i, sequences, 1);
217
218    % Stored to generate the legend
219    hLines = [hLines ; hLine];
220    codecs = {codecs{:} codec};
221end
222legend(hLines, codecs, 4);
223hold off;
224
225if ~strcmp(export, 'none')
226    % Export figure to an eps file
227    res = stripws(res);
228    frRate = stripws(frRate);
229    exportName = [outpath '/psnr-' res '-' frRate];
230    exportfig(hFig, exportName, 'Format', 'eps2', 'Color', 'cmyk');
231end
232
233if strcmp(export, 'pdf')
234    % Use the epstopdf utility to convert to pdf
235    system(['epstopdf ' exportName '.eps']);  
236end
237
238
239function casesOut = plotOneSSIM(cases, num, export, outpath)
240% Find matching specs
241plotIdx = 1;
242for i = 2:length(cases)
243    if strcmp(cases(1).resolution, cases(i).resolution) & ...
244        strcmp(cases(1).frameRate, cases(i).frameRate)
245        plotIdx = [plotIdx i];
246    end
247end
248
249% Return unplotted cases
250casesOut = cases(setdiff(1:length(cases), plotIdx));
251cases = cases(plotIdx);
252
253% Prune similar results
254for i = 1:length(cases)
255    simIndx = find(abs(cases(i).bitrate - [cases(i).bitrate(2:end) ; 0]) < 10);
256    while ~isempty(simIndx)
257        diffIndx = setdiff(1:length(cases(i).bitrate), simIndx);
258        cases(i).ssim = cases(i).ssim(diffIndx);
259        cases(i).bitrate = cases(i).bitrate(diffIndx);
260        simIndx = find(abs(cases(i).bitrate - [cases(i).bitrate(2:end) ; 0]) < 10);
261    end
262end
263
264% Prepare figure with axis labels and so on
265hFig = figure(num);
266clf;
267hold on;
268grid on;
269axis([0 1100 0.5 1]); % y-limit are set to 'auto' below
270set(gca, 'XTick', 0:200:1000);
271%set(gca, 'YTick', 20:10:60);
272xlabel(cases(1).bitrateLabel);
273ylabel(cases(1).ssimLabel);
274res = cases(1).resolution;
275frRate = cases(1).frameRate;
276title([res ', ' frRate]);
277
278hLines = [];
279codecs = {};
280sequences = {};
281i = 0;
282while ~isempty(cases)
283    i = i + 1;
284    [cases, hLine, codec, sequences] = plotOneCodec(cases, 'bitrate', 'ssim', i, sequences, 1);
285
286    % Stored to generate the legend
287    hLines = [hLines ; hLine];
288    codecs = {codecs{:} codec};
289end
290%set(gca,'YLimMode','auto')
291set(gca,'YLim',[0.5 1])
292set(gca,'YScale','log')
293legend(hLines, codecs, 4);
294hold off;
295
296if ~strcmp(export, 'none')
297    % Export figure to an eps file
298    res = stripws(res);
299    frRate = stripws(frRate);
300    exportName = [outpath '/psnr-' res '-' frRate];
301    exportfig(hFig, exportName, 'Format', 'eps2', 'Color', 'cmyk');
302end
303
304if strcmp(export, 'pdf')
305    % Use the epstopdf utility to convert to pdf
306    system(['epstopdf ' exportName '.eps']);  
307end
308
309
310function casesOut = plotOneSpeed(cases, num, export, outpath)
311% Find matching specs
312plotIdx = 1;
313for i = 2:length(cases)
314    if strcmp(cases(1).resolution, cases(i).resolution) & ...
315        strcmp(cases(1).frameRate, cases(i).frameRate) & ...
316        strcmp(cases(1).name, cases(i).name)
317        plotIdx = [plotIdx i];
318    end
319end
320
321% Return unplotted cases
322casesOut = cases(setdiff(1:length(cases), plotIdx));
323cases = cases(plotIdx);
324
325% Prune similar results
326for i = 1:length(cases)
327    simIndx = find(abs(cases(i).psnr - [cases(i).psnr(2:end) ; 0]) < 0.25);
328    while ~isempty(simIndx)
329        diffIndx = setdiff(1:length(cases(i).psnr), simIndx);
330        cases(i).psnr = cases(i).psnr(diffIndx);
331        cases(i).speed = cases(i).speed(diffIndx);
332        simIndx = find(abs(cases(i).psnr - [cases(i).psnr(2:end) ; 0]) < 0.25);
333    end
334end
335
336hFig = figure(num);
337clf;
338hold on;
339%grid on;
340xlabel(cases(1).psnrLabel);
341ylabel(cases(1).speedLabel);
342res = cases(1).resolution;
343name = cases(1).name;
344frRate = cases(1).frameRate;
345title([name ', ' res ', ' frRate]);
346
347hLines = [];
348codecs = {};
349sequences = {};
350i = 0;
351while ~isempty(cases)
352    i = i + 1;
353    [cases, hLine, codec, sequences] = plotOneCodec(cases, 'psnr', 'speed', i, sequences, 0);
354
355    % Stored to generate the legend
356    hLines = [hLines ; hLine];
357    codecs = {codecs{:} codec};
358end
359legend(hLines, codecs, 1);
360hold off;
361
362if ~strcmp(export, 'none')
363    % Export figure to an eps file
364    res = stripws(res);
365    frRate = stripws(frRate);
366    exportName = [outpath '/speed-' name '-' res '-' frRate];
367    exportfig(hFig, exportName, 'Format', 'eps2', 'Color', 'cmyk');
368end
369
370if strcmp(export, 'pdf')
371    % Use the epstopdf utility to convert to pdf
372    system(['epstopdf ' exportName '.eps']);  
373end
374
375
376function [casesOut, hLine, codec, sequences] = plotOneCodec(cases, xfield, yfield, num, sequences, annotatePlot)
377plotStr = {'gx-', 'bo-', 'r^-', 'kd-', 'cx-', 'go--', 'b^--'};
378% Find matching codecs
379plotIdx = 1;
380for i = 2:length(cases)
381    if strcmp(cases(1).codec, cases(i).codec)
382        plotIdx = [plotIdx i];
383    end
384end
385
386% Return unplotted cases
387casesOut = cases(setdiff(1:length(cases), plotIdx));
388cases = cases(plotIdx);
389
390for i = 1:length(cases)
391    % Plot a single case
392    hLine = plot(getfield(cases(i), xfield), getfield(cases(i), yfield), plotStr{num}, ...
393        'LineWidth', 1.1, 'MarkerSize', 6);
394end
395
396% hLine handle and codec are returned to construct the legend afterwards
397codec = cases(1).codec;
398
399if annotatePlot == 0
400    return;
401end
402
403for i = 1:length(cases)
404    % Print the codec name as a text label
405    % Ensure each codec is only printed once
406    sequencePlotted = 0;
407    for j = 1:length(sequences)
408        if strcmp(cases(i).name, sequences{j})
409            sequencePlotted = 1;
410            break;
411        end
412    end
413
414    if sequencePlotted == 0
415        text(getfield(cases(i), xfield, {1}), getfield(cases(i), yfield, {1}), ...
416            ['    ' cases(i).name]);
417        sequences = {sequences{:} cases(i).name};
418    end
419end
420
421
422% Strip whitespace from string
423function str = stripws(str)
424if ~isstr(str)
425    error('String required');
426end
427str = str(setdiff(1:length(str), find(isspace(str) == 1)));
428