classdef Slicer < handle
%GUI for exploration of 3D images, using Object Oriented Programming.
%
%   SLICER is an graphical interface to explore slices of a 3D image.
%   Index of the current slice is given under the slider, mouse position as
%   well as cursor value are indicated when mouse is moved over image, and
%   scrollbars allow to navigate within image.
%
%   SLICER should work with any kind of 3D images: binary, gray scale
%   (integer or floating-point) or color RGB.
%
%   slicer(IMG);
%   where IMG is a preloaded M*N*P matrix, opens the slicer GUI,
%   initialized with image IMG.
%   User can change current slice with the slider to the left, X and Y
%   position with the two corresponding sliders, and change the zoom in the
%   View menu.
%
%   Slicer(IMGNAME, ...);
%   Load the stack specified by IMGNAME. It can be either a tif bundle, the
%   first file of a series, or a 3D image stored in one of the medical
%   image format:
%   * DICOM (*.dcm)
%   * Analyze (*.hdr)
%   * MetaImage (*.mhd, *.mha)
%   It is also possible to import a raw data file, from the File->Import
%   menu.
%
%   Slicer
%   without argument opens with an empty interface, and allows to open an
%   image through the menu.
%
%   Slicer(..., PARAM, VALUE);
%   Specifies one or more display options as name-value parameter pairs.
%   Available parameter names are:
%
%   * 'Slice'       the display uses slice given by VALUE as current slice
%
%   * 'Name'        gives a name to the image (for display in title bar)
%
%   * 'Spacing'     specifies the size of voxel elements. VALUE is a 1-by-3
%         row vector containing spacing in x, y and z direction.
%
%   * 'Origin'      specifies coordinate of first voxel in user space
%
%   * 'UnitName'    the name of the unit used for spatial calibration (can
%         be any string, default is empty).
%
%   * 'DisplayRange' the values of min and max gray values to display. The
%         default behaviour is to use [0 255] for uint8 images, or to
%         compute bounds such as 95% of the voxels are converted to visible
%         gray levels for other image types.
%
%   * 'ColorMap'    The colormap used for displaying grayscale images
%         (default is gray). Should be a N-by-3 array of double, with N=256
%         for grayscale or intensity images, as N=the number of labels for
%         label images, and N=2 for binary images.
%
%   * 'BackgroundColor'    the color used as background for label images.
%
%   * 'ImageType'   The type of image, used for adapting display. Can be
%           one of 'binary', 'grayscale', 'intensity', 'label', 'color',
%           'vector', 'none'. Default value is assessed from data type and
%           size.
%
%   * 'Parent'  another instance of Slicer, that is used to initialize
%           several parameters like spatial resolution, display range, LUT
%           for display...
%
%
%   Example
%   % Explore human brain MRI
%     metadata = analyze75info('brainMRI.hdr');
%     I = analyze75read(metadata);
%     Slicer(I);
%
%   % show the 10-th slice, and add some setup
%     Slicer(I, 'Slice', 10, 'Spacing', [1 1 2.5], 'Name', 'Brain', 'DisplayRange', [0 90]);
%
%   See also
%     imStacks, imscrollpanel
%
%   Requires
%       GUI Layout Toolbox version 2.0, or 1.17 (try to switch depending on
%       Matlab version)
%

% ------
% Author: David Legland
% e-mail: david.legland@inrae.fr
% Created: 2011-04-12,    using Matlab 7.9.0.529 (R2009b)
% https://github.com/mattools/matImage
% Copyright 2011 INRAE - BIA - BIBS

%% Properties
properties
    % Image data stored as a 3D or 4D array, in YX(C)Z order.
    ImageData;

    % type of image. Can be one of:
    % 'binary'
    % 'grayscale'
    % 'intensity'
    % 'label'
    % 'color'
    % 'vector'
    % 'none'
    ImageType;

    % size of the reference image (1-by-3 row vector, in XYZ order)
    ImageSize;

    % extra info for image, such as the result of imfinfo
    ImageInfo;

    % extra info for image, such as the result of imfinfo
    ImageName;

    % displayed data (2D image)
    Slice;

    % z-index of slice within 3D image
    SliceIndex;

    % used to adjust constrast of the slice
    DisplayRange;

    % Look-up table for display of uint8, label and binary images.
    % can be empty, in obj case gray colormap is assumed
    ColorMap = [];

    % background color for label to RGB conversion. Given as RGB triplet of
    % values bewteen 0 and 1. Default is white = (1,1,1).
    BgColor = [1 1 1];

    % calibration information for image
    VoxelOrigin;
    VoxelSize;
    VoxelSizeUnit;

    % shortcut for avoiding many tests. Should be set to true when either
    % voxelOrigin, voxelsize or voxelSizeUnit is different from its default
    % value.
    Calibrated = false;

    % keep last path for opening new images
    LastPath = pwd;

    % list of handles to the widgets.
    % Structure with following fields:
    % * Figure:     the main figure
    % * SubFigures: a list of figures that can be closed when the main
    %           figure is closed
    % * Image:      the image display
    % * ImageAxis:  the axis containing the image
    % * ZSlider:    the slider used to change the slice index
    % * ZEdit:      the edit uicontrol used to change slice index
    % * ZProfileFigure used to display the profile along z
    % * ZProfileAxis used to display the profile along z
    Handles;

    % the last position of mouse click, in user coordinates.
    LastClickedPoint = [];
end

%% Constructor
methods
    function obj = Slicer(varargin)

        % call parent constructor
        obj = obj@handle();

        % initialize structure containing handles
        obj.Handles = struct();

        % keep pointer to current path
        obj.LastPath = pwd;

        % initialize using image given as argument
        if ~isempty(varargin)
            var = varargin{1};
            if isa(var, 'Image')
                setupImageFromClass(obj, var);
            elseif ischar(var)
                setupImageFromFile(obj, var);
            else
                if ndims(var) < 3 %#ok<ISMAT>
                    error('Requires input image with at least 3 dimensions');
                end
                setupImageData(obj, var, inputname(1));
            end
            varargin(1) = [];

            if isempty(obj.ImageData)
                return;
            end

        else
            obj.ImageData = [];
            obj.ImageType = 'none';
        end

        % parses input arguments, given as list of name-value pairs
        parsesInputArguments();

        % add checkup on visible image slice
        if ~isempty(obj.ImageData)
            obj.SliceIndex = min(obj.SliceIndex, obj.ImageSize(3));
        end

        updateCalibrationFlag(obj);

        % create default figure
        fig = figure();
        set(fig, 'Menubar', 'none');
        set(fig, 'NumberTitle', 'off');
        set(fig, 'Name', 'Slicer');

        obj.Handles.Figure = fig;
        obj.Handles.SubFigures = [];

        % create main figure menu and layout
        setupMenuBar(obj);
        setupLayout(fig);

        % setup new image display
        if ismember(obj.ImageType, {'label', 'binary'})
            maxi = max(obj.ImageData(:));
            obj.DisplayRange  = [0 maxi];

            if isempty(obj.ColorMap)
                obj.ColorMap = jet(double(maxi));
            end
            colormap([obj.BgColor ; obj.ColorMap]);
        end
        updateSlice(obj);
        displayNewImage(obj);
        updateTitle(obj);

        % setup listeners associated to the figure
        set(fig, 'WindowButtonDownFcn', @obj.mouseButtonPressed);
        set(fig, 'WindowButtonMotionFcn', @obj.mouseDragged);
        set(fig, 'WindowScrollWheelFcn', @obj.mouseWheelScrolled);
        set(fig, 'NextPlot', 'new');

        function parsesInputArguments()
            % iterate over couples of input arguments to setup display
            while length(varargin) > 1
                param = varargin{1};
                switch lower(param)
                    case 'parent'
                        % copy some settings from parent Slicer
                        parent = varargin{2};
                        obj.SliceIndex     = parent.SliceIndex;
                        obj.VoxelSize      = parent.VoxelSize;
                        obj.VoxelOrigin    = parent.VoxelOrigin;
                        obj.VoxelSizeUnit  = parent.VoxelSizeUnit;
                        obj.Calibrated     = parent.Calibrated;
                        obj.ImageType      = parent.ImageType;
                        obj.DisplayRange   = parent.DisplayRange;
                        obj.ColorMap       = parent.ColorMap;
                        obj.BgColor        = parent.BgColor;

                    case 'slice'
                        % setup initial slice
                        pos = varargin{2};
                        obj.SliceIndex = pos(1);

                    % Setup spatial calibration
                    case 'spacing'
                        obj.VoxelSize = varargin{2};
                        if ~obj.Calibrated
                            obj.VoxelOrigin = [0 0 0];
                        end
                        obj.Calibrated = true;

                    case 'origin'
                        obj.VoxelOrigin = varargin{2};
                        obj.Calibrated = true;

                    case 'unitname'
                        obj.VoxelSizeUnit = varargin{2};
                        obj.Calibrated = true;

                    case 'name'
                        obj.ImageName = varargin{2};

                    case 'imagetype'
                        obj.ImageType = varargin{2};

                    % Setup image display
                    case 'displayrange'
                        obj.DisplayRange = varargin{2};
                    case 'colormap'
                        obj.ColorMap = varargin{2};
                    case 'backgroundcolor'
                        obj.BgColor = varargin{2};

                    otherwise
                        error(['Unknown parameter name: ' param]);
                end
                varargin(1:2) = [];
            end

        end

        function setupLayout(hf)
            % Creates the widgets that constitute the Slicer main frame

            if verLessThan('matlab', '8.4')
                % horizontal layout
                mainPanel = uiextras.HBox('Parent', hf, ...
                    'Units', 'normalized', ...
                    'Position', [0 0 1 1]);

                % panel for slider + slice number
                leftPanel = uiextras.VBox('Parent', mainPanel, ...
                    'Units', 'normalized', ...
                    'Position', [0 0 1 1]);
            else
                % horizontal layout
                mainPanel = uix.HBox('Parent', hf, ...
                    'Units', 'normalized', ...
                    'Position', [0 0 1 1]);

                % panel for slider + slice number
                leftPanel = uix.VBox('Parent', mainPanel, ...
                    'Units', 'normalized', ...
                    'Position', [0 0 1 1]);
            end

            if ~isempty(obj.ImageData)
                % slider for slice
                zmin = 1;
                zmax = obj.ImageSize(3);
                zstep1 = 1/zmax;
                zstep2 = min(10/zmax, .5);
                obj.Handles.ZSlider = uicontrol('Style', 'slider', ...
                    'Parent', leftPanel, ...
                    'Min', zmin, 'Max', zmax', ...
                    'SliderStep', [zstep1 zstep2], ...
                    'Value', obj.SliceIndex, ...
                    'Callback', @obj.onSliceSliderChanged, ...
                    'BackgroundColor', [1 1 1]);

                % code for dragging the slider thumb
                % @see http://undocumentedmatlab.com/blog/continuous-slider-callback
                if verLessThan('matlab', '8.4')
                    hListener = handle.listener(obj.Handles.ZSlider, ...
                        'ActionEvent', @obj.onSliceSliderChanged);
                    setappdata(obj.Handles.ZSlider, 'sliderListeners', hListener);
                else
                    addlistener(obj.Handles.ZSlider, ...
                        'ContinuousValueChange', @obj.onSliceSliderChanged);
                end

                % edition of slice number
                obj.Handles.ZEdit = uicontrol('Style', 'edit', ...
                    'Parent', leftPanel, ...
                    'String', num2str(obj.SliceIndex), ...
                    'Callback', @obj.onSliceEditTextChanged, ...
                    'BackgroundColor', [1 1 1]);

                if verLessThan('matlab', '8.4')
                    leftPanel.Sizes = [-1 20];
                else
                    leftPanel.Heights = [-1 20];
                end
            end

            % panel for image display + info panel
            if verLessThan('matlab', '8.4')
                rightPanel = uiextras.VBox('Parent', mainPanel);
            else
                rightPanel = uix.VBox('Parent', mainPanel);
            end

            % scrollable panel for image display
            scrollPanel = uipanel('Parent', rightPanel);

            if ~isempty(obj.ImageData)
                ax = axes('parent', scrollPanel, ...
                    'units', 'normalized', ...
                    'position', [0 0 1 1]);
                obj.Handles.ImageAxis = ax;

                % initialize image display with default image.
                hIm = imshow(zeros([10 10], 'uint8'), 'parent', ax);
                obj.Handles.ScrollPanel = imscrollpanel(scrollPanel, hIm);
                set(scrollPanel, 'resizeFcn', @obj.onScrollPanelResized);

                % keep widgets handles
                obj.Handles.Image = hIm;
            end

            % info panel for cursor position and value
            obj.Handles.InfoPanel = uicontrol(...
                'Parent', rightPanel, ...
                'Style', 'text', ...
                'String', ' x=    y=     I=', ...
                'HorizontalAlignment', 'left');

            % set up relative sizes of layouts
            if verLessThan('matlab', '8.4')
                rightPanel.Sizes = [-1 20];
                mainPanel.Sizes = [30 -1];
            else
                rightPanel.Heights = [-1 20];
                mainPanel.Widths = [30 -1];
            end

            % once each panel has been resized, setup image magnification
            if ~isempty(obj.ImageData)
                api = iptgetapi(obj.Handles.ScrollPanel);
                mag = api.findFitMag();
                api.setMagnification(mag);
            end
        end

    end % constructor

