function varargout = imlook3d(varargin)
% IMLOOK3D M-file for imlook3d.fig
%      IMLOOK3D, by itself, creates a new IMLOOK3D or raises the existing
%      singleton*.
%
%      H = IMLOOK3D returns the handle to a new IMLOOK3D or the handle to
%      the existing singleton*.
%
%      IMLOOK3D('CALLBACK',hObject,eventData,handles,...) calls the local
%      function named CALLBACK in IMLOOK3D.M with the given input arguments.
%
%      IMLOOK3D('Property','Value',...) creates a new IMLOOK3D or raises the
%      existing singleton*.  Starting from the left, property value pairs are
%      applied to the GUI before imlook3d_OpeningFunction gets called.  An
%      unrecognized property name or invalid value makes property application
%      stop.  All inputs are passed to imlook3d_OpeningFcn via varargin.
%
%      *See GUI Options on GUIDE's Tools menu.  Choose "GUI allows only one
%      instance to run (singleton)".
%
% EXAMPLE:
%  Let us first load the mri image in Matlab using
%  >load mri;
%  and then run imlook3d
%  >imlook3d(squeeze(D)); % squeeze removes singleton dimensions
%
% Descriptions of Menus
%
% ImFile:
%
% -READ FROM WORKSPACE: Reads or displays images existing in the
%  workspace(base).
%
% -READ A STACK (DISK): Reads (displays)slices of images into a 3D array creating
%  a 3D image. It reads them from the disk. A file browser pups up and
%  allows the user to pick up the first file in the series. It reads all
%  the files in the directory that has similar names except a suffix
%  changing for each file.
%
%  For example: slice01.tif, slice01.tif,...,slice0N.tif
%
% -READ DICOM SERIES:
%  Reads/displays series of DICOM images into a 3D array. A file browser
%  pups up and allows users to select the first file in the series.
%  It reads all the files in the directory that has similar names
%  except a suffix changing for each file.

% -SAVE SLICE AS TIFF:
%  Saves the current slice as a tiff image.

% -SAVE INTO WORKSPACE: This saves displayed 3D image into workspace as
%  a 3D variable. This may be useful to save the 3D image that is
%  read by "read a stack (disk)".
%
% ImTools:
%
% -PIXVAL: Allows looking at pixel intensities.
% -HISTOGRAM: Displays the histogram of the current image (slice).
% -PROFILE: Allows plotting intensity profiles across the image in an
%  interactive manner.
% -ADJUST INTENSITY RANGE: Allows adjusting the intensity range for
%  the current slice.
% -GLOBAL INTENSITY WINDOWING:
%  If not checked,not global, the intensity limits are set to [min(currentslice)
%  max(currentslice)]
%
% VIEW MENU:
%
%  This pup-up menu allows users to change the view of the image
%  from transverse to sagittal or coronal views. It assumes that
%  original image is in transverse mode.
%

% Copyright(c) Omer Demirkaya
% Last Modified by GUIDE v2.5 22-May-2007 21:27:00
% Begin initialization code - DO NOT EDIT

% This entire code or part of it could be modified and used if the due
% credit to the original author is given.
%------------------------------------------------------------------

gui_Singleton = 0;
gui_State = struct('gui_Name',       mfilename, ...
    'gui_Singleton',  gui_Singleton, ...
    'gui_OpeningFcn', @imlook3d_OpeningFcn, ...
    'gui_OutputFcn',  @imlook3d_OutputFcn, ...
    'gui_LayoutFcn',  [] , ...
    'gui_Callback',   []);
if nargin & isstr(varargin{1})
    gui_State.gui_Callback = str2func(varargin{1});
end
if isempty(varargin)
    disp('There was no image to display');
    disp('USAGE: >>imlook3d(img) or use ImFile menu to load an image');
    varargin{1,1} = zeros(32,32);
end

if nargout
    [varargout{1:nargout}] = gui_mainfcn(gui_State, varargin{:});
else
    gui_mainfcn(gui_State, varargin{:});
end
% End initialization code - DO NOT EDIT

% --- Executes just before imlook3d is made visible.
function imlook3d_OpeningFcn(hObject, eventdata, handles, varargin)
% Choose default command line output for imlook3d
if isempty(varargin{:})
    return;
end
handles.output = hObject;
Std.Interruptible = 'off';
Std.BusyAction = 'queue';
Ax       = Std;
Ax.Units = 'Pixels';
Ax.ydir  = 'reverse';
Ax.XLim  = [.5 128.5];
Ax.YLim  = [.5 128.5];
Ax.XTick = [];
Ax.YTick = [];
Ax.CLimMode = 'auto';

set(handles.axes1,Ax);

Img = Std;Img.CData = [];Img.Xdata = [];Img.Ydata = [];
Img.CDataMapping = 'Scaled';
Img.Erasemode = 'none';
handles.image = Img;
inpargs = varargin{:};
[r,c,z] = size(inpargs);
handles.image.CData     = inpargs;
handles.imSize          = [r,c,z];
handles.viewtype        = 1;
handles.imageModality   = [];
handles.hText           = [];
handles.global_windowing= 0;
handles.SliceThickness  = [];
handles.imageInfo       = [];
handles.currentSliceNumber = 1;
handle.image.SliceSensitivityFactor =[];

handles.oimage = handles.image;
cimg = handles.image.CData(:,:,1);
% Create image object and set the properties
handles.ImgObject = image(Img,'Parent',handles.axes1);
set(handles.SliceNumEdit,'String',1);
set(handles.figure1,'Colormap',gray,'Units','Pixels');
% Set the properties of the axes
cclim =[min(cimg(:)) max(cimg(:))];
if (cclim(2)>0 & cclim(2)-cclim(1) ~= 0)
    set(handles.axes1,'CLim',cclim,...
        'PlotBoxAspectRatio',[c r 1]);
end
set(handles.axes1,'XLim',[1 c],'Ylim',[1 r],...
    'PlotBoxAspectRatio',[c r 1]);
set(handles.ImgObject,'Xdata',[1 c],'Ydata',[1 r],'CData',cimg);
drawnow
% Set the slider's value and step size
if z > 1
    turnDisplayButtons(handles,'on')
    set(handles.SliceNumSlider,'Min',1,'Max',z,'Value',...
        1,'SliderStep',[1.0/double(z-1) 1.0/double(z-1)]);
end
% Define the line and associate it with the context menu
set(handles.ImgObject,'UIContextMenu',handles.ImMaps);
% Update handles structure
%--------------------------------------------------------------------------
%% Set appdata for the inntensity adjustment GUI
setappdata(0  ,  'hMainGui'    , gcf);
hMainGui = getappdata(0, 'hMainGui');
maxINT = max(max(max(handles.image.CData)));
minINT = min(min(min(handles.image.CData)));
intensityParam = struct('lowerLimit',cclim(1),'upperLimit',cclim(2),...
    'maxIntensity', maxINT, 'minIntensity',minINT);
handles.intensityParam = intensityParam;
setappdata(hMainGui,  'intensityParam'  ,intensityParam);
%% Read and display logo image
% ----------------------------------------------------------------
fdir = which('imlook3d');
txt = [fdir(1:end-10) '\imlook3d.png'];
imlogo = imread(txt);
Sz= size(imlogo);
flogo = figure('Position',[10000 10000 Sz(2) + 4 Sz(1) + 4],'name','Imlook3D',...
    'numbertitle','off','menubar','none');
movegui(flogo,'center');
set(flogo,'Units', 'pixels');
image(imlogo(:,:,1:3))
set(gca, ...
    'Visible', 'off', ...
    'Units', 'pixels', ...
    'Position', [2 2 Sz(2) Sz(1)]);
text(220,16,'3D Image Display Tool','Fontsize',12);
text(130,355,'Version 1.2: Copyright 2006, Omer Demirkaya','Fontsize',8);
pause(3);delete(flogo);clear imlogo
%% ------------------------------------
%--------------------------------------------------------------------------
guidata(hObject, handles);
%--------------------------------------------------------------------------
% --- Outputs from this function are returned to the command line.
function varargout = imlook3d_OutputFcn(hObject, eventdata, handles)
 varargout{1} = handles.output;
% --- Executes during object creation, after setting all properties.
function SliceNumSlider_CreateFcn(hObject, eventdata, handles)
usewhitebg = 1;
if usewhitebg
    set(hObject,'BackgroundColor',[.9 .9 .9]);
else
    set(hObject,'BackgroundColor',get(0,'defaultUicontrolBackgroundColor'));
end

% --- Executes on slider movement.
%------------------------------------------------------------
function SliceNumSlider_Callback(hObject, eventdata, handles)
NewVal= round(get(hObject,'Value'));
cimg = handles.image.CData(:,:,NewVal);
if ~(handles.global_windowing)
    cclim = [min(cimg(:)) max(cimg(:))];
    if (cclim(2)>0 & cclim(2)-cclim(1) ~= 0)
        set(handles.axes1,'CLim',[min(cimg(:)) max(cimg(:))])
    end
else
    set(handles.axes1,'CLim',[handles.intensityParam.lowerLimit  handles.intensityParam.upperLimit])
end
set(handles.ImgObject,'Cdata',cimg);
set(handles.SliceNumEdit,'String',num2str(NewVal));
drawnow
guidata(hObject, handles);
% --- Executes during object creation, after setting all properties.
function SliceNumEdit_CreateFcn(hObject, eventdata, handles)
if ispc
    set(hObject,'BackgroundColor','white');
else
    set(hObject,'BackgroundColor',get(0,'defaultUicontrolBackgroundColor'));
end
%--------------------------------------------------------------------------
function SliceNumEdit_Callback(hObject, eventdata, handles)
z= size(handles.image.CData,3);
strg = get(hObject,'String');
if str2num(strg)>=1 & str2num(strg)<=z
    set(handles.SliceNumSlider,'Value',str2num(strg));
    cimg = handles.image.CData(:,:,str2num(strg));
    set(handles.ImgObject,'Cdata',cimg);
    handles.currentSliceNumber = str2num(strg);
    drawnow
end
if ~(handles.global_windowing)
    cclim = [min(cimg(:)) max(cimg(:))];
    if (cclim(2)>0 & cclim(2)-cclim(1) ~= 0)
        set(handles.axes1,'CLim',[min(cimg(:)) max(cimg(:))])
    end
else
    set(handles.axes1,'CLim',[handles.intensityParam.lowerLimit  handles.intensityParam.upperLimit])

end
guidata(hObject, handles);
% --------------------------------------------------------------------
% --------------------------------------------------------------------
function changeColorMaps_Callback(hObject, eventdata, handles)
% --------------------------------------------------------------------
function petColormaps_Callback(hObject, eventdata, handles)
% --------------------------------------------------------------------
function CTcolormaps_Callback(hObject, eventdata, handles)
% --------------------------------------------------------------------
% --------------------------------------------------------------------
function Hot_Callback(hObject, eventdata, handles)
htable = feval('hot');
set(handles.figure1,'Colormap',htable);
if handles.imSize(3)> 1
    set(handles.SliceNumEdit,'BackgroundColor',[.1 .1 .1],'ForegroundColor','r');
end
guidata(hObject, handles);
% --------------------------------------------------------------------
function Gray_Callback(hObject, eventdata, handles)
htable = feval('gray');
set(handles.figure1,'Colormap',htable);
if handles.imSize(3)> 1
    set(handles.SliceNumEdit,'BackgroundColor','k','ForegroundColor','w');
end
guidata(hObject, handles);
% --------------------------------------------------------------------
function InvGray_Callback(hObject, eventdata, handles)
htable = 1-feval('gray');
set(handles.figure1,'Colormap',htable);
if handles.imSize(3)> 1
    set(handles.SliceNumEdit,'BackgroundColor','w','ForegroundColor','k');
end
guidata(hObject, handles);
% --------------------------------------------------------------------
function Jet_Callback(hObject, eventdata, handles)
htable = feval('jet');
set(handles.figure1,'Colormap',htable);
if handles.imSize(3)> 1
    set(handles.SliceNumEdit,'BackgroundColor','b','ForegroundColor','y');
