Code covered by the BSD License  

Highlights from
4D data exploration

image thumbnail
from 4D data exploration by Thomas Deneux
Navigate through 2D+time or 3D+time data through interactively linked cross-sections, time course pl

fn_4Dview(varargin)
function fn_4Dview(varargin)
% function fn_4Dview(option1,optionArgs1,...)
% function fn_4Dview(action,actionArgs)
%---
% displays spatial or temporal slices of multi-dimensional data
% by default: data is considered to be 3D, 4D or 5D (dimensions ordered as
% x-y-z-t-m) and is displayed as 3 spatial sections at a given time point
% use option flags for other display types
%
% type 'fn_4Dview demo' to see a demo
%
% Input:
% - option..        string - specify an option (see below)
% - optionArgs..    argument of option
% - action..        specific usage of fn_4Dview, does not create a new
%                   interface (see below)
%
% Options:
% DATA
% - 'data',data     array, by default, dimensions should be x - y - z - t - m 
%                   (m is used for 'multiple data')   
%                   [the 'data' flag can be omitted]
%                   [default: data=0 or zeros(appropriate size)]
% - 'options',{options}     some display types allow additional options like 
%                   'plot' -> same option possibilities as the Matlab plot function, 
%                   'quiver'...     
%                   [the 'options' flag can be omitted] [default: {}]
% TYPE OF DISPLAY
% - 'space'         data will be displayed spatially, at a given time point [default]
% - 'plot'          data will be plotted against time, at a given space point
% - '3d'            data is three-dimensional (dimensions are x - y - z - t - m)
%                   and will be spatially displayed as three cross sections
%                   [default]
% - '2d'            data is two-dimensional (dimensions are x - y - t - m)
%                   and will be spatially displayed as a single image 
% - '0d',tidx       no data required (implies plot options)
%                   then, give plot instructions inside a cell array (see
%                   'options')
% - 'quiver'        data is a 2D vector field, plus possibly 2D image
%                   (dimensions are x - y - t - [2 vector components +
%                   image component])
%                   this is only applicable for spatial display
% - 'mesh',mesh     data is one-dimensional (dimensions are i - t - m),
%                   where i refers to the ith vertex of a 3D mesh specified
%                   as a cell array or struct following the 'mesh' flag 
%                   {[3 x nv vertices],[3 x nt triangles]}
% - 'ind',indices   data is one-dimensional (dimensions are i - t - m),  
%                   indices is a 2D or 3D image which entails indices of
%                   the first data component (see example)
%                   this is only applicable for temporal display
% - 'timeslider',tidx   creates a slider control to move in time array tidx
% BEHAVIOR
% - 'active'        allows callbacks (e.g. selecting point with mouse) [default]
% - 'passive'       disallows callbacks
% - 'key',k         use this to have independent links between windows
%                   [default: all windows are linked using same key k=1]
% DATA TRANSFORM
% - 'mat',M         defines a spatial linear transformation between
%                   data and world coordinates [default = eye(4)] (pixel
%                   indices start at 1)
%                   can be a 4x4 or 3x4 matrix (rotation+translation) or
%                   3x3 matrix (rotation only) or 3x1 vector (translation
%                   only) or 1x3 vector (special: scaling, first pixel at
%                   [0 0 0])
% - 'tidx',tidx     defines sampling points time coordinates [default = 1:#times]
%                   it is necessary that they are equally spaced
% - 'heeginv',H     provides a matrix to multiply data with before using it
%                   this is only applicable for spatial mesh display
% - 'applyfun',fun  function handler or {function handler, additional arguments, ...}
%                   data will be transformed according to fun before every
%                   display - NOT IMPLEMENTED YET
% MISCELLANEOUS
% - 'in',handle     forces display in figure, axes or uicontrol specified by handle
%                   [the 'in' flag can be ommited] 
%                   [default: in active figure or axes]
% - 'fifth',f       if data has a 'multiple data' component (dimension m is
%                   non-singleton), specifies which one to use for spatial
%                   display [default: f=1]
% - 'clip',[m M]    specify a clipping for image display
% - 'xyzperm',[a b c] defines a permutation of the dimensions before
%                   visualizing the 3 cross-sections of the data
% - 'zfact',r       ratio between z and xy resolutions for 3D display
% - 'labels',{xlabel,ylabel,zlabel} axis labels (ylabel and zlabel can be omitted)
%
% Actions:
% - 'demo'                  run the demo
% - 'changexyz',key,xyz     update space coordinates for objects linked by the key       
% - 'changet',key,t         update time coordinate for objects linked by the key
% - 'register',hobj,updatefcn[,key]   register object to be affected by future
%                           changes of space and time coordinates
%                           uptdate function should follow the model of
%                           Update2D, Update3D, and so in this m-file
%------
% Thomas Deneux
% last modification: September 20th, 2007

% try to execute action
firstchar = (nargin>0 && ischar(varargin{1}));
thirdchar = (nargin>2 && ischar(varargin{3})) ;
if firstchar || thirdchar
    if firstchar, ind=1; else ind=3; end
    a = varargin{ind};
    switch lower(a)
        case 'demo'
            demo, return
        case 'changexyz'
            ChangeXYZ(varargin{ind+1:end}), return
        case 'changet'
            ChangeT(varargin{ind+1:end}), return
        case 'register'
            Register(varargin{ind+1:end}), return
    end
end

% otherwise create new object
Init(varargin{:});

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% INIT
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function Init(varargin)

if nargin==0, help fn_4Dview, return, end
indarg=1;

%-------
% Input
%-------

% Options
type='3d'; show='space'; data=0; options={};
Heeginv=[]; in=[]; key=1; mat=[]; fifth=1; tidx=[]; active=true; ind=[]; 
mesh=[]; clip={}; xyzperm = [1 2 3]; zfact = 1; scale = [1 1 1]'; labels = {'','',''};
while indarg<=nargin
    flag = varargin{indarg};
    if isfigoraxeshandle(flag), flag='in';
    elseif iscell(flag), flag='options';
    elseif isnumeric(flag), flag='data';
    elseif ischar(flag), indarg=indarg+1;
    else error('argument error')
    end
    flag = lower(flag);
    switch flag
        case 'data'
            data = varargin{indarg};
            indarg = indarg+1;
        case 'options'
            options = varargin{indarg};
            indarg = indarg+1;
        case 'space'
            show = 'space';
        case {'plot','time'}
            show = 'time';
        case '2dplot'
            type = '2d';
            show = 'time';
        case '0d'
            type = '0d';
            show = 'time';
            tidx = varargin{indarg};            
            indarg = indarg+1;
        case {'2d','3d'}
            type = flag;
        case 'quiver'
            type = 'quiver';
            data = varargin{indarg};
            indarg = indarg+1;
            nt = size(data,3);
            while indarg<=nargin && all(size(varargin{indarg})>1) % 2D or more array
                data2 = varargin{indarg};
                indarg = indarg+1;
                nt2 = size(data2,3);
                if nt2>1 && nt==1, data = repmat(data,[1 1 nt2]); end
                if nt>1 && nt2==1, data2 = repmat(data2,[1 1 nt]); end
                try data = cat(4,data,data2); catch error('quiver data: dimensions are incommensurate'), end
            end
        case 'mesh'
            if iscell(varargin{indarg})
                mesh = varargin{indarg};
                indarg = indarg+1;
            elseif isstruct(varargin{indarg})
                mesh = varargin{indarg};
                mesh = {mesh.vertices,mesh.faces};
                indarg = indarg+1;
            else
                mesh = {varargin{indarg},varargin{indarg+1}};
                indarg = indarg+2;
            end
            if size(mesh{1},1)~=3, mesh{1}=mesh{1}'; end
            if size(mesh{2},1)~=3, mesh{2}=mesh{2}'; end
            type = 'mesh';
        case 'ind'
            ind = varargin{indarg};
            indarg = indarg+1;
            type = 'indices';
        case 'timeslider'
            show = 'time';
            type = 'timeslider';
            tidx = varargin{indarg};
            indarg = indarg+1;
        case {'active','passive'}
            active =  strcmp(flag,'active');
        case 'key'
            key = varargin{indarg};
            indarg = indarg+1;
        case 'tidx'
            tidx = varargin{indarg};            
            indarg = indarg+1;
        case 'mat' 
            mat = varargin{indarg};
            indarg = indarg+1;
            switch num2str(size(mat),'%1i')
                case '44'
                case {'34','33'}
                    mat(4,4) = 1;
                case '31' % translation
                    mat = [eye(3) mat(:) ; 0 0 0 1];
                case '13' % special
                    mat = [diag(mat) -mat'; 0 0 0 1];
                otherwise
                    error('wrong size for rotation/translation matrix')
            end
            scale = [norm(mat(1:3,1)) norm(mat(1:3,2)) norm(mat(1:3,3))]';
        case 'heeginv'
            Heeginv = varargin{indarg};
            indarg = indarg+1;
        case 'in'
            in = varargin{indarg}; 
            indarg = indarg+1; 
        case {'5th','fifth'}
            fifth = varargin{indarg};
            indarg = indarg+1;
        case 'clip'
            clip = {varargin{indarg}};
            indarg = indarg+1;   
        case 'xyzperm'
            xyzperm = varargin{indarg};
            indarg = indarg+1;
        case 'zfact'
            zfact = varargin{indarg};
            indarg = indarg+1;
        case 'labels'
            labels = varargin{indarg};
            indarg = indarg+1;
            if ~iscell(labels), labels={labels}; end            
        otherwise
            error(['unknown option: ' flag])
    end
end

% Sizes
sizes = [];
switch type
    case '0d' 
        if strcmp(show,'space'), error('no spatial display of temporal only data allowed'); end
        % this variable data will be ignored... the only data goes in the options 
        data = [];
        if isempty(tidx), error('0d flag requires tidx to be defined'), end
    case '2d' 
        % data dimensions should be ordered as: x-y-t-m -> transform into x-t-m
        sizes = size(data); sizes(end+1:4)=1; sizes = [sizes(1:2) 1 sizes(3:4)];
        data = reshape(data,[prod(sizes(1:3)) sizes(4:5)]);
    case '3d'
        % data dimensions should be ordered as: x-y-z-t-m -> transform into x-t-m
        sizes = size(data); sizes(end+1:5)=1;
        data = reshape(data,[prod(sizes(1:3)) sizes(4:5)]);
    case 'quiver'
        if strcmp(show,'time'), error('no plot of quiver data allowed'); end
        % data dimensions should be ordered as: x-y-t-m and there may be several arguments
        % to be concatenated along 4th dimension -> transform into x-t-m
        sizes = size(data); sizes(end+1:4) = 1;
        if ~ismember(sizes(4),[2 3]), error('quiver: not enough data'), end
        data = reshape(data,[prod(sizes(1:2)) sizes(3:4)]);
    case 'mesh'
        % data dimensions should be ordered as: x-t-m
        sizes = size(data); 
        % no data given
        if all(sizes==1), data = zeros(size(mesh{1},2),1); sizes = size(data); end
        if isempty(Heeginv), scomp=sizes(1); else scomp=length(Heeginv*data(:,1)); end
        if scomp~=size(mesh{1},2), error('mesh: data dimension does not match number of vertices'), end
    case 'indices'
        if strcmp(show,'space'), error('no image or section display of indexed data allowed'); end
        % data dimensions should be ordered as: x-t-m
        nind = size(data,1);
        sizes = size(ind); sizes(end+1:3) = 1;
        maxind = max(ind(:));
        if maxind~=nind, disp('attention: maximum indice does not match data dimension'), end
    case 'timeslider'
        if strcmp(show,'space'), error('no image or section display of indexed data allowed'); end
        data = data(:)';
        sizes = length(data);
    otherwise 
        error programmation
end

%-------------------
% Other definitions
%-------------------

% Time indices
nt = size(data,2);
if ~isempty(tidx)
    if length(tidx)~=nt && ~ismember(type,{'0d','timeslider'}), error('length of tidx and data are incommensurate'), end
    if any(abs(diff(diff(tidx)))>1e-7), error('tidx: time points must be equally spaced'), end
else
    if strcmp(type,'timeslider')
        tidx = data;
    else
        tidx = 1:nt;
    end
end

% Axis labels
switch length(labels)
    case 1
        labels = labels([1 1 1]);
    case 2
        if strcmp(type,'3d'), error('Z label not defined'), end
        labels{3} = '';
    case 3
    otherwise
        error('wrong number of axis label numbers')
end        

% Figure / axes and object handle
if isempty(in) && strcmp(type,'timeslider')
    in = figure('visible','off');
end
if in
    if length(in)>1
        if all(ishandle(in)) && strcmp(get(in(1),'type'),'uicontrol') && strcmp(type,'timeslider')
            set(in(1),'style','slider') 
            set(in(2),'style','text') 
        end
    else
        if ishandle(in), df = get(in,'DeleteFcn'); try feval(df{:}), catch end, end
        if (in>0 && mod(in,1)==0) || strcmp(get(in,'type'),'figure')
            figure(in), clf reset
        elseif ishandle(in) && strcmp(get(in,'type'),'axes')
            axes(in), cla reset
        elseif ishandle(in) && strcmp(get(in,'type'),'uicontrol') && strcmp(type,'timeslider')
            set(in,'style','slider')
        else
            error('a scalar argument should be a figure or axes handle')
        end
    end
end
switch show
    case 'time'
        switch type
            case 'timeslider'
                % hobj can be figure or uicontrol
                if in, hobj = in(1); else hobj = gcf; end             
            otherwise
                hobj = gca; cla
        end
    case 'space'
        switch type
            case {'2d','quiver'}
                hobj = gca; cla
            case {'mesh','3d'}
                hobj = gcf; clf
        end
end
if strcmp(get(hobj,'type'),'figure'), hf = hobj; else hf = get(hobj,'parent'); end
% enlever les trucs chiants de la fenetre utilis�e
set(hf,'WindowButtonDownFcn','','ResizeFcn','','WindowButtonMotionFcn','','Tag','');
%f = fieldnames(getappdata(hf)); for i=1:length(f), rmappdata(hf,f{i}); end

%---------------------------
% Graphical object settings
%---------------------------

info = struct('key',key,'show',show,'type',type,'data',data,'active',active, ...
    'sizes',sizes,'nind',size(data,1),'nt',size(data,2),'ncomp',size(data,3), ...
    'ind',ind,'mesh',{mesh},'xyzt',[0 0 0 0], ...
    'mat',mat,'options',{options},'heeginv',Heeginv, ...
    'fifth',fifth,'tidx',tidx,'clip',{clip},'xyzperm',xyzperm,'zfact',zfact,'scale', scale, ...
    'labels',{labels}, ...
    'idx',[],'frame',[],'indselection',{{}},'timeselection',{{}});
setappdata(hobj,'fn_4Dview',info)

% Tag (protection against fn_imvalue and enables link search)
set(hobj,'Tag','fn_4Dview')
if strcmp(get(hobj,'type'),'axes'), set(hf,'Tag','used by fn_4Dview'), end

% Figure display and callbacks
switch show
    case 'time'
        switch type
            case 'timeslider'
                InitTimeSlider(in)
            otherwise
                InitPlotAxes(hobj)
        end
    case 'space'
        switch type
            case '2d'
                Init2DAxes(hobj)
            case 'quiver'
                InitQuiverAxes(hobj)
            case '3d'
                Init3DFigure(hobj)
            case 'mesh'
                InitMeshFigure(hobj)
        end
end

% Update cursor position (TODO: zoom)
set(hobj,'Tag','')
xyzt = GetXYZT(key);
Update(hobj,xyzt)
set(hobj,'Tag','fn_4Dview')

% % display
% set(hf,'Tag','fn_4Dview_passive') % prevents fn_imvalue callbacks
% if ~strcmp(action,'userdefine'), set(ha,'NextPlot','replace'), end
% Display(hobj)
% 
% % set tag, replacechildren, callback, oldaxis
% set(hobj,'Tag','fn_4Dview') % allow LinkedT or LinkedXYZ to look for information in hobj
% set(ha,'NextPlot','replacechildren')
% for h=ha, setappdata(h,'oldaxis',axis(h)), end
% if ~strcmp(action,'plot') && ~strcmp(action,'userdefine'), set(ha,'ButtonDownFcn',{@fn_4Dview,action}), end
% if strcmp(action,'activeplot'), set(get(ha,'parent'),'KeyPressFcn',{@fn_4Dview,[action '_key']}), end


%---------------------------------------------------------------------
function Init2DAxes(ha)

axes(ha)
set(get(ha,'Parent'),'DoubleBuffer','on')

info = getappdata(ha,'fn_4Dview');
s = info.sizes;

hi = imagesc(zeros(s(1),s(2))','Parent',ha,'hittest','off',info.clip{:});
ax = axis(ha);
hc(1) = line([0 0],[ax(3) ax(4)],'Color','white','hittest','off');
hc(2) = line([ax(1) ax(2)],[0 0],'Color','white','hittest','off');
ptext = ax([1 3]) - [0 0.03].*(ax([2 4])-ax([1 3]));
ht = text('Parent',ha,'Position',ptext,'String','text');

info.image = hi;
info.cross = hc;
info.text = ht;
info.ijk = [0 0 1];
info.frame = 0;
setappdata(ha,'oldaxis',axis(ha))
if info.active, set(ha,'ButtonDownFcn',{@CallBack,ha,'2d'}), end
setappdata(ha,'fn_4Dview',info)

%---------------------------------------------------------------------
function InitQuiverAxes(ha)

axes(ha)
set(get(ha,'Parent'),'DoubleBuffer','on')

info = getappdata(ha,'fn_4Dview');
s = info.sizes;
data = info.data;

if size(data,3)==3
    hi = imagesc(zeros(s(1),s(2))','hittest','off',info.clip{:});
    hold on
elseif size(data,3)==2
    hi = 0;
else
    error programming
end

hq = quiver(zeros(s(1),s(2)),zeros(s(1),s(2)),info.options{:});
if hi, hold off, end

ax = axis(ha);
hc(1) = line([0 0],[ax(3) ax(4)],'Color','white','hittest','off');
hc(2) = line([ax(1) ax(2)],[0 0],'Color','white','hittest','off');

info.image = hi;
info.quiver = hq;
info.cross = hc;
info.ijk = [0 0 1];
info.frame = 0;
setappdata(ha,'oldaxis',axis(ha))
if info.active, set(ha,'ButtonDownFcn',{@CallBack,ha,'2d'}), end
setappdata(ha,'fn_4Dview',info)
set(ha,'NextPlot','Add')

%---------------------------------------------------------------------
function Init3DFigure(hf)

figure(hf), clf
set(hf,'DoubleBuffer','on','KeyPressFcn','')

info = getappdata(hf,'fn_4Dview');
s = info.sizes;
s = s(info.xyzperm);
scale = info.scale(info.xyzperm);
ticks = {(0:s(1)-1)*scale(1),(0:s(2)-1)*scale(2),(0:s(3)-1)*scale(3)};
labels = info.labels(info.xyzperm);

ax1 = axes('Parent',hf); %,'Position',[xblnk yblnk+.1+yscale*sm(4) xscale*sm(1) yscale*sm(2)]);
ax2 = axes('Parent',hf); %,'Position',[xblnk+.1+xscale*sm(1) yblnk+.1+yscale*sm(4) xscale*sm(3) yscale*sm(2)]);
ax3 = axes('Parent',hf); %,'Position',[xblnk yblnk xscale*sm(1) yscale*sm(4)]);
ax4 = axes('Parent',hf); %,'Position',[xblnk+.1+xscale*sm(1) yblnk xscale*sm(3) yscale*sm(4)]);
set(ax4,'visible','off')
ha = [ax1 ax2 ax3];