end % construction function

%% General use methods
methods
    function createNewSlicer(obj, imgData, newName, varargin)
        % Creates a new Slicer figure with given data, and keeping the
        % settings of the current slicer.
        options = {...
            'spacing', obj.VoxelSize, ...
            'origin', obj.VoxelOrigin, ...
            'slice', obj.SliceIndex, ...
            'name', newName};

        Slicer(imgData, options{:}, varargin{:});
    end

    function setupImageData(obj, img, imgName)
        % replaces all informations about image

        % Setup image data and type
        obj.ImageData = img;
        obj.ImageType = 'grayscale';

        % compute size, and detect RGB
        dim = size(img);
        nd = length(dim);

        % check image type
        if nd > 3
            % Image is 3D color or vector

            % extreme values
            valMin = min(img(:));
            valMax = max(img(:));

            % determines image nature
            if dim(3) ~= 3 || valMin < 0 || (isfloat(img) && valMax > 1)
                obj.ImageType = 'vector';
            else
                obj.ImageType = 'color';
            end

            % keep only spatial dimensions
            dim = dim([1 2 4]);
        elseif nd == 2
            dim = [dim 1];
        end

        % convert to use dim(1)=x, dim(2)=y, dim(3)=z
        obj.ImageSize = dim([2 1 3]);

        % eventually compute grayscale extent
        if ~strcmp(obj.ImageType, 'color')
            [mini, maxi] = computeGrayScaleExtent(obj);
            obj.DisplayRange  = [mini maxi];
        end

        % empty colorMap by default
        obj.ColorMap = [];

        % default slice index is in the middle of the stack
        obj.SliceIndex = ceil(dim(3) / 2);

        % setup default calibration
        obj.VoxelOrigin    = [1 1 1];
        obj.VoxelSize      = [1 1 1];
        obj.VoxelSizeUnit  = '';

        % update image name
        obj.ImageName = imgName;

        updateSlice(obj);
    end

    function setupImageFromClass(obj, img)
        % Initialize gui data from an Image class
        %

        if ndims(img) ~= 3
            error('Need an <Image> object of dimension 3');
        end

        % intialize with image data
        setupImageData(obj, getBuffer(img), img.Name);

        % extract spatial calibration
        obj.VoxelOrigin     = img.Origin;
        obj.VoxelSize       = img.Spacing;
        obj.VoxelSizeUnit   = img.UnitName;
    end

    function setupImageFromFile(obj, fileName)
        % replaces all informations about image

        [filepath, basename, ext] = fileparts(fileName); %#ok<ASGLU>

        switch lower(ext)
            case {'.mhd', '.mha'}
                importMetaImage(obj, fileName);
            case '.dcm'
                importDicomImage(obj, fileName);
            case '.hdr'
                importAnalyzeImage(obj, fileName);
            case '.vm'
                importVoxelMatrix(obj, fileName);
            otherwise
                readImageStack(obj, fileName);
        end

    end

    function readImageStack(obj, fileName)
        % Read image stack, either as single file bundle or as file series

        [img, map] = readstack(fileName);

        % determine image name
        [pathName, baseName, ext] = fileparts(fileName);
        imgName = [baseName ext];

        setupImageData(obj, img, imgName);

        % calibrate colormap if present
        if ~isempty(map)
            obj.ColorMap = map;
        end

        obj.LastPath = pathName;

        % try to read file info
        if exist(fileName, 'file')
            info = imfinfo(fileName);
            info = info(1);

            % extract image resolution (pixel size)
            if isfield(info, 'XResolution')
                xresol = info.('XResolution');
                yresol = xresol;
                if isfield(info, 'YResolution')
                    yresol = info.('YResolution');
                end
                obj.VoxelSize = [xresol yresol 1];
            end

            obj.ImageInfo = info;
        end
    end

    function importMetaImage(obj, fileName)
        % Load a metaImage file

        % determine image name
        [pathName, baseName, ext] = fileparts(fileName);
        imgName = [baseName ext];

        info = metaImageInfo(fileName);

        % update display
        setupImageData(obj, metaImageRead(info), imgName);

        % setup spatial calibration
        if isfield(info, 'ElementSize')
            obj.VoxelSize = info.('ElementSize');
        else
            isfield(info, 'ElementSpacing')
            obj.VoxelSize = info.('ElementSpacing');
        end
        if isfield(info, 'Offset')
            obj.VoxelOrigin = info.('Offset');
        end
        if isfield(info, 'ElementOrigin')
            obj.VoxelOrigin = info.('ElementOrigin');
        end

        obj.LastPath = pathName;
        obj.ImageInfo = info;
    end

    function importDicomImage(obj, fileName)

        % read image data
        info = dicominfo(fileName);
        [img, map] = dicomread(info);
        img = squeeze(img);

        % convert indexed image to true RGB image
        if ~isempty(map)
            dim = size(img);
            inds = img;
            img = zeros([dim(1) dim(2) 3 dim(3)]);
            for i = 1:3
                img(:,:,i,:) = reshape(map(inds(:), i), dim);
            end
        end

        % determine image name
        [pathName, baseName, ext] = fileparts(fileName);
        imgName = [baseName ext];

        % update display
        setupImageData(obj, img, imgName);

        obj.LastPath = pathName;
        obj.ImageInfo = info;
    end

    function importAnalyzeImage(obj, fileName)

        % determine image name
        [pathName, baseName, ext] = fileparts(fileName);
        imgName = [baseName ext];

        info = analyze75info(fileName);

        % update display
        setupImageData(obj, analyze75read(info), imgName);

        % setup calibration
        if isfield(info, 'PixelDimensions')
            obj.VoxelSize = info.('PixelDimensions');
        end
        if isfield(info, 'VoxelUnits')
            obj.VoxelSizeUnit = info.('VoxelUnits');
        end

        obj.LastPath = pathName;
        obj.ImageInfo = info;
    end

    function importInterfileImage(obj, fileName)

        % determine image name
        [pathName, baseName, ext] = fileparts(fileName);
        imgName = [baseName ext];

        % update display
        info = interfileinfo(fileName);
        setupImageData(obj, interfileread(info), imgName);

        obj.LastPath = pathName;
        obj.ImageInfo = info;
    end

    function importRawDataImage(obj, fileName)

        % dialog to choose image dimensions
        answers = inputdlg(...
            {'X Size (columns):', 'Y Size (rows):', 'Z Size (slices):', ...
            'Channel number:'}, ...
            'Input Image Dimensions',...
            1, {'10', '10', '10', '1'});
        if isempty(answers)
            return;
        end

        % parse dimensions
        dims = [0 0 0 0];
        for i = 1:4
            num = str2num(answers{i}); %#ok<ST2NM>
            if isempty(num)
                errordlg(sprintf('Could not parse input number %d', i), ...
                    'Parsing error');
                return;
            end
            dims(i) = num;
        end

        % dialog to choose data type
        types = {'uint8', 'int8', 'uint16', 'int16', 'single', 'double'};
        [selection, ok] = listdlg(...
            'ListString', types, ...
            'PromptString', 'Choose Data Type:', ...
            'SelectionMode', 'single', ...
            'Name', 'Data Type');
        if ~ok
            return;
        end

        % if number of channels is specified, use it as third dimension
        if dims(4) > 1
            dims = dims([1 2 4 3]);
        end

        % read raw stack (use correction of some bugs in 'readstack' function)
        dataType = types{selection};
        img = readstack(fileName, dataType, dims);

        % determine image name
        [pathName, baseName, ext] = fileparts(fileName);
        imgName = [baseName ext];

        Slicer(img, 'name', imgName);

        % setup file infos
        obj.LastPath = pathName;
    end

    function importVoxelMatrix(obj, fileName)

        types = {'uint8', 'uint16', 'int16', 'single', 'double'};
        [sel, ok] = listdlg(...
            'PromptString', 'Choose data type:',...
            'SelectionMode', 'single',...
            'ListString', types, ...
            'Name', 'Data Type');
        if ~ok
            return;
        end
        dataType = types{sel};

        % read image data
        try
            data = readVoxelMatrix(fileName, dataType);
        catch %#ok<CTCH>
            [path, name] = fileparts(fileName); %#ok<ASGLU>
            errordlg(['Could not import voxel Matrix File ' name], ...
                'File Error', 'modal');
            return;
        end

        % determine image name
        [pathName, baseName, ext] = fileparts(fileName);
        imgName = [baseName ext];

        % update display
        setupImageData(obj, data, imgName);

        obj.LastPath = pathName;

    end
end

%% Some methods for image manipulation
methods
    function [mini, maxi] = computeGrayScaleExtent(obj)
        % compute grayscale extent of obj inner image

        if isempty(obj.ImageData)
            mini = 0;
            maxi = 1;
            return;
        end

        % check image data type
        if strcmp(obj.ImageType, 'binary') || islogical(obj.ImageData)
            % for binary images, the grayscale extent is defined by the type
            mini = 0;
            maxi = 1;

        elseif strcmp(obj.ImageType, 'grayscale') && isa(obj.ImageData, 'uint8')
            % use min-max values depending on image type
            mini = 0;
            maxi = 255;

        elseif strcmp(obj.ImageType, 'vector')
            % case of vector image: compute max of norm

            dim = size(obj.ImageData);

            norm = zeros(dim([1 2 4]));

            for i = 1:dim(3)
                norm = norm + squeeze(obj.ImageData(:,:,i,:)) .^ 2;
            end

            mini = 0;
            maxi = sqrt(max(norm(:)));

        elseif strcmp(obj.ImageType, 'label')
            mini = 0;
            maxi = max(obj.ImageData(:));

        else
            % for float images, display 99 percents of dynamic
            [mini, maxi] = computeGrayscaleAdjustement(obj, .01);
        end
    end

    function [mini, maxi] = computeGrayscaleAdjustement(obj, alpha)
        % compute grayscale range that maximize vizualisation

        if isempty(obj.ImageData)
            mini = 0;
            maxi = 1;
            return;
        end

        % use default value for alpha if not specified
        if nargin == 1
            alpha = .01;
        end

        % sort values that are valid (avoid NaN's and Inf's)
        values = sort(obj.ImageData(isfinite(obj.ImageData)));
        n = length(values);

        % compute values that enclose (1-alpha) percents of all values
        mini = values( floor((n-1) * alpha/2) + 1);
        maxi = values( floor((n-1) * (1-alpha/2)) + 1);

        % small control to avoid mini==maxi
        minDiff = 1e-12;
        if abs(maxi - mini) < minDiff
            % use extreme values in image
            mini = values(1);
            maxi = values(end);

            % if not enough, set range to [0 1].
            if abs(maxi - mini) < minDiff
                mini = 0;
                maxi = 1;
            end
        end
    end

    function rotateImage(obj, axis, n)
        % Rotate the inner 3D image and the associated meta-information
        % axis is given between 1 and 3, in XYZ convention
        % n is the number of rotations (typically 1, 2, 3 or -1)

        if isempty(obj.ImageData)
            return;
        end

        % convert to ijk ordering
        indices = [2 1 3];
        axis = indices(axis);

        % performs image rotation, and get axis permutation parameters
        [obj.ImageData, inds] = rotateStack90(obj.ImageData, axis, n);

        % permute meta info
        obj.ImageSize   = obj.ImageSize(inds);
        obj.VoxelSize   = obj.VoxelSize(inds);
        obj.VoxelOrigin = obj.VoxelOrigin(inds);

        % for rotation that imply z axis, need to change zslice
        if axis ~= 3
            % update limits of zslider
            set(obj.Handles.ZSlider, 'min', 1);
            set(obj.Handles.ZSlider, 'max', obj.ImageSize(3));

            % setup current slice in the middle of the stack
            newIndex = ceil(obj.ImageSize(3) / 2);
            updateSliceIndex(obj, newIndex);
        end

        % update and display the new slice
        updateSlice(obj);
        displayNewImage(obj);
        updateTitle(obj);
    end

    function displayNewImage(obj)
        % Refresh image display of the current slice

        if isempty(obj.ImageData)
            return;
        end

        api = iptgetapi(obj.Handles.ScrollPanel);
        api.replaceImage(obj.Slice);

        % extract calibration data
        spacing = obj.VoxelSize(1:2);
        origin  = obj.VoxelOrigin(1:2);

        % set up spatial calibration
        dim     = obj.ImageSize;
        xdata   = ([0 dim(1)-1] * spacing(1) + origin(1));
        ydata   = ([0 dim(2)-1] * spacing(2) + origin(2));

        set(obj.Handles.Image, 'XData', xdata);
        set(obj.Handles.Image, 'YData', ydata);

        % compute image extent in physical coordinates
        p0 = ([0 0]    - .5) .* spacing + origin;
        p1 = (dim(1:2) - .5) .* spacing + origin;

        % setup axis extent
        set(obj.Handles.ImageAxis, 'XLim', [p0(1) p1(1)]);
        set(obj.Handles.ImageAxis, 'YLim', [p0(2) p1(2)]);

        % for grayscale and vector images, adjust display range and LUT
        if ~strcmp(obj.ImageType, 'color')
            set(obj.Handles.ImageAxis, 'CLim', obj.DisplayRange);

            % setup the appropriate color map (stored color map, plus
            % eventullay the background color for label images)
            cmap = obj.ColorMap;
            if  ~isempty(cmap)
                if strcmp(obj.ImageType, 'label')
                    cmap = [obj.BgColor ; cmap];
                end
                colormap(obj.Handles.ImageAxis, cmap);
            end
        end

        % adjust zoom to view the full image
        api = iptgetapi(obj.Handles.ScrollPanel);
        mag = api.findFitMag();
        api.setMagnification(mag);
    end