end
guidata(hObject, handles);
% --------------------------------------------------------------------
function boneWindow_Callback(hObject, eventdata, handles)
handles.intensityParam.lowerLimit = -250;
handles.intensityParam.upperLimit = 1250;
set(handles.axes1,'CLim',[-300 1250])
guidata(hObject,handles);
% --------------------------------------------------------------------
function lungWindow_Callback(hObject, eventdata, handles)
handles.intensityParam.lowerLimit = -2500;
handles.intensityParam.upperLimit = 1100;
set(handles.axes1,'CLim',[-2500 1100])
guidata(hObject,handles);
% --------------------------------------------------------------------
function mediasitnalWindow_Callback(hObject, eventdata, handles)
handles.intensityParam.lowerLimit = -450;
handles.intensityParam.upperLimit = 350;
set(handles.axes1,'CLim',[-450 350])
guidata(hObject,handles);
% --------------------------------------------------------------------
%---------------------------------------------------------
% Callbacks for imtoolsmenu
%----------------------------------------------------------
function imToolsMenu_Callback(hObject, eventdata, handles)
% --------------------------------------------------------------------
function imPixval_Callback(hObject, eventdata, handles)
if isempty(handles.hText)
handles.hText = impixelinfoval(handles.figure1,handles.ImgObject);
set(handles.hText,'FontWeight','bold');
set(handles.hText,'FontSize',10);
else
    delete(handles.hText);
    handles.hText =[];
end
guidata(hObject, handles);
% --------------------------------------------------------------------
function imHistogram_Callback(hObject, eventdata, handles)
myhistogram(1)
%-------------------------------------------------------------
function imProfile_Callback(hObject, eventdata, handles)
improfile;
title('Intensity profile');
ylabel('Intensity');
xlabel('Pixel position');
%------------------------------------------------
% Histogram function
% -----------------------------------------------
function myhistogram(htype)
img = getimage(gcbf);
if (htype==1)
    figure;hist(double(img(:)),64);
    title('Slice histogram');
    ylabel('Frequency');
    xlabel('Intensity');
elseif (htype == 2)
    figure;hist(log(double(img(:))+1),64);
else
    htype = 0;
end
return;

% --- Executes during object creation, after setting all properties.
function popup_view_CreateFcn(hObject, eventdata, handles)
if ispc
    set(hObject,'BackgroundColor','white');
else
    set(hObject,'BackgroundColor',get(0,'defaultUicontrolBackgroundColor'));
end
%-------------------------------------------------------------
% --- Executes on selection change in popup_view.
%-------------------------------------------------------------
function popup_view_Callback(hObject, eventdata, handles)
v = get(hObject,'value');
if v ~=1
    nimg = convert_view(handles.oimage.CData,v);
else
    nimg = handles.oimage.CData;
end
[r,c,z]=size(nimg);
handles.image.CData = nimg;
handles.image.Xdata = [1 c];
handles.image.Ydata = [1 r];
handles.viewtype = v;
handles.imSize = [r,c,z];
reset(handles.axes1); axis off;
set(handles.SliceNumSlider,'Min',1,'Max',z,'Value',...
    round(z/2),'SliderStep',[1.0/double(z-1) 1.0/double(z-1)]);
set(handles.ImgObject,'Xdata',[1 c],'Ydata',[1 r]);
% Set the properties of the axes

set(handles.axes1,'XLim',[1 c],'YLim',[1 r],'PlotBoxAspectRatio',[c r 1]);

if ~isempty(handles.imageInfo)
    Sthick = handles.imageInfo.SliceThickness;
    if v == 2
        psize = handles.imageInfo.PixelSpacing(1);
        set(handles.axes1,'DataAspectRatio',[1 psize/Sthick  1]);
    elseif v == 3
        psize = handles.imageInfo.PixelSpacing(2);
        set(handles.axes1,'DataAspectRatio',[1 psize/Sthick  1]);
    else
        psize = handles.imageInfo.PixelSpacing;
        set(handles.axes1,'DataAspectRatio',[psize(1) psize(2)  1]);
    end
end
set(handles.SliceNumEdit,'String',num2str(round(z/2)));
cimg = handles.image.CData(:,:,round(z/2));
handles.currentSliceNumber = round(z/2);
set(handles.ImgObject,'Cdata',cimg);
guidata(hObject, handles);
drawnow
%-------------------------------------------------------------
% function s= convert_view(img,desired_view)
% img: input image
% It assumes that the original image is in transverse view(1)
% 1: transverse, 2: coronal, 3: sagittal views
% s: output image
function s= convert_view(img,desired_view)
[r,c,z]=size(img);
switch desired_view
    case 2
        s = permute(img,[3 2 1]);
        s=flipdim(s,1);
    case 3
        s = permute(img,[3 1 2]);
        s=flipdim(s,1);
    otherwise;
end
% --------------------------------------------------------------------
function readOrdinaryImageSeries_Callback(hObject, eventdata, handles)
[filename, pathname] = uigetfile({'*.*'},'Image');
if isequal(filename,0)|isequal(pathname,0)
    setstatus('File not found');
else
    cd(pathname)
    k = findstr(filename,'.');
    fnames = dir(['*' filename(k+1:end)]);
    timg = imread(filename);
    [r,c,z] = size(timg);
    numSlices = size(fnames,1);
    nimg = zeros([r,c,numSlices]);
    if z == 3
        hw=warndlg('Color images will be converted to gray');
        uiwait;
        nimg(:,:,1) = rgb2gray(timg);
    else
        nimg(:,:,1) = timg;
    end
    hwb = waitbar(0,'Reading Images, please wait...');
    for i=2:numSlices
        waitbar(i/numSlices, hwb);
        timg = imread(fnames(i).name);
        [r,c,z] = size(timg);
        if z == 3
            nimg(:,:,i) = rgb2gray(timg);
        else
            nimg(:,:,i) = timg;
        end
    end
    close(hwb);
    clear timg;
    [r,c,z]=size(nimg);
    handles.image.CData = nimg;
    handles.image.Xdata = [1 c];
    handles.image.Ydata = [1 r];
    handles.imSize = [r,c,z];
    handles.SliceThickness = 1;
    if z==1
        sliderStep = 1;
    else
        sliderStep = 1.0/double(z-1);
    end
    set(handles.SliceNumSlider,'Min',1,'Max',z,'Value',...
        round(z/2),'SliderStep',[sliderStep sliderStep]);
    set(handles.ImgObject,'Xdata',[1 c],'Ydata',[1 r]);
    % Set the properties of the axes
    set(handles.axes1,'XLim',[1 c],'YLim',[1 r],'PlotBoxAspectRatio',[c r 1]);
    set(handles.SliceNumEdit,'String',num2str(round(z/2)));
    cimg = handles.image.CData(:,:,round(z/2));
    set(handles.ImgObject,'Cdata',cimg);
    handles.oimage = handles.image;
    guidata(hObject, handles);
    clear nimg;
    if z > 1
        turnDisplayButtons(handles,'on');
        set(handles.popupmenu_imageModality,'visible','off');
    end
    drawnow
end
% --------------------------------------------------------------------
function saveImageIntoWorkspace_Callback(hObject, eventdata, handles)
assignin('base','IIMG',handles.image.CData);
% --------------------------------------------------------------------
function imfileMenu_Callback(hObject, eventdata, handles)
% --------------------------------------------------------------------
function ImMaps_Callback(hObject, eventdata, handles)
% --------------------------------------------------------------------
% --------------------------------------------------------------------
function readWorkspaceImage_Callback(hObject, eventdata, handles)
Vars = evalin('base','who;');
if ~isempty (Vars)
    [s,v] = listdlg('PromptString','Select an image:',...
        'SelectionMode','single',...
        'Listsize',[150,200],...
        'ListString',Vars);
    if (v)
        imlook3d(evalin('base',Vars{s}));
    end
else
    disp('No workspace variable');
end

% --------------------------------------------------------------------
% Reads series of dicom images into a 3D array
% --------------------------------------------------------------------
function readDicomPETSeries_Callback(hObject, eventdata, handles)
[filename, pathname] = uigetfile({'*.dcm';'*.*'},'Image');
if isequal(filename,0)|isequal(pathname,0)
    setstatus('File not found');
else
    cd(pathname)
    k = findstr(filename,'.');
    fnames = dir([filename(1:2) '*']);
    info = dicominfo(filename);
    r = info.Width;
    c = info.Height;
    numSlices = length(fnames);
    nimg = zeros([r,c,numSlices]);
    hw = waitbar(0,'Reading volume image, please wait...');
    for i=1:numSlices
        info = dicominfo(fnames(i).name);
        nimg(:,:,info.ImageIndex) = info.RescaleSlope*dicomread(info)+info.RescaleIntercept;
        waitbar(i/numSlices,hw);
    end
    close(hw);

    fields = {'Filename','FileModDate','FileSize','ImageType','Width','Height','BitDepth',...
        'NumberOfFrames','Rows','Columns','BitsAllocated','SmallestImagePixelValue',...
        'LargestImagePixelValue','PixelDataGroupLength','StudyDescription','SeriesDescription',...
        'StudyID','ImageID','PatientID','SliceThickness','StudyDate','AcquisitionDate',...
        'AcquisitionTime','PixelSpacing'};

    myinfo = keepfield(info,fields);

    handles.imageInfo = myinfo;

    [r,c,z]=size(nimg);
    handles.image.CData = nimg;
    handles.image.Xdata = [1 c];
    handles.image.Ydata = [1 r];
    handles.imSize = [r,c,z];
    set(handles.SliceNumSlider,'Min',1,'Max',z,'Value',...
        round(z/2),'SliderStep',[1.0/double(z-1) 1.0/double(z-1)]);
    set(handles.ImgObject,'Xdata',[1 c],'Ydata',[1 r]);
    % Set the properties of the axes
    set(handles.axes1,'XLim',[1 c],'YLim',[1 r],'PlotBoxAspectRatio',[c r 1]);
    set(handles.SliceNumEdit,'String',num2str(round(z/2)));
    cimg = handles.image.CData(:,:,round(z/2));
    set(handles.ImgObject,'Cdata',cimg);
    handles.oimage = handles.image;
    guidata(hObject, handles);
    clear nimg;
    if z>1
        turnDisplayButtons(handles,'on')
    end
    handles.imageModality = 1;
    set(handles.popupmenu_imageModality,'visible','on');
    set(handles.popupmenu_imageModality,'value',1);
    drawnow
end
% --------------------------------------------------------------------
function saveCurrentSlice_Callback(hObject, eventdata, handles)
[filename, pathname,filterindex] = uiputfile({'*.png';'*.tif';'*.jpg'},'Image');
fformat = {'png','tif','jpg'};
if isequal(filename,0) | isequal(pathname,0)
    warndlg('No file name entered!');
else
    I= getframe(handles.axes1);
    I=frame2im(I);
    buffer=pwd;
    cd(pathname);
    imwrite(I,filename,fformat{filterindex});
    cd(buffer);
end
% --------------------------------------------------------------------
function adjust_intensity_window_Callback(hObject, eventdata, handles)
setappdata(0  ,  'hMainGui'    , gcf);
hMainGui = getappdata(0, 'hMainGui');
maxINT = max(max(max(handles.image.CData)));
minINT = min(min(min(handles.image.CData)));
cclim = get(handles.axes1,'CLim');
intensityParam = struct('lowerLimit',cclim(1),'upperLimit',cclim(2),'maxIntensity',maxINT, 'minIntensity',minINT);
setappdata(hMainGui,  'intensityParam', intensityParam);
adjustIntensityRangeGui;
uiwait;
hMainGui = getappdata(0,'hMainGui');
handles.intensityParam = getappdata(hMainGui,  'intensityParam');
guidata(hObject, handles);
% -----------------------------------------------------------------
function global_intensity_windowing_Callback(hObject, eventdata, handles)
str = get(gcbo,'Checked');
if strcmp(str,'off')
    handles.global_windowing =1;
    set(gcbo,'Checked','on');
else
    handles.global_windowing =0;
    set(gcbo,'Checked','off');
end
guidata(hObject,handles);
% --------------------------------------------------------------------
function renderVolume_Callback(hObject, eventdata, handles)
img= handles.image.CData;
if size(img,3) > 1
    prompt = {'Enter Lower Threshold:','Enter Upper Threshold:'};
    dlg_title = 'Enter Thresholds'; num_lines = 1; def = {'1','2'};
    answer = inputdlg(prompt,dlg_title,num_lines,def);
    T(1) = str2num(answer{1,1});
    T(2) = str2num(answer{2,1});
    nimg = img >= T(1) & img <= T(2);
    s = size(img);
    vlim=[1 s(2) 1 s(1) 1 s(3)];
    figure;
    % set(get(hndl,'CurrentAxes'),'YDir','rev');
    [x y z D] = subvolume(nimg,vlim);
    p = patch(isosurface(x,y,z,D,0),'FaceColor','r','EdgeColor','none');
    p2 = patch(isocaps(x,y,z,D,0),'FaceColor','interp','EdgeColor','none');
    set(gca,'XTick',[],'YTick',[],'ZTick',[],'Box','on','Color','c');
    isonormals(x,y,z,D,p);
    axis tight;
    daspect([1 1 1])
    lighting gouraud
