Skip to content
Permalink
release
Switch branches/tags

Name already in use

A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
Go to file
 
 
Cannot retrieve contributors at this time
function [pipeline] = ft_analysispipeline(cfg, data)
% FT_ANALYSIPIPELINE reconstructs the complete analysis pipeline that was used to create
% the input FieldTrip data structure. The pipeline will be visualized as a flowchart.
% In the future it will be possible to output the complete pipeline as a MATLAB script
% or in a specialized pipeline format (e.g. PSOM, JIST, LONI, Taverna).
%
% Use as
% output = ft_analysispipeline(cfg, data)
%
% The first cfg input contains the settings that apply to the behavior of this
% particular function and the second data input argument can be the output of any
% FieldTrip function, e.g. FT_PREPROCESSING, FT_TIMELOCKANALYSIS, FT_SOURCEANALYSIS,
% FT_FREQSTATISTICS or whatever you like.
%
% Alternatively, for the second input argument you can also only give the configuration
% of the processed data (i.e. "data.cfg") instead of the full data.
%
% The configuration options that apply to the behavior of this function are
% cfg.filename = string, filename without the extension
% cfg.filetype = string, can be 'matlab', 'html' or 'dot'
% cfg.feedback = string, 'no', 'text', 'gui' or 'yes', whether text and/or
% graphical feedback should be presented (default = 'yes')
% cfg.showinfo = string or cell-array of strings, information to display
% in the gui boxes, can be any combination of
% 'functionname', 'revision', 'matlabversion',
% 'computername', 'username', 'calltime', 'timeused',
% 'memused', 'workingdir', 'scriptpath' (default =
% 'functionname', only display function name). Can also
% be 'all', show all pipeline. Please note that if you want
% to show a lot of information, this will require a lot
% of screen real estate.
% cfg.remove = cell-array with strings, determines which objects will
% be removed from the configuration prior to writing it to
% file. For readibility of the script, you may want to
% remove the large objectssuch as event structure, trial
% definition, source positions
% cfg.keepremoved = 'yes' or 'no', determines whether removed fields are
% completely removed, or only replaced by a short textual
% description (default = 'no')
%
% This function uses the nested cfg and cfg.previous that are present in
% the data structure. It will use the configuration and the nested previous
% configurations to climb all the way back into the tree. This funtction
% will print a complete MATLAB script to screen (and optionally to file).
% Furthermore, it will show an interactive graphical flowchart
% representation of the steps taken during the pipeline(i). In the flowchart
% you can click on one of the steps to see the configuration details of
% that pipeline(i).
%
% Example use:
% data = ft_timelocksimulation([]);
% data_bl = ft_timelockbaseline([], data);
% data_avg = ft_timelockanalysis([], data_bl);
% ft_analysispipeline([], data_avg)
%
% Note that the nested cfg and cfg.previous in your data might not contain
% all details that are required to reconstruct a complete and valid
% analysis script.
%
% To facilitate data-handling and distributed computing you can use
% cfg.inputfile = ...
% If you specify this, the input data will be read from a *.mat file on disk. The
% file should contain only a single variable, corresponding with the input structure.
%
% See also FT_PREPROCESSING, FT_TIMELOCKANALYSIS, FT_FREQANALYSIS, FT_SOURCEANALYSIS,
% FT_CONNECTIVITYANALYSIS, FT_NETWORKANALYSIS
% Copyright (C) 2014-2022, Robert Oostenveld
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
% FieldTrip is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% FieldTrip is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$
% these are used by the ft_preamble/ft_postamble function and scripts
ft_revision = '$Id$';
ft_nargin = nargin;
ft_nargout = nargout;
% callinfo feedback is highly annoying in this recursive function
% do this here, otherwise ft_defaults will override our setting
if ~isfield(cfg, 'showcallinfo'), cfg.showcallinfo = 'no'; end
% do the general setup of the function
ft_defaults
ft_preamble init
ft_preamble debug
ft_preamble loadvar data
ft_preamble provenance data
% the ft_abort variable is set to true or false in ft_preamble_init
if ft_abort
return
end
% set the defaults
cfg.filename = ft_getopt(cfg, 'filename');
cfg.showinfo = ft_getopt(cfg, 'showinfo', {'functionname'});
cfg.keepremoved = ft_getopt(cfg, 'keepremoved', 'no');
cfg.feedback = ft_getopt(cfg, 'feedback', 'text');
cfg.prune = ft_getopt(cfg, 'prune', 'yes');
cfg.filetype = ft_getopt(cfg, 'filetype');
cfg.fontsize = ft_getopt(cfg, 'fontsize', 10);
if isempty(cfg.filetype) && ~isempty(cfg.filename)
[p, f, x] = fileparts(cfg.filename);
switch x
case '.m'
cfg.filetype = 'matlab';
case '.html'
cfg.filetype = 'html';
case '.dot'
cfg.filetype = 'dot';
otherwise
ft_error('cannot determine filetype');
end
end
if ~isfield(cfg, 'remove')
% this is the default list of configuration elements to be removed. These
% elements would be very large to print and make the script difficult to
% read. To get a correctly behaving script, you may have to change this.
cfg.remove = {
'sgncmb'
'channelcmb'
'event'
'trl'
'trlold'
'artfctdef.eog.trl'
'artfctdef.jump.trl'
'artfctdef.muscle.trl'
'pos'
'inside'
'outside'
'sourcemodel.pos'
'sourcemodel.inside'
'sourcemodel.outside'
'vol.bnd.pos'
'vol.bnd.tri'
'headmodel.bnd.pos'
'headmodel.bnd.tri'
};
elseif ~iscell(cfg.remove)
cfg.remove = {cfg.remove};
end
if strcmp(cfg.showinfo, 'all')
cfg.showinfo = {
'functionname'
'revision'
'matlabversion'
'computername'
'architecture'
'username'
'calltime'
'timeused'
'memused'
'workingdir'
'scriptpath'
};
end
if ~isfield(cfg, 'showinfo')
cfg.showinfo = {'functionname'};
elseif ~iscell(cfg.showinfo)
cfg.showinfo = {cfg.showinfo};
end
if ~isfield(data, 'cfg')
% assume that the user only passed the cfg instead of a complete data structure
data = struct('cfg', data);
end
% walk the tree, gather information about each node
ft_progress('init', cfg.feedback, 'parsing provenance...');
pipeline = walktree(data.cfg);
ft_progress('close');
% convert the cell-array into a structure array
for i=1:length(pipeline)
tmp(i) = pipeline{i};
end
pipeline = tmp;
if istrue(cfg.prune)
% prune the double occurences
[dummy, indx] = unique({pipeline.this});
pipeline = pipeline(sort(indx));
end
% start at the end of the tree and determine the level of each of the parents
hasparent = false(size(pipeline));
haschild = false(size(pipeline));
for i=1:length(pipeline)
hasparent(i) = ~isempty(pipeline(i).parent);
haschild(i) = any(strcmp(pipeline(i).this, [pipeline.parent]));
end
% construct a matrix with all pipeline steps
width = zeros(size(pipeline));
height = zeros(size(pipeline));
level = 1;
% the items without children start at height 1
sel = find(~haschild);
while ~isempty(sel)
height(sel) = level;
% find the parents of the items at this level
sel = match_str({pipeline.this}, [pipeline(sel).parent]);
% the parents should be at least one level higher
height(sel) = level + 1;
% continue with the next level
level = level + 1;
end
for i=1:max(height)
sel = find(height==i);
width(sel) = 1:length(sel);
end
for i=1:length(pipeline)
pipeline(i).position = [height(i) width(i)];
end
% sort according to a decreasing level, i.e. the last pipeline(i) at the end
[dummy, indx] = sortrows(-[height(:) width(:)]);
pipeline = pipeline(indx);
if isempty(cfg.filename)
pipeline2matlabfigure(cfg, pipeline);
else
switch cfg.filetype
case 'matlab'
pipeline2matlabscript(cfg, pipeline);
case 'dot'
pipeline2dotfile(cfg, pipeline);
case 'html'
pipeline2htmlfile(cfg, pipeline);
otherwise
ft_error('unsupported filetype');
end
end
% do the general cleanup and bookkeeping at the end of the function
ft_postamble debug
ft_postamble previous data
ft_postamble provenance
ft_postamble savefig
if isempty(cfg.filename)
% add a menu to the figure, note that the menu makes FT_ANALYSISPIPELINE recursive
menu_fieldtrip(gcf, cfg);
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION for recursive walking along the cfg.previous.previous info
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function info = walktree(cfg)
if isempty(cfg) || (isstruct(cfg) && numel(fieldnames(cfg))==0)
info = [];
return
end
this = getnode(cfg);
% parse all previous steps
if isfield(cfg, 'previous') && ~isempty(cfg.previous) && iscell(cfg.previous)
previous = cellfun(@walktree, cfg.previous, 'UniformOutput', false);
if iscell(previous{1})
previous = cat(2, previous{:});
end
elseif isfield(cfg, 'previous') && ~isempty(cfg.previous) && isstruct(cfg.previous)
previous = walktree(cfg.previous);
elseif isfield(cfg, 'previous') && ~isempty(cfg.previous)
ft_error('unexpected content in cfg.previous');
else
previous = {};
end
% parse the side branches, such as cfg.headmodel and cfg.layout
fn = fieldnames(cfg);
branch = {};
for i=1:numel(fn)
if isstruct(cfg.(fn{i})) && isfield(cfg.(fn{i}), 'cfg')
branch = [walktree(cfg.(fn{i}).cfg) branch];
this.parent{end+1} = branch{1}.this; % the start of the branch is a parent to this element
end
end
ft_progress(rand(1), 'parsing provenance for %s\n', this.name); % FIXME no percentage complete known
drawnow
% the order of the output elements matters for the recursion
info = [{this} branch previous];
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION for gathering the information about each pipeline
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function node = getnode(cfg)
[p, f, x] = myfileparts(getvalue(cfg, 'version.name'));
node.cfg = cfg;
node.name = f;
node.id = getvalue(cfg, 'version.id');
node.this = ft_hash(cfg);
if isfield(cfg, 'previous') && ~isempty(cfg.previous) && iscell(cfg.previous)
% skip the entries that are empty
cfg.previous = cfg.previous(~cellfun(@isempty, cfg.previous));
node.parent = cellfun(@ft_hash, cfg.previous, 'UniformOutput', false);
elseif isfield(cfg, 'previous') && ~isempty(cfg.previous) && isstruct(cfg.previous)
node.parent = {ft_hash(cfg.previous)};
elseif isfield(cfg, 'previous') && ~isempty(cfg.previous)
ft_error('unexpected content in cfg.previous');
else
node.parent = {};
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function v = getvalue(s, f)
if issubfield(s, f)
v = getsubfield(s, f);
else
v = 'unknown';
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function [p, f, x] = myfileparts(filename)
if ispc
filename(filename=='/') = filesep;
else
filename(filename=='\') = filesep;
end
[p, f, x] = fileparts(filename);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function varargout = button(h, eventdata, handles, varargin)
pos = get(get(gcbo, 'CurrentAxes'), 'CurrentPoint');
x = pos(1,1);
y = pos(1,2);
pipeline = guidata(h);
for i=1:numel(pipeline)
if (x >= pipeline(i).x(1) && x <= pipeline(i).x(2) && y >= pipeline(i).y(1) && y <= pipeline(i).y(3))
cfg = pipeline(i).cfg;
if isfield(cfg, 'previous')
cfg = rmfield(cfg, 'previous');
end
% use a helper function to remove uninteresting fields
cfg = removefields(cfg, ignorefields('pipeline'), 'recursive', true);
% use a helper function to remove too large fields
cfg.checksize = 3000;
cfg = ft_checkconfig(cfg, 'checksize', 'yes');
cfg = rmfield(cfg, 'checksize');
script = printstruct('cfg', cfg);
uidisplaytext(script, pipeline(i).name);
break;
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function pipeline2matlabfigure(cfg, pipeline)
fprintf('plotting pipeline as MATLAB figure\n');
layout = cell(numel(pipeline));
for i=1:length(pipeline)
% get the vertical and horizontal position in integer values
% high numbers are towards the begin, low numbers are towards the end of the pipeline
v = pipeline(i).position(1);
h = pipeline(i).position(2);
layout{v,h} = pipeline(i).this;
end
% remove the empty columns
maxheight = max(sum(~cellfun(@isempty, layout),1));
maxwidth = max(sum(~cellfun(@isempty, layout),2));
layout = layout(1:maxheight,1:maxwidth);
fig = figure;
hold on
axis manual; % the axis should not change during the contruction of the arrows, otherwise the arrowheads will be distorted
set(gca, 'Units', 'normalized'); % use normalized units
set(gcf, 'ToolBar', 'none');
axis([0 1 0 1])
axis off;
axis tight;
for i=1:numel(pipeline)
label = makelabel(pipeline(i), cfg.showinfo);
% dublicate backslashes to escape tex interpreter (in case of windows filenames)
label = strrep(label, '\', '\\');
label = strrep(label, '{\\bf', '{\bf'); % undo for bold formatting
% escape underscores
label = strrep(label, '_', '\_');
% strip blank line if present and not needed
if strcmp(label{end}, '')
label(end) = [];
end
% compute width and height of each box, note that axis Units are set to Normalized
boxsize = 1./[maxwidth+1 maxheight+3];
% create the 4 corners for our patch, close the patch by returning to the start point
x = ([0 1 1 0 0]-0.5) .* boxsize(1);
y = ([0 0 1 1 0]-0.5) .* boxsize(2);
% position the patch
location = pipeline(i).position([2 1]);
location(1) = (location(1)-0.5)/maxwidth;
location(2) = (location(2)-0.5)/maxheight;
% the location specifies the center of the patch
x = x + location(1);
y = y + location(2);
p = patch(x', y', 0);
set(p, 'Facecolor', [1 1 0.6])
pipeline(i).x = x;
pipeline(i).y = y;
guidata(fig, pipeline);
if length(label)==1
textloc = location;
l = text(textloc(1), textloc(2), label);
set(l, 'HorizontalAlignment', 'center');
set(l, 'VerticalAlignment', 'middle');
set(l, 'fontUnits', 'points');
set(l, 'fontSize', cfg.fontsize);
set(l, 'interpreter', 'tex');
else
textloc = location;
textloc(1) = textloc(1)-boxsize(1)/2;
textloc(2) = textloc(2)+boxsize(2)/2;
l = text(textloc(1), textloc(2), label);
set(l, 'HorizontalAlignment', 'left');
set(l, 'VerticalAlignment', 'top');
set(l, 'fontUnits', 'points');
set(l, 'fontSize', cfg.fontsize);
set(l, 'interpreter', 'tex');
end
% draw an arrow if appropriate
n = length(pipeline(i).parent);
for j=1:n
[parentlocation(2), parentlocation(1)] = ind2sub([maxheight, maxwidth], find(strcmp(layout(:), pipeline(i).parent{j}), 1, 'first'));
% parentlocation = info(find(strcmp({pipeline.this}, analysis.parent{j}), 1, 'first')).position;
parentlocation(1) = (parentlocation(1)-0.5)/maxwidth;
parentlocation(2) = (parentlocation(2)-0.5)/maxheight;
base = parentlocation + [0 -0.5].*boxsize;
if false % n>1
% distribute the inputs evenly over the box
rel = 2*(j+n-1)/(n-1)-1; % relative location between -1.0 and 1.0
rel = rel*(n-1)/(n+3); % compress the relative location
tip = location + [0 0.5].*boxsize + [rel 0].*boxsize/2;
else
% put it in the centre
tip = location + [0 0.5].*boxsize;
end
arrow(base, tip, 'length', 8, 'lineWidth', 1);
end
end % for numel(info)
set(fig, 'WindowButtonUpFcn', @button);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function label = makelabel(pipeline, showinfo)
% create the text information to display
label = {};
for k = 1:numel(showinfo)
switch showinfo{k}
case 'functionname'
% label{end+1} = ['{\bf ' pipeline(i).name '}'];
label{end+1} = pipeline.name;
if k == 1 % add blank line if function name is on top, looks nice
label{end+1} = '';
end
case 'revision'
if isfield(pipeline.cfg, 'version') && isfield(pipeline.cfg.version, 'id')
label{end+1} = pipeline.cfg.version.id;
else
label{end+1} = '<revision unknown>';
end
case 'matlabversion'
if isfield(pipeline.cfg, 'callinfo') && isfield(pipeline.cfg.callinfo, 'matlab')
label{end+1} = ['MATLAB ' pipeline.cfg.callinfo.matlab];
else
label{end+1} = '<MATLAB version unknown>';
end
case 'computername'
if isfield(pipeline.cfg, 'callinfo') && isfield(pipeline.cfg.callinfo, 'hostname')
label{end+1} = ['Hostname: ' pipeline.cfg.callinfo.hostname];
else
label{end+1} = '<hostname unknown>';
end
case 'architecture'
if isfield(pipeline.cfg, 'callinfo') && isfield(pipeline.cfg.callinfo, 'hostname')
label{end+1} = ['Architecture: ' pipeline.cfg.callinfo.computer];
else
label{end+1} = '<architecture unknown>';
end
case 'username'
if isfield(pipeline.cfg, 'callinfo') && isfield(pipeline.cfg.callinfo, 'user')
label{end+1} = ['Username: ' pipeline.cfg.callinfo.user];
else
label{end+1} = '<username unknown>';
end
case 'calltime'
if isfield(pipeline.cfg, 'callinfo') && isfield(pipeline.cfg.callinfo, 'calltime')
label{end+1} = ['Function called at ' datestr(pipeline.cfg.callinfo.calltime)];
else
label{end+1} = '<function call time unknown>';
end
case 'timeused'
if isfield(pipeline.cfg, 'callinfo') && isfield(pipeline.cfg.callinfo, 'proctime')
label{end+1} = sprintf('Function call required %d seconds', round(pipeline.cfg.callinfo.proctime));
else
label{end+1} = '<processing time unknown>';
end
case 'memused'
if isfield(pipeline.cfg, 'callinfo') && isfield(pipeline.cfg.callinfo, 'procmem')
label{end+1} = sprintf('Function call required %d MB', round(pipeline.cfg.callinfo.procmem/1024/1024));
else
label{end+1} = '<memory requirement unknown>';
end
case 'workingdir'
if isfield(pipeline.cfg, 'callinfo') && isfield(pipeline.cfg.callinfo, 'pwd')
label{end+1} = sprintf('Working directory was %s', pipeline.cfg.callinfo.pwd);
else
label{end+1} = '<working directory unknown>';
end
case 'scriptpath'
if isfield(pipeline.cfg, 'version') && isfield(pipeline.cfg.version, 'name')
label{end+1} = sprintf('Full path to script was %s', pipeline.cfg.version.name);
else
label{end+1} = '<script path unknown>';
end
end
end % for numel(showinfo)
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function pipeline2matlabscript(cfg, pipeline)
[p, f, x] = fileparts(cfg.filename);
filename = fullfile(p, [f '.m']);
fprintf('exporting MATLAB script to file ''%s''\n', filename);
varname = {};
varhash = {};
for i=1:length(pipeline)
varname{end+1} = sprintf('variable_%d_%d', pipeline(i).position(1), pipeline(i).position(2));
varhash{end+1} = pipeline(i).this;
end
for i=1:length(pipeline)
pipeline(i).inputvar = {};
pipeline(i).outputvar = varname{strcmp(varhash, pipeline(i).this)};
for j=1:length(pipeline(i).parent)
pipeline(i).inputvar{j} = varname{strcmp(varhash, pipeline(i).parent{j})};
end
end
sep = sprintf('\n\n%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n\n');
script = sep;
for i=1:length(pipeline)
thiscfg = pipeline(i).cfg;
if isfield(thiscfg, 'previous')
thiscfg = rmfield(thiscfg, 'previous');
end
cfgstr = printstruct('cfg', thiscfg);
if ~isempty(pipeline(i).inputvar)
inputvar = sprintf(', %s', pipeline(i).inputvar{:});
else
inputvar = '';
end
cmd = sprintf('\n%s = %s(cfg%s);', pipeline(i).outputvar, pipeline(i).name, inputvar);
script = cat(2, script, cfgstr, cmd, sep);
end
% write the complete script to file
fid = fopen(filename, 'wb');
fprintf(fid, '%s', script);
fclose(fid);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function pipeline2dotfile(cfg, pipeline)
[p, f, x] = fileparts(cfg.filename);
filename = fullfile(p, [f '.dot']);
fprintf('exporting DOT file to ''%s''\n', filename);
% write the complete script to file
fid = fopen(filename, 'wb');
fprintf(fid, 'digraph {\n');
varhash = {pipeline.this};
for i=1:length(pipeline)
for j=1:length(pipeline(i).parent)
fprintf(fid, '%d -> %d\n', find(strcmp(varhash, pipeline(i).parent{j})), i);
end
end
for i=1:length(pipeline)
label = makelabel(pipeline(i), cfg.showinfo);
if numel(label)>2
% left justified
label = sprintf('%s\\l', label{:});
label = label(1:end-2);
else
% centered
label = sprintf('%s\\n', label{:});
label = label(1:end-2);
end
fprintf(fid, '%d [label="%s",shape=box,fontsize=%d,URL="http://www.fieldtriptoolbox.org/reference/%s"]\n', i, label, cfg.fontsize, pipeline(i).name);
end
fprintf(fid, '}\n');
fclose(fid);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function pipeline2htmlfile(cfg, pipeline)
[p, f, x] = fileparts(cfg.filename);
filename = fullfile(p, [f '.html']);
fprintf('exporting HTML file to ''%s''\n', filename);
html = '';
totalproctime = 0;
ft_progress('init', cfg.feedback, 'serialising cfg-structures...');
for k = 1:numel(pipeline)
ft_progress(k/numel(pipeline), 'serialising cfg-structure %d from %d', k, numel(pipeline));
% strip away the cfg.previous fields, and all data-like fields
tmpcfg = removefields(pipeline(k).cfg, ignorefields('html'));
usercfg = [];
% record the usercfg and proctime if present
if isfield(tmpcfg, 'callinfo')
if isfield(tmpcfg.callinfo, 'usercfg')
usercfg = removefields(tmpcfg.callinfo.usercfg, ignorefields('html'));
% avoid processing usercfg twice
tmpcfg.callinfo = rmfield(tmpcfg.callinfo, 'usercfg');
end
if isfield(tmpcfg.callinfo, 'proctime')
totalproctime = totalproctime + tmpcfg.callinfo.proctime;
end
end
html = [html sprintf('nodes["%s"] = {"id": "%s", "name": "%s", "cfg": "%s", "usercfg": "%s", "parentIds": [',...
pipeline(k).this, pipeline(k).this, pipeline(k).name, escapestruct(tmpcfg), escapestruct(usercfg))];
if ~isempty(pipeline(k).parent)
for j = 1:numel(pipeline(k).parent)
html = [html '"' pipeline(k).parent{j} '"'];
if j < numel(pipeline(k).parent)
html = [html ','];
end
end
end
html = [html sprintf(']};\n')];
if k == numel(pipeline)
% we are at the single leaf node
html = [html sprintf('var leafId = "%s";\n', pipeline(k).this)];
end
end
ft_progress('close');
html = [html(1:end-2) newline];
% load the skeleton and put in the html code
thispath = fileparts(mfilename('fullpath'));
htmlfile = fileread([thispath '/private/pipeline-skeleton.html']);
timestamp = [datestr(now(), 'ddd') ' ' datestr(now())];
proctimestr = print_tim(totalproctime);
htmlfile = strrep(htmlfile, '${TIMESTAMP}', timestamp);
htmlfile = strrep(htmlfile, '${PIPELINE}', html);
htmlfile = strrep(htmlfile, '${USER}', getusername());
htmlfile = strrep(htmlfile, '${PROCTIME}', proctimestr);
% write the file
fid = fopen(filename, 'w');
fwrite(fid, htmlfile, 'uchar');
fclose(fid);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% SUBFUNCTION
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
function cfghtml = escapestruct(tmpcfg)
% convert the cfg structure to a suitable string (escape newlines and
% quotes)
% strip away big numeric fields
if isstruct(tmpcfg)
fields = fieldnames(tmpcfg);
for k = 1:numel(fields)
if isnumeric(tmpcfg.(fields{k})) && numel(tmpcfg.(fields{k})) > 400
tmpcfg.(fields{k}) = '[numeric data of &gt;400 elements stripped]';
end
end
end
cfghtml = strrep(printstruct('cfg', tmpcfg), '\', '\\');
cfghtml = strrep(cfghtml, sprintf('\r'), '\r');
cfghtml = strrep(cfghtml, sprintf('\n'), '\n');
cfghtml = strrep(cfghtml, sprintf('\t'), '\t');
cfghtml = strrep(cfghtml, '"', '\"');