%center = [xblnk+.1+xscale*sm(1)+xscale/2*sm(3) yblnk+yscale/2*sm(4)];
center = zeros(1,2);
if info.active
    % + control [position d�finie par un vecteur de taille 6 : position
    % du centre (en % de la taille de la fenetre), puis position / au centre
    % (en pixels), puis largeur et hauteur (en pixels)]
    hu(1) = uicontrol('parent',hf,'CallBack',{@MoveStep,hf,'controlbackward'},'String','|');
    setappdata(hu(1),'position',[center -10 30 20 20]);
    hu(2) = uicontrol('parent',hf,'CallBack',{@MoveStep,hf,'controlforward'},'String','|');
    setappdata(hu(2),'position',[center -10 -10 20 20]);
    hu(3) = uicontrol('parent',hf,'CallBack',{@MoveStep,hf,'controlleft'},'String','-');
    setappdata(hu(3),'position',[center -30 10 20 20]);
    hu(4) = uicontrol('parent',hf,'CallBack',{@MoveStep,hf,'controlright'},'String','-');
    setappdata(hu(4),'position',[center 10 10 20 20]);
    hu(5) = uicontrol('parent',hf,'CallBack',{@MoveStep,hf,'controldown'},'String','\');
    setappdata(hu(5),'position',[center 10 -10 20 20]);
    hu(6) = uicontrol('parent',hf,'CallBack',{@MoveStep,hf,'controlup'},'String','\');
    setappdata(hu(6),'position',[center -30 30 20 20]);
end

hi(1) = imagesc(ticks{1},ticks{2},zeros(s(2),s(1)),'parent',ax1,'hittest','off',info.clip{:}); 
hi(2) = imagesc(ticks{3},ticks{2},zeros(s(2),s(3)),'parent',ax2,'hittest','off',info.clip{:});
hi(3) = imagesc(ticks{1},ticks{3},zeros(s(3),s(1)),'parent',ax3,'hittest','off',info.clip{:});
set(ax1,'xdir','normal','ydir','normal')
set(ax2,'xdir','normal','ydir','normal')
set(ax3,'xdir','normal','ydir','reverse')
xlabel(ax1,labels{1}), ylabel(ax1,labels{2})
xlabel(ax2,labels{3}), ylabel(ax2,labels{2})
xlabel(ax3,labels{1}), ylabel(ax3,labels{3})
% pbaspect(ax1,[s(1) s(2) 1])
% pbaspect(ax2,[s(3)*zfact s(2) 1])
% pbaspect(ax3,[s(1) s(3)*zfact 1])

axis1 = axis(ha(1)); 
axis2 = axis(ha(2));
ax = [axis1 axis2(1:2)]; clear axis1 axis2
info.oldaxis = ax;
info.curaxis = ax;

x = 0; y = 0; z = 0;
hc(1) = line([x x],[ax(3) ax(4)],'Parent',ha(1),'Color','white','hittest','off');
hc(2) = line([ax(1) ax(2)],[y y],'Parent',ha(1),'Color','white','hittest','off');
hc(3) = line([z z],[ax(3) ax(4)],'Parent',ha(2),'Color','white','hittest','off');
hc(4) = line([ax(5) ax(6)],[y y],'Parent',ha(2),'Color','white','hittest','off');
hc(5) = line([x x],[ax(5) ax(6)],'Parent',ha(3),'Color','white','hittest','off');
hc(6) = line([ax(1) ax(2)],[z z],'Parent',ha(3),'Color','white','hittest','off');

ht(1) = uicontrol('parent',hf,'HorizontalAlignment','center','style','text');
setappdata(ht(1),'position',[center -80 -30 160 20]);
ht(2) = uicontrol('parent',hf,'HorizontalAlignment','center','style','text');
setappdata(ht(2),'position',[center -80 -50 160 20]);

info.axes = [ax1 ax2 ax3 ax4];
info.controls = hu;
info.images = hi;
info.cross = hc;
info.text = ht;
info.ijk = [0 0 0];
info.frame = 0;
setappdata(hf,'fn_4Dview',info)
set(hf,'ResizeFcn',{@Resize3DFigure})
Resize3DFigure(hf)
% set(hf,'ResizeFcn',{@UpdateControlPositions})
setappdata(ax1,'oldaxis',axis(ax1))
setappdata(ax2,'oldaxis',axis(ax2))
setappdata(ax3,'oldaxis',axis(ax3))
if info.active
    set(ax1,'ButtonDownFcn',{@CallBack,hf,'3d1'})
    set(ax2,'ButtonDownFcn',{@CallBack,hf,'3d2'})
    set(ax3,'ButtonDownFcn',{@CallBack,hf,'3d3'})
end

%---------------------------------------------------------------------
function Resize3DFigure(varargin)

hf = varargin{1};
info = getappdata(hf,'fn_4Dview');
fpos = get(hf,'position'); fpos = fpos([3 4]);

ax = info.curaxis;
zfact = info.zfact;
sm = [ax(2)-ax(1) ax(4)-ax(3) (ax(6)-ax(5))*zfact];
sa = sm(1)+sm(3); sb = sm(3)+sm(2);

fpos = get(hf,'position');
if fpos(3)/sa > fpos(4)/sb
    yscale = 0.8/sb;
    xscale = yscale*fpos(4)/fpos(3);
else
    xscale = 0.8/sa;
    yscale = xscale*fpos(3)/fpos(4);
end
xblnk = (.9-xscale*sa)/2;
yblnk = (.9-yscale*sb)/2;

set(info.axes(1),'Position',[xblnk yblnk+.1+yscale*sm(3) xscale*sm(1) yscale*sm(2)]);
set(info.axes(2),'Position',[xblnk+.1+xscale*sm(1) yblnk+.1+yscale*sm(3) xscale*sm(3) yscale*sm(2)]);
set(info.axes(3),'Position',[xblnk yblnk xscale*sm(1) yscale*sm(3)]);
set(info.axes(4),'Position',[xblnk+.1+xscale*sm(1) yblnk xscale*sm(3) yscale*sm(3)]);

center = [xblnk+.1+xscale*sm(1)+xscale/2*sm(3) yblnk+yscale/2*sm(3)];
UpdateControlPositions(hf,[],center)

%---------------------------------------------------------------------
function UpdateControlPositions(hf,dum,center)

s = get(hf,'Position'); 
s = s([3 4]);
hlist = findobj(hf,'type','uicontrol');
for hu = hlist(:)'
    position = getappdata(hu,'position');
    if nargin<3
        center = position([1 2]);
    end
    if isempty(position), continue, end
    set(hu,'position',[s(1)*center(1)+position(3) s(2)*center(2)+position(4) position(5:6)])
end

%---------------------------------------------------------------------
function DeleteControl(dum1,dum2,hu)

if nargin<3, hu=dum1; end
try delete(hu), catch end

%---------------------------------------------------------------------
function InitMeshFigure(hf)

figure(hf), clf
set(hf,'DoubleBuffer','on','KeyPressFcn','')

ha = gca;
info = getappdata(hf,'fn_4Dview');
mesh = info.mesh;
[vertex faces] = deal(mesh{:});
nvertex = size(vertex,2);
vcolor=ones(nvertex,1);

% TODO: changer pour rapidit�
ho=patch('Vertices',vertex','Faces',faces','FaceVertexCdata',vcolor,...
    'LineStyle','-',...
    'CDataMapping','scaled','FaceColor','flat',...
    'AmbientStrength', 0.6, 'FaceLighting', 'flat',...
    'HitTest','off');

cameratoolbar
camorbit(0,-90)
axis equal
% camorbit(-135,0)
% camlight
% camorbit(180,0)
% camlight

x = vertex(1,1); y = vertex(2,1); z = vertex(3,1);  
hc(1) = line(x,y,z,'Marker','*','Color','white','LineWidth',3,'HitTest','off');
hc(2) = line(x,y,z,'Marker','o','Color','red','LineWidth',3,'MarkerSize',12,'HitTest','off');

ht = uicontrol('style','text','position',[5 5 220 15],'string','text','HorizontalAlignment','left');

info.object = ho;
info.cross = hc;
info.text = ht;
info.idx = 1;
info.frame = 0;
set(ha,'ButtonDownFcn',{@CallBack,hf,'mesh'})
setappdata(hf,'fn_4Dview',info)

%---------------------------------------------------------------------
function InitPlotAxes(ha)

info = getappdata(ha,'fn_4Dview');
info.curaxis = [];
info.idx = 0;
set(ha,'ButtonDownFcn',{@CallBack,ha,'plot'},'NextPlot','ReplaceChildren')
hf = get(ha,'parent');
pos = get(ha,'position');
hu(1) = uicontrol('parent',hf,'CallBack',{@MoveStep,ha,'timeleft'},'String','<');
setappdata(hu(1),'position',[pos(1) pos(2) -20 -20 20 20]);
hu(2) = uicontrol('parent',hf,'CallBack',{@MoveStep,ha,'timeright'},'String','>');
setappdata(hu(2),'position',[pos(1)+pos(3) pos(2) 0 -20 20 20]);
hu(3) = uicontrol('parent',hf,'CallBack',{@MoveStep,ha,'windowleft'},'String','<<');
setappdata(hu(3),'position',[pos(1) pos(2) -40 -20 20 20]);
hu(4) = uicontrol('parent',hf,'CallBack',{@MoveStep,ha,'windowright'},'String','>>');
setappdata(hu(4),'position',[pos(1)+pos(3) pos(2) 20 -20 20 20]);
%set(hf,'KeyPressFcn',{@MoveStep,ha,'keyboard'}, ...
%    'ResizeFcn',{@UpdateControlPositions},'DoubleBuffer','on')
set(hf,'ResizeFcn',{@UpdateControlPositions},'DoubleBuffer','on')
set(ha,'DeleteFcn',{@DeleteControl,hu})
UpdateControlPositions(hf)
setappdata(ha,'fn_4Dview',info);

%---------------------------------------------------------------------
function InitTimeSlider(handle)
% handle can be figure or uicontrol

info = getappdata(handle(1),'fn_4Dview');
switch get(handle(1),'type')
    case 'figure'
        hf = handle;
        set(hf,'position',[800 500 300 45],'menubar','none', ...
            'name','Time control','visible','on')
        info.slider = uicontrol('style','slider','position',[5 5 290 20]);
        info.text = uicontrol('style','text','position',[100 30 100 10],'String','text');
        info.roundbutton = uicontrol('position',[230 27 20 15],'String','r', ...
            'CallBack',{@SliderCallback,handle});
    case 'uicontrol'
        info.slider = handle(1);
        if length(handle)>1
            info.text = handle(2);
        else
            info.text = uicontrol('style','text','visible','off');
        end
end

m = info.tidx(1); M = info.tidx(end); n = length(info.tidx);
set(info.slider,'Min',m,'Max',M,'SliderStep',[1/(n-1) 1/15],'Value',(m+M)/2, ...
    'CallBack',{@SliderCallback,handle(1)});

setappdata(handle(1),'fn_4Dview',info);

%---------------------------------------------------------------------
function Register(hobj,updatefcn,key)

if nargin<3, key=1; end

info = struct('key',key,'type','ext','xyzt',[0 0 0 0], ...
    'updatefcn',updatefcn);
    % 'show',[],'data',[],'active',[], ...
    % 'sizes',[],'nind',[],'nt',[],'ncomp',[], ...
    % 'ind',[],'mesh',[], ...
    % 'mat',[],'options',[],'heeginv',[], ...
    % 'fifth',[],'tidx',[],'clip',[],'xyzperm',[],'zfact',[],...
    % 'idx',[],'frame',[],'indselection',[],'timeselection',[]);
setappdata(hobj,'fn_4Dview',info)

set(hobj,'Tag','')
xyzt = GetXYZT(key);
Update(hobj,xyzt)
set(hobj,'Tag','fn_4Dview')


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% DISPLAY
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function Update(hobj,xyzt)

info = getappdata(hobj,'fn_4Dview');
if strcmp(info.type,'ext')
    feval(info.updatefcn,hobj,xyzt)
    return 
end
switch info.show
    case 'time'
        switch info.type
            case 'timeslider'
                UpdateTimeSlider(hobj,xyzt)
            otherwise
                UpdatePlot(hobj,xyzt)
        end
    case 'space'
        switch info.type
            case '2d'
                Update2D(hobj,xyzt)
            case '3d'
                try
                    Update3D(hobj,xyzt)
                catch % figure a �t� abim�e ? on tente de la reconstruire
                    Init3DFigure(hobj)
                    Update3D(hobj,xyzt)
                end
            case 'quiver'
                UpdateQuiver(hobj,xyzt)
            case 'mesh'
                try
                    UpdateMesh(hobj,xyzt)
                catch % figure a �t� abim�e ? on tente de la reconstruire
                    InitMeshFigure(hobj)
                    UpdateMesh(hobj,xyzt)
                end             
        end
end

%---------------------------------------------------------------------
function Update2D(hobj,xyzt)

info = getappdata(hobj,'fn_4Dview');
info.xyzt = xyzt;
oldframe = info.frame;
if isempty(info.timeselection)
    info.frame = World2Frame(hobj,xyzt);
else
    info.frame = -1;
end
[ind ijk] = World2Ind(hobj,xyzt);
oldijk = info.ijk;
info.ijk = ijk(:)';
setappdata(hobj,'fn_4Dview',info)

% redraw image if info.frame changed (info.frame = -1 if there is a current
% time points selection for averaging)
if info.frame~=oldframe 
    if isempty(info.timeselection)
        frames = info.frame;
    else
        frames = TSel2Frame(hobj,info.timeselection);
    end
    im = info.data(:,frames,info.fifth);
    im = mean(im,2);
    im = reshape(im,info.sizes(1:2));
    % make the image available in base workspace
    setappdata(hobj,'currentdisplay',im);
    if ~isempty(info.indselection)
        if isempty(info.clip)
            m = min(im(:));
            M = max(im(:));
        else
            m = info.clip{1}(1);
            M = info.clip{1}(2);
        end
        grey = max(0,min(1,(im(:)-m)/(M-m)));
        im = repmat(grey,1,3);
        
        colors = colorset;
        ncolors = size(colors,1);
        
        for i=1:length(info.indselection)        
            indsel = info.indselection{i};
        
            % l'image a l'interieur de la zone est 'filtree' en tenant
            % compte du code couleur
            for j=1:3
                im(indsel,j) = (2*im(indsel,j) + colors(mod(i-1,ncolors)+1,j))/3;
            end
        end
        
        im = reshape(im,[info.sizes(1:2) 3]);
    end
    set(info.image,'CData',permute(im,[2 1 3]))
end

% redraw cross
if any(info.ijk~=oldijk) 
    set(info.cross(1),'XData',ijk([1 1]))
    set(info.cross(2),'YData',ijk([2 2]))
end

% redraw value
rijk = min(max(round(ijk(1:2)),[1 1]'),info.sizes(1:2)');
im = get(info.image,'CData');
set(info.text,'String',['val(' num2str(rijk(1)) ',' num2str(rijk(2)) ')=' num2str(im(rijk(2),rijk(1)))])

%---------------------------------------------------------------------
function UpdateQuiver(hobj,xyzt)

sf = gcf;

info = getappdata(hobj,'fn_4Dview');
info.xyzt = xyzt;
oldframe = info.frame;
if isempty(info.timeselection)
    info.frame = World2Frame(hobj,xyzt);
else
    info.frame = -1;
end
[ind ijk] = World2Ind(hobj,xyzt);
info.ijk = ijk(:)';
setappdata(hobj,'fn_4Dview',info)

if info.frame~=oldframe
    if isempty(info.timeselection)
        frames = info.frame;
    else
        frames = TSel2Frame(hobj,info.timeselection);
    end
    if info.image
        im = reshape(info.data(:,info.frame,3),info.sizes(1:2));
        set(info.image,'CData',im')
    end
    
    delete(info.quiver)
    a = reshape(mean(info.data(:,frames,1),2),info.sizes(1:2));
    b = reshape(mean(info.data(:,frames,2),2),info.sizes(1:2));
    axes(hobj)
    info.quiver = quiver(a,b,info.options{:});
    set(info.quiver,'hittest','off')
    setappdata(hobj,'fn_4Dview',info)
end

set(info.cross(1),'XData',ijk([1 1]))
set(info.cross(2),'YData',ijk([2 2]))

figure(sf)

%---------------------------------------------------------------------
function Update3D(hobj,xyzt)

info = getappdata(hobj,'fn_4Dview');
info.xyzt = xyzt;
info.frame = World2Frame(hobj,xyzt);
[ind ijk] = World2Ind(hobj,xyzt);
info.ijk = ijk(:)';
setappdata(hobj,'fn_4Dview',info)
rijk = min(max(round(ijk),[1 1 1]'),info.sizes(1:3)');

frame = info.frame; fifth = info.fifth;
data = reshape(info.data,info.sizes);
xyzperm = info.xyzperm;
dimdisp = {xyzperm([1 2]),xyzperm([3 2]),xyzperm([1 3])};
for k=1:3
   subidx = mat2cell(rijk,[1 1 1],1);     % ex: subidx = {23,15,4}
   [subidx{dimdisp{k}}] = deal(':');      % ex: subidx = {':',15,':'}
   subdata = data(subidx{:},frame,fifth); % ex: subddata  = data(:,15,:,frame,fifth)
   im = squeeze(subdata);
   if diff(dimdisp{k})>0, im=im'; end
   set(info.images(k),'CData',im)
end

ijkperm = ijk(xyzperm);
scale = info.scale(xyzperm);
pt = (ijkperm-1).*scale;
set(info.cross(1),'XData',pt([1 1]))
set(info.cross(2),'YData',pt([2 2]))
set(info.cross(3),'XData',pt([3 3]))
set(info.cross(4),'YData',pt([2 2]))
set(info.cross(5),'XData',pt([1 1]))
set(info.cross(6),'YData',pt([3 3]))

set(info.text(1),'String', ...
    ['val(' num2str(rijk(1)) ',' num2str(rijk(2)) ',' num2str(rijk(3)) ')=' ...
        num2str(data(rijk(1),rijk(2),rijk(3),frame,fifth))])
set(info.text(2),'String', ...
    ['xyzt = ' num2str(xyzt(1:3),'%.1f ')])

%---------------------------------------------------------------------
function UpdateMesh(hf,xyzt)

info = getappdata(hf,'fn_4Dview');
oldxyzt = info.xyzt;
info.xyzt = xyzt(:)';
oldframe = info.frame;
if isempty(info.timeselection)
    info.frame = World2Frame(hf,xyzt);
else
    info.frame = -1;
end
setappdata(hf,'fn_4Dview',info)

if info.frame~=oldframe
    if isempty(info.timeselection)
        frames = info.frame;
    else
        frames = TSel2Frame(hf,info.timeselection);
    end
    vcolor = mean(info.data(:,frames,info.fifth),2);
    if ~isempty(info.indselection)
        m = min(vcolor(:));
        M = max(vcolor(:));
        grey = max(0,min(1,(vcolor(:)-m)/(M-m)));
        vcolor = repmat(grey,1,3);
        
        colors = colorset;
        ncolors = size(colors,1);
        
        for i=1:length(info.indselection)        
            indsel = info.indselection{i};
        
            % l'image a l'interieur de la zone est 'filtree' en tenant
            % compte du code couleur
            for j=1:3
                vcolor(indsel,j) = (2*vcolor(indsel,j) + colors(mod(i-1,ncolors)+1,j))/3;
            end
        end
    end
    if ~isempty(info.heeginv), vcolor = info.heeginv * vcolor; end
    faces = info.mesh{2}; 
    nf = size(faces,2); nc = size(vcolor,2);
    fcolor = vcolor(faces,:);
    fcolor = shiftdim(mean(reshape(fcolor,[3 nf nc])),1);
    set(info.object,'FaceVertexCdata',fcolor)
end

if isempty(info.indselection)
    vertex = info.mesh{1};
    if any(info.xyzt(1:3)~=oldxyzt(1:3)) || info.idx==0
        idx = World2Ind(hf,xyzt);
        info.idx = idx;
        x = vertex(1,idx); y = vertex(2,idx); z = vertex(3,idx);
        set(info.cross,'XData',x,'YData',y,'ZData',z)
    else
        x = vertex(1,info.idx); y = vertex(2,info.idx); z = vertex(3,info.idx);
    end

    % TODO : speed efficient ?
    vcolor = get(info.object,'FaceVertexCdata'); val = vcolor(info.idx);
    set(info.text,'string',['val(' num2str(x,'%.1f') ',' num2str(y,'%.1f') ',' num2str(z,'%.1f') ...
        ') = ' num2str(val)])
else
    set(info.text,'string','')
end

setappdata(hf,'fn_4Dview',info)

    
%---------------------------------------------------------------------
function UpdatePlot(ha,xyzt)

info = getappdata(ha,'fn_4Dview');
info.xyzt = xyzt(:)';
info.frame = World2Frame(ha,xyzt);
oldidx = info.idx;
if isempty(info.indselection)
    info.idx = World2Ind(ha,xyzt);
else
    info.idx = -1;
end
t = xyzt(4);

% info.idx represents which indice will be plotted; it is set to -1 if
% there is a current selection of indices
% if info.idx did not change since the last plot update, no need to update
% now
% TODO: changer la maniere dont on force a redessiner ou non - ce info.idx
% est trop obscur, avec comportement diffent selon qu'on a une selection ou
% non
if info.idx~=oldidx
    if strcmp(info.type,'0d') % TODO : pas de cas sp�cial Od
        plot(info.options{:},'parent',ha,'HitTest','off')
        if isempty(info.curaxis)
            axis(ha,'tight')
        else
            ax = axis(ha);
            axis(ha,[info.curaxis([1 2]) ax([3 4])])
        end
    else
        % TODO : remove try end blocks
        if isempty(info.curaxis)
            f = 1:length(info.tidx);
        else
            f = find(info.tidx>info.curaxis(1));
            if isempty(f), f=length(info.tidx); elseif f(1)>1; f=[f(1)-1 f(:)']; end
            f = f(info.tidx(f)<info.curaxis(2));
            if isempty(f), f=1; elseif f(end)<length(info.tidx); f=[f f(end)+1]; end
        end
        try
            if isempty(info.indselection)
                indselection = {info.idx};
            else
                indselection = info.indselection;
            end
            datatoplot = zeros(length(f),length(indselection)*info.ncomp);
            for i=1:length(indselection)
                indsel = indselection{i};
                if isempty(indsel), continue, end
                if isempty(info.heeginv)
                    subdata = info.data(indsel,f,:);
                else
                    % TODO: verifier ca
                    subdata = (info.heeginv(indsel,:)*info.data(:,f,1))';
                end 
                subdata = mean(subdata,1); % average over selected indices
                subdata = shiftdim(subdata,1); % subdata is now time x component
                datatoplot(:,1+(i-1)*info.ncomp:i*info.ncomp) = subdata;
            end
            % make the data available in base workspace
            setappdata(ha,'currentdisplay',datatoplot);
            h=plot(info.tidx(f),datatoplot,info.options{:},'parent',ha,'HitTest','off');
            colors = colorset; ncolors = size(colors,1);
            if length(info.indselection)>1
                for i=1:length(indselection)
                    set(h(1+(i-1)*info.ncomp:i*info.ncomp),'color',colors(mod(i-1,ncolors)+1,:))
                end
            end
        catch
            plot(info.tidx(f),info.tidx(f),'parent',ha,'HitTest','off')
        end
        % TODO: mieux g�r�r l'axis
        axis(ha,'tight'), ax=axis(ha); 
        if ~isempty(info.curaxis)
            axis(ha,[info.curaxis(1:2) ax(3:4)])
        end
    end
else
    delete(info.line)
end

ax = axis(ha);
info.line = line([t t],ax([3 4]),'color','black','parent',ha,'HitTest','off');

% show the time selection for averaging images
if ~isempty(info.timeselection)
    for k=1:length(info.timeselection)
        tsel = info.timeselection{k};
        switch length(tsel)
            case 1
                info.line(2) = line(tsel,ax(3),'marker','.','color','r','parent',ha,'HitTest','off');
            case 2
                info.line(2) = line(tsel,[ax(3) ax(3)],'linestyle','-','linewidth',5,'color','r','parent',ha,'HitTest','off');
        end
    end
end

setappdata(ha,'fn_4Dview',info) 

%---------------------------------------------------------------------
function UpdateTimeSlider(handle,xyzt)

info = getappdata(handle,'fn_4Dview');
info.xyzt = xyzt(:)';
setappdata(handle,'fn_4Dview',info) 

val = xyzt(4);
val = min(get(info.slider,'Max'),max(get(info.slider,'Min'),val));
set(info.text,'String',[num2str(floor(val*100)/100)])
set(info.slider,'Value',val);


%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% CALLBACK
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function CallBack(dum1,dum2,hobj,action)

oldselectiontype = getappdata(hobj,'oldselectiontype');
selectiontype = get(gcf,'selectiontype');
setappdata(hobj,'oldselectiontype',selectiontype)

info = getappdata(hobj,'fn_4Dview');
point =  get(gca,'CurrentPoint');

switch selectiontype
    case 'normal'                               % CHANGE VIEW AND/OR MOVE CURSOR
        switch action
            case {'2d','3d1','3d2','3d3','plot'}
                rect = fn_mouse('rect-');
                if all(rect(3:4))               % zoom in
                    switch action
                        case 'plot'
                            ChangeAxisPlot(hobj,[rect(1)+[0 rect(3)] rect(2)+[0 rect(4)]])
                        case '2d'
                            ChangeAxis(info.key,'2d', ...
                                [rect(1)+[0 rect(3)] rect(2)+[0 rect(4)]],getappdata(gca,'oldaxis'))
                        case {'3d1','3d2','3d3'}
                            newax = info.curaxis;
                            ax = [rect(1)+[0 rect(3)] rect(2)+[0 rect(4)]];
                            switch action
                                case '3d1'
                                    newax([1 2 3 4]) = ax;
                                case '3d2'
                                    newax([5 6 3 4]) = ax;
                                case '3d3'
                                    newax([1 2 5 6]) = ax;
                            end
                            ChangeAxis(info.key,'3d',newax,info.oldaxis)
                    end
                else    
                    if strcmp(action,'plot')    % move t
                        t = point(1,1);
                        ChangeT(info.key,t);
                    else                        % move cross
                        ijk = info.ijk(:);
                        % since ticks are scaled, it is necessary to
                        % bring back point coordinates to indices coordinates
                        scale = info.scale(info.xyzperm);                      
                        xyzperm = info.xyzperm;
                        switch action
                            case '2d'
                                pt = [point(1,1) point(1,2)];
                                ijk([1 2]) = pt;
                            case '3d1'
                                pt = [point(1,1)/scale(1)+1 point(1,2)/scale(2)+1];
                                ijk(xyzperm([1 2])) = pt;
                            case '3d2'
                                pt = [point(1,1)/scale(3)+1 point(1,2)/scale(2)+1];
                                ijk(xyzperm([3 2])) = pt;
                            case '3d3'
                               pt = [point(1,1)/scale(1)+1 point(1,2)/scale(3)+1];
                               ijk(xyzperm([1 3])) = pt;
                        end
                        xyz = IJK2XYZ(info.mat,ijk);
                        ChangeXYZ(info.key,xyz)
                    end
                end
            case 'mesh'                         % move cursor
                [idx ijk] = fn_meshselectpoint(info.mesh,point);
                xyz = IJK2XYZ(info.mat,ijk);
                ChangeXYZ(info.key,xyz)
        end
    case {'extend','alt'}                       % POINTS SELECTION
        switch action
            case '2d'
                %if info.mat, disp(['Points selection not possible for ' ...
                %    'transformed images (with a ''mat'' field)']), return, end
                poly = fn_mouse('poly-');
                if size(poly,1)>1               % select points in a region
                    mask = poly2mask(poly(:,2),poly(:,1),info.sizes(1),info.sizes(2));
                else                            % select one point
                    mask = zeros(info.sizes(1:2));
                    mask(round(poly(1)),round(poly(2))) = 1;
                end
                indsel = find(mask);
                indselection = info.indselection;
                switch selectiontype
                    case 'extend'
                        if isempty(indselection), indselection={[]}; end
                        indselection{end} = union(indselection{end},indsel);
                    case 'alt'
                        indselection{end+1} = indsel;
                end
                ChangeIndsel(info,indselection)
            case 'quiver'
                disp('Points selection not implemented for quiver yet')
                return
            case 'mesh'
                [indsel ijk] = fn_meshselectpoint(info.mesh,point);
                indselection = info.indselection;
                switch selectiontype
                    case 'extend'
                        if isempty(indselection), indselection={[]}; end
                        indselection{end} = union(indselection{end},indsel);
                    case 'alt'
                        indselection{end+1} = indsel;
                end
                ChangeIndsel(info,indselection)
            case {'3d1','3d2','3d3'}
                disp('Points selection not implemented for 3D images yet')
                return
             case 'plot'
                if ~ismember(info.type,{'2d','quiver','mesh'})
                    disp('Points selection implemented for 2D, quiver and mesh only')
                    return
                end
                rect = fn_mouse('rect-');
                if rect(3)==0                   % select a time point
                    tseg = rect(1);
                else                            % select a temporal segment
                    tseg = [rect(1) rect(1)+rect(3)];
                end
                timeselection = info.timeselection;
                switch selectiontype
                    case 'extend'
                        timeselection{end+1} = tseg;
                    case 'alt'
                        timeselection = {tseg};
                end
                ChangeTimesel(info.key,timeselection)
           otherwise
                return
        end
    case 'open'
        switch oldselectiontype
            case 'normal'                       % zoom out
                switch action
                    case 'plot'
                        ChangeAxisPlot(hobj,[])
                    case '2d'
                        ChangeAxis(info.key,action, ...
                            getappdata(gca,'oldaxis'),getappdata(gca,'oldaxis'))
                    case {'3d1','3d2','3d3'}
                        ChangeAxis(info.key,'3d',info.oldaxis,info.oldaxis)
                end
            case 'extend'                       % unselect current set of points/instants
                switch action
                    case {'2d','mesh'}
                        ChangeIndsel(info,{info.indselection{1:end-1}})
                    case 'plot'
                        ChangeTimesel(info.key,[])
                end
            case 'alt'                          % unselect all points/instants
                switch action
                    case {'2d','mesh'}
                        ChangeIndsel(info,{})
                    case 'plot'
                        ChangeTimesel(info.key,{})
                end
        end
        
end 


%---------------------------------------------------------------------
function MoveStep(dum1,dum2,hobj,action)
% appele par les controles dans la vue sections (3D)
% ou en appuyant sur le clavier dans un plot

info = getappdata(hobj,'fn_4Dview');
s = info.sizes; xyzt = info.xyzt(:);
if strcmp(action,'keyboard')  
    keypressed = double(get(gcf,'CurrentCharacter'));
    if isempty(keypressed), return, end
    switch keypressed
        case 28
            frame = max(info.frame-1,1);
        case 29
            frame = min(info.frame+1,length(info.tidx));
        otherwise
            return
    end
    t = info.tidx(frame);
    ChangeT(info.key,t);
elseif findstr(action,'control')
    ijk = info.ijk(:); 
    xyzperm = info.xyzperm;
    switch action
        case 'controlup'
            ijk(xyzperm(3)) = max(1,ijk(xyzperm(3))-1);
        case 'controldown'
            ijk(xyzperm(3)) = min(s(3),ijk(xyzperm(3))+1);
        case 'controlleft'
            ijk(xyzperm(1)) = max(1,ijk(xyzperm(1))-1);
        case 'controlright'
            ijk(xyzperm(1)) = min(s(1),ijk(xyzperm(1))+1);
        case 'controlforward'
            ijk(xyzperm(2)) = max(1,ijk(xyzperm(2))-1); 
        case 'controlbackward'
            ijk(xyzperm(2)) = min(s(2),ijk(xyzperm(2))+1);
    end 
    xyz = IJK2XYZ(info.mat,ijk);
    ChangeXYZ(info.key,xyz);
elseif findstr(action,'window')
    curaxis = info.curaxis;
    if isempty(curaxis), return, end   
    taille = curaxis(2)-curaxis(1);
    switch action
        case 'windowleft'
            step = -taille;
        case 'windowright'
            step = taille;
    end
    ChangeAxisPlot(hobj,curaxis+step)
    ChangeT(info.key,xyzt(4)+step)
elseif findstr(action,'time')
    switch action
        case 'timeleft'
            frame = max(info.frame-1,1);
        case 'timeright'
            frame = min(info.frame+1,length(info.tidx));
    end
    t = info.tidx(frame);
    ChangeT(info.key,t);
else
    error programming
end

%---------------------------------------------------------------------
function SliderCallback(hu,dum2,handle)
% appel� par le 'time slider'

info = getappdata(handle,'fn_4Dview');
switch get(hu,'style')
    case 'slider'
        t = get(hu,'Value');
        ChangeT(info.key,t);
    case 'pushbutton'
        t = get(info.slider,'Value');
        frame = T2Frame(handle,t);
        ChangeT(info.key,info.tidx(frame));
end



%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% LINKS
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function hlist = Linked(key)

hlist0 = findobj('Tag','fn_4Dview');
hlist = [];
for hobj = hlist0'
    info = getappdata(hobj,'fn_4Dview');
    if info.key == key
        hlist(end+1) = hobj;
    end
end

%---------------------------------------------------------------------
function hlist = Similar(infobase)

hlist0 = findobj('Tag','fn_4Dview');
hlist = [];
for hobj = hlist0'
    info = getappdata(hobj,'fn_4Dview');
    switch info.type
        case {'ext','timeslider'}
            continue
        case '3d'
            nspdim=3;
        otherwise
            nspdim=2; 
    end
    if info.key == infobase.key && all(info.sizes(1:nspdim) == infobase.sizes(1:nspdim))
        hlist(end+1) = hobj;
    end
end

%---------------------------------------------------------------------
function xyzt = GetXYZT(key)

hlist = Linked(key);
if ~isempty(hlist)
    info = getappdata(hlist(1),'fn_4Dview');
    xyzt = info.xyzt;
else
    xyzt = [1 1 1 1];
end

%---------------------------------------------------------------------
function ChangeAxis(key,action,ax,oldaxis)
% action = '2d', '3d1', '3d2' ou '3d3'
% oldaxis est l'axe d'origine de l'endroit o� on a cliqu�
% 'key', 'action' et 'oldaxis' permettent d'identifier dans quelles fenetres on
% va r�aliser le changement de zoom avec 'ax'

hlist = Linked(key);

% effectuer le changement de zoom dans toutes les fenetres liees et de
% memes dimensions
for hobj = hlist
    info = getappdata(hobj,'fn_4Dview');
    if strcmp(info.type,'ext') || strcmp(info.show,'time'), continue, end
    if strcmp(action,'2d') && ismember(info.type,{'2d','quiver'})
        if all(getappdata(hobj,'oldaxis')==oldaxis)
            axis(hobj,ax)
            if strcmp(info.type,'2d')
                ptext = ax([1 3]) - [0 0.03].*(ax([2 4])-ax([1 3]));
                set(info.text,'Position',ptext)
            end
        end
    elseif strcmp(action,'3d') && strcmp(info.type,'3d')
        axes = info.axes;
        axis(axes(1),ax([1 2 3 4]))
        axis(axes(2),ax([5 6 3 4]))
        axis(axes(3),ax([1 2 5 6]))
        info.curaxis = ax;
        setappdata(hobj,'fn_4Dview',info)
        Resize3DFigure(hobj)
    end
end

%---------------------------------------------------------------------
function ChangeAxisPlot(ha,ax)
% l�g�rement diff�rent de ChangeAxis: pour les repr�sentations temporelles :
% pas de liens entre les fenetres
% soit curaxis vaut [] et alors on fait axis tight
% soit curaxis est fix� et impose un nouvel intervalle temporel (abscisses)

% effectuer le changement de zoom dans les bonnes fenetres
info = getappdata(ha,'fn_4Dview');
if ~strcmp(info.show,'time') || strcmp(info.type,'timeslider'), error programming, end
emptyflag = isempty(ax);
if emptyflag, info.curaxis = []; else info.curaxis = ax([1 2]); end
info.idx = 0; setappdata(ha,'fn_4Dview',info)  
Update(ha,info.xyzt) % idx = 0 permet de forcer 'redraw cross' - TODO: forcer autrement 
%useless?%if ~isempty(ax), curax=axis(ha); axis(ha,[ax(1:2) curax(3:4)]), end

%---------------------------------------------------------------------
function ChangeXYZ(key,xyz)

hlist = Linked(key);
xyzt = GetXYZT(key);
xyzt(1:3) = xyz(:);

for hobj = hlist
    Update(hobj,xyzt)
end

%---------------------------------------------------------------------
function ChangeT(key,t)

hlist = Linked(key);
xyzt = GetXYZT(key);
xyzt(4) = t;


for hobj = hlist
    Update(hobj,xyzt)
end

%---------------------------------------------------------------------
function ChangeIndsel(info,indselection)

hlist = Similar(info);

for hobj = hlist
    info = getappdata(hobj,'fn_4Dview');
    info.indselection = indselection;
    info.frame = 0; info.idx = 0; setappdata(hobj,'fn_4Dview',info)  
    Update(hobj,info.xyzt) % idx,frame = 0 permet de forcer 'redraw image' - TODO: forcer autrement
end

%---------------------------------------------------------------------
function ChangeTimesel(key,timeselection)

hlist = Linked(key);

for hobj = hlist
    info = getappdata(hobj,'fn_4Dview');
    info.timeselection = timeselection;
    %info.idx = 0; 
    info.frame = 0; setappdata(hobj,'fn_4Dview',info)  
    Update(hobj,info.xyzt) % idx,frame = 0 permet de forcer 'redraw image/replot' - TODO: forcer autrement
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% CONVERSION XYZT <-> INDICES
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function [ind, ijk] = World2Ind(hobj,xyzt)

info = getappdata(hobj,'fn_4Dview');
xyz = xyzt(1:3); xyz = xyz(:);
if ~isempty(info.mat)
    ijk = eye(3,4)*inv(info.mat)*[xyz ; 1];
else
    ijk = xyz;
end
switch info.type
    case '0d'
        ind = 1;
    case {'2d','quiver','3d','indices'}
        s = info.sizes;  
        rijk = min(max(round(ijk),[1 1 1]'),s(1:3)');
        ind = rijk(1) + s(1)*(rijk(2)-1+s(2)*(rijk(3)-1));
        if strcmp(info.type,'indices'), ind = info.ind(ind); end
    case 'mesh'
        ind = fn_meshclosestpoint(info.mesh{1},xyz);
    otherwise
        error programming 
end

%---------------------------------------------------------------------
function frame = World2Frame(hobj,xyzt)

info = getappdata(hobj,'fn_4Dview');
tidx = info.tidx;
if length(tidx)==1
    frame = 1;
else
    t = xyzt(4); 
    frame = 1 + (length(tidx)-1)*(t-tidx(1))/(tidx(end)-tidx(1));
    frame = min(max(round(frame),1),length(tidx));
end

%---------------------------------------------------------------------
function frame = T2Frame(hobj,t)
% t can be a vector

info = getappdata(hobj,'fn_4Dview');
tidx = info.tidx;
frame = 1 + (length(tidx)-1)*(t-tidx(1))/(tidx(end)-tidx(1));
frame = min(max(round(frame),1),length(tidx));

%---------------------------------------------------------------------
function frame = TSel2Frame(hobj,tselection)
% tselection is a cell array of instants or time segments

frame = [];
for k=1:length(tselection)
    tseg = tselection{k};
    switch length(tseg)
        case 1
            fseg = T2Frame(hobj,tseg);
        case 2
            fseg = T2Frame(hobj,tseg(1)):T2Frame(hobj,tseg(2));
    end
    frame = union(frame,fseg);
end

%---------------------------------------------------------------------
function xyz = IJK2XYZ(mat,ijk)

if ~isempty(mat), xyz = eye(3,4)*mat*[ijk(:) ; 1]; else xyz = ijk(:); end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% OTHER TOOLS
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function b = isfigoraxeshandle(h)

b = (length(h)==1) && (ishandle(h) || (isnumeric(h) && h>0 && ~mod(h,1)));

function colors = colorset

colors = [0 0 1 ; 0 .5 0 ; 1 0 0 ; 0 .75 .75 ; .75 0 .75 ; .75 .75 0 ; 0 0 0 ; ...
    .75 .35 0 ; 0 1 0 ; 0 .3 0 ; .3 0 0 ; .3 0 .5];

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% DEMO
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function demo
% show a demo

% make data: - 3D volume using flow, 
% - 2 time evolutions from original volume to a symetrical one 
% - surface using isosurface
% - second time evolution interpolated on that surface
% - 2D spatial gradient of first vertical slice

disp('%This is a demonstration of parts of the ''fn_4Dview'' functionalities.')
disp('%Attention, it will close all windows : press CTRL+C if you want to stop.')
fprintf('\npress any key to continue...'), pause, fprintf(repmat('\b',1,28))
close all

disp('%let''s create a 3D volume:')
disp('[x y z v] = flow;')
[x y z v] = flow;
disp('%v is the data, x,y,z are the spatial coordinates of this data')
disp('%each of v,x,y,z is a 3D 25x50x25 array')
disp('%let''s explore this data!')
fprintf('\npress any key to continue...'), pause, fprintf(repmat('\b',1,28))

disp('fn_4Dview(v)')
fn_4Dview(v)
disp('%try clicking and dragging in the graphs, and clicking on the buttons')
fprintf('\npress any key to continue...'), pause, fprintf(repmat('\b',1,28))

disp('%now let''s create data with a temporal dimension:')
disp('vevol = zeros(25,50,25,11);')     
disp('for i=1:11')
disp('  [xx yy zz w] = flow(25+2*(i-1));')
disp('  vevol(:,:,:,i)=w(i:end-(i-1),1+2*(i-1):end-2*(i-1),i:end-(i-1));')
disp('end')
vevol = zeros(25,50,25,11);
for i=1:11
  [xx yy zz w] = flow(25+2*(i-1));
  vevol(:,:,:,i)=w(i:end-(i-1),1+2*(i-1):end-2*(i-1),i:end-(i-1));
end
disp('%vevol is a 4D 25x50x25x11 array')
disp('%let''s explore it...')
fprintf('\npress any key to continue...'), pause, fprintf(repmat('\b',1,28))

disp('%spatial visualization')
disp('fn_4Dview(''in'',2,vevol)')
disp('%temporal visualization')
disp('fn_4Dview(''in'',3,vevol,''plot'')')
fn_4Dview('in',2,vevol)
fn_4Dview('in',3,vevol,'plot')
disp('%try clicking and dragging in the plot graph, and clicking on the buttons')
fprintf('\npress any key to continue...'), pause, fprintf(repmat('\b',1,28))

disp('%a single array can even contain several data of the same dimensions,')
disp('%by adding a fifth dimension:')
disp('vevol(:,:,:,:,2) = vevol + rand(size(vevol));')
vevol(:,:,:,:,2) = vevol + rand(size(vevol));
disp('%now, vevol is a 5D 25x50x25x11x2 array')
disp('%let''s explore it')
fprintf('\npress any key to continue...'), pause, fprintf(repmat('\b',1,28))

disp('%spatial visualization, first data component')
disp('fn_4Dview(''in'',2,vevol,''fifth'',1)')
disp('%spatial visualization, second data component')
disp('fn_4Dview(''in'',3,vevol,''fifth'',2)')
disp('%temporal visualization, both components together')
disp('fn_4Dview(''in'',4,vevol,''plot'')')
fn_4Dview('in',2,vevol,'fifth',1)
fn_4Dview('in',3,vevol,'fifth',2)
fn_4Dview('in',4,vevol,'plot')
fprintf('\nnext examples will be given with less explanation...')
fprintf('\npress any key to continue...'), pause, fprintf(repmat('\b',1,81))

disp('close all')
disp('s = isosurface(x, y, z, v, -3);       % s is a mesh, 3104 vertices, 6020 triangles')
disp('sv = interp3(x,y,z,v,s.vertices(:,1),s.vertices(:,2),s.vertices(:,3));')
disp('sv2 = sv + rand(size(sv));')
disp('nv = length(s.vertices);')
disp('svevol = zeros(nv,nt,1);              % svevol dim� are vertices-t-m')
disp('for i=1:nt')
disp('  svevol(:,i)=(nt-i)/(nt-1)*sv+sin((i-1)*sv2/4);')
disp('end')
disp('va = squeeze(vevol(:,:,1,:,1));       % va is a slice, dim� are y-x-t-m')
disp('[vax vay] = gradient(va);')
disp('% 2D image and quiver')
disp('figure(1)')
disp('fn_4Dview(''in'',subplot(2,2,1),''2D'',va(:,:,1))')
disp('fn_4Dview(''in'',subplot(2,2,2),''quiver'',vax(:,:,1),vay(:,:,1),va(:,:,1))')
disp('% quiver + time displayed spatially, 2D + time displayed temporally, time control')
disp('fn_4Dview(''in'',subplot(2,2,3),''quiver'',vax,vay,va)')
disp('fn_4Dview(''in'',subplot(2,2,4),''2D'',''plot'',va)')
disp('fn_4Dview(''in'',2,''timeslider'',1:nt)')
disp('% 3D + time + multiple data, spatial and temporal display')
disp('M = [diag([.2 .25 .25])*[0 1 0; 1 0 0; 0 0 1] [-.1; -3.25; -3.25] ; 0 0 0 1];')
disp('fn_4Dview(''key'',2,''in'',3,''mat'',M,''fifth'',1,vevol)')
disp('fn_4Dview(''key'',2,''in'',4,''mat'',M,''fifth'',2,vevol)')
disp('figure(5)')
disp('fn_4Dview(''key'',2,''in'',subplot(2,1,1),''mat'',M,''plot'',vevol)')
disp('% mesh spatial and temporal display')
disp('fn_4Dview(''key'',2,''in'',6,''mesh'',s,svevol)')
disp('figure(5)')
disp('fn_4Dview(''key'',2,''in'',subplot(2,1,2),''mesh'',s,''plot'',svevol)')

close all
s = isosurface(x, y, z, v, -3);       % s is a mesh, 3104 vertices, 6020 triangles
sv = interp3(x,y,z,v,s.vertices(:,1),s.vertices(:,2),s.vertices(:,3));
sv2 = sv + rand(size(sv));
nv = length(s.vertices);
svevol = zeros(nv,11,1);              % svevol dim� are vertices-t-m
for i=1:11
  svevol(:,i)=(11-i)/(11-1)*sv+sin((i-1)*sv2/4);
end
va = squeeze(vevol(:,:,1,:,1));       % va is a slice, dim� are y-x-t-m
[vax vay] = gradient(va);
% 2D image and quiver
figure(1)
fn_4Dview('in',subplot(2,2,1),'2D',va(:,:,1))
fn_4Dview('in',subplot(2,2,2),'quiver',vax(:,:,1),vay(:,:,1),va(:,:,1))
% quiver + time displayed spatially, 2D + time displayed temporally, time control
fn_4Dview('in',subplot(2,2,3),'quiver',vax,vay,va)
fn_4Dview('in',subplot(2,2,4),'2D','plot',va)
fn_4Dview('in',2,'timeslider',1:11)
% 3D + time + multiple data, spatial and temporal display
M = [diag([.2 .25 .25])*[0 1 0; 1 0 0; 0 0 1] [-.1; -3.25; -3.25] ; 0 0 0 1];
fn_4Dview('key',2,'in',3,'mat',M,'fifth',1,vevol)
fn_4Dview('key',2,'in',4,'mat',M,'fifth',2,vevol)
figure(5)
fn_4Dview('key',2,'in',subplot(2,1,1),'mat',M,'plot',vevol)
% mesh spatial and temporal display
fn_4Dview('key',2,'in',6,'mesh',s,svevol)
figure(5)
fn_4Dview('key',2,'in',subplot(2,1,2),'mesh',s,'plot',svevol)
fprintf('\npress any key to finish...'), pause, fprintf(repmat('\b',1,26)), close all

Contact us at files@mathworks.com