else
    msgbox('This is not a volume image','Error','modal');
end

% --------------------------------------------------------------------
% READS CT DICOM IMAGES
% --------------------------------------------------------------------
function readDicomCTSeries_Callback(hObject, eventdata, handles)
[filename, pathname] = uigetfile({'*.dcm';'*.*'},'Image');
if isequal(filename,0)|isequal(pathname,0)
    setstatus('File not found');
else
    cd(pathname)
    k = findstr(filename,'.');
    fnames = dir(['*' filename(k(end)+1:end)]);
    info = dicominfo(filename);
    r = info.Width;
    c = info.Height;
    numSlices = length(fnames);
    nimg = zeros([r,c,numSlices]);
    hwb = waitbar(0,'Reading Images, please wait...');
    for i=1:numSlices
        waitbar(i/numSlices, hwb);
        info = dicominfo(fnames(i).name);
        nimg(:,:,i) = info.RescaleSlope*dicomread(info)+info.RescaleIntercept;
    end
    close(hwb);

    fields = {'Filename','FileModDate','FileSize','ImageType','Width','Height','BitDepth',...
        'NumberOfFrames','Rows','Columns','BitsAllocated','SmallestImagePixelValue',...
        'LargestImagePixelValue','PixelDataGroupLength','StudyDescription','SeriesDescription',...
        'StudyID','ImageID','PatientID','SliceThickness','StudyDate','AcquisitionDate','AcquisitionTime','PixelSpacing'};

    myinfo = keepfield(info,fields);
    handles.imageInfo = myinfo;

    [r,c,z]=size(nimg);
    handles.image.CData = nimg;
    handles.image.Xdata = [1 c];
    handles.image.Ydata = [1 r];
    handles.imSize = [r,c,z];
    set(handles.SliceNumSlider,'Min',1,'Max',z,'Value',...
        round(z/2),'SliderStep',[1.0/double(z-1) 1.0/double(z-1)]);
    set(handles.ImgObject,'Xdata',[1 c],'Ydata',[1 r]);
    % Set the properties of the axes
    set(handles.axes1,'XLim',[1 c],'YLim',[1 r],'PlotBoxAspectRatio',[c r 1]);
    set(handles.SliceNumEdit,'String',num2str(round(z/2)));
    cimg = handles.image.CData(:,:,round(z/2));
    set(handles.ImgObject,'Cdata',cimg);
    handles.oimage = handles.image;
    guidata(hObject, handles);
    clear nimg;
    handles.imageModality = 4;
    if z>1
        turnDisplayButtons(handles,'on')
    end
    handles.imageModality = 2;
    set(handles.popupmenu_imageModality,'visible','on');
    set(handles.popupmenu_imageModality,'value',2);
    drawnow
end
% --------------------------------------------------------------------
function imageProcessMenu_Callback(hObject, eventdata, handles)
% --------------------------------------------------------------------
function imageThresholdManual_Callback(hObject, eventdata, handles)
img = getimage(gcbf);
th = cell2mat(inputdlg('Enter Threshold','Threshold',1,{'100'}));
if ~isempty(th)
    th = str2num(th);
    figure;imagesc(img>th);axis image;axis off
    title('Thresholded Image');
end
% --------------------------------------------------------------------
function automatedThresholding_Callback(hObject, eventdata, handles)
img = getimage(gcbf);
th=otsu_th(single(img(:)),64);
figure;imagesc(img>th);axis image;axis off
title('Thresholded Image');
% --------------------------------------------------------------------
% READS DICOM IMAGE SINGLE OR VOLUME
% --------------------------------------------------------------------
function readSingleDicomImage_Callback(hObject, eventdata, handles)
[filename, pathname] = uigetfile({'*.dcm';'*.*'},'Image');
if isequal(filename,0)|isequal(pathname,0)
    setstatus('File not found');
else
    cd(pathname)
    info = dicominfo(filename);
    if isfield(info,'NumberOfSlices')
        z = info.NumberOfSlices;
    elseif isfield(info,'NumberOfFrames')
        z = info.NumberOfFrames;
    end
    if isfield(info,'SliceThickness')
        handles.SliceThickness= info.SliceThickness;
    end
    % Reading dicom image
    nimg = squeeze(dicomread(info));
    [r,c,z]=size(nimg);
    handles.image.CData = nimg;
    handles.image.Xdata = [1 c];
    handles.image.Ydata = [1 r];
    handles.imSize = [r,c,z];

    if z >1
        turnDisplayButtons(handles,'on');
        set(handles.SliceNumSlider,'Min',1,'Max',z,'Value',...
            round(z/2),'SliderStep',[1.0/double(z-1) 1.0/double(z-1)]);
        set(handles.SliceNumEdit,'String',num2str(round(z/2)));
        turnDisplayButtons(handles,'on');
        cimg = handles.image.CData(:,:,round(z/2));
    else
        turnDisplayButtons(handles,'off');
        cimg = handles.image.CData;
    end
    fields = {'Filename','FileModDate','FileSize','ImageType','Width','Height','BitDepth',...
        'NumberOfFrames','Rows','Columns','BitsAllocated','SmallestImagePixelValue',...
        'LargestImagePixelValue','PixelDataGroupLength','StudyDescription','SeriesDescription',...
        'StudyID','ImageID','PatientID','SliceThickness','StudyDate','AcquisitionDate','AcquisitionTime','PixelSpacing'};
    myinfo = keepfield(info,fields);
    handles.imageInfo = myinfo;

    % Set the properties of the axes and image object
    set(handles.ImgObject,'XData',[1 c],'YData',[1 r],'CData',cimg);
    set(handles.axes1,'XLim',[1 c],'YLim',[1 r],'PlotBoxAspectRatio',[c r 1],'DataAspectRatio',...
        [1 info.PixelSpacing(2)/info.PixelSpacing(1)  1]);
    handles.oimage = handles.image;
    guidata(hObject, handles);
    clear nimg;
    drawnow
end
% --------------------------------------------------------------------
% --- Executes on selection change in popup_flip.
function popup_flip_Callback(hObject, eventdata, handles)
v = get(hObject,'value');
switch v
    case 1
       handles.image.CData = flipdim(handles.image.CData,2);
    case 2
        handles.image.CData = flipdim(handles.image.CData,1);
    case 3
        handles.image.CData = flipdim(handles.image.CData,3);
    case 4
    handles.image.CData = handles.oimage.CData;
    otherwise
end
guidata(hObject,handles);

% --- Executes during object creation, after setting all properties.
function popup_flip_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end

function turnDisplayButtons(handles,bmode)
set(handles.SliceNumSlider,'visible',bmode);
set(handles.SliceNumEdit,'visible',bmode);
set(handles.popup_view,'visible',bmode);
set(handles.popup_flip,'visible',bmode);
set(handles.text1,'visible',bmode);
set(handles.text2,'visible',bmode);
set(handles.text3,'visible',bmode);
set(handles.popup_flip,'visible',bmode);
return

% --------------------------------------------------------------------
function helpCallback_Callback(hObject, eventdata, handles)
% --------------------------------------------------------------------
% --------------------------------------------------------------------
function helpMenu_Callback(hObject, eventdata, handles)
% --------------------------------------------------------------------
function helpAboutImlook3d_Callback(hObject, eventdata, handles)
s = sprintf(['IMLOOK3D: 3D Image Display Tool\n',...
    'Version 1.2, Copyright (2006 - Inf and Beyond)\n',...
    'Omer Demirkaya, KFSH&RC']);
msgbox(s,'About IMLOOK3D Tool','modal');
% --------------------------------------------------------------------
function helpDisclaimer_Callback(hObject, eventdata, handles)
s = {'This SOFTWARE DOES NOT (A) MAKE ANY WARRANTY, EXPRESS OR IMPLIED',
    'WITH RESPECT TO THE USE OF THE INFORMATION PROVIDED HEREBY; NOR (B)',
    'GUARANTEE THE ACCURACY,COMPLETENESS,USEFULNESS,OR ADEQUACY OF ANY',
    'RESOURCES, INFORMATION,SYSTEM,PRODUCT,OR PROCESS AVAILABLE - AT OR',
    'THROUGH THIS SOFTWARE.'};
msgbox(s,'Disclaimer','modal');

% --------------------------------------------------------------------
function imageInformation_Callback(hObject, eventdata, handles)
if ~isempty(handles.imageInfo)
    imageinfo(handles.imageInfo);
else
    msgbox('There is no image or no information about it','Image Info..','modal');
end

% --------------------------------------------------------------------
function readSingleOrdinaryImage_Callback(hObject, eventdata, handles)
[filename, pathname] = uigetfile({'*.*'},'Image');
if isequal(filename,0)|isequal(pathname,0)
    setstatus('File not found');
else
    cd(pathname)
    info = imfinfo(filename);
    [img, map]=imread(filename);
    [r,c,z] = size(img);
    handles.image.CData = img;
    handles.image.Xdata = [1 c];
    handles.image.Ydata = [1 r];
    handles.imSize = [r,c,z];

    if z > 1   % single slice
    set(handles.SliceNumSlider,'Min',1,'Max',z,'Value',...
        round(z/2),'SliderStep',[1.0/double(z-1) 1.0/double(z-1)]);
    set(handles.SliceNumEdit,'String',num2str(round(z/2)));
    turnDisplayButtons(handles,'on');
    cimg = handles.image.CData(:,:,round(z/2));
    else
        turnDisplayButtons(handles,'off');
        cimg = handles.image.CData;
    end
    handles.imageInfo = info;
   % Set the properties of the axes and image object
    set(handles.ImgObject,'XData',[1 c],'YData',[1 r],'CData',cimg);
    set(handles.axes1,'XLim',[1 c],'YLim',[1 r],'PlotBoxAspectRatio',[c r 1],'DataAspectRatio',[1 1 1]);
    handles.oimage = handles.image;
    guidata(hObject, handles);
    clear nimg;
    drawnow
end
% --------------------------------------------------------------------
% SMOOTH IMAGE
% --------------------------------------------------------------------
function smoothVolume_Callback(hObject, eventdata, handles)
if size(handles.image.CData,3) > 1
h = waitbar(0,'Smoothing, please wait...');
handles.image.CData= smooth3(handles.image.CData,'gaussian');
close(h);
set(handles.ImgObject,'Cdata',handles.image.CData(:,:,handles.currentSliceNumber));
guidata(hObject,handles);
end
% --------------------------------------------------------------------
% Voxel dimensions
% --------------------------------------------------------------------
function voxelSize_Callback(hObject, eventdata, handles)
prompt = {'X Size:','Y Size:', 'Z Size'};
dlg_title = 'Enter Voxel Sizes'; num_lines = 1; def = {'1','1','1'};
answer = inputdlg(prompt,dlg_title,num_lines,def);
if ~isempty(answer)
    voxdim(1) = str2num(cell2mat(answer(1,1)));
    voxdim(2) = str2num(cell2mat(answer(2,1)));
    voxdim(3) = str2num(cell2mat(answer(3,1)));
    bw = voxdim > 0; % checking for zero or negative
    if prod(double(bw))
        handles.imageInfo.PixelSpacing(1) = voxdim(1);
        handles.imageInfo.PixelSpacing(2) = voxdim(2);
        handles.imageInfo.SliceThickness  = voxdim(3);
        if handles.viewtype == 2
            set(handles.axes1,'DataAspectRatio',[1 voxdim(1)/voxdim(3)  1]);
        elseif handles.viewtype == 3
            set(handles.axes1,'DataAspectRatio',[1 voxdim(2)/voxdim(3)  1]);
        else
            set(handles.axes1,'DataAspectRatio',[1 voxdim(2)/voxdim(1)  1]);
        end

    end
end
guidata(hObject,handles);
% --------------------------------------------------------------------
% Horizontal Profile
% --------------------------------------------------------------------
function horizontalProfile_Callback(hObject, eventdata, handles)
h = warndlg('Double-click on the row','Pixel Selection');
uiwait;
[c,r,P] = impixel;
f = get(findobj(gcf,'Type','image'));
txt = ['Profile for row: ' num2str(r)];
figure;
plot(f.CData(r,:));title(txt);xlabel('Pixel location');ylabel('Intensity');
% --------------------------------------------------------------------
% Vertical Profile
% --------------------------------------------------------------------
function verticalProfile_Callback(hObject, eventdata, handles)
h = warndlg('Double-click on the column','Pixel Selection');
uiwait;
[c,r,P] = impixel;
f = get(findobj(gcf,'Type','image'));
txt = ['Profile for column: ' num2str(c)];
figure;
plot(f.CData(:,c));title(txt);xlabel('Pixel location');ylabel('Intensity');