end

%% Callbacks for File Menu
methods
    function onOpenImage(obj, hObject, eventdata)
        showOpenImageDialog(obj);
    end

    function onOpenDemoImage(obj, hObject, eventdata)  %#ok<INUSL>

        demoName = get(hObject, 'UserData');
        switch demoName
            case 'brainMRI'
                metadata = analyze75info('brainMRI.hdr');
                img = analyze75read(metadata);
                Slicer(img, ...
                    'Spacing', [1 1 2.5], ...
                    'Name', 'Brain', ...
                    'DisplayRange', [0 90]);

            case 'unitBall'
                lx = linspace(-1, 1, 101);
                [x, y, z] = meshgrid(lx, lx, lx);
                dist = sqrt(max(1 - (x.^2 + y.^2 + z.^2), 0));
                Slicer(dist, ...
                    'Origin', [-1 -1 -1], ...
                    'Spacing', [.02 .02 .02], ...
                    'Name', 'Unit Ball');

            otherwise
                error(['Unknown demo image: ' demoName]);
        end
    end

    function onImportRawData(obj, hObject, eventdata)
        [fileName, pathName] = uigetfile( ...
       {'*.raw', 'Raw data file(*.raw)'; ...
        '*.*',   'All Files (*.*)'}, ...
        'Import Raw Data', ...
        obj.LastPath);

        if isequal(fileName,0) || isequal(pathName,0)
            return;
        end

        importRawDataImage(obj, fullfile(pathName, fileName));
    end

    function showOpenImageDialog(obj, hObject, eventdata)
        % Display the dialog, determines image type, and setup image accordingly

        [fileName, pathName] = uigetfile( ...
            {'*.gif;*.jpg;*.jpeg;*.tif;*.tiff;*.bmp;*.hdr;*.dcm;*.mhd;*.lsm;*.vm', ...
            'All Image Files (*.tif, *.hdr, *.dcm, *.mhd, *.lsm, *.bmp, *.jpg)'; ...
            '*.tif;*.tiff',             'TIF Files (*.tif, *.tiff)'; ...
            '*.bmp',                    'BMP Files (*.bmp)'; ...
            '*.hdr',                    'Mayo Analyze Files (*.hdr)'; ...
            '*.dcm',                    'DICOM Files (*.dcm)'; ...
            '*.mhd;*.mha',              'MetaImage data files (*.mha, *.mhd)'; ...
            '*.lsm',                    'Zeiss LSM files(*.lsm)'; ...
            '*.vm',                     'Voxel Matrix data files(*.vm)'; ...
            '*.*',                      'All Files (*.*)'}, ...
            'Choose a stack or the first slice of a series:', ...
            obj.LastPath);

        if isequal(fileName,0) || isequal(pathName,0)
            return;
        end

        % open a new Slicer with the specified file
        Slicer(fullfile(pathName, fileName));

    end

    function onImportFromWorkspace(obj, hObject, eventdata)
        % Import an image from a variable in current workspace

        % open dialog to input image name
        prompt = {'Enter Variable Name:'};
        title = 'Import From Workspace';
        lines = 1;
        def = {'ans'};
        answer = inputdlg(prompt, title, lines, def);

        % if user cancels, answer is empty
        if isempty(answer)
            return;
        end

        data = evalin('base', answer{1});

        if ndims(data) < 3 %#ok<ISMAT>
            errordlg('Input Image must have at least 3 dimensions');
            return;
        end

        Slicer(data, 'Name', answer{1});
    end

    function onSaveImage(obj, hObject, eventdata)
        % Display the dialog, determines image type, and save image
        % accordingly

        if isempty(obj.ImageData)
            return;
        end

        % show save image dialog
        [fileName, pathName, filterIndex] = uiputfile( ...
            {'*.tif;*.tiff;*.hdr;*.dcm;*.mhd', ...
            'All Supported Files (*.tif, *.dcm, *.mhd)'; ...
            '*.tif;*.tiff',             'TIF stacks (*.tif, *.tiff)'; ...
            '*.dcm',                    'DICOM Files (*.dcm)'; ...
            '*.mhd;*.mha',              'MetaImage data files (*.mha, *.mhd)'; ...
            }, ...
            'Save 3D Image', ...
            obj.LastPath);

        % if user cancel, quit dialog
        if isequal(fileName,0) || isequal(pathName,0)
            return;
        end

        % if no extension is specified, put another one
        [path, baseName, ext] = fileparts(fileName); %#ok<ASGLU>
        if isempty(ext)
            filterExtensions = {'.mhd', '.tif', '.dcm', '.mhd'};
            fileName = [fileName filterExtensions{filterIndex}];
        end

        % create full file name
        fullName = fullfile(pathName, fileName);

        % save image data
        [path, baseName, ext] = fileparts(fileName); %#ok<ASGLU>
        switch (ext)
            case {'.mha', '.mhd'}
                metaImageWrite(obj.ImageData, fullName);

            case {'.tif', '.tiff'}
                savestack(obj.ImageData, fullName);

            case '.dcm'
                dicomwrite(obj.ImageData, fullName);

            otherwise
                error(['Non supported File Format: ' ext]);
        end
    end

    function onExportToWorkspace(obj, hObject, eventdata)
        % Export current image data to workspace

        % open dialog to input image name
        prompt = {'Enter image name:'};
        title = 'Export Image Data';
        lines = 1;
        def = {'img'};
        answer = inputdlg(prompt, title, lines, def);

        % if user cancels, answer is empty
        if isempty(answer)
            return;
        end

        assignin('base', answer{1}, obj.ImageData);
    end

end

