function cosmo_disp(x,varargin)
% display the input as a string representation
%
% cosmo_disp(x,opt)
%
% Inputs:
% x any type of data element (can be a dataset struct)
% opt Optional struct with fields
% .threshold If the number of values in an array along a dimension
% exceeds threshold, then an array is showed in summary
% style along that dimension. Default: 5
% .edgeitems When an array is shown in summary style, edgeitems sets
% the number of items at the beginning and end of the
% array to be shown (separated by '...' in rows and by ':'
% in columns).
% Default: 3
% .precision Numeric precision, indicating number of decimals after
% the floating point
% Default: 3
% .strlen Maximal string lenght, if a string is longer the
% beginning and end are shown separated by ' ... '.
% Default: 20
% .depth Maximum recursion depth
% Default: 6
%
% Side effect: Calling this function caused the representation of x
% to be displayed.
%
%
% Examples:
% % display a complicated data structure
% x=struct();
% x.a_cell={[],{'cell within cell',[1 2; 3 4]}};
% x.small_matrix=[10 11 12; 13 14 15];
% x.big_matrix=reshape(1:200,10,20);
% x.huge=2^40;
% x.tiny=2^-40;
% x.a_string='hello world';
% x.a_struct.another_struct.name='me';
% x.a_struct.another_struct.func=@abs;
% cosmo_disp(x);
% %|| .a_cell
% %|| { [ ] { 'cell within cell' [ 1 2
% %|| 3 4 ] } }
% %|| .small_matrix
% %|| [ 10 11 12
% %|| 13 14 15 ]
% %|| .big_matrix
% %|| [ 1 11 21 ... 171 181 191
% %|| 2 12 22 ... 172 182 192
% %|| 3 13 23 ... 173 183 193
% %|| : : : : : :
% %|| 8 18 28 ... 178 188 198
% %|| 9 19 29 ... 179 189 199
% %|| 10 20 30 ... 180 190 200 ]@10x20
% %|| .huge
% %|| [ 1.1e+12 ]
% %|| .tiny
% %|| [ 9.09e-13 ]
% %|| .a_string
% %|| 'hello world'
% %|| .a_struct
% %|| .another_struct
% %|| .name
% %|| 'me'
% %|| .func
% %|| @abs
% %
% cosmo_disp(x.a_cell)
% %|| { [ ] { 'cell within cell' [ 1 2
% %|| 3 4 ] } }
% cosmo_disp(x.a_cell{2}{2})
% %|| [ 1 2
% %|| 3 4 ]
%
% % make a cell in a cell in a cell in a cell ...
% m={{{{{{{{{{{'hello'}}}}}}}}}}};
% cosmo_disp(m)
% %|| { { { { { { <cell> } } } } } }
% cosmo_disp(m,'depth',8)
% %|| { { { { { { { { <cell> } } } } } } } }
% cosmo_disp(m,'depth',Inf)
% %|| { { { { { { { { { { { 'hello' } } } } } } } } } } }
%
% % illustrate 'threshold' and 'edgeitems' arguments
% cosmo_disp(num2cell('a':'k'))
% %|| { 'a' 'b' 'c' ... 'i' 'j' 'k' }@1x11
% cosmo_disp(num2cell('a':'k'),'threshold',Inf)
% %|| { 'a' 'b' 'c' 'd' 'e' 'f' 'g' 'h' 'i' 'j' 'k' }
% cosmo_disp(num2cell('a':'k'),'edgeitems',2)
% %|| { 'a' 'b' ... 'j' 'k' }@1x11
%
% % illustrate 'precision' argument
% cosmo_disp(pi*[1 2],'precision',1);
% %|| [ 3 6 ]
% cosmo_disp(pi*[1 2],'precision',3);
% %|| [ 3.14 6.28 ]
% cosmo_disp(pi*[1 2],'precision',5);
% %|| [ 3.1416 6.2832 ]
% cosmo_disp(pi*[1 2],'precision',7);
% %|| [ 3.141593 6.283185 ]
%
% % illustrate n-dimensional arrays
% x=zeros([2 2 1 2 3]);
% x(:)=2*(1:numel(x));
% cosmo_disp(x)
% %|| <double>@2x2x1x2x3
% %|| (:,:,1,1,1) = [ 2 6
% %|| 4 8 ]
% %|| (:,:,1,2,1) = [ 10 14
% %|| 12 16 ]
% %|| (:,:,1,1,2) = [ 18 22
% %|| 20 24 ]
% %|| (:,:,1,2,2) = [ 26 30
% %|| 28 32 ]
% %|| (:,:,1,1,3) = [ 34 38
% %|| 36 40 ]
% %|| (:,:,1,2,3) = [ 42 46
% %|| 44 48 ]
% cosmo_disp(reshape(char(65:72),[2 2 2]))
% %|| <char>@2x2x2
% %|| (:,:,1) = 'AC
% %|| BD'
% %|| (:,:,2) = 'EG
% %|| FH'
% cosmo_disp(zeros([2 3 5 7 0 2]))
% %|| <double>@2x3x5x7x0x2 (empty)
%
% % illustrate non-singleton structs
% x=struct('x',{1 2; 3 4});
% cosmo_disp(x);
% %|| <struct>@2x2
% %|| (1,1).x
% %|| [ 1 ]
% %|| (2,1).x
% %|| [ 3 ]
% %|| (1,2).x
% %|| [ 2 ]
% %|| (2,2).x
% %|| [ 4 ]
% x3=cat(3,x,x,x);
% cosmo_disp(x3);
% %|| <struct>@2x2x3
% %|| (1,1,1).x
% %|| [ 1 ]
% %|| (2,1,1).x
% %|| [ 3 ]
% %|| (1,2,1).x
% %|| [ 2 ]
% %|| : :
% %|| (2,1,3).x
% %|| [ 3 ]
% %|| (1,2,3).x
% %|| [ 2 ]
% %|| (2,2,3).x
% %|| [ 4 ]
%
% Notes:
% - Unlike the builtin 'disp' function, this function shows the contents
% of the input using recursion. For example if a cell contains a
% struct, then the contents of that struct is shown as well
% - A use case is displaying dataset structs
%
% # For CoSMoMVPA's copyright information and license terms, #
% # see the COPYING file distributed with CoSMoMVPA. #
defaults.threshold=5; % max #items before triggering summary style
defaults.edgeitems=3; % #items at edges in summary style
defaults.precision=3; % show floats with 3 decimals
defaults.strlen=20; % insert '...' with strings more than 20 chars
defaults.depth=6; % maximal depth
defaults.show_size=false;% whether to show size of matrices
opt=cosmo_structjoin(defaults,varargin);
% get string representation of x
s=disp_helper(x, opt);
% print string representation of x
disp(s);
function s=disp_helper(x, opt)
% general helper function to get a string representation. Unlike the
% main function this function returns a string, which makes it suitable
% for recursion
depth=opt.depth;
if depth<=0
s=any2summary_str(x,opt);
return
end
opt.depth=depth-1;
if ~has_size(x)
s=any2summary_str(x,opt);
else
s=nd_any2str(x,opt);
end
function s=any2summary_str(x,unused)
if has_size(x)
sz=size(x);
else
sz=[1 1];
end
s=surround_with(true,'<',class(x),'>',sz);
function tf=has_size(x)
% helper, because some classes have no 'size'
try
size(x);
tf=true;
catch
tf=false;
end
function y=nd_any2str(x,opt)
if isstruct(x)
if numel(x)<=1
y=struct2str(x,opt);
else
y=multi_any2string(x,0,opt);
end
elseif numel(size(x))==2
if iscell(x)
y=cell2str(x,opt);
elseif isnumeric(x) || islogical(x)
y=matrix2str(x,opt);
elseif ischar(x)
y=string2str(x,opt);
elseif isa(x, 'function_handle')
y=function_handle2str(x,opt);
else
y=any2summary_str(x,opt);
end
else
y=multi_any2string(x,2,opt);
end
function y=multi_any2string(x,ndim_post,opt)
sz=size(x);
sz_rest=sz((ndim_post+1):end);
ndim_rest=numel(sz_rest);
n_rest=prod(sz_rest);
parts=cell(ndim_rest,1);
for k=1:ndim_rest
parts{k}=num2cell(1:sz_rest(k));
end
p=cosmo_cartprod(parts);
if ndim_post>0
xflat=reshape(x,[sz(1:ndim_post) n_rest]);
else
xflat=x(:);
end
[pre,post]=get_mx_idxs(xflat,opt.edgeitems,opt.threshold,ndim_post+1);
header=any2summary_str(x,opt);
s_pre=nd_any2str_helper(xflat,p, pre,opt);
if isempty(post)
s_post={'',''};
s_dots={'',''};
else
s_post=nd_any2str_helper(xflat,p,post,opt);
s_dots=cell(1,2);
for k=1:2
szs=cellfun(@(x)size(x,2),s_post(:,k));
pos=round(max(szs)/2);
s_dots{k}=[spaces(1,pos) ':'];
end
end
s_all=cat(1,s_pre,s_dots,s_post);
y=strcat_({header;strcat_(s_all)});
function s=nd_any2str_helper(xflat, p, idxs, opt)
n_rest=numel(idxs);
s=cell(n_rest,2);
sz=size(xflat);
npre=numel(sz)-1;
for k=1:n_rest
idx=idxs(k);
switch npre
case 1
% for struct
v=xflat(idx);
idx_prefix='';
idx_postfix='';
case 2
% anything else
v=xflat(:,:,idx);
idx_prefix=':,:,';
idx_postfix=' = ';
otherwise
assert(false);
end
v_str=disp_helper(v,opt);
idx_str=sprintf(',%d',p(idx,:));
s{k,1}=sprintf(' (%s%s)%s',idx_prefix,idx_str(2:end),idx_postfix);
s{k,2}=v_str;
end
function y=strcat_(xs)
if isempty(xs)
y='';
return
end
% all elements in xs are char
[nr,nc]=size(xs);
ys=cell(1,nc);
% sizes for each element
width_per_col=max_element_size(xs,2);
height_per_row=max_element_size(xs,1);
for k=1:nc
xcol=cell(nr,1);
width=width_per_col(k);
row_pos=0;
for j=1:nr
height=height_per_row(j);
if height==0
continue;
end
x=xs{j,k};
if ~ischar(x) && isempty(x)
x='';
end
sx=size(x);
to_add=[height width]-sx;
% pad with spaces
row_pos=row_pos+1;
xcol{row_pos}=[[x spaces(sx(1),to_add(2))];...
spaces(to_add(1), width)];
end
ys{k}=char(xcol{1:row_pos});
end
y=[ys{:}];
function m=max_element_size(x,dim)
% faster than cellfun
n=numel(x);
sizes=zeros(size(x));
for k=1:n
sizes(k)=size(x{k},dim);
end
m=max(sizes,[],3-dim);
function y=spaces(nx,ny)
% faster than repmat(' ',nx,ny)
if nx>0 && ny>0
y(nx,ny)=' ';
y(:)=' ';
else
if nx<0
nx=0;
end
if ny<0
ny=0;
end
y=reshape('',nx,ny);
end
function y=struct2str(x,opt)
if numel(x)==0
show_size=opt.show_size;
y=[surround_with(show_size,'', 'struct', '', size(x)) ' (empty)'];
return;
end
assert(numel(x)==1)
fns=fieldnames(x);
n=numel(fns);
if n==0
show_size=opt.show_size;
y=[surround_with(show_size,'', 'struct', '', size(x)) ' (empty)'];
else
r=cell(n*2,1);
for k=1:n
fn=fns{k};
r{k*2-1}=['.' fn];
d=disp_helper(x.(fn),opt);
r{k*2}=[spaces(size(d,1),2) d];
end
y=strcat_(r);
end
function s=function_handle2str(x,opt)
s_with_quotes=string2str(func2str(x),opt);
s=['@' s_with_quotes(2:(end-1))];
function s=string2str(x, opt)
if ~ischar(x), error('expected a char'); end
[nrows,ncols]=size(x);
if ncols>opt.strlen
infix=' ... ';
h=floor((opt.strlen-numel(infix))/2);
x=strcat_({x(:,1:h), infix ,x(:,ncols+((1-h):0))});
end
quote='''';
pre=quote;
post=[spaces(nrows-1,1);quote];
s=strcat_({pre,x,post});
function s=cell2str(x, opt)
% display a cell
edgeitems=opt.edgeitems;
threshold=opt.threshold;
% get indices of rows and columns to show
[r_pre, r_post]=get_mx_idxs(x, edgeitems, threshold, 1);
[c_pre, c_post]=get_mx_idxs(x, edgeitems, threshold, 2);
part_idxs={{r_pre, r_post}, {c_pre, c_post}};
nrows=numel([r_pre r_post])+~isempty(r_post);
ncols=numel([c_pre c_post])+~isempty(c_post);
sinfix=cell(nrows,ncols*2+1);
for k=1:(ncols-1)
sinfix{1,k*2+2}=' ';
end
cpos=1;
for cpart=1:2
col_idxs=part_idxs{2}{cpart};
nc=numel(col_idxs);
rpos=0;
for rpart=1:2
row_idxs=part_idxs{1}{rpart};
nr=numel(row_idxs);
if nr==0
continue
end
for ci=1:nc
col_idx=col_idxs(ci);
trgc=cpos+ci*2;
for ri=1:nr
row_idx=row_idxs(ri);
sinfix{rpos+ri,trgc}=disp_helper(x{row_idx,...
col_idx},opt);
if cpart==2 && ci==1 && nc>0
sinfix{rpos+ri,cpos+ci*2-1}=' ... ';
end
end
if rpart==2
max_length=max(cellfun(@numel,sinfix(:,trgc)));
pre_spaces=spaces(1,floor(max_length/2-1));
sinfix{rpos,cpos+ci*2}=[pre_spaces ':'];
end
end
rpos=rpos+nr+1;
end
cpos=cpos+nc*2;
end
show_size=opt.show_size || ~isempty(r_post) || ~isempty(c_post);
s=surround_with(show_size,'{ ', strcat_(sinfix), ' }', size(x));
function pre_infix_post=surround_with(show_size, pre, infix, post, matrix_sz)
% surround infix by pre and post, doing
n=prod(matrix_sz);
if show_size && n~=1
size_str=sprintf('x%d',matrix_sz);
size_str(1)='@';
if n==0
size_str=[size_str ' (empty)'];
end
else
size_str='';
end
post=strcat_({spaces(size(infix,1)-1,1); [post size_str]});
pre_infix_post=strcat_({pre, infix, post});
function s=matrix2str(x,opt)
if isempty(x)
show_size=opt.show_size;
s=surround_with(show_size,'[',' ',']',size(x));
return
end
% display a matrix
edgeitems=opt.edgeitems;
threshold=opt.threshold;
precision=opt.precision;
% get indices of rows and columns to show
[r_pre, r_post]=get_mx_idxs(x, edgeitems, threshold, 1);
[c_pre, c_post]=get_mx_idxs(x, edgeitems, threshold, 2);
% data to be shown
y=x([r_pre r_post],[c_pre c_post]);
% convert to string
s=num2str(y,precision);
% number of characters in first and second dimension
[nc_row,nc_col]=size(s);
% see where each column is a space; that's a potential split point
sp_col=sum(s==' ',1)==nc_row;
% col_index has value k for characters in the k-th column, else zero
col_index=zeros(1,nc_col);
col_count=1;
in_num=true;
for k=1:nc_col
if in_num
if sp_col(k)
col_count=col_count+1;
in_num=false;
else
col_index(k)=col_count;
end
elseif ~sp_col(k)
in_num=true;
col_index(k)=col_count;
end
end
% deal with rows
row_blocks=cell(3,1);
if isempty(r_post)
row_blocks{1,1}=s;
else
% insert ':' for each column
line=spaces(1,nc_col);
for k=1:max(col_index)
idxs=find(col_index==k);
median_pos=round(mean(idxs));
line(median_pos)=':';
end
row_blocks{1}=s(1:edgeitems,:);
row_blocks{2}=line;
row_blocks{3}=s(edgeitems+(1:edgeitems),:);
end
% deal with columns
row_and_col_blocks=cell(3,3);
for row=1:3
if isempty(c_post)
row_and_col_blocks{row}=row_blocks{row};
else
% insert ' ... ' halfway each row
pre_end=find(col_index==edgeitems,1,'last')+1;
post_start=find(col_index==(edgeitems+1),1,'first')-1;
r=row_blocks{row,1};
if isempty(r)
continue;
end
row_and_col_blocks{row,1}=r(:,1:pre_end);
if row~=2
row_and_col_blocks{row,2}=repmat(' ... ',size(r,1),1);
end
row_and_col_blocks{row,3}=r(:,post_start:end);
end
end
show_size=opt.show_size || ~isempty(r_post) || ~isempty(c_post);
s=surround_with(show_size,'[ ',strcat_(row_and_col_blocks),' ]',...
size(x));
function [pre,post]=get_mx_idxs(x, edgeitems, threshold, dim)
% returns the first and last indices for showing an array along
% dimension dim. If size(x,dim)<2*edgeitems, then pre has all the
% indices, otherwise pre and post have the first and last edgeitems
% indices, respectively
n=size(x,dim);
if n>max(threshold,2*edgeitems) % properly deal with Inf values
pre=1:edgeitems;
post=n-edgeitems+(1:edgeitems);
else
pre=1:n;
post=[];
end