% --------------------------------------------------------------------
% --- Executes during object creation, after setting all properties.
% --------------------------------------------------------------------
function popupmenu_imageModality_CreateFcn(hObject, eventdata, handles)
if ispc && isequal(get(hObject,'BackgroundColor'), get(0,'defaultUicontrolBackgroundColor'))
    set(hObject,'BackgroundColor','white');
end
% --------------------------------------------------------------------
% --- Image modality selection
% --------------------------------------------------------------------
function popupmenu_imageModality_Callback(hObject, eventdata, handles)
imMod = get(hObject,'value');
handles.imageModality = imMod;

% --------------------------------------------------------------------
%  Executes MATLAB IMTOOL
% --------------------------------------------------------------------
function imtoolMATLAB_Callback(hObject, eventdata, handles)
f = get(findobj(gcf,'Type','image'));
imtool(f.CData,[min(f.CData(:)) max(f.CData(:))]);
% --------------------------------------------------------------------
%  helpMenu on Imlook3D
% --------------------------------------------------------------------
function helpIMLOOK3D_Callback(hObject, eventdata, handles)
warndlg('Under Construction and needs help :)');
% --------------------------------------------------------------------
%  Reading MRI images
% --------------------------------------------------------------------
function readDICOMMRISeries_Callback(hObject, eventdata, handles)
[filename, pathname] = uigetfile({'*.dcm';'*.*'},'Image');
if isequal(filename,0)|isequal(pathname,0)
    setstatus('File not found');
else
    cd(pathname)
    k = findstr(filename,'.');
    fnames = dir(['*' filename(k+1:end)]);
    info = dicominfo(filename);
    r = info.Width;
    c = info.Height;
    numSlices = length(fnames);
    nimg = zeros([r,c,numSlices]);
    hwb = waitbar(0,'Reading Images, please wait...');
    for i=1:numSlices
        waitbar(i/numSlices, hwb);
        info = dicominfo(fnames(i).name);
        nimg(:,:,i) = dicomread(info);
    end
    close(hwb);

    fields = {'Filename','FileModDate','FileSize','ImageType','Width','Height','BitDepth',...
        'NumberOfFrames','Rows','Columns','BitsAllocated','SmallestImagePixelValue',...
        'LargestImagePixelValue','PixelDataGroupLength','StudyDescription','SeriesDescription',...
        'StudyID','ImageID','PatientID','SliceThickness','StudyDate','AcquisitionDate','AcquisitionTime','PixelSpacing'};

    myinfo = keepfield(info,fields);
    handles.imageInfo = myinfo;

    [r,c,z]=size(nimg);
    handles.image.CData = nimg;
    handles.image.Xdata = [1 c];
    handles.image.Ydata = [1 r];
    handles.imSize = [r,c,z];
    set(handles.SliceNumSlider,'Min',1,'Max',z,'Value',...
        round(z/2),'SliderStep',[1.0/double(z-1) 1.0/double(z-1)]);
    set(handles.ImgObject,'Xdata',[1 c],'Ydata',[1 r]);
    % Set the properties of the axes
    set(handles.axes1,'XLim',[1 c],'YLim',[1 r],'PlotBoxAspectRatio',[c r 1]);
    set(handles.SliceNumEdit,'String',num2str(round(z/2)));
    cimg = handles.image.CData(:,:,round(z/2));
    set(handles.ImgObject,'Cdata',cimg);
    handles.oimage = handles.image;
    guidata(hObject, handles);
    clear nimg;
    if z>1
        turnDisplayButtons(handles,'on')
    end
    handles.imageModality = 4;
    set(handles.popupmenu_imageModality,'visible','on');
    set(handles.popupmenu_imageModality,'value',4);
    drawnow
end

% --------------------------------------------------------------------
function cropVolume_Callback(hObject, eventdata, handles)
% hObject    handle to cropVolume (see GCBO)
% eventdata  reserved - to be defined in a future version of MATLAB
% handles    structure with handles and user data (see GUIDATA)

[I,rect] = imcrop;
s = handles.image.CData;
switch handles.viewtype
    case 1
        [r,c,z]  = size(s);
        oimg = crop3d(s,rect,[1 z]);
    case 2
%         s = permute(img,[3 2 1]);
%         s = flipdim(s,1);
        [r,c,z]  = size(s);
        oimg = crop3d(s,rect,[1 z]);
    case 3
%         s = permute(img,[3 1 2]);
%         s = flipdim(s,1);
        [r,c,z]  = size(s);
        oimg = crop3d(s,rect,[1 z]);
    otherwise;
end
imlook3d(oimg);


function test_suite = test_searchlight
% tests for cosmo_searchlight
%
% #   For CoSMoMVPA's copyright information and license terms,   #
% #   see the COPYING file distributed with CoSMoMVPA.           #
    try % assignment of 'localfunctions' is necessary in Matlab >= 2016
        test_functions=localfunctions();
    catch % no problem; early Matlab versions can use initTestSuite fine
    end
    initTestSuite;

function test_searchlight_singlethread()
    opt=struct();
    opt.progress=false;
    helper_test_searchlight(opt);

function test_searchlight_matlab_multithread()
    has_function=@(x)~isempty(which(x));
    has_parallel_toolbox=all(cellfun(has_function, {'gcp','parpool'}));

    if ~has_parallel_toolbox
        cosmo_notify_test_skipped('Matlab parallel toolbox not available');
        return;
    end

    warning_state=cosmo_warning();
    warning_resetter=onCleanup(@()cosmo_warning(warning_state));
    cosmo_warning('off');

    opt=struct();
    opt.progress=false;
    opt.nproc=2;
    helper_test_searchlight(opt);

function test_searchlight_octave_multithread()
    has_function=@(x)~isempty(which(x));
    has_parallel_toolbox=all(cellfun(has_function, {'parcellfun',...
                                                    'pararrayfun'}));

    if ~has_parallel_toolbox
        cosmo_notify_test_skipped('Octave parallel toolbox not available');
        return;
    end

    warning_state=cosmo_warning();
    warning_resetter=onCleanup(@()cosmo_warning(warning_state));
    cosmo_warning('off');

    opt=struct();
    opt.progress=false;
    opt.nproc=2;
    helper_test_searchlight(opt);

function helper_test_searchlight(opt)

    ds=cosmo_synthetic_dataset('size','normal');
    m=any(abs(ds.samples)>3,1);
    ds=cosmo_slice(ds,~m,2);
    ds=cosmo_dim_prune(ds);

    measure=@(x,a) cosmo_structjoin('samples',size(x.samples,2));
    nh=cosmo_spherical_neighborhood(ds,'radius',2,'progress',0);

    m=cosmo_searchlight(ds,nh,measure,opt);

    assertEqual(m.samples,[8 12 10 9 12 10 16 13 12 17 14 15 ...
                            13 11 15 14 10 9 14 11 5 7 6 7]);
    assertEqual(m.fa.i,ds.fa.i);
    assertEqual(m.fa.j,ds.fa.j);
    assertEqual(m.fa.k,ds.fa.k);
    assertEqual(m.a,ds.a);

    nh2=cosmo_spherical_neighborhood(ds,'count',17,'progress',0);
    m=cosmo_searchlight(ds,nh2,measure,opt);
    assertEqual(m.samples,[17 17 17 17 17 17 17 17 17 17 18 16 ...
                                17 17 16 15 17 17 17 17 17 17 17 17]);

    measure=@cosmo_correlation_measure;

    nh3=cosmo_spherical_neighborhood(ds,'radius',2,...
                                cosmo_structjoin('progress',0));
    m=cosmo_searchlight(ds, nh3, measure,...
                            'center_ids',[4 21],opt);

    assertVectorsAlmostEqual(m.samples, [0.9742,-.0273]...
                                        ,'relative',.001);
    assertEqual(m.fa.i,[1 1]);
    assertEqual(m.fa.j,[2 1]);
    assertEqual(m.fa.k,[1 5]);

    sa=struct();
    sa.time=(1:6)';
    sdim=struct();
    sdim.values={10:15};
    sdim.labels={'time'};

    nh4=cosmo_spherical_neighborhood(ds,'radius',0,'progress',false);
    measure2=@(x,opt)cosmo_structjoin('samples',mean(x.samples,2),...
                                       'sa',sa,...
                                       'a',cosmo_structjoin('sdim',sdim));
    m2=cosmo_searchlight(ds,nh4,measure2,opt);
    assertEqual(m2.sa,sa);
    assertEqual(m2.a.sdim,sdim);
    assertElementsAlmostEqual(m2.samples,ds.samples);

function test_searchlight_partial_classification
    ds=cosmo_synthetic_dataset('nchunks',6);

    partitions=struct();
    train_msk=mod(ds.sa.chunks,2)==0;
    partitions.train_indices={find(train_msk)};
    partitions.test_indices={find(~train_msk)};

    measure=@cosmo_crossvalidation_measure;
    opt=struct();
    opt.classifier=@cosmo_classify_lda;
    opt.partitions=partitions;
    opt.progress=false;
    opt.output='winner_predictions';

    nh=cosmo_spherical_neighborhood(ds,'radius',1,'progress',false);
    res=cosmo_searchlight(ds,nh,measure,opt);

    assertEqual(res.samples([3 4 7 8 11 12],:),NaN(6,6))
    s=res.samples([1 2 5 6 9 10],:);
    assertTrue(all(s(:)==1 | s(:)==2));

function test_searchlight_exceptions
    aet=@(varargin)assertExceptionThrown(@()...
                            cosmo_searchlight(varargin{:},...
                            'progress',0),'');
    ds=cosmo_synthetic_dataset();
    nh=cosmo_spherical_neighborhood(ds,'radius',1,'progress',false);
    measure=@(x,opt)cosmo_structjoin('samples',mean(x.samples,2));

    aet(struct,nh,measure);
    aet(ds,ds,measure);
    aet(ds,measure,nh);

    measure_bad=@(x,opt)cosmo_structjoin('samples',mean(x.samples,1));
    aet(ds,nh,measure_bad);

function test_searchlight_progress()
    if cosmo_skip_test_if_no_external('!evalc')
        return;
    end

    ds=cosmo_synthetic_dataset();
    nh=cosmo_spherical_neighborhood(ds,'count',2,'progress',false);
    measure=@(x,opt)cosmo_structjoin('samples',mean(x.samples,2));
    f=@()cosmo_searchlight(ds,nh,measure);
    res=evalc('f();');
    assert(~isempty(strfind(res,'[####################]')));


function rgb = skin_medium

% This returns a predefined color as [red green blue] values
%   red               = [255   0   0]/255;
%   green             = [  0 192   0]/255;
%   blue              = [  0   0 255]/255;
%   magenta           = [255 255   0]/255;
%   cyan              = [  0 255 255]/255;
%   yellow            = [255 255   0]/255;
%   white             = [255 255 255]/255;
%   black             = [  0   0   0]/255;
%
%   skull             = [140  85  85]/255
%   cortex            = [255 213 119]/255;
%   cortex_light      = [199 194 169]/255;
%   cortex_dark       = [100  97  85]/255;
%   skin              = [249 223 192]/255;
%   skin_light        = [249 223 192]/255;
%   skin_medium_light = [225 194 158]/255;
%   skin_medium       = [188 142 106]/255;
%   skin_medium_dark  = [155 102	65]/255;
%   skin_dark         = [ 91  71  61]/255;
%
% The different skin-based colors follow the Fitzpatrick scale with type I and II
% combined, and return RGB values that approximate those used by Apple in the emoji
% skin tones. See also https://emojipedia.org/emoji-modifier-sequence/
%
% If no specific skin tone is specified, this function returns a light skin color.
% This corresponds with that of one of the developers who approximated his own skin
% color more than 15 years ago upon the first implementation of this function.

% returns a predefined color as [red green blue] values
rgb = [188	142	106]/255;


function [cMap]=bloodbone(varargin)

switch nargin
    case 0
        n=250;
    case 1
        n=varargin{1};
end

b=blood(8*20);
bb=linspacen([1 0.95 0.8],b(1,:),3*20)';
cMap=[bb(1:end-1,:); b];
[cMap]=resampleColormap(cMap,n);

end

