function [results, simIDs, filePaths, funNames, prefixes] = dsImportResults(src, varargin)
%dsImportResults - Import analysis results of a simulation
%
% Usage:
% results = dsImportResults(src)
% Function style 1 (as argument 2):
% results = dsImportResults(src,func) % func optional
% results = dsImportResults(src,func,'option1',value1,...)
% Function style 2 (as option):
% results = dsImportResults(src,'option1',value1,...)
%
% Inputs:
% - src: DynaSim study_dir path or studyinfo structure
% - func: function handle of analysis function whose results to return
% - options: (key/value pairs are passed on to the analysis function)
% 'import_scope' : 'studyinfo' only looks for files listed in studyinfo.mat
% that were specified in initial dsSimulate call
% 'results' looks in 'results' folder
% 'postHocResults' looks in 'postHocResults' folder
% 'allResults' does above without studyinfo
% 'all' does all of the above (default)'
% 'func' : optional argument to return matching function name(s) or index(ies).
% 1) name as function handle or string, or cell array of
% handles. one can mix in function number indicies also as
% strings or numeric. name can be partial for matching using 'contains' fn.
% 2) index number(s) for function, typically following analysis in
% name, e.g. 'study_sim1_analysis#_func.mat' as mat. If index not
% specified and func name matches multiple functions, will
% return results as as structure fields (see Outputs below).
% 'simIDs' : numeric array of simIDs to import results from (default: [])
% 'as_cell' : output as cell array {0,1} (default: 0)
% 'add_prefix' : whether to add prefix to output struct field names
% 'simplify2cell_bool' : whether to simplify output variable structs to cell if only 1 result (default:1)
%
% Outputs:
% - results: If multiple result function instances found, it will return structure
% with fields of 'funcName__#', where number is the index number for a function
% instance, usually following analysis in name. Inside each field
% is a cell array of results of length = num sims.
% If add_prefix=1, it will return structure with fields of 'prefix__funcName__#'.
% If only 1 function, then just returns the cell array for that function,
% unless simplify2cell_bool=0.
% - simIDs: simIDs for each result value. Will be a mat vector or a struct of
% mat vectors with field names matching those of results variable.
% - filePaths: file paths for each result. Will be a cellstr vector or a struct of
% cellstr vectors with field names matching those of results variable.
% - funNames: function name for each result. Will be a cellstr vector or a struct of
% cellstr vectors with field names matching those of results variable.
% - prefixes: file prefixes for each result. Will be a cellstr vector or a struct of
% cellstr vectors with field names matching those of results variable.
%
% Author: Jason Sherfey, PhD <jssherfey@gmail.com>
% Updated: Erik Roberts
% Copyright (C) 2016 Jason Sherfey, Boston University, USA
% TODO:
% - This command breaks when "results" are figures e.g. outputs of dsPlot
% (dave, Feb 2017). Does not know how to "load" an image, nor does it
% recognize the image extensions. I wrote "dsImportPlots" as a way around this,
% but there might be better solutions for differentiating "plots" from other
% "results"
%% Check inputs
if ~nargin || isempty(src)
src = pwd;
end
if rem(length(varargin),2)~=0 % == odd
func = varargin{1};
varargin(1) = [];
funcVarBool = true;
else
funcVarBool = false;
end
options = dsCheckOptions(varargin,{...
'func',[],[],...
'import_scope','all',{'studyinfo','results','postHocResults','allResults','all','custom'},...
'simIDs',[],[],...
'as_cell',0,{0,1},... % guarantee output as cell array and leave mising data as empty cells
'add_prefix',0,{0,1},... %add prefix to output struct field names
'simplify2cell_bool',1,{0,1},... % whether to simplify struct to cell if only 1 result. used by gimbl-vis.
},false);
if ~funcVarBool
func = options.func;
end
if ~isempty(func)
% convert func to cell
switch class(func)
case 'cell'
% do nothing
case 'function_handle'
func = {func};
case 'char' % fn string
func = {func};
case 'double' % scalar or numeric vector
func = num2cell(func);
func = cellfun(@num2str, func,'Uni',0);
end
% convert function handles to strings
fhandInd = cellfun(@(x) isa(x,'function_handle'), func);
if any(fhandInd)
for k = find(fhandInd)
func{k} = func2str(func{k});
end
end
else
% instantiate outputs
simIDs = [];
filePaths = [];
funNames = [];
prefixes = [];
end
nFnInput = length(func);
% at this point, func should be empty cell or a cell array of strings
%% Find all Result files in scope
% goal: find all function files according to import_scope
% studyinfo
if any(strcmp(options.import_scope, {'studyinfo','all'}))
if ischar(src) && (isdir(src) || isfile(src)) % study directory
study_dir = src;
clear studyinfo
studyinfo.study_dir = study_dir;
end
if isstruct(src) && isfield(src,'study_dir')
% retrieve most up-to-date studyinfo structure from studyinfo.mat file
studyinfo = dsCheckStudyinfo(studyinfo.study_dir, varargin{:});
if exist('study_dir','var')
studyinfo.study_dir = src;
end
% get list of data_files from studyinfo
result_functions = studyinfo.simulations(1).result_functions;
matches = cellfun(@(x) strcmp((x), (func)),result_functions);
if ~any(matches)
wprintf('Didnt find match for result function parameter')
return
end
result_files = cellfun(@(x)x(matches),{studyinfo.simulations.result_files},'uni',0);
num_sims = length(studyinfo.simulations);
else
num_sims = nan;
result_files = {};
end % if isstruct(src) && isfield(src,'study_dir')
else
studyinfo.study_dir = src;
num_sims = nan;
result_files = {};
end
% results dir
if any(strcmp(options.import_scope, {'results','all','allResults'}))
thisDir = fullfile(studyinfo.study_dir, 'results');
files = lscell(fullfile(thisDir, '*.mat'), 0);
result_files = [result_files(:); files(:)];
% get num_sims
simInd = regexpi(files, 'sim(\d+)', 'tokens');
simInd = [simInd{:}];
if ~isempty(simInd)
simInd = [simInd{:}];
simInd = cellfun(@str2double, simInd);
num_sims = max(max(simInd), num_sims);
end
end
% postHocResults dir
if any(strcmp(options.import_scope, {'postHocResults','all','allResults'}))
thisDir = fullfile(studyinfo.study_dir, 'postHocResults');
files = lscell(fullfile(thisDir, '*.mat'), 0);
result_files = [result_files(:); files(:)];
% get num_sims
simInd = regexpi(files, 'sim(\d+)', 'tokens');
simInd = [simInd{:}];
if ~isempty(simInd)
simInd = [simInd{:}];
simInd = cellfun(@str2double, simInd);
num_sims = max(max(simInd), num_sims);
end
end
% custom dir
if strcmp(options.import_scope, 'custom')
thisDir = src;
files = lscell(fullfile(thisDir, '*.mat'), 0);
result_files = [result_files(:); files(:)];
% get num_sims
simInd = regexpi(files, 'sim(\d+)', 'tokens');
simInd = [simInd{:}];
if ~isempty(simInd)
simInd = [simInd{:}];
simInd = cellfun(@str2double, simInd);
num_sims = max(max(simInd), num_sims);
end
end
% now have num_sims and result_files, where num_sims >= length(result_files)
%% check for result_files
if isempty(result_files)
fprintf('No result files found. \n');
results = [];
return
elseif length(result_files) == 1
[~, filename] = fileparts(result_files{1});
if strcmp(filename, 'results_merged')
results = load(result_files{1});
return
end
end
%% Filter and Sort Results Files by Desired Function(s) and simID(s)
% goal: filter files by given function(s) or take all functions. If multiple
% functions, output needs to be different structure fields for each function.
% parse filenames
filePrefixSimIndFnName = cellfun(@filepartsNameExt, result_files, 'uni',0);
filePrefixSimIndFnName = regexpi(filePrefixSimIndFnName, '(.+)_sim(\d+)_analysis(\d+)_(.+).mat', 'tokens');
filePrefixSimIndFnName = [filePrefixSimIndFnName{:}];
filePrefixSimIndFnName = cat(1, filePrefixSimIndFnName{:});
% make fn_# names
nF = size(filePrefixSimIndFnName,1);
fnIdStr = cell(nF,1);
for iF = 1:nF
fnIdStr{iF} = [filePrefixSimIndFnName{iF,4}, '__', filePrefixSimIndFnName{iF,3}];
end
resultLabels = fnIdStr;
% check if overlapping fn/ind by appending sim id
tempNames = strcat(resultLabels, '_', filePrefixSimIndFnName(:,2));
if length(unique(tempNames)) < nF % then there is overlap
simInds = str2double(filePrefixSimIndFnName(:,2));
% pick first simID
testSimID = simInds(1);
% get all labels for that simID
overlappingResultLabels = fnIdStr(simInds == testSimID);
% get non unique labels
[~,ia] = unique(overlappingResultLabels);
[overlappingResultLabels{ia}] = deal({});
overlappingResultLabels = overlappingResultLabels(~cellfun(@isempty,overlappingResultLabels));
% get prefixes
allPrefixes = filePrefixSimIndFnName(:,1);
% make prefixes valid names
try
if strcmp(reportUI,'matlab')
allPrefixes = matlab.lang.makeValidName(allPrefixes);
end
end
for iLabel = 1:length(overlappingResultLabels)
thisLabel = overlappingResultLabels{iLabel};
thisInds = strcmp(resultLabels, thisLabel);
resultLabels(thisInds) = strcat(allPrefixes(thisInds), '_', resultLabels(thisInds));
end
% check if overlapping fn/ind by appending sim id
tempNames = strcat(resultLabels, '_', filePrefixSimIndFnName(:,2));
if length(unique(tempNames)) < nF % then there is overlap
warning('Overlapping output labels due to results with same function name and analysis index.')
end
clear allPrefixes tempNames nF thisInds thisLabel iLabel overlappingResultLabels testSimID simInds
end
% Unique fn vars
[uResultLabels, ia] = unique(resultLabels);
uFnIdStr = fnIdStr(ia);
resultFns = filePrefixSimIndFnName(ia,4);
fnPrefixes = filePrefixSimIndFnName(ia,1);
nResultFn = length(uResultLabels);
% filter for desired fn if given
if ~isempty(func)
%loop over fn
fnMatchInd = false(nResultFn,1);
for iF = 1:nFnInput
thisFn = func{iF};
fnMatchInd = fnMatchInd | strcmp(resultFns, thisFn);
end
else
fnMatchInd = true(nResultFn,1);
end
% fnMatchInd is true for all matching fn
% fnNameInd fnNameInd for matching fn
uResultLabels = uResultLabels(fnMatchInd,:);
uFnIdStr = uFnIdStr(fnMatchInd);
resultFns = resultFns(fnMatchInd);
nResultFn = length(uResultLabels);
if ~any(fnMatchInd)
wprintf('Did not find any files matching function inputs.')
return
end
% filter by sim ID
if ~isempty(options.simIDs)
simInd = regexpi(result_files, 'sim(\d+)', 'tokens');
simInd = [simInd{:}];
if ~isempty(simInd)
simInd = [simInd{:}];
simInd = cellfun(@str2double, simInd);
result_files = result_files( ismember(simInd, options.simIDs) ); % filter result_files for simID number
end
end
%% Load Result Files
% goal: load the results for the different functions and insert them into the
% structure from previous section, replacing paths with data
results = struct();
for iFn = 1:nResultFn
thisFunName = resultFns{iFn};
thisLabel = uResultLabels{iFn};
thisFnFiles = result_files( strcmp(resultLabels, thisLabel) );
nFiles = length(thisFnFiles);
% get simIDs from file paths
simInds = regexpi(thisFnFiles, 'sim(\d+)', 'tokens');
simInds = [simInds{:}];
simInds = [simInds{:}]; % note: removes missing entries
simInds = cellfun(@str2double, simInds);
% sort filepaths
[simInds, sortedFileOrder] = sort(simInds);
thisFnFiles = thisFnFiles(sortedFileOrder);
thisFnResults = cell(num_sims, 1);
for iFile = 1:nFiles
thisFilePath = thisFnFiles{iFile};
existBool = exist(thisFilePath,'file');
%check relative path for studyinfo paths since may be different system
if ~existBool && any(strcmp(options.import_scope, {'studyinfo','all'}))
[~,fname,fext] = fileparts2(thisFilePath);
thisFilePath = fullfile(studyinfo.study_dir,'results',[fname fext]);
end
% check if file exists
if existBool
thisFileContents = load(thisFilePath,'result');
% get simInd
simInd = simInds(iFile);
% store result
if ~options.as_cell && isstruct(thisFileContents.result) && isfield(thisFileContents.result,'time')
% dynasim type structure to store as struct array
thisFnResults(simInd) = thisFileContents.result;
else
if iscell(thisFileContents.result) && length(thisFileContents.result) == 1
% if single cell result, store as cell array cell
thisFnResults(simInd) = thisFileContents.result;
else
% if not single cell result, store inside cell array cell
thisFnResults(simInd) = {thisFileContents.result};
end
end
end
clear thisFilePath thisFileContents
end % file
% fix label for var/field name
try
if strcmp(reportUI,'matlab')
thisLabel = matlab.lang.makeValidName(thisLabel);
end
end
% add_prefix
thisPrefix = fnPrefixes{iFn};
if options.add_prefix
thisLabel = [thisPrefix '__' thisLabel];
end
% store sorted simIDs
if nResultFn == 1 && options.simplify2cell_bool
simIDs = simInds;
else
simIDs.(thisLabel) = simInds;
end
% store results file paths
if nargout > 2
if nResultFn == 1 && options.simplify2cell_bool
filePaths = thisFnFiles;
else
filePaths.(thisLabel) = thisFnFiles;
end
end
% store funNames
if nargout > 3
if nResultFn == 1 && options.simplify2cell_bool
funNames = thisFunName;
else
funNames.(thisLabel) = thisFunName;
end
end
% store prefixes
if nargout > 4
if nResultFn == 1 && options.simplify2cell_bool
prefixes = thisPrefix;
else
prefixes.(thisLabel) = thisPrefix;
end
end
% store results
results.(thisLabel) = thisFnResults;
clear thisFnResults
end % fn
% convert to inner struct fld if only 1 fn
if nResultFn == 1 && options.simplify2cell_bool
thisLabel = uResultLabels{1};
if options.add_prefix
thisLabel = [thisPrefix '__' thisLabel];
end
results = results.(thisLabel);
end
end % main fn