%% Callbacks for Image menu
methods
    function onDisplayImageInfo(obj, varargin)
        % hObject    handle to itemDisplayImageInfo (see GCBO)
        % eventdata  reserved - to be defined in a future version of MATLAB
        % handles    structure with handles and user data (see GUIDATA)

        if isempty(obj.ImageData)
            errordlg('No image loaded', 'Image Error', 'modal');
            return;
        end

        info = obj.ImageInfo;
        if isempty(info)
            errordlg('No meta-information defined for obj image', ...
                'Image Error', 'modal');
            return;
        end

        % extract field names
        fields = fieldnames(info);
        nFields = length(fields);

        % create data table as a cell array of strings
        data = cell(nFields, 2);
        for i = 1:nFields
            data{i, 1} = fields{i};
            dat = info.(fields{i});
            if ischar(dat)
                data{i,2} = dat;
            elseif isnumeric(dat)
                data{i,2} = num2str(dat);
            else
                data{i,2} = '...';
            end
        end

        % create name for figure
        if isempty(obj.ImageName)
            name = 'Image Metadata';
        else
            name = sprintf('MetaData for image <%s>', obj.ImageName);
        end

        % creates and setup new figure
        f = figure('MenuBar', 'none', 'Name', name);
        set(f, 'units', 'pixels');
        pos = get(f, 'position');
        width = pos(3);
        % sum of width is not equal to 1 to avoid rounding errors.
        columnWidth = {round(width * .30), round(width * .69)};

        % display the data table
        uitable(...
            'Parent', f, ...
            'Units','normalized',...
            'Position', [0 0 1 1], ...
            'Data', data, ...
            'RowName', [], ...
            'ColumnName', {'Name', 'Value'}, ...
            'ColumnWidth', columnWidth, ...
            'ColumnFormat', {'char', 'char'}, ...
            'ColumnEditable', [false, false]);

    end

    function onChangeResolution(obj, varargin)

        if isempty(obj.ImageData)
            return;
        end

        % configure dialog
        spacing = obj.VoxelSize;
        prompt = {...
            'Voxel size in X direction:', ...
            'Voxel size in Y direction:', ...
            'Voxel size in Z direction:', ...
            'Unit name:'};
        title = 'Image resolution';
        defaultValues = [cellstr(num2str(spacing'))' {obj.VoxelSizeUnit}];

        % ask for answer
        answer = inputdlg(prompt, title, 1, defaultValues);
        if isempty(answer)
            return;
        end

        % parse resolution in each direction
        for i = 1:3
            spacing(i) = str2double(answer{i});
            if isnan(spacing(i))
                warning('Slicer:onChangeResolution:Parsing', ...
                    ['Could not parse the string: ' answer{i}]);
                return;
            end
        end

        % set up the new resolution
        obj.VoxelSize = spacing;
        obj.VoxelSizeUnit = answer{4};

        updateCalibrationFlag(obj);

        % re-display the image
        displayNewImage(obj);
        updateTitle(obj);
    end

    function onChangeImageOrigin(obj, varargin)

        if isempty(obj.ImageData)
            return;
        end

        % configure dialog
        origin = obj.VoxelOrigin;
        prompt = {...
            'Image origin in X direction:', ...
            'Image origin in Y direction:', ...
            'Image origin in Z direction:'};
        title = 'Change Image origin';
        defaultValues = [cellstr(num2str(origin'))' {obj.VoxelSizeUnit}];

        % ask for answer
        answer = inputdlg(prompt, title, 1, defaultValues);
        if isempty(answer)
            return;
        end

        % parse resolution in each direction
        for i = 1:3
            origin(i) = str2double(answer{i});
            if isnan(origin(i))
                warning('Slicer:onChangeResolution:Parsing', ...
                    ['Could not parse the string: ' answer{i}]);
                return;
            end
        end

        % set up the new resolution
        obj.VoxelOrigin = origin;
        updateCalibrationFlag(obj);

        % re-display the image
        displayNewImage(obj);
        updateTitle(obj);
    end

    function updateCalibrationFlag(obj)
        obj.Calibrated = ...
            sum(obj.VoxelSize ~= 1) > 0 || ...
            sum(obj.VoxelOrigin ~= 1) > 0 || ...
            ~isempty(obj.VoxelSizeUnit);
    end

    function onConvertColorToGray(obj, hObject, eventData)
        % convert RGB image to grayscale

        if isempty(obj.ImageData)
            return;
        end

        % check that image is color
        if ~strcmp(obj.ImageType, 'color')
            return;
        end

        % compute conversion coefficients
        mat = inv([...
            1.0  0.956  0.621; ...
            1.0 -0.272 -0.647; ...
            1.0 -1.106  1.703 ]);
        coefs = mat(1, :);

        % convert inner image data
        newData = squeeze(imlincomb(...
            coefs(1), obj.ImageData(:,:,1,:), ...
            coefs(2), obj.ImageData(:,:,2,:), ...
            coefs(3), obj.ImageData(:,:,3,:)));
        createNewSlicer(obj, newData, obj.ImageName);
    end

    function onConvertIntensityToColor(obj, hObject, eventData)
        % convert grayscale to RGB using current colormap

        if isempty(obj.ImageData)
            return;
        end

        % check that image is grayscale
        if ~ismember(obj.ImageType, {'grayscale', 'intensity'})
            return;
        end

        % choose the colormap
        cmap = obj.ColorMap;
        if isempty(cmap)
            cmap = gray(max(obj.ImageData(:)));
        end

        % convert inner image data
        newData = uint8(double2rgb(obj.ImageData, ...
            cmap, obj.DisplayRange, obj.BgColor) * 255);
        createNewSlicer(obj, newData, obj.ImageName);
    end

    function onConvertLabelsToColor(obj, hObject, eventData)
        % convert grayscale to RGB using current colormap

        if isempty(obj.ImageData)
            return;
        end

        % check that image is label
        if ~ismember(obj.ImageType, {'label', 'binary'})
            return;
        end

        % choose the colormap
        cmap = obj.ColorMap;
        if isempty(cmap)
            cmap = jet(256);
        end

        % colormap has 256 entries, we need only a subset
        nLabels = max(obj.ImageData(:));
        if size(cmap, 1) ~= nLabels
            inds = round(linspace(1, size(cmap,1), nLabels));
            cmap = cmap(inds, :);
        end

        % convert inner image data
        newData = label2rgb3d(obj.ImageData, cmap, obj.BgColor, 'shuffle');
        createNewSlicer(obj, newData, obj.ImageName);
    end

    function onChangeImageType(obj, hObject, eventData)
        if isempty(obj.ImageData)
            return;
        end

        % check that image is grayscale
        if ~ismember(obj.ImageType, {'grayscale', 'binary', 'label', 'intensity'})
            return;
        end

        % convert inner data array
        newType = get(hObject, 'UserData');
        switch newType
            case 'binary'
                obj.ImageData = obj.ImageData > 0;
            case 'grayscale'
                obj.ImageData = uint8(obj.ImageData);
            case 'intensity'
                obj.ImageData = double(obj.ImageData);
            case 'label'
                maxValue = max(obj.ImageData(:));
                if maxValue <= 255
                    obj.ImageData = uint8(obj.ImageData);
                elseif maxValue < 2^16
                    obj.ImageData = uint16(obj.ImageData);
                end
        end

        % update image type
        obj.ImageType = newType;

        % update display range
        [mini, maxi] = computeGrayScaleExtent(obj);
        obj.DisplayRange  = [mini maxi];

        % update display
        setupMenuBar(obj);
        updateSlice(obj);
        displayNewImage(obj);
        updateColorMap(obj);
        updateTitle(obj);
    end

    function onChangeDataType(obj, hObject, eventData)

        if isempty(obj.ImageData)
            return;
        end

        % check that image is grayscale
        if ~strcmp(obj.ImageType, 'grayscale')
            return;
        end

        % convert inner data array
        newType = get(hObject, 'UserData');
        switch newType
            case 'binary'
                obj.ImageData = obj.ImageData > 0;
            case 'gray8'
                obj.ImageData = uint8(obj.ImageData);
            case 'gray16'
                obj.ImageData = uint16(obj.ImageData);
            case 'double'
                obj.ImageData = double(obj.ImageData);
        end

        % update display range
        [mini, maxi] = computeGrayScaleExtent(obj);
        obj.DisplayRange  = [mini maxi];

        % update display
        updateSlice(obj);
        displayNewImage(obj);
        updateTitle(obj);
    end

    function onSplitRGB(obj, hObject, eventdata)

        if isempty(obj.ImageData)
            return;
        end

        % check that image is grayscale
        if ~strcmp(obj.ImageType, 'color')
            return;
        end

        createNewSlicer(obj, squeeze(obj.ImageData(:,:,1,:)), ...
            [obj.ImageName '-red']);
        createNewSlicer(obj, squeeze(obj.ImageData(:,:,2,:)), ...
            [obj.ImageName '-green']);
        createNewSlicer(obj, squeeze(obj.ImageData(:,:,3,:)), ...
            [obj.ImageName '-blue']);
    end
end

%% Callbacks for View menu
methods
    function onSetImageDisplayExtent(obj, hObject, eventdata)
        % compute grayscale extent from data in image

        if isempty(obj.ImageData)
            return;
        end

        if ~ismember(obj.ImageType, {'grayscale', 'intensity'})
            return;
        end

        % extreme values in image
        minValue = min(obj.ImageData(isfinite(obj.ImageData)));
        maxValue = max(obj.ImageData(isfinite(obj.ImageData)));

        % avoid special degenerate cases
        if abs(maxValue - minValue) < 1e-12
            minValue = 0;
            maxValue = 1;
        end

        % set up range
        obj.DisplayRange = [minValue maxValue];
        set(obj.Handles.ImageAxis, 'CLim', obj.DisplayRange);

        if isfield(obj.Handles, 'zProfileFigure')
            updateZProfileDisplay(obj);
        end
    end

    function onSetDatatypeDisplayExtent(obj, hObject, eventdata)
        % compute grayscale extent from image datatype

        if isempty(obj.ImageData)
            return;
        end

        if ~ismember(obj.ImageType, {'grayscale', 'intensity'})
            return;
        end

        mini = 0; maxi = 1;
        if isinteger(obj.ImageData)
            type = class(obj.ImageData);
            mini = intmin(type);
            maxi = intmax(type);
        end

        obj.DisplayRange = [mini maxi];
        set(obj.Handles.ImageAxis, 'CLim', obj.DisplayRange);

        if isfield(obj.Handles, 'zProfileFigure')
            updateZProfileDisplay(obj);
        end
    end

    function onSetManualDisplayExtent(obj, hObject, eventdata)
        if isempty(obj.ImageData)
            return;
        end

        if ~ismember(obj.ImageType, {'grayscale', 'intensity'})
            return;
        end

        % get extreme values for grayscale in image
        minimg = min(obj.ImageData(:));
        maximg = max(obj.ImageData(:));

        % get actual value for grayscale range
        clim = get(obj.Handles.ImageAxis, 'CLim');

        % define dialog options
        if isinteger(minimg)
            prompt = {...
                sprintf('Min grayscale value (%d):', minimg), ...
                sprintf('Max grayscale value (%d):', maximg)};
        else
            prompt = {...
                sprintf('Min grayscale value (%f):', minimg), ...
                sprintf('Max grayscale value (%f):', maximg)};
        end
        dlg_title = 'Input for grayscale range';
        num_lines = 1;
        def = {num2str(clim(1)), num2str(clim(2))};

        % open the dialog
        answer = inputdlg(prompt, dlg_title, num_lines, def);

        % if user cancel, return
        if isempty(answer)
            return;
        end

        % convert input texts into numerical values
        mini = str2double(answer{1});
        maxi = str2double(answer{2});
        if isnan(mini) || isnan(maxi)
            return;
        end

        obj.DisplayRange = [mini maxi];

        % setup appropriate grayscale for image
        set(obj.Handles.ImageAxis, 'CLim', [mini maxi]);

        if isfield(obj.Handles, 'zProfileFigure')
            updateZProfileDisplay(obj);
        end
    end

    function onSelectLUT(obj, hObject, eventdata)
        % Change the LUT of the grayscale image, and refresh the display
        % colorMap name is specified by 'UserData' field of hObject

        if isempty(obj.ImageData)
            return;
        end

        cmapName = get(hObject, 'UserData');
        disp(['Change LUT to: ' cmapName]);

        nGrays = 256;
        if strcmp(obj.ImageType, 'label')
            nGrays = double(max(obj.ImageData(:)));
        end

        if strcmp(cmapName, 'gray')
            % for gray-scale, use an empty LUT
            obj.ColorMap = [];
            obj.ColorMap = gray(nGrays);

        elseif strcmp(cmapName, 'inverted')
            grayMax = nGrays - 1;
            obj.ColorMap = repmat((grayMax:-1:0)', 1, 3) / grayMax;

        elseif strcmp(cmapName, 'blue-gray-red')
            obj.ColorMap = gray(nGrays);
            obj.ColorMap(1,:) = [0 0 1];
            obj.ColorMap(end,:) = [1 0 0];

        elseif strcmp(cmapName, 'colorcube')
            nLabels = round(double(max(obj.ImageData(:))));
            map = colorcube(nLabels+2);
            % remove black and white colors
            isValidColor = sum(map==0, 2) ~= 3 & sum(map==1, 2) ~= 3;
            obj.ColorMap = [0 0 0; map(isValidColor, :)];

        elseif strcmp(cmapName, 'redLUT')
            obj.ColorMap = gray(nGrays);
            obj.ColorMap(:, 2:3) = 0;

        elseif strcmp(cmapName, 'greenLUT')
            obj.ColorMap = gray(nGrays);
            obj.ColorMap(:, [1 3]) = 0;

        elseif strcmp(cmapName, 'blueLUT')
            obj.ColorMap = gray(nGrays);
            obj.ColorMap(:, 1:2) = 0;

        elseif strcmp(cmapName, 'yellowLUT')
            obj.ColorMap = gray(nGrays);
            obj.ColorMap(:, 3) = 0;

        elseif strcmp(cmapName, 'cyanLUT')
            obj.ColorMap = gray(nGrays);
            obj.ColorMap(:, 1) = 0;

        elseif strcmp(cmapName, 'magentaLUT')
            obj.ColorMap = gray(nGrays);
            obj.ColorMap(:, 2) = 0;

        else
            obj.ColorMap = feval(cmapName, nGrays);
        end

        updateColorMap(obj);
    end

    function updateColorMap(obj)
        % refresh the color map of current display

        % get current color map, or create a new one
        cmap = obj.ColorMap;
        if isempty(cmap)
            cmap = jet(256);
        end

        % adapt color map for label
        if strcmp(obj.ImageType, 'label')
            cmap = [obj.BgColor; cmap(2:end,:)];
        end

        colormap(obj.Handles.ImageAxis, cmap);
    end

    function onSelectBackgroundColor(obj, varargin)

        colorNames = {'White', 'Black', 'Red', 'Green', 'Blue', 'Cyan', 'Magenta', 'Yellow'};
        colors = [1 1 1; 0 0 0; 1 0 0; 0 1 0; 0 0 1; 0 1 1; 1 0 1; 1 1 0];

        [ind, ok] = listdlg(...
            'PromptString', 'Background Color:',...
            'SelectionMode', 'Single',...
            'ListString', colorNames);
        if ~ok
            return;
        end

        obj.BgColor = colors(ind, :);
        updateColorMap(obj);
    end

    function onShowOrthoPlanes(obj, varargin)

        if isempty(obj.ImageData)
            return;
        end

        % compute display settings
        pos = ceil(obj.ImageSize / 2);
        spacing = obj.VoxelSize;

        % determine the LUT to use (default is empty)
        ortholut = [];
        if ~isColorStack(obj.ImageData) && ~isempty(obj.ColorMap)
            ortholut = obj.ColorMap;
        end

        % create figure with 3 orthogonal slices
        figure();
        orthoSlices(obj.ImageData, pos, spacing, ...
            'displayRange', obj.DisplayRange, 'LUT', ortholut);
    end

    function onShowOrthoSlices3d(obj, varargin)
        % Open a dialog to choose options, then display 3D orthoslices
        if isempty(obj.ImageData)
            return;
        end
        OrthoSlicer3dOptionsDialog(obj);
    end

    function onShowIsosurface(obj, varargin)
        % Choose an isosurface value, then computes the corresponding
        % surface mesh

        % check validity of input image
        if isempty(obj.ImageData)
            return;
        end
        if ~ismember(obj.ImageType, {'grayscale', 'intensity'})
            return;
        end

        % open options dialog for isosurface
        IsosurfaceOptionsDialog(obj);
    end

    function onShowLabelIsosurfaces(obj, varargin)
        % Open a dialog to choose options, then display label isosurfaces

        % check validity of input image
        if isempty(obj.ImageData)
            return;
        end
        if ~ismember(obj.ImageType, {'label', 'binary'})
            return;
        end

        % open options dialog for isosurface of label images
        LabelIsosurfacesOptionsDialog(obj);
    end

    function rgb = createRGBImage(obj)
        % compute a RGB stack that can be easily displayed

        if isempty(obj.ImageData)
            return;
        end

        if strcmp(obj.ImageType, 'color')
            rgb = obj.ImageData;
            return;
        end

        if ismember(obj.ImageType, {'grayscale', 'label', 'binary'})
            data = double(obj.ImageData);

        elseif strcmp(obj.ImageType, 'vector')
            data = zeros(obj.ImageSize);
            for i = 1:size(obj.ImageData, 3)
                data = data + squeeze(obj.ImageData(:,:,i,:)) .^ 2;
            end
            data = sqrt(data);
        end

        % convert to uint8, using current display range
        range = obj.DisplayRange;
        data = uint8(255 * (data - range(1)) / (range(2) - range(1)));

        % eventually apply a LUT
        if ~isempty(obj.ColorMap)
            lutMax = max(obj.ColorMap(:));
            dim = obj.ImageSize;
            rgb = zeros([dim(2) dim(1) 3 dim(3)], 'uint8');

            % compute each channel
            for c = 1 : size(obj.ColorMap, 2)
                res = zeros(size(data));
                for i = 0:size(obj.ColorMap, 1)-1
                    res(data == i) = obj.ColorMap(i+1, c);
                end
                rgb(:,:,c,:) = uint8(res * 255 / lutMax);
            end

        else
            rgb = repmat(permute(data, [1 2 4 3]), [1 1 3 1]);
        end
    end

    function onZoomIn(obj, varargin)
        if isempty(obj.ImageData)
            return;
        end

        api = iptgetapi(obj.Handles.ScrollPanel);
        mag = api.getMagnification();
        api.setMagnification(mag * 2);
        obj.updateTitle();
    end

    function onZoomOut(obj, varargin)
        if isempty(obj.ImageData)
            return;
        end

        api = iptgetapi(obj.Handles.ScrollPanel);
        mag = api.getMagnification();
        api.setMagnification(mag / 2);
        obj.updateTitle();
    end

    function onZoomOne(obj, varargin)
        if isempty(obj.ImageData)
            return;
        end

        api = iptgetapi(obj.Handles.ScrollPanel);
        api.setMagnification(1);
        obj.updateTitle();
    end

    function onZoomBest(obj, varargin)
        if isempty(obj.ImageData)
            return;
        end

        api = iptgetapi(obj.Handles.ScrollPanel);
        mag = api.findFitMag();
        api.setMagnification(mag);
        obj.updateTitle();
    end

    function onZoomValue(obj, hObject, eventData)
        % zoom to a given scale, stored in user data of calling object.
        % zoom value is given as binary power:
        % v positive ->  zoom = 2^v:1
        % v = 0      ->  zoom =   1:1
        % v negative ->  zoom = 1:2^v
        if isempty(obj.ImageData)
            return;
        end

        % get magnification
        mag = get(hObject, 'Userdata');

        % setup magnification of current view
        api = iptgetapi(obj.Handles.ScrollPanel);
        api.setMagnification(mag);
        obj.updateTitle();
    end
end

%% Callbacks for Process menu
methods
    function onFlipImage(obj, hObject, eventdata)

        if isempty(obj.ImageData)
            return;
        end

        dim = get(hObject, 'UserData');
        if verLessThan('matlab', '8.1')
            obj.ImageData = flipdim(obj.ImageData, dim); %#ok<DFLIPDIM>
        else
            obj.ImageData = flip(obj.ImageData, dim);
        end
        obj.updateSlice;
        obj.displayNewImage;
    end

    function onRotateImage(obj, hObject, eventdata)
        % Rotate an image by 90 degrees along x, y or z axis.
        % Axis ID and number of 90 degrees rotations are given by user data
        % of the calling menu

        if isempty(obj.ImageData)
            return;
        end

        data = get(hObject, 'UserData');
        obj.rotateImage(data(1), data(2));
    end

    function onCropImage(obj, hObject, eventdata)
        % Crop the 3D image.
        % Opens a dialog, that choose options, then create new slicer
        % object with cropped image.

        if isempty(obj.ImageData)
            return;
        end

        CropStackDialog(obj);
    end

    function onCropLabel(obj, hObject, eventdata)
        % Crop the 3D image portion corresponding to a label.
        % Opens a dialog, that choose options, then create new slicer
        % object with cropped image.

        % basic input check
        if isempty(obj.ImageData)
            return;
        end
        if ~strcmp(obj.ImageType, 'label')
            return;
        end

        % open a dialog to choose a label
        maxLabel = max(obj.ImageData(:));
        prompt = sprintf('Input Label Index (max=%d)', maxLabel);
        answer = inputdlg(...
            prompt,...
            'Crop Label Dialog', ...
            1, ...
            {'1'});

        % parse answer
        if isempty(answer)
            return;
        end
        index = str2double(answer{1});
        if isnan(index)
            return;
        end

        % apply crop label operation
        img2 = imCropLabel(obj.ImageData, index);

        % compute colormap of new slicer object
        cmap = obj.ColorMap;
        if isempty(cmap)
            cmap = jet(maxLabel);
        end
        cmap = [obj.BgColor ; cmap(index, :)];

        % create new Slicer object with the crop result
        Slicer(img2, 'imageType', 'binary', ...
            'colorMap', cmap, 'backgroundColor', obj.BgColor);
    end

    function onToggleZProfileDisplay(obj, src, eventData)

        if isfield(obj.Handles, 'ZProfileFigure')
            % close figure
            hFig = obj.Handles.ZProfileFigure;
            if ishandle(hFig)
                close(hFig);
            end

            % remove from sub-figure list
            obj.Handles.SubFigures(obj.Handles.SubFigures == hFig) = [];

            % clear field
            obj.Handles = rmfield(obj.Handles, 'ZProfileFigure');

            % set menu entry to false
            set(src, 'Checked', 'Off');
            return;
        end

        % creates a new figure, display profile
        obj.Handles.ZProfileFigure = figure;
        set(obj.Handles.ZProfileFigure, 'Name', 'Z-Profile');

        % add to list of sub-figures
        obj.Handles.SubFigures = [obj.Handles.SubFigures, obj.Handles.ZProfileFigure];

        % configure axis
        ax = gca;
        hold(ax, 'on');
        set(ax, 'xlim', [1 obj.ImageSize(3)]);
        set(ax, 'ylim', obj.DisplayRange);
        titleStr = 'Z-profile';
        if ~isempty(obj.ImageName)
            titleStr = [titleStr ' of ' obj.ImageName];
        end
        title(ax, titleStr);
        xlabel(ax, 'Slice index');
        ylabel(ax, 'Image intensity');

        % plot line marker for current slice
        hZLine = plot(ax, [obj.SliceIndex obj.SliceIndex], obj.DisplayRange, 'k');

        % store settings
        userdata = struct('profiles', [], 'profileHandles', [], 'zLineHandle', hZLine);
        set(gca, 'userdata', userdata);
        obj.Handles.ZProfileAxis = ax;

        % set menu entry to true
        set(src, 'Checked', 'On');

%         if ~isempty(obj.LastClickedPoint)
%             updateZProfiles(obj);
%         end
    end

    function onDisplayHistogram(obj, varargin)

        if isempty(obj.ImageData)
            return;
        end

        % in the case of vector image, compute histogram of image norm
        img = obj.ImageData;
        if strcmp(obj.ImageType, 'vector')
            img = sqrt(sum(double(img) .^ 2, 3));
        end

        useBackground = ~strcmp(obj.ImageType, 'label');
        hd = SlicerHistogramDialog(img, 'useBackground', useBackground);

        obj.Handles.SubFigures = [obj.Handles.SubFigures, hd];
    end
end

%% Callbacks for Help menu
methods
    function onAbout(obj, varargin)
        title = 'About Slicer';
        info = dir(which('Slicer.m'));
        message = {...
            '       3D Slicer for Matlab', ...
            ['         v ' datestr(info.datenum, 1)], ...
            '', ...
            '      Author: David Legland', ...
            '      david.legland@inra.fr ', ...
            '       (c) INRA - Cepia', ...
            '', ...
            };
        msgbox(message, title);
    end

end

%% GUI Items callback
methods
    function onSliceSliderChanged(obj, hObject, eventdata) %#ok<*INUSD>
        if isempty(obj.ImageData)
            return;
        end

        zslice = round(get(hObject, 'Value'));
        zslice = max(get(hObject, 'Min'), min(get(hObject, 'Max'), zslice));

        updateSliceIndex(obj, zslice);
    end

    function onSliceEditTextChanged(obj, varargin)

        if isempty(obj.ImageData)
            return;
        end

        % get entered value for z-slice
        zslice = str2double(get(obj.Handles.ZEdit, 'String'));

        % in case of wrong edit, set the string to current value of zslice
        if isnan(zslice)
            zslice = obj.SliceIndex;
        end

        % compute slice number, inside of image bounds
        zslice = min(max(1, round(zslice)), obj.ImageSize(3));
        updateSliceIndex(obj, zslice);
    end

    function updateSliceIndex(obj, newIndex)
        if isempty(obj.ImageData)
            return;
        end

        obj.SliceIndex = newIndex;

        updateSlice(obj);

        set(obj.Handles.Image, 'CData', obj.Slice);

        % update gui information for slider and textbox
        set(obj.Handles.ZSlider, 'Value', newIndex);
        set(obj.Handles.ZEdit, 'String', num2str(newIndex));

        % Eventually updates display of z-profile
        if isfield(obj.Handles, 'ZProfileFigure')
            updateZProfileDisplay(obj);
        end
    end

    function updateSlice(obj)
        if isempty(obj.ImageData)
            return;
        end

        index = obj.SliceIndex;
        if strcmp(obj.ImageType, 'vector')
            % vector image
            dim = size(obj.ImageData);
            obj.Slice = zeros([dim(1) dim(2)]);
            for i = 1:dim(3)
                obj.Slice = obj.Slice + obj.ImageData(:,:,i,index) .^ 2;
            end
            obj.Slice = sqrt(obj.Slice);
        else
            % graycale or color image
            obj.Slice = stackSlice(obj.ImageData, 3, index);
        end
    end

    function updateTitle(obj)
        % set up title of the figure, containing name of figure and current zoom

        % small checkup, because function can be called before figure was
        % initialised
        if ~isfield(obj.Handles, 'Figure')
            return;
        end

        if isempty(obj.ImageData)
            set(obj.Handles.Figure, 'Name', 'Slicer - No image');
            return;
        end

        % setup name
        if isempty(obj.ImageName)
            imgName = 'Unknown Image';
        else
            imgName = obj.ImageName;
        end

        % determine the type to display:
        % * data type for intensity / grayscale image
        % * type of image otherwise
        switch obj.ImageType
            case 'grayscale'
                type = class(obj.ImageData);
            otherwise
                type = obj.ImageType;
        end

        % compute image zoom
        api = iptgetapi(obj.Handles.ScrollPanel);
        zoom = api.getMagnification();

        % compute new title string
        titlePattern = 'Slicer - %s [%d x %d x %d %s] - %g:%g';
        titleString = sprintf(titlePattern, imgName, ...
            obj.ImageSize, type, max(1, zoom), max(1, 1/zoom));

        % display new title
        set(obj.Handles.Figure, 'Name', titleString);
    end
end

%% Mouse management
methods
    function mouseButtonPressed(obj, hObject, eventdata)
        if isempty(obj.ImageData)
            return;
        end

        point = get(obj.Handles.ImageAxis, 'CurrentPoint');

        obj.LastClickedPoint = point(1,:);
        displayPixelCoords(obj, point);

        % Eventually process display of z-profile
        if isfield(obj.Handles, 'ZProfileFigure')
            updateZProfiles(obj);
        end
    end

    function mouseDragged(obj, hObject, eventdata)
        if isempty(obj.ImageData)
            return;
        end

        % update display of mouse cursor coordinates
        point = get(obj.Handles.ImageAxis, 'CurrentPoint');
        obj.LastClickedPoint = point(1,:);
        displayPixelCoords(obj, point);
    end

    function mouseWheelScrolled(obj, hObject, eventdata) %#ok<INUSL>
        if isempty(obj.ImageData)
            return;
        end

        % refresh display of current slice
        newIndex = obj.SliceIndex - eventdata.VerticalScrollCount;
        newIndex = min(max(newIndex, 1), obj.ImageSize(3));
        updateSliceIndex(obj, newIndex);

        % update display of mouse cursor coordinates
        point = get(obj.Handles.ImageAxis, 'CurrentPoint');
        displayPixelCoords(obj, point);
    end

    function displayPixelCoords(obj, point)
        if isempty(obj.ImageData)
            return;
        end

        point = point(1, 1:2);
        coord = round(pointToIndex(obj, point));

        % control on bounds of image
        if sum(coord < 1) > 0 || sum(coord > obj.ImageSize(1:2)) > 0
            set(obj.Handles.InfoPanel, 'string', '');
            return;
        end

        % Display coordinates of clicked point
        if obj.Calibrated
            % Display pixel + physical position
            locString = sprintf('(x,y) = (%d,%d) px = (%5.2f,%5.2f) %s', ...
                coord(1), coord(2), point(1), point(2), obj.VoxelSizeUnit);
        else
            % Display only pixel position
            locString = sprintf('(x,y) = (%d,%d) px', coord(1), coord(2));
        end

        % Display value of selected pixel
        if strcmp(obj.ImageType, 'color')
            % case of color pixel: values are red, green and blue
            rgb = obj.ImageData(coord(2), coord(1), :, obj.SliceIndex);
            valueString = sprintf('  RGB = (%d %d %d)', ...
                rgb(1), rgb(2), rgb(3));

        elseif strcmp(obj.ImageType, 'vector')
            % case of vector image: compute norm of the pixel
            values  = obj.ImageData(coord(2), coord(1), :, obj.SliceIndex);
            norm    = sqrt(sum(double(values(:)) .^ 2));
            valueString = sprintf('  value = %g', norm);

        else
            % case of a gray-scale pixel
            value = obj.ImageData(coord(2), coord(1), obj.SliceIndex);
            if ~isfloat(value)
                valueString = sprintf('  value = %3d', value);
            else
                valueString = sprintf('  value = %g', value);
            end
        end

        set(obj.Handles.InfoPanel, 'string', [locString '  ' valueString]);
    end

    function updateZProfiles(obj)
        % add or replace z-profiles using last clicked point

        % convert mouse coordinates to pixel coords
        if isempty(obj.LastClickedPoint)
            return;
        end
        coord = round(pointToIndex(obj, obj.LastClickedPoint(1, 1:2)));

        % control on bounds of image
        if sum(coord < 1) > 0 || sum(coord > obj.ImageSize(1:2)) > 0
            return;
        end

        % extract profile
        profile = permute(obj.ImageData(coord(2), coord(1), :, :), [4 3 1 2]);

        % add profile to axis user data
        userdata = get(obj.Handles.ZProfileAxis, 'userdata');
        if strcmp(get(obj.Handles.Figure, 'SelectionType'), 'normal')
            % replace profile list with current profile
            userdata.profiles = profile;
            delete(userdata.profileHandles);
            h = plot(obj.Handles.ZProfileAxis, profile, 'b');
            userdata.profileHandles = h;
        else
            % add the current profile to profile list
            userdata.profiles = [userdata.profiles profile];
            h = plot(obj.Handles.ZProfileAxis, profile, 'b');
            userdata.profileHandles = [userdata.profileHandles h];
        end

        set(obj.Handles.ZProfileAxis, 'userdata', userdata);
    end

    function updateZProfileDisplay(obj)
        % update display of Z-profiles

        % update axis settings
        set(obj.Handles.ZProfileAxis, 'ylim', obj.DisplayRange);

        % update position of Z line marker
        userdata = get(obj.Handles.ZProfileAxis, 'userdata');
        hZLine = userdata.zLineHandle;
        set(hZLine, 'XData', [obj.SliceIndex obj.SliceIndex]);
        set(hZLine, 'YData', obj.DisplayRange);
    end

    function index = pointToIndex(obj, point)
        % Converts coordinates of a point in physical dimension to image index
        % First element is column index, second element is row index, both are
        % given in floating point and no rounding is performed.
        spacing = obj.VoxelSize(1:2);
        origin  = obj.VoxelOrigin(1:2);
        index   = (point - origin) ./ spacing + 1;
    end

end

%% Figure Callbacks
methods
    function close(obj, hObject, eventdata)
        % close the main figure, and all sub figures
        close(obj.Handles.Figure);
    end

    function onScrollPanelResized(obj, varargin)
        % function called when the Scroll panel has been resized

        if isempty(obj.ImageData)
            return;
        end

        scrollPanel = obj.Handles.ScrollPanel;
        api = iptgetapi(scrollPanel);
        mag = api.findFitMag();
        api.setMagnification(mag);
        updateTitle(obj);
    end

end % general methods

%% GUI Utilities
methods
    function setupMenuBar(obj)
        % Refresh the menu bar of application main figure.
        % Some menu items may be valid or invalide depending on the current
        % image class or size

        hf = obj.Handles.Figure;

        % remove menuitems that could already exist
        % (obj is the case when the image type is changed, for example)
        children = get(hf, 'children');
        delete(children(strcmp(get(children, 'Type'), 'uimenu')));

        % setup some flags that will able/disable some menu items
        if ~isempty(obj.ImageData)
            imageFlag = 'on';
        else
            imageFlag = 'off';
        end
        if strcmp(obj.ImageType, 'color')
            colorFlag = 'on';
        else
            colorFlag = 'off';
        end
        if ismember(obj.ImageType, {'label', 'binary'})
            labelFlag = 'on';
        else
            labelFlag = 'off';
        end
        if ismember(obj.ImageType, {'grayscale', 'label', 'binary'})
            grayscaleFlag = 'on';
        else
            grayscaleFlag = 'off';
        end
        if ismember(obj.ImageType, {'grayscale', 'label', 'binary', 'intensity'})
            scalarFlag = 'on';
        else
            scalarFlag = 'off';
        end
        if ismember(obj.ImageType, {'grayscale', 'intensity'})
            intensityFlag = 'on';
        else
            intensityFlag = 'off';
        end

        % files
        menuFiles = uimenu(hf, 'Label', '&Files');

        uimenu(menuFiles, ...
            'Label', '&Open...', ...
            'Accelerator', 'O', ...
            'Callback', @obj.onOpenImage);
        uimenu(menuFiles, ...
            'Label', 'Import &Raw Data...', ...
            'Callback', @obj.onImportRawData);
        uimenu(menuFiles, ...
            'Label', '&Import From Workspace...', ...
            'Callback', @obj.onImportFromWorkspace);
        menuDemo = uimenu(menuFiles, ...
            'Label', 'Demo Images');
        uimenu(menuDemo, ...
            'Label', 'Brain MRI', ...
            'UserData', 'brainMRI', ...
            'Callback', @obj.onOpenDemoImage);
        uimenu(menuDemo, ...
            'Label', 'Unit Ball', ...
            'UserData', 'unitBall', ...
            'Callback', @obj.onOpenDemoImage);

        uimenu(menuFiles, ...
            'Label', '&Save Image...', ...
            'Separator', 'On', ...
            'Enable', imageFlag, ...
            'Accelerator', 'S', ...
            'Callback', @obj.onSaveImage);
        uimenu(menuFiles, ...
            'Label', '&Export To Workspace...', ...
            'Enable', imageFlag, ...
            'Callback', @obj.onExportToWorkspace);
        uimenu(menuFiles, ...
            'Label', '&Close', ...
            'Separator', 'On', ...
            'Accelerator', 'W', ...
            'Callback', @obj.close);

        % Image
        menuImage = uimenu(hf, 'Label', '&Image', ...
            'Enable', imageFlag);

        uimenu(menuImage, ...
            'Label', 'Image &Info...', ...
            'Enable', imageFlag, ...
            'Accelerator', 'I', ...
            'Callback', @obj.onDisplayImageInfo);
        uimenu(menuImage, ...
            'Label', 'Spatial &Calibration...', ...
            'Enable', imageFlag, ...
            'Callback', @obj.onChangeResolution);
        uimenu(menuImage, ...
            'Label', 'Image &Origin...', ...
            'Enable', imageFlag, ...
            'Callback', @obj.onChangeImageOrigin);

        menuChangeImageType = uimenu(menuImage, ...
            'Label', 'Change Image Type', ...
            'Enable', scalarFlag, ...
            'Separator', 'On');
        uimenu(menuChangeImageType, ...
            'Label', 'Binary', ...
            'UserData', 'binary', ...
            'Callback', @obj.onChangeImageType);
        uimenu(menuChangeImageType, ...
            'Label', 'Gray scale', ...
            'UserData', 'grayscale', ...
            'Callback', @obj.onChangeImageType);
        uimenu(menuChangeImageType, ...
            'Label', 'Intensity', ...
            'UserData', 'intensity', ...
            'Callback', @obj.onChangeImageType);
        uimenu(menuChangeImageType, ...
            'Label', 'Label', ...
            'UserData', 'label', ...
            'Callback', @obj.onChangeImageType);

        menuChangeGrayLevels = uimenu(menuImage, ...
            'Label', 'Change Gray levels', ...
            'Enable', grayscaleFlag);
        uimenu(menuChangeGrayLevels, ...
            'Label', '2 levels (Binary)', ...
            'UserData', 'binary', ...
            'Callback', @obj.onChangeDataType);
        uimenu(menuChangeGrayLevels, ...
            'Label', '8 levels (uint8)', ...
            'UserData', 'gray8', ...
            'Callback', @obj.onChangeDataType);
        uimenu(menuChangeGrayLevels, ...
            'Label', '16 levels (uint16)', ...
            'UserData', 'gray16', ...
            'Callback', @obj.onChangeDataType);
        uimenu(menuChangeGrayLevels, ...
            'Label', 'intensity (double)', ...
            'UserData', 'double', ...
            'Callback', @obj.onChangeDataType);

        uimenu(menuImage, ...
            'Label', 'Convert Intensity to Color', ...
            'Enable', grayscaleFlag, ...
            'Callback', @obj.onConvertIntensityToColor);

        uimenu(menuImage, ...
            'Label', 'Convert Labels to Color', ...
            'Enable', labelFlag, ...
            'Callback', @obj.onConvertLabelsToColor);

        uimenu(menuImage, ...
            'Label', 'RGB to Gray', ...
            'Enable', colorFlag, ...
            'Callback', @obj.onConvertColorToGray);

        uimenu(menuImage, ...
            'Label', 'Split RGB', ...
            'Enable', colorFlag, ...
            'Callback', @obj.onSplitRGB);

        % View
        menuView = uimenu(hf, 'Label', '&View', ...
            'Enable', imageFlag);

        % Display range menu
        displayRangeMenu = uimenu(menuView, ...
            'Label', '&Display Range', ...
            'Enable', grayscaleFlag);
        uimenu(displayRangeMenu, ...
            'Label', '&Image', ...
            'Callback', @obj.onSetImageDisplayExtent);
        uimenu(displayRangeMenu, ...
            'Label', '&Data Type', ...
            'Callback', @obj.onSetDatatypeDisplayExtent);
        uimenu(displayRangeMenu, ...
            'Label', '&Manual', ...
            'Callback', @obj.onSetManualDisplayExtent);

        % LUTs menu
        menuLut = uimenu(menuView, ...
            'Label', '&Look-Up Table', ...
            'Enable', grayscaleFlag);
        uimenu(menuLut, ...
            'Label', 'Gray', ...
            'UserData', 'gray', ...
            'Callback', @obj.onSelectLUT);
        uimenu(menuLut, ...
            'Label', 'Inverted', ...
            'UserData', 'inverted', ...
            'Callback', @obj.onSelectLUT);
        uimenu(menuLut, ...
            'Label', 'Gray with Blue and Red', ...
            'UserData', 'blue-gray-red', ...
            'Callback', @obj.onSelectLUT);
        uimenu(menuLut, ...
            'Label', 'Jet', ...
            'UserData', 'jet', ...
            'Separator', 'On', ...
            'Callback', @obj.onSelectLUT);
        uimenu(menuLut, ...
            'Label', 'HSV', ...
            'UserData', 'hsv', ...
            'Callback', @obj.onSelectLUT);
        uimenu(menuLut, ...
            'Label', 'Color Cube', ...
            'UserData', 'colorcube', ...
            'Callback', @obj.onSelectLUT);
        uimenu(menuLut, ...
            'Label', 'Prism', ...
            'UserData', 'prism', ...
            'Callback', @obj.onSelectLUT);

        % Matlab LUT's
        matlabLutsMenu = uimenu(menuLut, ...
            'Label', 'Matlab''s LUT', ...
            'Separator', 'On');
        uimenu(matlabLutsMenu, ...
            'Label', 'Hot', ...
            'UserData', 'hot', ...
            'Callback', @obj.onSelectLUT);
        uimenu(matlabLutsMenu, ...
            'Label', 'Cool', ...
            'UserData', 'cool', ...
            'Callback', @obj.onSelectLUT);
        uimenu(matlabLutsMenu, ...
            'Label', 'Spring', ...
            'UserData', 'spring', ...
            'Callback', @obj.onSelectLUT);
        uimenu(matlabLutsMenu, ...
            'Label', 'Summer', ...
            'UserData', 'summer', ...
            'Callback', @obj.onSelectLUT);
        uimenu(matlabLutsMenu, ...
            'Label', 'Autumn', ...
            'UserData', 'autumn', ...
            'Callback', @obj.onSelectLUT);
        uimenu(matlabLutsMenu, ...
            'Label', 'Winter', ...
            'UserData', 'winter', ...
            'Callback', @obj.onSelectLUT);
        uimenu(matlabLutsMenu, ...
            'Label', 'Bone', ...
            'UserData', 'bone', ...
            'Callback', @obj.onSelectLUT);
        uimenu(matlabLutsMenu, ...
            'Label', 'Copper', ...
            'UserData', 'copper', ...
            'Callback', @obj.onSelectLUT);
        uimenu(matlabLutsMenu, ...
            'Label', 'Pink', ...
            'UserData', 'pink', ...
            'Callback', @obj.onSelectLUT);
        uimenu(matlabLutsMenu, ...
            'Label', 'Lines', ...
            'UserData', 'lines', ...
            'Callback', @obj.onSelectLUT);

        colorLutsMenu = uimenu(menuLut, ...
            'Label', 'Simple Colors');
        uimenu(colorLutsMenu, ...
            'Label', 'Red', ...
            'UserData', 'redLUT', ...
            'Callback', @obj.onSelectLUT);
        uimenu(colorLutsMenu, ...
            'Label', 'Green', ...
            'UserData', 'greenLUT', ...
            'Callback', @obj.onSelectLUT);
        uimenu(colorLutsMenu, ...
            'Label', 'Blue', ...
            'UserData', 'blueLUT', ...
            'Callback', @obj.onSelectLUT);
        uimenu(colorLutsMenu, ...
            'Label', 'Yellow', ...
            'UserData', 'yellowLUT', ...
            'Callback', @obj.onSelectLUT);
        uimenu(colorLutsMenu, ...
            'Label', 'Cyan', ...
            'UserData', 'cyanLUT', ...
            'Callback', @obj.onSelectLUT);
        uimenu(colorLutsMenu, ...
            'Label', 'Magenta', ...
            'UserData', 'magentaLUT', ...
            'Callback', @obj.onSelectLUT);

        uimenu(menuLut, ...
            'Label', 'Background Color...', ...
            'Separator', 'On', ...
            'Callback', @obj.onSelectBackgroundColor);

        uimenu(menuView, ...
            'Label', 'Show Ortho Slices', ...
            'Separator', 'On', ...
            'Enable', imageFlag, ...
            'Callback', @obj.onShowOrthoPlanes);
        uimenu(menuView, ...
            'Label', 'Show 3D Ortho Slices', ...
            'Enable', imageFlag, ...
            'Callback', @obj.onShowOrthoSlices3d);
        uimenu(menuView, ...
            'Label', 'Isosurface Rendering', ...
            'Enable', intensityFlag, ...
            'Callback', @obj.onShowIsosurface);
        uimenu(menuView, ...
            'Label', 'Binary/Labels Surface Rendering', ...
            'Enable', labelFlag, ...
            'Callback', @obj.onShowLabelIsosurfaces);

        % Zoom menu items
        uimenu(menuView, ...
            'Label', 'Zoom &In', ...
            'Separator', 'On', ...
            'Callback', @obj.onZoomIn);
        uimenu(menuView, ...
            'Label', 'Zoom &Out', ...
            'Callback', @obj.onZoomOut);
        uimenu(menuView, ...
            'Label', 'Zoom 1:1', ...
            'Callback', @obj.onZoomOne);
        menuZoom = uimenu(menuView, ...
            'Label', 'Choose value');
        uimenu(menuView, ...
            'Label', 'Zoom &Best', ...
            'Accelerator', 'B', ...
            'Callback', @obj.onZoomBest);
        uimenu(menuZoom, ...
            'Label', '10:1', ...
            'UserData', 10, ...
            'Callback', @obj.onZoomValue);
        uimenu(menuZoom, ...
            'Label', '4:1', ...
            'UserData', 4, ...
            'Callback', @obj.onZoomValue);
        uimenu(menuZoom, ...
            'Label', '2:1', ...
            'UserData', 2, ...
            'Callback', @obj.onZoomValue);
        uimenu(menuZoom, ...
            'Label', '1:1', ...
            'UserData', 1, ...
            'Callback', @obj.onZoomValue);
        uimenu(menuZoom, ...
            'Label', '1:2', ...
            'UserData', 1/2, ...
            'Callback', @obj.onZoomValue);
        uimenu(menuZoom, ...
            'Label', '1:4', ...
            'UserData', 1/4, ...
            'Callback', @obj.onZoomValue);
        uimenu(menuZoom, ...
            'Label', '1:10', ...
            'UserData', 1/10, ...
            'Callback', @obj.onZoomValue);

        % Process menu
        menuProcess = uimenu(hf, ...
            'Label', '&Process', ...
            'Enable', imageFlag);

        menuTransform = uimenu(menuProcess, ...
            'Label', 'Geometric &Transforms');
        uimenu(menuTransform, ...
            'Label', '&Horizontal Flip', ...
            'UserData', 2, ...
            'Callback', @obj.onFlipImage);
        uimenu(menuTransform, ...
            'Label', '&Vertical Flip', ...
            'UserData', 1, ...
            'Callback', @obj.onFlipImage);
        uimenu(menuTransform, ...
            'Label', 'Flip &Z', ...
            'UserData', 3, ...
            'Callback', @obj.onFlipImage);
        uimenu(menuTransform, ...
            'Label', 'Rotate &Left', ...
            'Separator', 'On', ...
            'UserData', [3 -1], ...
            'Accelerator', 'L', ...
            'Callback', @obj.onRotateImage);
        uimenu(menuTransform, ...
            'Label', 'Rotate &Right', ...
            'UserData', [3 1], ...
            'Callback', @obj.onRotateImage);
        uimenu(menuTransform, ...
            'Label', 'Rotate X Up', ...
            'UserData', [1 -1], ...
            'Accelerator', 'R', ...
            'Callback', @obj.onRotateImage);
        uimenu(menuTransform, ...
            'Label', 'Rotate X Down', ...
            'UserData', [1 1], ...
            'Callback', @obj.onRotateImage);
        uimenu(menuTransform, ...
            'Label', 'Rotate Y Left', ...
            'UserData', [2 1], ...
            'Callback', @obj.onRotateImage);
        uimenu(menuTransform, ...
            'Label', 'Rotate Y Right', ...
            'UserData', [2 -1], ...
            'Callback', @obj.onRotateImage);

        uimenu(menuProcess, ...
            'Label', 'Crop Image', ...
            'Callback', @obj.onCropImage);
        uimenu(menuProcess, ...
            'Label', 'Crop Label', ...
            'Enable', labelFlag, ...
            'Callback', @obj.onCropLabel);
        uimenu(menuProcess, ...
            'Label', 'Toggle Z-Profile Display', ...
            'Callback', @obj.onToggleZProfileDisplay);

        uimenu(menuProcess, ...
            'Label', 'View &Histogram', ...
            'Separator', 'On', ...
            'Accelerator', 'H', ...
            'Callback', @obj.onDisplayHistogram);

        % Help
        menuHelp = uimenu(hf, 'Label', '&Help');
        uimenu(menuHelp, ...
            'Label', '&About...', ...
            'Accelerator', 'A', ...
            'Callback', @obj.onAbout);
    end

end

%% Methods for text display
methods
    function disp(obj)
        % display a resume of the slicer structure

        if isempty(obj.ImageData)
            fprintf('Slicer object, with no image.\n')
        else
            fprintf('Slicer object, containing a %d x %d x %d %s image.\n', ...
                obj.ImageSize, obj.ImageType);
        end

        % calibration information for image
        if obj.Calibrated
            fprintf('  Voxel spacing = [ %g %g %g ] %s\n', ...
                obj.VoxelSize, obj.VoxelSizeUnit');
            fprintf('  Image origin  = [ %g %g %g ] %s\n', ...
                obj.VoxelOrigin, obj.VoxelSizeUnit');
        end

        % determines whether empty lines should be printed or not
        if strcmp(get(0, 'FormatSpacing'), 'loose')
            fprintf('\n');
        end

    end
end

end


function [t,EA,EAd] = fig_animate(tout,Xout,Uout,Xdout,Udout,Uext,p)

flag_movie = p.flag_movie;

if flag_movie

    try
        name = ['test.mp4'];
        vidfile = VideoWriter(name,'MPEG-4');
    catch ME
        name = ['test'];
        vidfile = VideoWriter(name,'Motion JPEG AVI');
    end
    open(vidfile);
end

%% smoothen for animation
t = (tout(1):p.simTimeStep:tout(end));
X = interp1(tout,Xout,t);
U = interp1(tout,Uout,t);
Xd = interp1(tout,Xdout,t);
Ud = interp1(tout,Udout,t);
Ue = interp1(tout,Uext,t);

%% loop through frames
figure('Position',[200 100 1000 600]);
set(0, 'DefaultFigureRenderer', 'opengl');
set(gcf, 'Color', 'white')
N = 3;M = 3;    % subplot size

nt = length(t);
EA = zeros(nt,3);
EAd = zeros(nt,3);
for ii = 1:nt
    EA(ii,:) = fcn_X2EA(X(ii,:));
    EAd(ii,:) = fcn_X2EA(Xd(ii,:));
end

% for ZOH force
t2 = repelem(t,2);
t2(1) = []; t2(end+1) = t2(end);
U2 = repelem(U,2,1);

%%%%%% subfigure handles %%%%%%%
h_x = subplot(N,M,3);
h_dx = subplot(N,M,6);
h_w = subplot(N,M,9);
h_u = subplot(N,M,[7,8]);

for ii = 1:p.playSpeed:nt
    %% The main animation
    % plot setting
    pcom = X(ii,1:3)';
    h_main = subplot(N,M,[1,2,4,5]);
    hold on; grid on;axis square;axis equal;
    h_main.XLim = [pcom(1)-0.5 pcom(1)+0.5];
    h_main.YLim = [pcom(2)-0.5 pcom(2)+0.5];
    h_main.ZLim = [-0.2 0.6];

    viewPt = [0.2,0.5,0.2];
    view(viewPt);

    % plot robot & GRF
    % real
    fig_plot_robot(X(ii,:)',U(ii,:)',Ue(ii,:)',p)
    % desired
    fig_plot_robot_d(Xd(ii,:)',0*Ud(ii,:)',p)

    % text
    txt_time = ['t = ',num2str(t(ii),2),'s'];
    text(pcom(1),pcom(2),0.4,txt_time)
    txt_vd = ['vd = ',num2str(Xd(ii,4),2),'m/s'];
    text(pcom(1),pcom(2),0.5,txt_vd)
    txt_v = ['v = ',num2str(X(ii,4),2),'m/s'];
    text(pcom(1),pcom(2),0.45,txt_v)

    %% states
    %%%%%%%%% position %%%%%%%%%
    plot(h_x,t(1:ii),X(1:ii,1),'r',...
         t(1:ii),X(1:ii,2),'g',...
         t(1:ii),X(1:ii,3),'b',...
         t(1:ii),Xd(1:ii,1),'r--',...
         t(1:ii),Xd(1:ii,2),'g--',...
         t(1:ii),Xd(1:ii,3),'b--','linewidth',1)
    h_x.XLim = [t(1) t(end)];
    set( get(h_x,'Title'), 'String', 'Position [m]');

    %%%%%%%%% velocity %%%%%%%%%
    plot(h_dx,t(1:ii),X(1:ii,4),'r',...
         t(1:ii),X(1:ii,5),'g',...
         t(1:ii),X(1:ii,6),'b',...
         t(1:ii),Xd(1:ii,4),'r--',...
         t(1:ii),Xd(1:ii,5),'g--',...
         t(1:ii),Xd(1:ii,6),'b--','linewidth',1)
    h_dx.XLim = [t(1) t(end)];
    set( get(h_dx,'Title'), 'String', 'Velocity [m/s]');

    %%%%%%%%% Angular velocity %%%%%%%%%
    plot(h_w,t(1:ii),X(1:ii,16),'r',...
         t(1:ii),X(1:ii,17),'g',...
         t(1:ii),X(1:ii,18),'b',...
         t(1:ii),Xd(1:ii,16),'r--',...
         t(1:ii),Xd(1:ii,17),'g--',...
         t(1:ii),Xd(1:ii,18),'b--','linewidth',1)
    h_w.XLim = [t(1) t(end)];
    set( get(h_w,'Title'), 'String', 'Angular velocity [rad/s]' );

    %% control
    plot(h_u,t2(1:2*ii),U2(1:2*ii,3),'r',...
         t2(1:2*ii),U2(1:2*ii,6),'g',...
         t2(1:2*ii),U2(1:2*ii,9),'b',...
         t2(1:2*ii),U2(1:2*ii,12),'k',...
         t(1:ii),Ud(1:ii,3),'r--',...
         t(1:ii),Ud(1:ii,6),'g--',...
         t(1:ii),Ud(1:ii,9),'b--',...
         t(1:ii),Ud(1:ii,12),'k--','linewidth',1)
    h_u.XLim = [t(1) t(end)];
    set( get(h_u,'Title'), 'String', 'Fz [N]' );

    %% make movie
    if flag_movie
        writeVideo(vidfile, getframe(gcf));
    end

    drawnow
    if ii < nt
        cla(h_main);
    end

end

if flag_movie
    close(vidfile);
end


function taille = imgsize(adresse)

M=imread(adresse);
taille = size(M);

if length(taille) ==2
    taille(3) = 1;
end


function c = compute_children(bnet)
% COMPUTE_CHILDREN
% precomputes the children of nodes in a bnet
%
% The return value is a cell array for now

ss = size(bnet.dag, 1);
c = cell(ss, 1);
for i = 1:ss
  c{i} = children(bnet.dag, i);
end


%RCSSM		read CSSM statistics from google group
%		RCSSM extracts CSSM's posting activity
%		since its inauguration on jan 8, 1993
%		from google's group site and
%		- returns the data in a table
%		- plots an activity surface [abs/gradient]
%
%		CSSM = MATLAB's newsgroup: comp.soft-sys.matlab
%
%SYNTAX
%		T = RCSSM;
%
%OUTPUT
% T		output table with fields
%		.y	years: 1993 - current
%		.m	month: 1:12
%		.d	activity table
%			rows = year	[1=1993]
%			cols = month	[1= jan]
%
%EXAMPLE
%		tbl=rcssm;

% created:
%	us	10-Jan-2007 us@neurol.unizh.ch
% modified:
%	us	20-Feb-2008 13:14:18

function	t=rcssm

% currently valid google web address and rex templates
% - 20feb08	us

		url='http://groups.google.com/group/comp.soft-sys.matlab/about';
		rex='(?<=month/)\d+-\d+">\d+';
		fmt='%d-%d">%d';

		t=get_html(url,rex);
		t=get_tbl(t,fmt);
	if	~isempty(t)
		t=plt_tbl(t);
	end
end
%-------------------------------------------------------------------------------
function	t=get_html(url,rex)

		import com.mathworks.mde.desk.*;
		wb=com.mathworks.mde.webbrowser.WebBrowser.createBrowser;
		wb.setCurrentLocation(url(8:end));
		pause(1);

		t={};
	while	isempty(t)
		s=char(wb.getHtmlText);
		t=regexp(s,rex,'match').';
% make sure we can bail-out with CTR-C
		pause(.1);
	end
		desk=MLDesktop.getInstance;
		desk.removeClient(wb);
end
%-------------------------------------------------------------------------------
function	tbl=get_tbl(t,fmt)

		[t,n]=cellfun(@(x) sscanf(char(x),fmt),t,'uni',false);
	if	~all([n{:}]==3)
		disp(sprintf('RCSSM> error reading table'));
		tbl=[];
		return;
	end
		t=cat(2,t{:});
		iy=t(1,:)-t(1,1)+1;
		im=t(2,:);

		tbl.y=unique(t(1,:));
		tbl.iy=unique(iy);
		tbl.m=1:12;
		tbl.d=accumarray([iy.',im.'],t(3,:),[],[],nan);
end
%-------------------------------------------------------------------------------
function	tbl=plt_tbl(tbl)

		m={
			'jan',	'feb',	'mar',...
			'apr',	'may',	'jun',...
			'jul',	'aug',	'sep',...
			'oct',	'nov',	'dec'
		};

% surfaces
		surf(tbl.d.');
		colormap(jet(256));
		shading interp;
		hold on;
		surf(gradient(tbl.d).');
		shading interp;

		mh=mesh(tbl.d.'+10);
		set(mh,...
			'facecolor','none',...
			'edgecolor',[0,0,0]);

		brighten(.6);
		view(22,25);

% labels
		y=tbl.y;
		iy=1:2:max(tbl.iy);
	if	iy(end) ~= max(tbl.iy)
		iy=[iy,max(tbl.iy)];
		y=[y(1:2:end),y(end)];
	else
		y=y(1:2:end);
	end
		y=strread(sprintf('%2.2d\n',rem(y-1900,100)),'%s');

		set(gca,...
			'xlim',[1,iy(end)],...
			'xtick',iy,...
			'xticklabel',y,...
			'ylim',[1,12],...
			'ytick',1:12,...
			'yticklabel',m,...
			'fontsize',10,...
			'fontname','arial');

		lh(1)=xlabel('year');
		lh(2)=ylabel('month');
		lh(3)=zlabel('entries   [absolute / gradient]');
		set(lh,...
			'fontweight','bold',...
			'fontsize',12,...
			'fontname','arial');

		title('CSSM activity',...
			'fontweight','bold',...
			'fontsize',16,...
			'fontname','arial');

		shg;
		rotate3d on;
end


function drawStopActionAcrobot(soln,p)

clf; hold on;

length = p.l1+p.l2;
axis equal; axis(length*[-1,1,-1,1]); axis off;

% Start by drawing the trajectory of the tip
nCurve = 250;
t = linspace(soln.grid.time(1), soln.grid.time(end), nCurve);
Z = soln.interp.state(t);
[~,p2] = acrobotKinematics(Z,p);
plot(p2(1,:),p2(2,:),'LineWidth',2,'Color',[0.2,0.2,0.8]);

% Now draw the stop-action frames
nFrame = 10;
t = linspace(soln.grid.time(1), soln.grid.time(end), nFrame);
Z = soln.interp.state(t);

for i=1:nFrame
    z = Z(:,i);

    [p1,p2] = acrobotKinematics(z,p);
    pos = [[0;0],p1,p2];

    val = 0.3 + 0.7*(i-1)/(nFrame-1);
    color = hsv2rgb([0.0, 0.8, val]);

    plot(0,0,'ks','MarkerSize',25,'LineWidth',3)
    plot(pos(1,:),pos(2,:),'Color',color,'LineWidth',3)
    plot(pos(1,1),pos(2,1),'k.','MarkerSize',20)
    plot(pos(1,2),pos(2,2),'k.','MarkerSize',20)
    plot(pos(1,3),pos(2,3),'k.','MarkerSize',40)

end

end


function RoiFileName=dtiExportRoiToNifti(roi, refImage, outFile)
%
% dtiExportRoiToNifti(roi, ref, [outFile='roi'])
% Note:  refImage and outFile filenames: prefixes only
% Save a mrDiffusion ROI as a binary NIFTI image. Useful for
% loading the ROI into another program, like FSL or SPM.
%
% HISTORY:
% 2007.10.06 Kaustubh Supekar wrote it.

% Allow roi to be either a fileName or the roi struct.
if(ischar(roi))
   roi = dtiReadRoi(roi);
end

if(~exist('outFile','var')||isempty(outFile))
    outFile = 'roi';
end
l = length(outFile);
if(l<5||(~strcmpi(outFile(l-3:l),'.nii')&&~strcmpi(outFile(l-2:l),'.gz')))
    outFile = strcat(outFile,'.nii.gz');
end
% ref can either be a NIFTI filename or a NIFTI struct
if(ischar(refImage))
   l = length(refImage);
   if(l<5||(~strcmpi(refImage(l-3:l),'.nii')&&~strcmpi(refImage(l-2:l),'.gz')))
      refImage = [refImage '.nii.gz'];
   end
   ref = niftiRead(refImage);
end
c = mrAnatXformCoords(ref.qto_ijk, roi.coords);
c = round(c);
roiIm = ref;
roiIm.data = zeros(size(roiIm.data),'uint8');
roiIm.data(sub2ind(size(roiIm.data), c(:,1), c(:,2), c(:,3))) = 1;
roiIm.fname = outFile;
writeFileNifti(roiIm);
RoiFileName=roiIm.fname;
return;


function newdata=struct2jdata(data,varargin)
%
% newdata=struct2jdata(data,opt,...)
%
% convert a JData object (in the form of a struct array) into an array
%
% author: Qianqian Fang, <q.fang at neu.edu>
%
% input:
%      data: a struct array. If data contains JData keywords in the first
%            level children, these fields are parsed and regrouped into a
%            data object (arrays, trees, graphs etc) based on JData
%            specification. The JData keywords are
%               "_ArrayType_", "_ArraySize_", "_ArrayData_"
%               "_ArrayIsSparse_", "_ArrayIsComplex_"
%      opt: (optional) a list of 'Param',value pairs for additional options
%           The supported options include
%               'Recursive', if set to 1, will apply the conversion to
%                            every child; 0 to disable
%
% output:
%      newdata: the covnerted data if the input data does contain a JData
%               structure; otherwise, the same as the input.
%
% examples:
%      obj=struct('_ArrayType_','double','_ArraySize_',[2 3],
%                 '_ArrayIsSparse_',1 ,'_ArrayData_',null);
%      ubjdata=struct2jdata(obj);
%
% license:
%     BSD or GPL version 3, see LICENSE_{BSD,GPLv3}.txt files for details
%
% -- this function is part of JSONLab toolbox (http://iso2mesh.sf.net/cgi-bin/index.cgi?jsonlab)
%

fn=fieldnames(data);
newdata=data;
len=length(data);
if(jsonopt('Recursive',0,varargin{:})==1)
  for i=1:length(fn) % depth-first
    for j=1:len
        if(isstruct(getfield(data(j),fn{i})))
            newdata(j)=setfield(newdata(j),fn{i},jstruct2array(getfield(data(j),fn{i})));
        end
    end
  end
end
if(~isempty(strmatch('x0x5F_ArrayType_',fn)) && ~isempty(strmatch('x0x5F_ArrayData_',fn)))
  newdata=cell(len,1);
  for j=1:len
    ndata=cast(data(j).x0x5F_ArrayData_,data(j).x0x5F_ArrayType_);
    iscpx=0;
    if(~isempty(strmatch('x0x5F_ArrayIsComplex_',fn)))
        if(data(j).x0x5F_ArrayIsComplex_)
           iscpx=1;
        end
    end
    if(~isempty(strmatch('x0x5F_ArrayIsSparse_',fn)))
        if(data(j).x0x5F_ArrayIsSparse_)
            if(~isempty(strmatch('x0x5F_ArraySize_',fn)))
                dim=double(data(j).x0x5F_ArraySize_);
                if(iscpx && size(ndata,2)==4-any(dim==1))
                    ndata(:,end-1)=complex(ndata(:,end-1),ndata(:,end));
                end
                if isempty(ndata)
                    % All-zeros sparse
                    ndata=sparse(dim(1),prod(dim(2:end)));
                elseif dim(1)==1
                    % Sparse row vector
                    ndata=sparse(1,ndata(:,1),ndata(:,2),dim(1),prod(dim(2:end)));
                elseif dim(2)==1
                    % Sparse column vector
                    ndata=sparse(ndata(:,1),1,ndata(:,2),dim(1),prod(dim(2:end)));
                else
                    % Generic sparse array.
                    ndata=sparse(ndata(:,1),ndata(:,2),ndata(:,3),dim(1),prod(dim(2:end)));
                end
            else
                if(iscpx && size(ndata,2)==4)
                    ndata(:,3)=complex(ndata(:,3),ndata(:,4));
                end
                ndata=sparse(ndata(:,1),ndata(:,2),ndata(:,3));
            end
        end
    elseif(~isempty(strmatch('x0x5F_ArraySize_',fn)))
        if(iscpx && size(ndata,2)==2)
             ndata=complex(ndata(:,1),ndata(:,2));
        end
        ndata=reshape(ndata(:),data(j).x0x5F_ArraySize_);
    end
    newdata{j}=ndata;
  end
  if(len==1)
      newdata=newdata{1};
  end
end