%%
% _*GIBBON footer text*_
%
% License: <https://github.com/gibbonCode/GIBBON/blob/master/LICENSE>
%
% GIBBON: The Geometry and Image-based Bioengineering add-On. A toolbox for
% image segmentation, image-based modeling, meshing, and finite element
% analysis.
%
% Copyright (C) 2006-2022 Kevin Mattheus Moerman and the GIBBON contributors
%
% This program 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.
%
% This program 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 this program.  If not, see <http://www.gnu.org/licenses/>.


function res = cputime_res
    % clock resolution:
    res = 0; while (res == 0), res = abs(cputime - cputime); end
end

%!test
%! res = cputime_res;


function info(DEM)

%INFO detailed information on GRIDobj instance
%
% Syntax
%
%     info(DEM)
%
% Description
%
%     info displays detailed information about an instance of an GRIDobj in
%     the command window.
%
% Input arguments
%
%     DEM    instance of GRIDobj
%
% See also: DISP
%
% Author: Wolfgang Schwanghart (w.schwanghart[at]geo.uni-potsdam.de)
% Date: 8. January, 2013

[ul] = [1 1 1] * DEM.refmat;
[lr] = [DEM.size 1] * DEM.refmat;

disp(' ')
disp('TopoToolbox GRIDobj')
disp(['  name:                  ' DEM.name])
disp(['  data type:             ' class(DEM.Z)])
disp(['  number of rows:        ' num2str(DEM.size(1))])
disp(['  number of columns:     ' num2str(DEM.size(2))])
disp(['  cellsize:              ' num2str(DEM.cellsize)])
disp(['  extent in x-direction: ' num2str(ul(1)) ' -- ' num2str(lr(1))])
disp(['  extent in y-direction: ' num2str(ul(2)) ' -- ' num2str(lr(2))])
disp(['  maximum z-value:       ' num2str(max(DEM.Z(:)))])
disp(['  minimum z-value:       ' num2str(min(DEM.Z(:)))])
disp(['  z-unit:                ' DEM.zunit])

if ~isempty(DEM.georef);
    try
        disp(['  coordinate system:     ' DEM.georef.GeoKeyDirectoryTag.GTCitationGeoKey])
    catch
        try
            disp(['  coordinate system:     ' DEM.georef.GeoKeyDirectoryTag.GeogCitationGeoKey]);
        catch
        end
    end
else
    disp(['  coordinate system:     ' 'undefined'])
end

disp(' ')


% @author: B. Schauerte
% @date:   2013
% @url:    http://cvhci.anthropomatik.kit.edu/~bschauer/

% create sub-folder
libs_folder = 'libs';
mkdir(libs_folder);

% install the spectral saliency toolbox
mkdir(libs_folder,'svst');
unzip('http://www.mathworks.com/matlabcentral/fileexchange/32455-spectral-phase-based-visual-saliency?download=true',fullfile(libs_folder,'svst',''));
cd(fullfile('libs','svst',''));
get_additional_files();
cd(fileparts(mfilename('fullpath')));

% install the progressbar package
mkdir(libs_folder,'prog');
unzip('http://www.mathworks.com/matlabcentral/fileexchange/6922-progressbar?download=true',fullfile(libs_folder,'prog',''));


function varargout = process_extract_values( varargin )
% PROCESS_EXTRACT_VALUES Extract values from multiple files and concatenate in a matrix file.
%
% USAGE:                   OutputFiles = process_extract_values('Run',     sProcess, sInputs)
%       [ConcatMat, FileType, matName] = process_extract_values('Extract', sProcess, sInputs, OPTIONS)

% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Francois Tadel, 2015-2022

eval(macro_method);
end

%% ===== GET DESCRIPTION =====
function sProcess = GetDescription() %#ok<DEFNU>
    % Description the process
    sProcess.Comment     = 'Extract values';
    sProcess.Category    = 'Custom';
    sProcess.SubGroup    = 'Extract';
    sProcess.Index       = 350;
    sProcess.Description = 'https://neuroimage.usc.edu/brainstorm/Tutorials/Statistics?highlight=%28Extract+values%29#Histograms';
    % Definition of the input accepted by this process
    sProcess.InputTypes  = {'data', 'results', 'timefreq', 'matrix'};
    sProcess.OutputTypes = {'data', 'results', 'timefreq', 'matrix'};
    sProcess.nInputs     = 1;
    sProcess.nMinFiles   = 1;

    % === GENERIC EXTRACT OPTIONS
    sProcess = DefineExtractOptions(sProcess);

    % === CONCATENATE
    sProcess.options.dim.Comment = {'Concatenate signals (dimension 1)', 'Concatenate time (dimension 2)'};
    sProcess.options.dim.Type    = 'radio';
    sProcess.options.dim.Value   = 2;
    % === OUTPUT COMMENT
    sProcess.options.Comment.Comment = 'Comment (empty=default): ';
    sProcess.options.Comment.Type    = 'text';
    sProcess.options.Comment.Value   = '';
end

%% ===== DEFINE EXTRACT OPTIONS =====
function sProcess = DefineExtractOptions(sProcess)
    % === SELECT: TIME WINDOW
    sProcess.options.timewindow.Comment    = 'Time window:';
    sProcess.options.timewindow.Type       = 'timewindow';
    sProcess.options.timewindow.Value      = [];
    sProcess.options.timewindow.InputTypes = {'data', 'results', 'timefreq', 'matrix'};
    % === SELECT: CHANNELS
    sProcess.options.sensortypes.Comment    = 'Sensor types or names (empty=all): ';
    sProcess.options.sensortypes.Type       = 'text';
    sProcess.options.sensortypes.Value      = '';
    sProcess.options.sensortypes.InputTypes = {'data'};
    % === SELECT: FREQUENCY RANGE
    sProcess.options.freqrange.Comment    = 'Frequency range: ';
    sProcess.options.freqrange.Type       = 'freqrange';
    sProcess.options.freqrange.Value      = [];
    sProcess.options.freqrange.InputTypes = {'timefreq'};
    % === SELECT: ROWS
    sProcess.options.rows.Comment    = 'Signals names or indices (empty=all): ';
    sProcess.options.rows.Type       = 'text';
    sProcess.options.rows.Value      = '';
    sProcess.options.rows.InputTypes = {'timefreq', 'matrix'};

    % === SCOUTS SELECTION ===
    sProcess.options.scoutsel.Comment    = 'Use scouts';
    sProcess.options.scoutsel.Type       = 'scout_confirm';
    sProcess.options.scoutsel.Value      = {};
    sProcess.options.scoutsel.InputTypes = {'results'};
    % === SCOUTS: FUNCTION
    sProcess.options.scoutfunc.Comment    = {'Mean', 'Max', 'PCA', 'Std', 'All', 'Scout function:'};
    sProcess.options.scoutfunc.Type       = 'radio_line';
    sProcess.options.scoutfunc.Value      = 1;
    sProcess.options.scoutfunc.InputTypes = {'results'};

    % === NORM XYZ
    sProcess.options.isnorm.Comment    = 'Compute absolute values (or norm for unconstrained sources)';
    sProcess.options.isnorm.Type       = 'checkbox';
    sProcess.options.isnorm.Value      = 0;
    sProcess.options.isnorm.InputTypes = {'results'};
    % === ABSOLUTE VALUE
    sProcess.options.isabs.Comment    = 'Compute absolute values';
    sProcess.options.isabs.Type       = 'checkbox';
    sProcess.options.isabs.Value      = 0;
    sProcess.options.isabs.InputTypes = {'data', 'timefreq', 'matrix'};

    % === AVERAGE: TIME
    sProcess.options.avgtime.Comment    = 'Average selected time window';
    sProcess.options.avgtime.Type       = 'checkbox';
    sProcess.options.avgtime.Value      = 0;
    sProcess.options.avgtime.InputTypes = {'data', 'results', 'timefreq', 'matrix'};
    % === AVERAGE: CHANNELS
    sProcess.options.avgrow.Comment    = 'Average selected signals';
    sProcess.options.avgrow.Type       = 'checkbox';
    sProcess.options.avgrow.Value      = 0;
    sProcess.options.avgrow.InputTypes = {'data', 'timefreq', 'matrix'};
    % === AVERAGE: FREQUENCY
    sProcess.options.avgfreq.Comment    = 'Average selected frequency band';
    sProcess.options.avgfreq.Type       = 'checkbox';
    sProcess.options.avgfreq.Value      = 0;
    sProcess.options.avgfreq.InputTypes = {'timefreq'};
    % === MATCH ROWS WITH NAMES
    sProcess.options.matchrows.Comment    = 'Match signals between files using their names';
    sProcess.options.matchrows.Type       = 'checkbox';
    sProcess.options.matchrows.Value      = 1;
    sProcess.options.matchrows.InputTypes = {'timefreq', 'matrix'};
end

%% ===== GET EXTRACT OPTIONS =====
function OPTIONS = GetExtractOptions(sProcess, sInputs)
    % Time window
    if isfield(sProcess.options, 'timewindow') && ~isempty(sProcess.options.timewindow) && ~isempty(sProcess.options.timewindow.Value) && iscell(sProcess.options.timewindow.Value)
        OPTIONS.TimeWindow = sProcess.options.timewindow.Value{1};
    else
        OPTIONS.TimeWindow = [];
    end
    % Sensor type
    if ismember(sInputs(1).FileType, {'data'}) && isfield(sProcess.options, 'sensortypes') && ~isempty(sProcess.options.sensortypes) && ~isempty(sProcess.options.sensortypes.Value)
        OPTIONS.SensorTypes = sProcess.options.sensortypes.Value;
    else
        OPTIONS.SensorTypes = [];
    end
    % Row indices
    if ismember(sInputs(1).FileType, {'results', 'timefreq', 'matrix'}) && isfield(sProcess.options, 'rows') && ~isempty(sProcess.options.rows) && ~isempty(sProcess.options.rows.Value)
        OPTIONS.Rows = sProcess.options.rows.Value;
    else
        OPTIONS.Rows = [];
    end
    % Freq indices
    if ismember(sInputs(1).FileType, {'timefreq'}) && isfield(sProcess.options, 'freqrange') && isfield(sProcess.options.freqrange, 'Value') && iscell(sProcess.options.freqrange.Value) && (length(sProcess.options.freqrange.Value) == 3) && (length(sProcess.options.freqrange.Value{1}) == 2)
        OPTIONS.FreqRange = sProcess.options.freqrange.Value{1};
    else
        OPTIONS.FreqRange = [];
    end
    % Scouts: Selection
    if isfield(sProcess.options, 'scoutsel') && isfield(sProcess.options.scoutsel, 'Value') && isfield(sProcess.options, 'scoutfunc') && isfield(sProcess.options.scoutfunc, 'Value')
        OPTIONS.ScoutSel = sProcess.options.scoutsel.Value;
        switch lower(sProcess.options.scoutfunc.Value)
            case {1, 'mean'}, OPTIONS.ScoutFunc = 'mean';
            case {2, 'max'},  OPTIONS.ScoutFunc = 'max';
            case {3, 'pca'},  OPTIONS.ScoutFunc = 'pca';
            case {4, 'std'},  OPTIONS.ScoutFunc = 'std';
            case {5, 'all'},  OPTIONS.ScoutFunc = 'all';
            otherwise,  bst_report('Error', sProcess, [], 'Invalid scout function.');  return;
        end
    else
        OPTIONS.ScoutSel = [];
    end
    % Absolute values / Norm
    OPTIONS.isAbsolute = 0;
    if isfield(sProcess.options, 'isabs') && isfield(sProcess.options.isabs, 'Value')
        OPTIONS.isAbsolute = sProcess.options.isabs.Value;
    end
    if isfield(sProcess.options, 'isnorm') && isfield(sProcess.options.isnorm, 'Value')
        OPTIONS.isAbsolute = sProcess.options.isnorm.Value;
    end
    % Averages
    if isfield(sProcess.options, 'avgtime') && isfield(sProcess.options.avgtime, 'Value')
        OPTIONS.isAvgTime = sProcess.options.avgtime.Value;
    else
        OPTIONS.isAvgTime = 0;
    end
    if isfield(sProcess.options, 'avgrow') && isfield(sProcess.options.avgrow, 'Value')
        OPTIONS.isAvgRow = sProcess.options.avgrow.Value;
    else
        OPTIONS.isAvgRow = 0;
    end
    if isfield(sProcess.options, 'avgfreq') && isfield(sProcess.options.avgfreq, 'Value')
        OPTIONS.isAvgFreq = sProcess.options.avgfreq.Value;
    else
        OPTIONS.isAvgFreq = 0;
    end
    % Match signals between files using their names
    if isfield(sProcess.options, 'matchrows') && isfield(sProcess.options.matchrows, 'Value') && ~isempty(sProcess.options.matchrows.Value)
        OPTIONS.isMatchRows = sProcess.options.matchrows.Value;
    else
        OPTIONS.isMatchRows = 1;
    end
end

%% ===== FORMAT COMMENT =====
function Comment = FormatComment(sProcess)
    Comment = [sProcess.Comment, ': [', process_extract_time('GetTimeString', sProcess) ']'];
    if isfield(sProcess.options, 'freqrange') && isfield(sProcess.options.freqrange, 'Value') && iscell(sProcess.options.freqrange.Value) && (length(sProcess.options.freqrange.Value) == 3) && (length(sProcess.options.freqrange.Value{1}) == 2)
        FreqRange = sProcess.options.freqrange.Value{1};
        if (FreqRange(1) == FreqRange(2))
            Comment = [Comment, ' ' num2str(FreqRange(1)) 'Hz'];
        else
            Comment = [Comment, ' ' num2str(FreqRange(1)) '-' num2str(FreqRange(2)) 'Hz'];
        end
    end
    if isfield(sProcess.options, 'sensortypes') && isfield(sProcess.options.sensortypes, 'Value') && ~isempty(sProcess.options.sensortypes.Value)
        Comment = [Comment, ' ', sProcess.options.sensortypes.Value];
    end
    if isfield(sProcess.options, 'rows') && isfield(sProcess.options.rows, 'Value') && ~isempty(sProcess.options.rows.Value)
        Comment = [Comment, ' ', sProcess.options.rows.Value];
    end
    if isfield(sProcess.options, 'scoutsel') && isfield(sProcess.options.scoutsel, 'Value') && iscell(sProcess.options.scoutsel.Value) && (length(sProcess.options.scoutsel.Value) >= 2) && ~isempty(sProcess.options.scoutsel.Value{1,2})
        listScouts = sProcess.options.scoutsel.Value{1,2};
        scoutComment = sprintf(' %s', listScouts{:});
        if (length(scoutComment) > 20)
            Comment = [Comment, ' ', num2str(length(listScouts)), ' scouts'];
        else
            Comment = [Comment, scoutComment];
        end
    end
    if (isfield(sProcess.options, 'isabs') && isfield(sProcess.options.isabs, 'Value') && sProcess.options.isabs.Value) || ...
       (isfield(sProcess.options, 'isnorm') && isfield(sProcess.options.isnorm, 'Value') && sProcess.options.isnorm.Value)
        Comment = [Comment, ' abs'];
    end
end

%% ===== RUN =====
function OutputFiles = Run(sProcess, sInputs) %#ok<DEFNU>
    % Initialize returned variable
    OutputFiles = {};

    % ===== GET OPTIONS =====
    % Get generic options
    OPTIONS = GetExtractOptions(sProcess, sInputs);
    % Output options
    OPTIONS.Dim = sProcess.options.dim.Value;

    % ===== EXTRACT =====
    [newMat, FileType] = Extract(sProcess, sInputs, OPTIONS);
    if isempty(newMat)
        return;
    end

    % ===== SAVE FILE =====
    % Get output study
    [sStudy, iStudy, Comment] = bst_process('GetOutputStudy', sProcess, sInputs);
    % Comment: forced in the options
    if isfield(sProcess.options, 'Comment') && isfield(sProcess.options.Comment, 'Value') && ~isempty(sProcess.options.Comment.Value)
        newMat.Comment = sProcess.options.Comment.Value;
    % Comment: Process default
    else
        newMat.Comment = FormatComment(sProcess);
        if (length(sInputs) > 1)
            newMat.Comment = [newMat.Comment, sprintf(' (%d files)', length(sInputs))];
        end
    end
    % Timefreq: the file tag might be longer than the file type
    if strcmpi(FileType, 'timefreq')
        fileTag = bst_process('GetFileTag', sInputs(1).FileName);
    else
        fileTag = FileType;
    end
    % Output filename
    OutputFiles{1} = bst_process('GetNewFilename', bst_fileparts(sStudy.FileName), [fileTag '_concat']);
    % Save on disk
    bst_save(OutputFiles{1}, newMat, 'v6');
    % Register in database
    db_add_data(iStudy, OutputFiles{1}, newMat);
end

%% ===== EXTRACT =====
function [newMat, newFileType, matName] = Extract(sProcess, sInputs, OPTIONS)

    % ===== GET INPUTS =====
    % Intialize returned variables
    newMat = [];
    newFileType = [];
    OutValue = cell(1, length(sInputs));
    OutNames = {};
    OutEvents = [];
    OutTFmask = [];
    ChannelFile = [];
    ChannelFlag = [];
    % Default options
    defOPTIONS = struct(...
        'TimeWindow',  [], ...
        'SensorTypes', [], ...
        'Rows',        [], ...
        'FreqRange',   [], ...
        'ScoutSel',    [], ...
        'ScoutFunc',   [], ...
        'isAbsolute',  0, ...
        'isAvgTime',   0, ...
        'isAvgRow',    0, ...
        'isAvgFreq',   0, ...
        'isMatchRows', 0, ...
        'Dim',         2, ...  % Dimension on which to concatenate (0,1,2,3,4)
        'isBadZero',   0);     % If 1, all the bad values are set to zero
    OPTIONS = struct_copy_fields(OPTIONS, defOPTIONS, 0);

    % ===== CHECK INPUTS =====
    % Types of files in input
    inFileType = sInputs(1).FileType;
    % Is time modified with these options
    isTimeModified = ~OPTIONS.isAvgTime && ((OPTIONS.Dim == 1) || (length(sInputs) == 1));
    % Forbid concatenating in dimension 1 multiple source files
    if strcmpi(inFileType, 'results') && (OPTIONS.Dim == 1) && (length(sInputs) > 1) && isempty(OPTIONS.ScoutSel)
        bst_report('Error', sProcess, sInputs(1), 'Cannot concatenate full source files in dimension 1.');
        return;
    end
    % Do not accept PAC files
    if ~isempty(strfind(sInputs(1).FileName, '_pac'))|| ~isempty(strfind(sInputs(1).FileName, '_dpac'))
        bst_report('Error', sProcess, sInputs(1), 'PAC files are not supported yet. Ask on the forum if you need it.');
        return;
    end

    % ===== PREPARE HISTORY =====
    % History
    strHistory = ['Extract data: ' FormatComment(sProcess)];
    if OPTIONS.isAvgRow
        strHistory = [strHistory ' AvgRow'];
    end
    if OPTIONS.isAvgTime
        strHistory = [strHistory ' AvgTime'];
    end
    if OPTIONS.isAvgFreq
        strHistory = [strHistory ' AvgFreq'];
    end
    if (length(sInputs) > 1)
        strHistory = [strHistory sprintf(' (%d files)', length(sInputs))];
    end
    % History: Add all the file names
    historyMat.History = [];
    historyMat = bst_history('add', historyMat, 'extract', strHistory);
    if ismember(OPTIONS.Dim, [1 2 3 4])
        historyMat.History = repmat(historyMat.History, length(sInputs) + 1, 1);
        historyMat.History(2:end,3) = cellfun(@(c)cat(2, ' - File: ', c), {sInputs.FileName}, 'UniformOutput', 0);
    end

    % ===== GET COMMON ROWS =====
    % Get the list of common rows (only the files with named signals: matrix and timefreq)
    if OPTIONS.isMatchRows && ismember(inFileType, {'matrix','timefreq','pmatrix','ptimefreq'})
        bst_progress('text', 'Reading the signal names...');
        % Cannot use this option in combination with connectivity files
        if ~isempty(strfind(sInputs(1).FileName, '_connect1')) || ~isempty(strfind(sInputs(1).FileName, '_connectn'))
            bst_report('Error', sProcess, [], 'EXTRACT> Cannot use the option "Match signals" on connectivity files.');
            return;
        end
        % Identify the list of all the rows in all the files
        [DestRowNames, AllRowNames, iRowsSrc, iRowsDest, Messages] = process_stdrow('GetUniformRows', {sInputs.FileName}, 'all');
        if ~isempty(Messages)
            bst_report('Warning', sProcess, [], Messages);
        end
        if isempty(DestRowNames)
            bst_report('Warning', sProcess, [], [...
                'EXTRACT> Could not find a common list of rows: Trying to concatenate directly the matrices...' 10 ...
                'EXTRACT> To avoid this warning, uncheck the option "Match signals" in the process options.']);
        end
    else
        DestRowNames = [];
    end

    % ===== LOOP ON FILES =====
    DisplayUnits = [];
    for iInput = 1:length(sInputs)
        bst_progress('text', sprintf('Reading input files... [%d/%d]', iInput, length(sInputs)));

        % === LOAD FILE ===
        isAbsApplied = 0;
        % Extract scout
        if strcmpi(sInputs(1).FileType, 'results') && ~isempty(OPTIONS.ScoutSel)
            % Options for LoadInputFile()
            LoadOptions.LoadFull    = 1;    % Load full source results
            LoadOptions.IgnoreBad   = 1;    % Do not read bad segments
            LoadOptions.ProcessName = func2str(sProcess.Function);
            LoadOptions.TargetFunc  = OPTIONS.ScoutFunc;
            LoadOptions.isNorm      = OPTIONS.isAbsolute;
            % Load reference signal
            sLoaded = bst_process('LoadInputFile', sInputs(iInput).FileName, OPTIONS.ScoutSel, OPTIONS.TimeWindow, LoadOptions);
            if isempty(sLoaded) || isempty(sLoaded.Data)
                bst_report('Error', sProcess, sInputs(iInput), 'Could not extract scouts.');
                return;
            end
            % If there is only one time point: collapse the data that was read
            if ~isempty(OPTIONS.TimeWindow) && (OPTIONS.TimeWindow(1) == OPTIONS.TimeWindow(2))
                sLoaded.Data = sLoaded.Data(:,1,:,:);
                sLoaded.Time = OPTIONS.TimeWindow(1);
            end
            % Add components labels
            if (sLoaded.nComponents == 3)
                sLoaded.RowNames = sLoaded.RowNames(:)';
                sLoaded.RowNames = [cellfun(@(c)cat(2,c,'.1'), sLoaded.RowNames, 'UniformOutput', 0); ...
                                    cellfun(@(c)cat(2,c,'.2'), sLoaded.RowNames, 'UniformOutput', 0); ...
                                    cellfun(@(c)cat(2,c,'.3'), sLoaded.RowNames, 'UniformOutput', 0)];
                sLoaded.RowNames = sLoaded.RowNames(:);
            end
            % Convert to matrix structure
            FileMat = db_template('matrixmat');
            FileMat.Value       = sLoaded.Data;
            FileMat.Description = sLoaded.RowNames;
            FileMat.Comment     = sLoaded.Comment;
            FileMat.Time        = sLoaded.Time;
            FileMat.nAvg        = sLoaded.nAvg;
            FileMat.Leff        = sLoaded.Leff;
            FileMat.SurfaceFile = sLoaded.SurfaceFile;
            FileMat.Atlas       = sLoaded.Atlas;
            FileMat.DisplayUnits= sLoaded.DisplayUnits;
            % Interpret as matrix file in the rest of the function
            inFileType = 'matrix';
            MatValues  = FileMat.Value;
            %isAbsApplied = 1;
        % Regular file
        else
            % Load file
            [FileMat, matName] = in_bst(sInputs(iInput).FileName, OPTIONS.TimeWindow);
            % File could not be loaded
            if isempty(FileMat) || isempty(FileMat.(matName))
                bst_report('Error', sProcess, sInputs(iInput), 'Could not load anything from the input file. Check the requested time window.');
                return;
            end
            % Unconstrained source files: compute norm
            if strcmpi(inFileType, 'results') && OPTIONS.isAbsolute && (FileMat.nComponents > 1)
                FileMat = process_source_flat('Compute', FileMat, 'rms');
                isAbsApplied = 1;
            end
            % Get values to extract
            MatValues = FileMat.(matName);
        end
        % Make sure that units are the same for all the files
        if isfield(FileMat, 'DisplayUnits') && ~isempty(FileMat.DisplayUnits)
            if isempty(DisplayUnits)
                DisplayUnits = FileMat.DisplayUnits;
            elseif ~strcmpi(FileMat.DisplayUnits, DisplayUnits)
                DisplayUnits = 'Mixed units';
            end
        end

        % Get the signals descriptions
        switch (inFileType)
            case 'data'
                % Load channel file (only if new one)
                if ~isequal(ChannelFile, sInputs(iInput).ChannelFile)
                    ChannelFile = sInputs(iInput).ChannelFile;
                    ChannelMat = in_bst_channel(ChannelFile);
                end
                % Select sensors
                if ~isempty(OPTIONS.SensorTypes)
                    % Find selected channels
                    iChannels = channel_find(ChannelMat.Channel, OPTIONS.SensorTypes);
                    if isempty(iChannels)
                        bst_report('Error', sProcess, sInputs(iInput), 'Could not load anything from the input file. Check the sensor selection.');
                        return;
                    end
                    % Keep only selected channels
                    MatValues = MatValues(iChannels,:,:);
                    Description = {ChannelMat.Channel(iChannels).Name}';
                    FileMat.ChannelFlag = FileMat.ChannelFlag(iChannels);
                else
                    Description = {ChannelMat.Channel.Name}';
                end
                % Set the bad values to zero
                if OPTIONS.isBadZero
                    MatValues(FileMat.ChannelFlag == -1, :) = 0;
                end
                % Report good/bad channels
                if isempty(ChannelFlag)
                    ChannelFlag = FileMat.ChannelFlag;
                % When setting the bad channels to zero: set as good all the channels that are good in at least one file
                elseif OPTIONS.isBadZero
                    ChannelFlag(FileMat.ChannelFlag == 1) = 1;
                % Regular case: Set as bad all the channels that are bad in at least one file
                else
                    ChannelFlag(FileMat.ChannelFlag == -1) = -1;
                end
                Freqs = [];
                TFmask = [];
            case 'results'
                Description = [];
                Freqs = [];
                TFmask = [];
            case 'timefreq'
                Description = FileMat.RowNames;
                % Get file frequency vector
                if iscell(FileMat.Freqs)
                    BandBounds = process_tf_bands('GetBounds', FileMat.Freqs);
                    FreqVector = mean(BandBounds,2);
                else
                    FreqVector = FileMat.Freqs;
                end
                % Rounds the frequency vector, to have the same level of precision as the process (3 significant digits)
                FreqVector = round(FreqVector * 1000) / 1000;
                % Get TFmask
                if isfield(FileMat, 'TFmask') && ~isempty(FileMat.TFmask) % && ((length(sInputs) == 1) || (OPTIONS.Dim == 0))
                    TFmask = FileMat.TFmask;
                else
                    TFmask = [];
                end
                % Keep only selected frequencies
                if ~isempty(OPTIONS.FreqRange) && ~isempty(FreqVector) && ~isequal(FreqVector, 0)
                    iFreqs = find((FreqVector >= OPTIONS.FreqRange(1)) & (FreqVector <= OPTIONS.FreqRange(2)));
                    if isempty(iFreqs)
                        bst_report('Error', sProcess, sInputs(iInput), 'Invalid frequency range.');
                        return;
                    end
                    MatValues = MatValues(:,:,iFreqs);
                    % Keep only the selected frequencies
                    if iscell(FileMat.Freqs)
                        Freqs = FileMat.Freqs(iFreqs,:);
                    else
                        Freqs = FileMat.Freqs(iFreqs);
                    end
                    FreqVector = FreqVector(iFreqs);
                    % Report selection on TFmask
                    if ~isempty(TFmask)
                        TFmask = TFmask(iFreqs,:);
                    end
                else
                    Freqs = FileMat.Freqs;
                end
            case 'matrix'
                Description = FileMat.Description;
                Freqs = [];
                TFmask = [];
        end

        % === KEEP COMMON ROWS ===
        if ~isempty(DestRowNames) && ~isempty(Description) && ~isequal(Description, DestRowNames)
            tmpValues = zeros(length(DestRowNames), size(MatValues,2), size(MatValues,3));
            tmpValues(iRowsDest{iInput},:,:) = MatValues(iRowsSrc{iInput},:,:);
            MatValues = tmpValues;
            Description = DestRowNames;
        end

        % === SELECT ROWS ===
        if ~isempty(OPTIONS.Rows)
            % Cannot do any sensor selection on connectivity NxN results
            if isfield(FileMat, 'RefRowNames') && (length(FileMat.RefRowNames) > 1)
                bst_report('Error', sProcess, sInputs(iInput), 'Cannot select rows for connectivity [NxN] results.');
                return;
            end
            % Cannot select signals in sources results
            if ~isempty(Description) && isnumeric(Description)
                bst_report('Error', sProcess, sInputs(iInput), 'Cannot select rows in source maps.');
                return;
            end
            % Try to find row names in the file
            SelRowNames = strtrim(str_split(OPTIONS.Rows, ',;'));
            iRows = find(ismember(Description, SelRowNames));
            % Else, try selecting by index number
            if isempty(iRows)
                iRows = str2num(OPTIONS.Rows);
                iRows((iRows < 1) | (iRows > length(Description))) = [];
            end
            % Else, try selecting by channel type (for time-frequency computed on channels data)
            if isempty(iRows) && strcmpi(inFileType, 'timefreq') && isequal(FileMat.DataType, 'data') && ~isempty(sInputs(iInput).ChannelFile)
                % Load channel file (only if new one)
                if ~isequal(ChannelFile, sInputs(iInput).ChannelFile)
                    ChannelFile = sInputs(iInput).ChannelFile;
                    ChannelMat = in_bst_channel(ChannelFile);
                end
                % Select sensors
                iChanTF = channel_find(ChannelMat.Channel, OPTIONS.Rows);
                % If something was found: try finding the corresponding channel names in the TF file
                if ~isempty(iChanTF)
                    iRows = find(ismember(Description, {ChannelMat.Channel(iChanTF).Name}));
                end
            end
            % Invalid indices selected
            if (any(iRows > size(MatValues,1)))
                bst_report('Error', sProcess, sInputs(iInput), 'Invalid signals selection.');
                return;
            end
            % If there is something to select
            if ~isempty(iRows)
                MatValues = MatValues(iRows,:,:);
                if ~isempty(Description)
                    Description = Description(iRows);
                end
            end
        else
            iRows = [];
        end
        % Error: Nothing selected
        if isempty(MatValues)
            bst_report('Error', sProcess, sInputs(iInput), 'Could not load anything from the input file. Please check the signal selection.');
            return;
        end

        % === ABSOLUTE VALUES ===
        if OPTIONS.isAbsolute && ~isAbsApplied
            MatValues = abs(MatValues);
        end
        % === AVERAGE ROWS ===
        if OPTIONS.isAvgRow && (size(MatValues,1) > 1)
            % Cannot average rows on connectivity NxN results
            if isfield(FileMat, 'RefRowNames') && (length(FileMat.RefRowNames) > 1)
                bst_report('Error', sProcess, sInputs(iInput), 'Cannot average rows for connectivity [NxN] results.');
                return;
            end
            % Average rows
            MatValues = mean(MatValues, 1);
            Description = {'AVG'};
        end
        % === AVERAGE TIME ===
        if OPTIONS.isAvgTime && (size(MatValues,2) > 1)
            % Compute average in time
            MatValues = mean(MatValues, 2);
            % If we are concatenating multiple files in time dimension: keep only one time point for each file
            if (OPTIONS.Dim == 2) && (length(sInputs) > 1)
                FileMat.Time = FileMat.Time(1);
            % If we are not concatenating in time: duplicate the matrix in time so we keep a time segment
            else
                MatValues = [MatValues, MatValues];
                FileMat.Time = [FileMat.Time(1), FileMat.Time(end)];
            end
            % Discard edge effects map
            TFmask = [];
        end
        % === AVERAGE FREQUENCY ===
        if OPTIONS.isAvgFreq && (size(MatValues,3) > 1) && ~isempty(Freqs)
            MatValues = mean(MatValues, 3);
            Freqs = {'AVG', [num2str(FreqVector(1)), ', ' num2str(FreqVector(end))], 'mean'};
            % Edge effects map: good only if good in all the frequencies
            if ~isempty(TFmask)
                TFmask = all(TFmask, 1);
            end
        end
        % === TFMASK ===
        if ~isempty(TFmask)
            if isempty(OutTFmask)
                OutTFmask = TFmask;
            elseif isequal(size(OutTFmask), size(TFmask))
                OutTFmask = OutTFmask & TFmask;
            end
        end

        % === STORE SIGNALS ===
        % Add to load of loaded blocks
        OutValue{iInput} = MatValues;
        % Concatenating in dimension 1: Add the file name
        if (OPTIONS.Dim == 1) && ~isempty(Description)
            % Sources maps: Cannot concatenate in dimension 1
            if ~iscell(Description)
                % If there are more than one file: error
                if (length(sInputs) > 1)
                    bst_report('Error', sProcess, sInputs, 'Cannot concatenate source files in dimension #1.');
                    return;
                end
            % Other file types
            else
                Description = cellfun(@(c)cat(2, c, ' @ ', sInputs(iInput).FileName), Description, 'UniformOutput', 0);
            end
        % No concatenation: Update the modified signals list
        elseif (OPTIONS.Dim == 0) && ~isempty(Description)
            switch (inFileType)
                case 'data',      FileMat.Description = Description;
                case 'timefreq',  FileMat.RowNames    = Description;
                case 'matrix',    FileMat.Description = Description;
            end
        end
        % First signal
        if (iInput == 1)
            % Save signal names
            OutNames = Description(:);
            % Get time definition
            if (length(FileMat.Time) == 1)
                if (OPTIONS.Dim == 2) && (length(sInputs) > 1)
                    sfreq = 1;
                    tstart = 1;
                else
                    sfreq = 1000;
                    tstart = FileMat.Time(1);
                end
            elseif isfield(FileMat, 'TimeBands') && ~isempty(FileMat.TimeBands)
                sfreq = 1;
                tstart = 1;
                FileMat.TimeBands = [];
            else
                sfreq = 1/(FileMat.Time(2) - FileMat.Time(1));
                tstart = FileMat.Time(1);
            end
            % Save first file in memory: will be used as a base (or store all the files if not need of concatenation)
            if (OPTIONS.Dim == 0)
                LoadedMat = cell(1, length(sInputs));
            else
                LoadedMat = cell(1,1);
            end
            LoadedMat{1} = FileMat;
        else
            % Make sure the dimensions are ok
            switch (OPTIONS.Dim)
                case 1
                    isDimOk = (size(OutValue{1},2) == size(OutValue{iInput},2)) && (size(OutValue{1},3) == size(OutValue{iInput},3));
                    % Concatenate names of signals
                    OutNames = cat(1, OutNames, Description(:));
                case 2
                    isDimOk = (size(OutValue{1},1) == size(OutValue{iInput},1)) && (size(OutValue{1},3) == size(OutValue{iInput},3));
                case 3
                    error('Dimension 3 is not supported for concatenation.');
                case 4
                    isDimOk = (size(OutValue{1},1) == size(OutValue{iInput},1)) && (size(OutValue{1},2) == size(OutValue{iInput},2)) && (size(OutValue{1},3) == size(OutValue{iInput},3));
                case 0   % No check, just stacking all the loaded files
                    isDimOk = 1;
                    LoadedMat{iInput} = FileMat;
                otherwise
                    error('Concatenation dimension is not specified.');
            end
            % Error management
            if ~isDimOk
                bst_report('Error', sProcess, sInputs(iInput), sprintf('The dimensions of file #%d do not match file #1.', iInput));
                return;
            end
        end

        % === EVENTS ===
        if isfield(FileMat, 'Events') && ~isempty(FileMat.Events) && isTimeModified
            if isempty(OutEvents)
                OutEvents = FileMat.Events;
            else
                sFile.events = OutEvents;
                sFile.prop.sfreq = sfreq;
                sFile = import_events(sFile, [], FileMat.Events);
                OutEvents = sFile.events;
            end
        end
    end

    % ===== CONCATENATE SIGNALS ====
    bst_progress('text', 'Concatenating files...');
    % Concatenate data matrix
    if (OPTIONS.Dim ~= 0)
        OutValue = {cat(OPTIONS.Dim, OutValue{:})};
    end

    % ===== CREATE STRUCTURE =====
    % Determine the output file format
    switch (inFileType)
        case 'data'
            if OPTIONS.isAvgRow || ((OPTIONS.Dim == 1) && (length(sInputs) > 1)) || (length(ChannelMat.Channel) ~= size(OutValue{1},1))
                newFileType = 'matrix';
            else
                newFileType = inFileType;
            end
        case 'results'
            if OPTIONS.isAvgRow || ((OPTIONS.Dim == 1) && (length(sInputs) > 1)) || ~isempty(iRows)
                newFileType = 'matrix';
            else
                newFileType = inFileType;
            end
        case 'timefreq'
            % Simplify output (not available for statistics)
            % if (length(FreqVector) == 1) && (OPTIONS.Dim ~= 0) && (OPTIONS.Dim ~= 4)
            if (length(FreqVector) == 1) && (OPTIONS.Dim ~= 0) && (OPTIONS.Dim ~= 4) && (length(sInputs) > 1)   %% ADDED FOR EB TO PLOT TOPOGRAPHY OF EXTRACT FREQUENCY OF A SINGLE FILE
                % TF of data or matrix
                if iscell(OutNames)
                    newFileType = 'matrix';
                % TF of sources
                else
                    newFileType = 'results';
                end
            else
                newFileType = inFileType;
            end
        case 'matrix'
            newFileType = inFileType;
    end
    % If the input and output file types are the same: keep the loaded structure
    if isequal(newFileType, inFileType)
        newMat = LoadedMat;
    % Else: Create a new empty sources structure
    elseif strcmpi(newFileType, 'results')
        newMat = {db_template('resultsmat')};
        % Copy source information
        newMat{1}.HeadModelFile = LoadedMat{1}.HeadModelFile;
        newMat{1}.HeadModelType = LoadedMat{1}.HeadModelType;
        newMat{1}.SurfaceFile   = LoadedMat{1}.SurfaceFile;
        newMat{1}.nAvg          = LoadedMat{1}.nAvg;
        newMat{1}.Leff          = LoadedMat{1}.Leff;
    % Else: Create a new empty matrix structure
    else
        newMat = {db_template('matrixmat')};
        newMat{1}.nAvg = LoadedMat{1}.nAvg;
        newMat{1}.Leff = LoadedMat{1}.Leff;
        if (OPTIONS.Dim == 0)
            newMat = repmat(newMat, 1, length(LoadedMat));
            % Copy channel names
            if strcmpi(inFileType, 'data')
                for i = 1:length(LoadedMat)
                    newMat{i}.Description = LoadedMat{i}.Description;
                    newMat{i}.ChannelFlag = LoadedMat{i}.ChannelFlag;
                    newMat{i}.nAvg        = LoadedMat{i}.nAvg;
                    newMat{i}.Leff        = LoadedMat{i}.Leff;
                end
            end
        end
    end
    % Process all the structures in the same way
    for i = 1:length(LoadedMat)
        % Replicate value in time for display if necessary
        if ismember(newFileType, {'data', 'results', 'matrix'}) && (size(OutValue{i},2) == 1) && (OPTIONS.Dim ~= 0)
            OutValue{i} = [OutValue{i}, OutValue{i}];
        end
        % Copy the values in the appropriate fields
        switch (newFileType)
            case 'data'
                newMat{i}.F           = OutValue{i};
                newMat{i}.Events      = OutEvents;
                newMat{i}.ChannelFlag = ChannelFlag;
            case 'results'
                newMat{i}.ImageGridAmp = OutValue{i};
                % Disconnect from parent file if the time was altered
                if isTimeModified || (length(sInputs) > 1)
                    newMat{i}.DataFile = [];
                end
            case 'timefreq'
                newMat{i}.TF     = OutValue{i};
                newMat{i}.TFmask = OutTFmask;
                newMat{i}.Freqs  = Freqs;
                % If there is not concatenation: do not force the row names to be all the same
                if (OPTIONS.Dim ~= 0)
                    newMat{i}.RowNames = OutNames;
                end
                % Disconnect from parent file if the time was altered
                if (length(sInputs) > 1)
                    newMat{i}.DataFile = [];
                end
                % Change the type of data if necessary
                switch (newMat{i}.DataType)
                    case 'data'
                        if OPTIONS.isAvgRow || ((OPTIONS.Dim == 1) && (length(sInputs) > 1)) || ~isempty(iRows)
                            newMat{i}.DataType = 'matrix';
                        end
                    case 'results'
                        if OPTIONS.isAvgRow || ((OPTIONS.Dim == 1) && (length(sInputs) > 1)) || ~isempty(iRows)
                            newMat{i}.DataType = 'matrix';
                        end
                end
            case 'matrix'
                newMat{i}.Value  = OutValue{i};
                newMat{i}.Events = OutEvents;
                % If there is not concatenation: do not force the row names to be all the same
                if (OPTIONS.Dim ~= 0)
                    newMat{i}.Description = OutNames;
                end
        end
        % Final time vector
        newMat{i}.Time = (0:size(OutValue{i},2)-1) ./ sfreq + tstart;
        % Add units
        newMat{i}.DisplayUnits = DisplayUnits;
        % Add common history field
        newMat{i} = bst_history('add', newMat{i}, historyMat.History);
    end
    % Just return one structure
    if (OPTIONS.Dim ~= 0)
        newMat = newMat{1};
    end
    % Return fieldname
    switch (newFileType)
        case 'data',     matName = 'F';
        case 'results',  matName = 'ImageGridAmp';
        case 'timefreq', matName = 'TF';
        case 'matrix',   matName = 'Value';
    end
end


function varargout=loadbnm(fname)
% a compact binary matrix format.
%
% [M1,M2,M3,...]=loadbnm(fname);
%
% see savebnm
%
% Aslak Grinsted April 2004

% -------------------------------------------------------------------------
%   Copyright (C) 2002-2004, Aslak Grinsted
%   This software may be used, copied, or redistributed as long as it is not
%   sold and this copyright notice is reproduced on each copy made.  This
%   routine is provided as is without any express or implied warranties
%   whatsoever.

if (length(strfind(fname,'.'))==0)
    fname=[fname '.bnm'];
end

skip=(nargout==0); % should it skip loading data and just show info for the file?

header='BNM Matrix File';

[fid,message]=fopen(fname,'r','ieee-le');
if (fid<0)
    message=strrep(message,'Sorry. No help in figuring out the problem . . .','');
    error(['Could not open the specified file. ' message ' file=' fname])
end

hdr=[fread(fid,length(header),'uchar')' ''];
if ~strcmp(hdr,header)
    error([fname 'is not a ' header ' file!']);
end

version=fread(fid,1,'int16');
if version>2
    warning('File format version > 2')
end
if (version==1)
    nout=1;
else
    nout=fread(fid,1,'int16');
end

nn=min(nargout,nout);
if skip
    disp(sprintf('File: "%s" , Version: %i , MatrixCount : %i\n',fname,version,nout));
    nn=nout;
end

for ii=1:nn
    infos=fread(fid,2,'int16');
    sz=fread(fid,infos(1),'uint32')';
    rl=infos(2); %isreal???
    if skip
        disp(sprintf('Matrix %i,  size: [%s], isimag: %i',ii,num2str(sz),~rl))
        fseek(fid,prod(sz)*(1+(~rl))*4,0);
    else
        varargout{ii}=fread(fid,prod(sz),'float');
        if ~rl %if imaginary numbers...
            varargout{ii}=varargout{ii}+fread(fid,prod(sz),'float')*i;
        end
        varargout{ii}=reshape(varargout{ii},sz);
        %varargout{ii}=M;
    end
end

fclose(fid);

function s=loadstring(fid)
ll=fread(fid,1,'uint32');
s=[fread(fid,ll,'uchar')' ''];


function mprob = convBaron(prob,opts)
%CONVBARON Convert OPTI problem to BARON Nonlinear problem
%
%   mprob = convBaron(prob,opts)

%   Copyright (C) 2013 Jonathan Currie (IPL)

%Ensure all args passed
if(nargin < 2)
    error('You must supply both the problem + options');
end
if(~isstruct(prob) || ~isstruct(opts))
    error('Both prob + opts must be structures!');
end

%Get Warning Level
if(strcmpi(opts.warnings,'all'))
    warn = 2;
elseif(strcmpi(opts.warnings,'critical'))
    warn = 1;
else
    warn = 0;
end

%Assign Common Args
mprob.A = prob.A;
mprob.rl = prob.rl;
mprob.ru = prob.ru;
mprob.lb = prob.lb;
mprob.ub = prob.ub;
if(isfield(prob,'int') && isfield(prob.int,'str'))
    mprob.xtype = prob.int.str;
end

%Check if the user wants to use BARON LP/QP/QCQP solver
switch(lower(prob.type))
    case {'lp','milp','bilp','qp','qcqp','miqp','miqcqp'}
        if(warn > 1)
            optiwarn('OPTI:QCQP2NLP','Quadratic Program converted to (MI)NLP to suit BARON interface');
        end
        [prob,opts] = QCQP2NLP(prob,opts);
    case {'uno','nlp','minlp'}
        %nothing
    otherwise
        error('Problem type ''%s'' is not supported by BARON',prob.type);
end

%Assign remaining args
mprob.fun = prob.fun;
mprob.nlcon = prob.nlcon;
mprob.cl = prob.cl;
mprob.cu = prob.cu;
mprob.x0 = prob.x0;

% Set BARON options (optiset options override common ones)
%Build baronset struct, adding any passed options (ignored by baronset if not recognised)
options = baronset(opts.solverOpts);
%Add OPTISET options
options.epsa = opts.tolafun;
options.epsr = opts.tolrfun;
options.inttol = opts.tolint;
options.maxiter = opts.maxiter;
options.maxtime = opts.maxtime;
options.prlevel = dispLevel(opts.display);
%Save Options Structure
mprob.options = options;

function  print_level = dispLevel(lev)
%Return IPOPT compatible display level
switch(lower(lev))
    case'off'
        print_level = 0;
    case 'iter'
        print_level = 1;
    case 'final'
        print_level = 1;
end


function test_ft_plot_dipole

% MEM 1gb
% WALLTIME 00:10:00
% DEPENDENCY ft_plot_dipole

figure
ft_plot_dipole([0 0 0], [1 2 3])


function installBayesFactor
% Install the bayesFactor class in Matlab.

lmm = which('linearmixedmodel');
if isempty(lmm)
    disp('Hmm. You don''t seem to have the the Statistics and Machine Learning Toolbox. This is not likely to work');
end

filename = mfilename('fullpath');
pth = fileparts(filename);
addpath(pth)
savepath;

% Store the default options as prefs on this machine.
% The user can change these with bf.options
bf.options;
end


function croppedImages = crop_all(this, rangeIndexArray, otherImages)
%Crops all given images to same range as this image
%
%   Y = MrDataNd()
%   Y.crop_all(rangeIndexArray, otherImages)
%
% This is a method of class MrDataNd.
%
% IN
%   rangeIndexArray     cell(1,2*nDimsToCrop) of pairs of
%                       dimLabel, [startSampleIndex endSampleIndex]
%                       referring to dimensions of reference image for
%                       cropping (this), e.g.,
%                       {'x', [10 100], 'y', [50 120], 'z', [10 30]}
%
%   otherImages         cell(nImages,1) of MrDataNd
%                           OR
%                       single MrDataNd object to be cropped
% OUT
%   croppedImages       cell(nImages+1,1) of equally cropped images,
%                       including reference image for cropping (this)
% EXAMPLE
%   crop_all
%
%   See also MrDataNd

% Author:   Saskia Bollmann & Lars Kasper
% Created:  2019-09-06
% Copyright (C) 2019 Institute for Biomedical Engineering
%                    University of Zurich and ETH Zurich
%
% This file is part of the TAPAS UniQC Toolbox, which is released
% under the terms of the GNU General Public License (GPL), version 3.
% You can redistribute it and/or modify it under the terms of the GPL
% (either version 3 or, at your option, any later version).
% For further details, see the file COPYING or
%  <http://www.gnu.org/licenses/>.

if ~iscell(otherImages)
    otherImages = {otherImages};
end
nImages = numel(otherImages) + 1;

%% create
croppedImages = cell(nImages,1);

dimLabels = rangeIndexArray(1:2:end);
cropIndexRanges = rangeIndexArray(2:2:end);

%% convert index crop of this image into range and do the crop

nDims = numel(dimLabels);
selectionIndexArray = cell(1, 2*nDims);

%% convert cropping to current image and apply crop
croppedImages = [{this}; reshape(otherImages, [], 1)];
for iImage = 1:nImages

    %% setup selectionIndexArray for cropping, dim by dim
    for iDim = 1:nDims
        cropIndexRange = cropIndexRanges{iDim};
        dimLabel = dimLabels{iDim};
        idxDim = this.dimInfo.get_dim_index(dimLabel);
        crop = this.dimInfo.index2sample(cropIndexRange, idxDim);
        res = croppedImages{iImage}.dimInfo.resolutions;
        selectionIndexArray{2*iDim-1} = dimLabel;
        selectionIndexArray{2*iDim} = crop(1):res(idxDim):crop(2);
    end

    %% crop
    croppedImages{iImage} = croppedImages{iImage}.select('type', 'samples', ...
        selectionIndexArray{:});
end