%KWAVETRANSDUCER   Class definition for k-Wave transducer
%
% DESCRIPTION:
%       See makeTransducer for function arguments.
%
% ABOUT:
%       author          - Bradley Treeby
%       date            - 9th December 2010
%       last update     - 4th November 2013
%
% This function is part of the k-Wave Toolbox (http://www.k-wave.org)
% Copyright (C) 2009-2014 Bradley Treeby and Ben Cox

% This file is part of k-Wave. k-Wave is free software: you can
% redistribute it and/or modify it under the terms of the GNU Lesser
% General Public License as published by the Free Software Foundation,
% either version 3 of the License, or (at your option) any later version.
%
% k-Wave is distributed in the hope that it will be useful, but WITHOUT ANY
% WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
% FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for
% more details.
%
% You should have received a copy of the GNU Lesser General Public License
% along with k-Wave. If not, see <http://www.gnu.org/licenses/>.

% Define as a handle class - A handle class constructor returns a handle
% object that is a reference to the object created. You can assign the
% handle object to multiple variables or pass it to functions without
% causing MATLAB to make a copy of the original object. A function that
% modifies a handle object passed as an input argument does not need to
% return the object.
classdef kWaveTransducer < handle

    % define the properties of the transducer that cannot be modified by
    % the user after the transducer is initialised (these parameters are
    % stored). The numbers assigned here are the default values used if the
    % parameters are not set explicitly by the user.
    properties (GetAccess = 'public', SetAccess = 'private')

        % the total number of transducer elements
        number_elements = 128;

        % the width of each element in grid points
        element_width = 1;

        % the length of each element in grid points
        element_length = 20;

        % the spacing (kerf width) between the transducer elements in grid
        % points
        element_spacing = 0;

        % the position of the corner of the transducer in the grid
        position = [1, 1, 1];

        % the radius of curvature of the transducer [m]
        radius = inf;

    end

    % define the properties of the transducer that can be modified by the
    % user after the transducer is initialised (these parameters are
    % stored). The numbers assigned here are the default values used if the
    % parameters are not set explicitly by the user.
    properties (Access = 'public')

        % the transducer elements that are currently active elements
        active_elements;

        % the focus depth in the elevation direction [m]
        elevation_focus_distance = inf;

        % transmit apodization
        transmit_apodization = 'Rectangular';

        % receive apodization
        receive_apodization = 'Rectangular';

        % sound speed used to calculate beamforming delays [m/s]
        sound_speed = 1540;

        % focus distance used to calculate beamforming delays [m]
        focus_distance = inf;

        % time index to start recording if transducer is used as a sensor
        record_start_index = 1;

    end

    % define the dependent properties (these parameters are computed when
    % queried). Some of these properties act like normal user-modifiable
    % properties (e.g., steering_angle), but instead have explicit set and
    % get functions that change an internal stored value. This allows error
    % checking when the properties are changed.
    properties(Dependent = true)

        % binary mask of the active transducer elements
        active_elements_mask;

        % indexed mask of the active transducer elements, where the index
        % indicates the transducer element that each grid point belongs to.
        indexed_active_elements_mask;

        % binary mask of all the transducer elements (both active and
        % inactive)
        all_elements_mask;

        % indexed mask of all the transducer elements, where the index
        % indicates the transducer element that each grid point belongs to.
        indexed_elements_mask;

        % pseudonym for the active_elements_mask to maintain compatability
        % when the transducer is used in place of sensor or source.
        mask;

        % total width of the transducer in grid points
        transducer_width;

        % current number of active transducer elements
        number_active_elements;

        % user defined input signal with appended zeros dependent on the
        % beamforming (steering_angle, focus_distance) settings
        input_signal;

        % maximum steering angle - this sets the beamforming delay offset
        steering_angle_max;

        % number of zeros appended to input signal to allow a single time
        % series to be used within kspaceFirstOrder3D (either set to 'auto'
        % or based on the setting for steering_angle_max)
        appended_zeros;

        % offset used to make all the delays in the delay_mask positive
        % (either set to 'auto' or based on the setting for
        % steering_angle_max)
        beamforming_delays_offset;

        % steering angle used to calculate beamforming delays [deg]
        steering_angle;

    end

    % define the hidden properties - these cannot be seen or accessed
    % directly by the user, and are used to store internal properties
    properties (Hidden = true, Access = 'private')

        % indexed sensor mask from which the other masks are computed
        indexed_mask;

        % indexed mask for the voxels in each element used to compute
        % elevation focussing
        indexed_element_voxel_mask;

        % size of the grid in which the transducer is defined
        stored_grid_size;

        % corresponding grid spacing
        grid_spacing;

        % time spacing
        dt;

        % original copy of the user defined input signal
        stored_input_signal;

        % stored value of appended_zeros (accessed using get and
        % set methods). This is used to set the number of zeros that are
        % appended and prepended to the input signal.
        stored_appended_zeros = 'auto';

        % stored value of the minimum beamforming delay. This is used to
        % offset the delay mask so that all the delays are >= 0
        stored_beamforming_delays_offset = 'auto';

        % stored value of the steering_angle_max (accessed using get and
        % set methods). This can be set by the user and is used to derive
        % the two parameters above.
        stored_steering_angle_max = 'auto';

        % stored value of the steering_angle (accessed using get and
        % set methods)
        stored_steering_angle = 0;

    end

    % constructor function
    methods
        function transducer = kWaveTransducer(kgrid, transducer_properties)

            % allocate the grid size and spacing
            transducer.stored_grid_size = [kgrid.Nx, kgrid.Ny, kgrid.Nz];
            transducer.grid_spacing = [kgrid.dx, kgrid.dy, kgrid.dz];

            % allocate the temporal spacing
            if isnumeric(kgrid.dt)
                transducer.dt = kgrid.dt;
            elseif isnumeric(kgrid.t_array)
                transducer.dt = kgrid.t_array(2) - kgrid.t_array(1);
            else
                error('kgrid.dt or kgrid.t_array must be explicitly defined');
            end

            % check for properties input
            if nargin == 1
                transducer_properties = [];
            end

            % check the input fields
            checkFieldNames(transducer_properties, {...
                'number_elements', 'element_width', 'element_length', 'element_spacing',...
                'position', 'radius', ...
                'active_elements', 'elevation_focus_distance', 'focus_distance', 'steering_angle',...
                'receive_apodization', 'transmit_apodization',...
                'sound_speed', 'input_signal', 'steering_angle_max'});

            % replace default settings with user defined properties if
            % given
            % -------------------------------------------------------------

            if isfield(transducer_properties, 'number_elements')
                % force value to be a positive integer
                transducer.number_elements = round(abs(transducer_properties.number_elements));
            end

            if isfield(transducer_properties, 'element_width')
                % force value to be a positive integer
                transducer.element_width = round(abs(transducer_properties.element_width));
            end

            if isfield(transducer_properties, 'element_length')
                % force value to be a positive integer
                transducer.element_length = round(abs(transducer_properties.element_length));
            end

            if isfield(transducer_properties, 'element_spacing')
                % force value to be a positive integer
                transducer.element_spacing = round(abs(transducer_properties.element_spacing));
            end

            if isfield(transducer_properties, 'position')
                % force values to be positive integers
                transducer.position = round(abs(transducer_properties.position));
            end

            if isfield(transducer_properties, 'radius')
                transducer.radius = transducer_properties.radius;

                % only allow an infinite radius for now
                if ~isinf(transducer.radius)
                    error('Only a value of transducer.radius = inf is currently supported');
                end
            end

            if isfield(transducer_properties, 'active_elements')
                transducer.active_elements = transducer_properties.active_elements;
            else
                transducer.active_elements = ones(transducer_properties.number_elements, 1);
            end

            if isfield(transducer_properties, 'elevation_focus_distance')
                transducer.elevation_focus_distance = transducer_properties.elevation_focus_distance;
            end

            if isfield(transducer_properties, 'receive_apodization')
                % if a user defined apodization is given explicitly, check
                % the length of the input
                if isnumeric(transducer_properties.receive_apodization) ...
                        && (length(transducer_properties.receive_apodization) ~= transducer.number_active_elements)
                    error('The length of the receive apodization input must match the number of active elements');
                end

                % assign the input
                transducer.receive_apodization = transducer_properties.receive_apodization;
            end

            if isfield(transducer_properties, 'transmit_apodization')
                % if a user defined apodization is given explicitly, check
                % the length of the input
                if isnumeric(transducer_properties.transmit_apodization) ...
                        && (length(transducer_properties.transmit_apodization) ~= transducer.number_active_elements)
                    error('The length of the transmit apodization input must match the number of active elements');
                end

                % assign the input
                transducer.transmit_apodization = transducer_properties.transmit_apodization;
            end

            if isfield(transducer_properties, 'sound_speed')
                transducer.sound_speed = transducer_properties.sound_speed;

                % check to see the sound_speed is positive
                if ~(transducer.sound_speed> 0)
                    error('transducer.sound_speed must be greater than 0');
                end
            end

            if isfield(transducer_properties, 'focus_distance')
                transducer.focus_distance = transducer_properties.focus_distance;
            end

            if isfield(transducer_properties, 'input_signal')
                transducer.stored_input_signal = transducer_properties.input_signal;

                % force the input signal to be a column vector
                if numDim(transducer.input_signal) == 1
                    transducer.stored_input_signal = reshape(transducer.stored_input_signal, [], 1);
                else
                    error('transducer.input_signal must be a one-dimensional array');
                end
            end

            if isfield(transducer_properties, 'steering_angle_max')
                % set the maximum steering angle using the set method (this
                % avoids having to duplicate error checking)
                transducer.steering_angle_max = transducer_properties.steering_angle_max;
            end

            if isfield(transducer_properties, 'steering_angle')
                % set the maximum steering angle using the set method (this
                % avoids having to duplicate error checking)
                transducer.steering_angle = transducer_properties.steering_angle;
            end

            % -------------------------------------------------------------

            % check the transducer fits into the grid
            if sum(transducer.position(:) == 0)
                error('The defined transducer must be positioned within the grid');
            elseif (transducer.position(2) + transducer.number_elements*transducer.element_width + (transducer.number_elements - 1)*transducer.element_spacing) > transducer.stored_grid_size(2)
                error('The defined transducer is too large or positioned outside the grid in the y-direction');
            elseif (transducer.position(3) + transducer.element_length) > transducer.stored_grid_size(3)
                transducer.position(3)
                transducer.element_length
                transducer.stored_grid_size(3)
                error('The defined transducer is too large or positioned outside the grid in the z-direction');
            elseif transducer.position(1) > transducer.stored_grid_size(1)
                error('The defined transducer is positioned outside the grid in the x-direction');
            end

            % assign the data type for the transducer matrix based on the
            % number of different elements (uint8 supports 255 numbers so
            % most of the time this data type will be used)
            if transducer.number_elements < intmax('uint8');
                mask_type = 'uint8';
            elseif transducer.number_elements < intmax('uint16');
                mask_type = 'uint16';
            elseif transducer.number_elements < intmax('uint32');
                mask_type = 'uint32';
            else
                mask_type = 'uint64';
            end

            % create an empty transducer mask (the grid points within
            % element 'n' are all given the value 'n')
            transducer.indexed_mask = zeros(transducer.stored_grid_size, mask_type);

            % create a second empty mask used for the elevation beamforming
            % delays (the grid points across each element are numbered 1 to
            % M, where M is the number of grid points in the elevation
            % direction)
            transducer.indexed_element_voxel_mask = zeros(transducer.stored_grid_size, mask_type);

            % create the corresponding indexing variable 1:M
            element_voxel_index = repmat(1:transducer.element_length, transducer.element_width, 1);

            % for each transducer element, calculate the grid point indices
            for element_index = 1:transducer.number_elements

                % assign the current transducer position
                element_pos_x = transducer.position(1);
                element_pos_y = transducer.position(2) + (transducer.element_width + transducer.element_spacing)*(element_index - 1);
                element_pos_z = transducer.position(3);

                % assign the grid points within the current element the
                % index of the element
                transducer.indexed_mask(element_pos_x, element_pos_y:element_pos_y + transducer.element_width - 1, element_pos_z:element_pos_z + transducer.element_length - 1) = element_index;

                % assign the individual grid points an index corresponding
                % to their order across the element
                transducer.indexed_element_voxel_mask(element_pos_x, element_pos_y:element_pos_y + transducer.element_width - 1, element_pos_z:element_pos_z + transducer.element_length - 1) = element_voxel_index;

            end

            % double check the transducer fits within the desired grid size
            if any(size(transducer.indexed_elements_mask) ~= transducer.stored_grid_size)
                error('Desired transducer is larger than the input grid_size');
            end

        end
    end

    % set and get functions for dependent variables
    methods

        % return input signal
        function signal = get.input_signal(obj)

            % copy the stored input signal
            signal = obj.stored_input_signal;

            % check the signal is not empty
            if isempty(signal)
                error('Transducer input signal is not defined');
            end

            % automatically prepend and append zeros if the beamforming
            % delay offset is set

            % check if the beamforming delay offset is set. If so, use this
            % number to prepend and append this number of zeros to the
            % input signal. Otherwise, calculate how many zeros are needed
            % and prepend and append these.
            if ~strcmp(obj.stored_appended_zeros, 'auto')

                % use the current value of the beamforming offset to add
                % zeros to the input signal
                signal = [zeros(obj.stored_appended_zeros, 1); signal; zeros(obj.stored_appended_zeros, 1)];

            else

                % get the current delay beam forming
                delay_mask = obj.delay_mask;

                % find the maximum delay
                delay_max = max(delay_mask(:));

                % count the number of leading zeros in the input signal
                leading_zeros = find(signal ~= 0, 1) - 1;

                % count the number of trailing zeros in the input signal
                trailing_zeros = find(flipud(signal) ~= 0, 1) - 1;

                % check the number of leading zeros is sufficient given the
                % maximum delay
                if leading_zeros < delay_max + 1
                    disp(['  prepending transducer.input_signal with ' num2str(delay_max - leading_zeros + 1) ' leading zeros']);

                    % prepend extra leading zeros
                    signal = [zeros(delay_max - leading_zeros + 1, 1); signal];
                end

                % check the number of leading zeros is sufficient given the
                % maximum delay
                if trailing_zeros < delay_max + 1
                    disp(['  appending transducer.input_signal with ' num2str(delay_max - trailing_zeros + 1) ' trailing zeros']);

                    % append extra trailing zeros
                    signal = [signal; zeros(delay_max - trailing_zeros + 1, 1)];
                end
            end

        end

        function transducer_width = get.transducer_width(obj)
            % return the overall length of the transducer
            transducer_width = obj.number_elements*obj.element_width + (obj.number_elements - 1)*obj.element_spacing;
        end

        function number_active_elements = get.number_active_elements(obj)
            % return the current number of active elements
            number_active_elements = sum(obj.active_elements(:));
        end

        % allow mask query to allow compatability with regular sensor
        % structure - return the active sensor mask
        function mask = get.mask(obj)
            mask = obj.active_elements_mask;
        end

        % return a binary mask showing the locations of the active elements
        function mask = get.active_elements_mask(obj)

            % copy the indexed elements mask
            mask = obj.indexed_mask;

            % remove inactive elements from the mask
            for element_index = 1:obj.number_elements
                mask(mask == element_index) = obj.active_elements(element_index);
            end

            % convert remaining mask to binary
            mask(mask ~= 0) = 1;

        end

        % return a binary mask showing the locations of all the elements
        % (both active and inactive)
        function mask = get.all_elements_mask(obj)
            mask = obj.indexed_mask;
            mask(mask ~= 0) = 1;
        end

        % return a copy of the indexed elements mask
        function mask = get.indexed_elements_mask(obj)
            mask = obj.indexed_mask;
        end

        % return a copy of the indexed elements mask only for the active
        % elements
        function mask = get.indexed_active_elements_mask(obj)

            % copy the indexed elements mask
            mask = obj.indexed_mask;

            % remove inactive elements from the mask
            for element_index = 1:obj.number_elements
                if ~(obj.active_elements(element_index))
                    mask(mask == element_index) = 0;
                end
            end

            % force the lowest element index to be 1
            lowest_active_element_index = find(obj.active_elements, 1);
            mask(mask ~= 0) = mask(mask ~= 0) - lowest_active_element_index + 1;

        end

        % return the stored value of the steering angle
        function steering_angle = get.steering_angle(obj)
            steering_angle = obj.stored_steering_angle;
        end

        % set the stored value of the steering angle
        function set.steering_angle(obj, steering_angle)

            % force to be scalar
            steering_angle = steering_angle(1);

            % check if the steering angle is between -90 and 90
            if ~(steering_angle < 90 && steering_angle > -90)
                error('Input for steering_angle must be betweeb -90 and 90 degrees');
            end

            % check if the steering angle is less than the maximum steering
            % angle
            if ~strcmp(obj.stored_steering_angle_max, 'auto') && (abs(steering_angle) > obj.stored_steering_angle_max)
                error('Input for steering_angle cannot be greater than steering_angle_max');
            end

            % update the stored value
            obj.stored_steering_angle = steering_angle;

        end

        % return the stored value of the maximum steering angle
        function steering_angle_max = get.steering_angle_max(obj)
            steering_angle_max = obj.stored_steering_angle_max;
        end

        % set the stored value of the maximum steering angle and
        % beamforming offset
        function set.steering_angle_max(obj, steering_angle_max)

            % force input to be scalar and positive
            steering_angle_max = steering_angle_max(1);

            % check the steering angle is within range
            if ~(steering_angle_max < 90 && steering_angle_max > -90)
                error('Input for steering_angle_max must be between -90 and 90');
            end

            % check the maximum steering angle is greater than the current
            % steering angle
            if ~strcmp(obj.stored_steering_angle_max, 'auto') && (abs(obj.stored_steering_angle) > steering_angle_max)
                error('Input for steering_angle_max cannot be less than the current steering_angle');
            end

            % overwrite the stored value
            obj.stored_steering_angle_max = steering_angle_max;

            % store a copy of the current value for the steering angle
            current_steering_angle = obj.stored_steering_angle;

            % overwrite with the user defined maximum value
            obj.stored_steering_angle = steering_angle_max;

            % set the beamforming delay offset to zero (this means the
            % delays will contain negative values which we can use to
            % derive the new values for the offset)
            obj.stored_appended_zeros = 0;
            obj.stored_beamforming_delays_offset = 0;

            % get the element beamforming delays and reverse
            delay_times = -obj.beamforming_delays;

            % get the maximum and minimum beamforming delays
            max_delay = max(delay_times);
            min_delay = min(delay_times);

            % add the maximum and minimum elevation delay if the elevation
            % focus is not set to infinite
            if ~isinf(obj.elevation_focus_distance)
                max_delay = max_delay + max(obj.elevation_beamforming_delays);
                min_delay = min_delay + min(obj.elevation_beamforming_delays);
            end

            % set the beamforming offset to the difference between the
            % maximum and minimum delay
            obj.stored_appended_zeros = max_delay - min_delay;

            % set the minimum beamforming delay (converting to a positive
            % number)
            obj.stored_beamforming_delays_offset = - min_delay;

            % reset the previous value of the steering angle
            obj.stored_steering_angle = current_steering_angle;

        end

        % return the stored value of appended zeros
        function appended_zeros = get.appended_zeros(obj)
            appended_zeros = obj.stored_appended_zeros;
        end

        % return the stored value of the offset used to force the values in
        % delay_mask to be >= 0
        function beamforming_delays_offset = get.beamforming_delays_offset(obj)
            beamforming_delays_offset = obj.stored_beamforming_delays_offset;
        end

    end

    % general class methods
    methods

        % calculate the beamforming delays based on the focus and steering
        % settings
        function delay_times = beamforming_delays(obj)

            % calculate the element pitch in [m]
            element_pitch = (obj.element_spacing + obj.element_width)*obj.grid_spacing(2);

            % create indexing variable
            element_index = -(obj.number_active_elements - 1)/2:(obj.number_active_elements - 1)/2;

            % check for focus depth
            if isinf(obj.focus_distance)
                % calculate time delays for a steered beam
                delay_times = element_pitch*element_index*sin(obj.steering_angle*pi/180)/(obj.sound_speed);        % [s]
            else
                % calculate time delays for a steered and focussed beam
                delay_times = obj.focus_distance/obj.sound_speed * (1 - sqrt(...
                    1 ...
                    + (element_index*element_pitch./obj.focus_distance).^2 ...
                    - 2 * (element_index*element_pitch./obj.focus_distance) * sin(obj.steering_angle*pi/180) ...
                    ));      % [s]
            end

            % convert the delays to be in units of time points
            delay_times = round(delay_times/obj.dt);

        end

        % calculate the elevation beamforming delays based on the focus
        % setting
        function delay_times = elevation_beamforming_delays(obj)

            if ~isinf(obj.elevation_focus_distance)

                % create indexing variable
                element_index = -(obj.element_length - 1)/2:(obj.element_length - 1)/2;

                % calculate time delays for a focussed beam
                delay_times = (obj.elevation_focus_distance - sqrt( (element_index*obj.grid_spacing(3)).^2 + obj.elevation_focus_distance^2 ))./obj.sound_speed;

                % convert the delays to be in units of time points and then
                % reverse
                delay_times = -round(delay_times/obj.dt);

            else

                % create an empty array
                delay_times = zeros(1, obj.element_length);

            end

        end

        % function to create a scan line based on the input sensor data and
        % the current apodization and beamforming setting
        function line = scan_line(obj, sensor_data)

            % get the current apodization setting
            apodization = obj.get_receive_apodization;

            % get the current beamforming weights and reverse
            delays = -obj.beamforming_delays;

            % offset the received sensor_data by the beamforming delays and
            % apply receive apodization
            for element_index = 1:obj.number_active_elements
                if delays(element_index) > 0
                    % shift element data forwards
                    sensor_data(element_index, :) = apodization(element_index).*[sensor_data(element_index, 1 + delays(element_index):end), zeros(1, delays(element_index))];
                elseif delays(element_index) < 0
                    % shift element data backwards
                    sensor_data(element_index, :) = apodization(element_index).*[zeros(1, -delays(element_index)), sensor_data(element_index, 1:end + delays(element_index))];
                end
            end

            % form the a-line summing across the elements
            line = sum(sensor_data);

        end

        % function to average the individual time series recorded at each
        % grid point of each transducer element (as returned by the C++
        % code) and return a single time series per active transducer
        % element (as returned by the MATLAB code)
        function sensor_data_sum = combine_sensor_data(obj, sensor_data)

            % check the data is the correct size
            if size(sensor_data, 1) ~= (obj.number_active_elements * obj.element_width * obj.element_length)
                error('The number of time series in the input sensor_data must match the number of grid points in the active tranducer elements.');
            end

            % get index of which element each time series belongs to
            ind = obj.indexed_active_elements_mask(obj.indexed_active_elements_mask > 0);

            % create empty output
            sensor_data_sum = zeros(obj.number_active_elements, size(sensor_data, 2));

            % check if an elevation focus is set
            if isinf(obj.elevation_focus_distance)

                % loop over time series and sum
                for ii = 1:length(ind)
                    sensor_data_sum(ind(ii), :) = sensor_data_sum(ind(ii), :) + sensor_data(ii, :);
                end

            else

                % get the elevation delay for each grid point of each
                % active transducer element (this is given in units of grid
                % points)
                dm = obj.delay_mask(2);
                dm = dm(obj.active_elements_mask ~= 0);

                % loop over time series, shift and sum
                for ii = 1:length(ind)
                    sensor_data_sum(ind(ii), 1:end - dm(ii)) = sensor_data_sum(ind(ii), 1:end - dm(ii)) + sensor_data(ii, 1 + dm(ii):end);
                end

            end

            % divide by number of time series in each element
            sensor_data_sum = sensor_data_sum .* (1/ (obj.element_width * obj.element_length));

        end

        % plot the transducer using voxelPlot
        function plot(obj)
            voxelPlot(double(obj.all_elements_mask));
        end

        % print out the transducer properties
        function properties(obj)
            disp(' ');
            disp('k-Wave Transducer Properties');
            disp(['  transducer position:       [' num2str(obj.position) ']']);
            disp(['  transducer width:          ' scaleSI(obj.transducer_width*obj.grid_spacing(2)) 'm (' num2str(obj.transducer_width) ' grid points)']);
            disp(['  number of elements:        ' num2str(obj.number_elements)]);
            disp(['  number of active elements: ' num2str(obj.number_active_elements) ' (elements ' num2str(find(obj.active_elements, 1, 'first')) ' to ' num2str(find(obj.active_elements, 1, 'last')) ')']);
            disp(['  element width:             ' scaleSI(obj.element_width*obj.grid_spacing(2)) 'm (' num2str(obj.element_width) ' grid points)']);
            disp(['  element spacing (kerf):    ' scaleSI(obj.element_spacing*obj.grid_spacing(2)) 'm (' num2str(obj.element_spacing) ' grid points)']);
            disp(['  element pitch:             ' scaleSI((obj.element_spacing + obj.element_width)*obj.grid_spacing(2)) 'm (' num2str((obj.element_spacing + obj.element_width)) ' grid points)']);
            disp(['  element length:            ' scaleSI(obj.element_length*obj.grid_spacing(3)) 'm (' num2str(obj.element_length) ' grid points)']);
            disp(['  sound speed:               ' num2str(obj.sound_speed) 'm/s']);
            if isinf(obj.focus_distance)
                disp('  focus distance:             infinite');
            else
                disp(['  focus distance:            ' scaleSI(obj.focus_distance) 'm']);
            end
            if isinf(obj.elevation_focus_distance)
                disp('  elevation focus distance:  infinite');
            else
                disp(['  elevation focus distance:  ' scaleSI(obj.elevation_focus_distance) 'm']);
            end
            disp(['  steering angle:            ' num2str(obj.steering_angle) ' degrees']);
            disp(['  steering angle max:        ' num2str(obj.steering_angle_max) ' degrees']);
        end

    end

    % hidden class methods used with kspaceFirstOrder3D
    methods (Hidden = true, Access = 'public')

        % return a delay mask based on the transmit beamforming delays.
        % This is used if the transducer is a source. The delays are set
        % for each grid point of the active_elements_mask and define which
        % element of the input_signal should be used at each source point.
        % This mask is incremented within kspaceFirstOrder3D after each
        % time step. The mask is indexed from 0 and is incremented by 1
        % within kspaceFirstOrder3D .
        function mask = delay_mask(obj, mode)
            % mode == 1: both delays
            % mode == 2: elevation only
            % mode == 3: azimuth only

            % assign the delays to a new mask using the indexed_element_mask
            indexed_active_elements_mask = obj.indexed_active_elements_mask;
            active_elements_index = find(indexed_active_elements_mask ~= 0);
            mask = zeros(obj.stored_grid_size);

            % calculate azimuth focus delay times provided they are not all
            % zero
            if (~isinf(obj.focus_distance) || (obj.steering_angle ~= 0)) && (nargin == 1 || mode ~= 2)

                % get the element beamforming delays and reverse
                delay_times = -obj.beamforming_delays;

                % add delay times
                mask(active_elements_index) = delay_times(indexed_active_elements_mask(active_elements_index));

            end

            % calculate elevation focus time delays provided each element
            % is longer than one grid point
            if ~isinf(obj.elevation_focus_distance) && (obj.element_length > 1) && (nargin == 1 || mode ~= 3)

                % get elevation beamforming delays
                elevation_delay_times = obj.elevation_beamforming_delays;

                % get current mask
                element_voxel_mask = obj.indexed_element_voxel_mask;

                % add delay times
                mask(active_elements_index) = mask(active_elements_index) + elevation_delay_times(element_voxel_mask(active_elements_index)).';

            end

            % shift delay times (these should all be >= 0, where a value of
            % 0 means no delay)
            if strcmp(obj.stored_appended_zeros, 'auto')
                mask(active_elements_index) = mask(active_elements_index) - min(min(min(mask(active_elements_index))));
            else
                mask(active_elements_index) = mask(active_elements_index) + obj.stored_beamforming_delays_offset;
            end

        end

        % return a binary mask based on the elevation focus. This sets the
        % elements to extract at each timestep from the sensor_data_buffer
        % used in kspaceFirstOrder3D. This is used if the transducer is a
        % sensor and the elevation focus is set. The mask is of the form:
        %
        %   1 0 0 0
        %   0 1 0 0
        %   0 0 1 0
        %   0 0 1 0
        %   0 0 0 1
        %   0 0 0 1
        %   0 0 0 1
        %   0 0 1 0
        %   0 0 1 0
        %   0 1 0 0
        %   1 0 0 0
        %
        % The mask remains constant, but extracts new elements at each time
        % step as the FIFO buffer sensor_data_buffer is updated
        function mask = elevation_beamforming_mask(obj)

            % get elevation beamforming mask
            delay_mask = obj.delay_mask(2);

            % extract the active elements
            delay_mask = delay_mask(obj.active_elements_mask ~= 0);

            % force delays to start from zero
            delay_mask = delay_mask - min(delay_mask(:));

            % create an empty output mask
            mask = zeros(length(delay_mask), max(delay_mask) + 1);

            % populate the mask by setting 1's at the index given by the
            % delay time
            for index = 1:length(delay_mask)
                mask(index, delay_mask(index) + 1) = 1;
            end

            % flip the mask so the shortest delays are at the right
            mask = fliplr(mask);
        end

        % convert the transmit apodization into the form of a element mask,
        % where the apodization values are placed at the grid points
        % belonging to the active transducer elements. These values are
        % then extracted in the correct order within
        % kspaceFirstOrder_inputChecking using apodization =
        % transmit_apodization_mask(active_elements_mask ~= 0)
        function mask = transmit_apodization_mask(obj)

            % check if a user defined apodization is given and whether this
            % is still the correct size (in case the number of active
            % elements has changed)
            if isnumeric(obj.transmit_apodization)
                if (length(obj.transmit_apodization) ~= obj.number_active_elements)
                    error('The length of the transmit apodization input must match the number of active elements');
                else
                    % assign apodization
                    apodization = obj.transmit_apodization;
                end
            else
                % create apodization using getWin
                apodization = getWin(obj.number_active_elements, obj.transmit_apodization);
            end

            % create an empty mask;
            mask = zeros(obj.stored_grid_size);

            % assign the apodization values to every grid point in the
            % transducer
            mask_index = obj.indexed_active_elements_mask;
            mask_index = mask_index(mask_index ~= 0);
            mask(obj.active_elements_mask == 1) = apodization(mask_index);

        end

        % allow the mask to be resized by the simulation functions (this is
        % used when 'PMLInside' is set to false)
        function expand_grid(obj, expand_size)
            obj.indexed_mask = expandMatrix(obj.indexed_mask, expand_size, 0);
        end

        % allow the mask to be resized by the simulation functions (this is
        % used when 'PMLInside' is set to false)
        function retract_grid(obj, retract_size)
            obj.indexed_mask = obj.indexed_mask(1 + retract_size(1):end - retract_size(1), 1 + retract_size(2):end - retract_size(2), 1 + retract_size(3):end - retract_size(3));
        end

        % return the size of the grid
        function grid_size = grid_size(obj)
            grid_size = obj.stored_grid_size;
        end

    end

    % internal class methods only accessible by other functions within
    % kWaveTransducer
    methods (Hidden = true, Access = 'private')

        % return the transmit apodization, converting strings of window
        % type to actual numbers using getWin
        function apodization = get_transmit_apodization(obj)

            % check if a user defined apodization is given and whether this
            % is still the correct size (in case the number of active
            % elements has changed)
            if isnumeric(obj.transmit_apodization)
                if (length(obj.transmit_apodization) ~= obj.number_active_elements)
                    error('The length of the transmit apodization input must match the number of active elements');
                else
                    % assign apodization
                    apodization = obj.transmit_apodization;
                end
            else
                % create apodization using getWin
                apodization = getWin(obj.number_active_elements, obj.transmit_apodization);
            end
        end

        % return the receive apodization, converting strings of window
        % type to actual numbers using getWin
        function apodization = get_receive_apodization(obj)

            % check if a user defined apodization is given and whether this
            % is still the correct size (in case the number of active
            % elements has changed)
            if isnumeric(obj.receive_apodization)
                if (length(obj.receive_apodization) ~= obj.number_active_elements)
                    error('The length of the receive apodization input must match the number of active elements');
                else
                    % assign apodization
                    apodization = obj.receive_apodization;
                end
            else
                % create apodization using getWin
                apodization = getWin(obj.number_active_elements, obj.receive_apodization);
            end
        end

    end
end


% this function return the transformer's (input argument) loss
function FValue=SP_tlosscost1(InValue)

global KWTransformersTypes ShortCircuitLosses
Tag = find(KWTransformersTypes == InValue);
FValue = ShortCircuitLosses(1,Tag);


% Fix ac-pc origin misalignment in t1's and tensors. (y1)
spm_defaults; global defaults;
estParams = defaults.coreg.estimate;
estParams.params = [0 0 0];
bd = '/biac3/wandell4/data/reading_longitude/dti_y1';
od = '/biac3/wandell4/data/reading_longitude/dti_y1_old';
d = dir(bd);
for(ii=1:length(d))
  dtFn = fullfile(od,d(ii).name,[d(ii).name '_dt6_noMask.mat']);
  t1Fn = fullfile(bd,d(ii).name,'t1','t1.nii.gz');
  dtiFn = fullfile(bd,d(ii).name,'dti06','bin');
  if(d(ii).isdir && exist(dtFn,'file'))
	dt = load(dtFn,'anat');
	Vdst.uint8 = uint8(mrAnatHistogramClip(double(dt.anat.img), 0.4, 0.99)*255+0.5);
	Vdst.mat = dt.anat.xformToAcPc;
	clear dt;
	if(~exist(t1Fn,'file'))
	  disp(['ERROR: t1 not found- skipping t1 for ' d(ii).name '.']);
	else
	  disp(['Processing t1 for ' d(ii).name '...']);
	  t1 = niftiRead(t1Fn);
	  Vsrc.uint8 = uint8(mrAnatHistogramClip(double(t1.data), 0.4, 0.99)*255+0.5);
	  Vsrc.mat = t1.qto_xyz;
	  x = spm_coreg(Vdst,Vsrc,estParams);
	  x = round(x);
	  if(any(x~=0))
		xf = t1.qto_xyz;
		xf(1:3,4) = xf(1:3,4)-x';
		% o=mrAnatXformCoords(inv(xf),[0 0 0]); imagesc(t1.data(:,:,o(3))); axis image; colormap gray; hold on; plot(o(2),o(1),'ro'); hold off
		t1 = niftiSetQto(t1, xf);
		writeFileNifti(t1);
	  end
	  clear t1;
	end
	if(~exist(dtiFn,'dir')||~exist(fullfile(dtiFn,'b0.nii.gz'),'file'))
	  disp(['ERROR: dti dir not found- skipping tensors for ' d(ii).name '.']);
	else
	  disp(['Processing tensors for ' d(ii).name '...']);
	  b0 = niftiRead(fullfile(dtiFn,'b0.nii.gz'));
	  Vsrc.uint8 = uint8(mrAnatHistogramClip(double(b0.data), 0.4, 0.99)*255+0.5);
	  Vsrc.mat = b0.qto_xyz;
	  clear b0;
	  x = spm_coreg(Vdst,Vsrc,estParams);
	  x = round(x);
	  if(any(x~=0))
		xf = Vsrc.mat;
		xf(1:3,4) = xf(1:3,4)-x';
		% o=mrAnatXformCoords(inv(xf),[0 0 0]); imagesc(b0.data(:,:,o(3))); axis image; colormap gray; hold on; plot(o(2),o(1),'ro'); hold off
		tf = dir(fullfile(dtiFn,'*.nii.gz'));
		for(jj=1:length(tf))
		   ni = niftiRead(fullfile(dtiFn,tf(jj).name));
		   if(all(ni.qto_xyz(:)==Vsrc.mat(:)))
			 disp(['  Fixing xform in ' tf(jj).name '...']);
			 ni = niftiSetQto(ni, xf);
			 writeFileNifti(ni);
		   else
			 disp(['  ERROR: xform mismatch in ' tf(jj).name '- xform not fixed.']);
		   end
		end
	  end
	end
  end
end

%
% Fix ac-pc origin misalignment in t1's and tensors. (y2 & up)
%
spm_defaults; global defaults;
estParams = defaults.coreg.estimate;
estParams.params = [0 0 0];
d2Dir = '/biac3/wandell4/data/reading_longitude/dti_y3';
d1Dir = '/biac3/wandell4/data/reading_longitude/dti_y2';
d2 = dir(fullfile(d2Dir,'*06*'));
d1 = dir(fullfile(d1Dir,'*05*'));
d1 = {d1.name}; d2 = {d2.name};
for(ii=1:length(d2))
  y2sc = d2{ii};
  sc = explode('0',y2sc); sc = sc{1};
  y1sc = strmatch(sc,d1);
  if(isempty(y1sc)||numel(y1sc)>1)
	disp(['Problem locating previous year data for ' y2sc '- skipping.']);
	continue;
  end
  y1sc = d1{y1sc};
  y1T1Fn = fullfile(d1Dir,y1sc,'t1','t1.nii.gz');
  y2T1Fn = fullfile(d2Dir,y2sc,'t1','t1.nii.gz');
  y2DtFn = fullfile(d2Dir,y2sc,'dti06','bin');
  if(exist(y1T1Fn,'file'))
	ni = niftiRead(y1T1Fn);
	Vdst.uint8 = uint8(mrAnatHistogramClip(double(ni.data), 0.4, 0.99)*255+0.5);
	Vdst.mat = ni.qto_xyz;
	clear ni;
	if(~exist(y2T1Fn,'file'))
	  disp(['ERROR: t1 not found- skipping t1 for ' y2sc '.']);
	else
	  disp(['Processing t1 for ' y2sc '...']);
	  ni = niftiRead(y2T1Fn);
	  Vsrc.uint8 = uint8(mrAnatHistogramClip(double(ni.data), 0.4, 0.99)*255+0.5);
	  Vsrc.mat = ni.qto_xyz;
	  x = spm_coreg(Vdst,Vsrc,estParams);
	  x = round(x);
	  if(any(x~=0))
		xf = ni.qto_xyz;
		xf(1:3,4) = xf(1:3,4)-x';
        % o=mrAnatXformCoords(inv(xf),[0 0 0]); imagesc(ni.data(:,:,o(3))); axis image; colormap gray; hold on; plot(o(2),o(1),'ro'); hold off
		ni = niftiSetQto(ni, xf);
		writeFileNifti(ni);
	  end
	  clear ni;
	end
	if(~exist(y2DtFn,'dir')||~exist(fullfile(y2DtFn,'b0.nii.gz'),'file'))
	  disp(['ERROR: dti bin dir not found- skipping tensors for ' y2sc '.']);
	else
	  disp(['Processing tensors for ' y2sc '...']);
	  b0 = niftiRead(fullfile(y2DtFn,'b0.nii.gz'));
	  Vsrc.uint8 = uint8(mrAnatHistogramClip(double(b0.data), 0.4, 0.99)*255+0.5);
	  Vsrc.mat = b0.qto_xyz;
	  clear b0;
	  x = spm_coreg(Vdst,Vsrc,estParams);
	  x = round(x);
	  if(any(x~=0))
        xf = Vsrc.mat;
        xf(1:3,4) = xf(1:3,4)-x';
		% o=mrAnatXformCoords(inv(xf),[0 0 0]); imagesc(b0.data(:,:,o(3))); axis image; colormap gray; hold on; plot(o(2),o(1),'ro'); hold off
		tf = dir(fullfile(y2DtFn,'*.nii.gz'));
		for(jj=1:length(tf))
		   ni = niftiRead(fullfile(y2DtFn,tf(jj).name));
		   if(all(ni.qto_xyz(:)==Vsrc.mat(:)))
			 disp(['  Fixing xform in ' tf(jj).name '...']);
			 ni = niftiSetQto(ni, xf);
			 writeFileNifti(ni);
		   else
			 disp(['  ERROR: xform mismatch in ' tf(jj).name '- xform not fixed.']);
		   end
		end
	  end
	end
  end
end

%
% Making an fMRI movie:
%
ni = niftiRead('214_visual_mcf.nii.gz');
sliceNum = 10;
sz = size(ni.data);
ni.data = uint8(mrAnatHistogramClip(single(ni.data),0.4,0.99)*255);
M = squeeze(ni.data(:,:,sliceNum,:));
clear ni;
M = flipdim(permute(M,[2,1,3]),1);

% Autocrop the movie frames
mask = imblur(mean(M,3),2)>40;
tmp = find(sum(mask,2));
crop(:,1) = [min(tmp) max(tmp)];
tmp = find(sum(mask,1));
crop(:,2) = [min(tmp) max(tmp)];
pad = 5;
if(any(diff(crop)./sz([1:2]) < 0.8))
    crop(1,:) = max([1 1],crop(1,:)-pad);
    crop(2,:) = min(sz([1:2]),crop(2,:)+pad);
    [X,Y,Z] = ndgrid([crop(1,1):crop(2,1)],[crop(1,2):crop(2,2)],[1:sz(3)]);
    x = sub2ind(sz,X(:), Y(:), Z(:)); clear X Y Z;
    newSz = [diff(crop)+1 sz(3)];
    M = reshape(M(x),newSz);
end

outBaseName = '/tmp/fmri2_mcf';
%mpOrig = mplay(int2struct(M),10);
% Save a gif. Cropping and using only 64 colors makes for a
% fairly compact file, suitable for a website. Using 256 colors
% might make a slightly nicer image.
M = M./4; M(M>63) = 63;
% The gif writer wants a 4d dataset (???)
M = reshape(M,[size(M,1),size(M,2),1,size(M,3)]);
imwrite(M,gray(64),[outBaseName '.gif'],'DelayTime',0.1,'LoopCount',65535);

%
% Hack to allow old CINCH to read new-format data
%
bd = '/biac3/wandell4/data/reading_longitude/dti_y4/';
d = dir(fullfile(bd,'*0*'));
for(ii=1:length(d))
   binDir = fullfile(bd,d(ii).name,'dti06','bin');
   t1 = fullfile(bd,d(ii).name,'t1','t1.nii.gz');
   bkDir = fullfile(binDir,'backgrounds');
   if(exist(binDir,'dir')&&exist(t1,'file')&&~exist(bkDir))
      disp(['Processing ' d(ii).name '...']);
      mkdir(bkDir);
      ni = niftiRead(t1);
      ni.data = single(mrAnatHistogramClip(double(ni.data),0.4,0.99));
      ni.fname = fullfile(bkDir,'t1.nii.gz');
      writeFileNifti(ni);
   else
      disp(['Skipping ' d(ii).name '.']);
   end
end


%FASTNLMEANSDENOISINGMULTI  Modification of fastNlMeansDenoising function for colored images sequences
%
%     dst = cv.fastNlMeansDenoisingMulti(srcImgs, imgToDenoiseIndex, temporalWindowSize)
%     dst = cv.fastNlMeansDenoisingMulti(..., 'OptionName',optionValue, ...)
%
% ## Input
% * __srcImgs__ Input 8-bit (or 16-bit with 'L1' norm) 1-channel, 2-channel,
%   3-channel or 4-channel images sequence. All images should have the same
%   type and size.
% * __imgToDenoiseIndex__ Target image to denoise index in `srcImgs` sequence
%   (0-based index).
% * __temporalWindowSize__ Number of surrounding images to use for target
%   image denoising. Should be odd. Images from
%   `imgToDenoiseIndex - temporalWindowSize/2` to
%   `imgToDenoiseIndex + temporalWindowSize/2` from `srcImgs` will be used to
%   denoise `srcImgs{imgToDenoiseIndex}` image.
%
% ## Output
% * __dst__ Output image with the same size and type as `srcImgs` images.
%
% ## Options
% * __H__ Array of parameters regulating filter strength, either one parameter
%   applied to all channels or one value per channel in `dst`. Big `H` value
%   perfectly removes noise but also removes image details, smaller `H` value
%   preserves details but also preserves some noise. default [3]
% * __TemplateWindowSize__ Size in pixels of the template patch that is used
%   to compute weights. Should be odd. Recommended value 7 pixels. default 7
% * __SearchWindowSize__ Size in pixels of the window that is used to compute
%   weighted average for given pixel. Should be odd. Affect performance
%   linearly: greater `SearchWindowsSize` - greater denoising time.
%   Recommended value 21 pixels. default 21
% * __NormType__ Type of norm used for weight calculation. Can be either:
%   * __L2__ (default)
%   * __L1__
%
% Modification of cv.fastNlMeansDenoising function for images sequence where
% consecutive images have been captured in small period of time. For example
% video. This version of the function is for grayscale images or for manual
% manipulation with colorspaces. For more details see:
% [CiteSeerX](http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.131.6394)
%
% See also: cv.fastNlMeansDenoising, cv.fastNlMeansDenoisingColored,
%  cv.fastNlMeansDenoisingColoredMulti
%


function buzsakilab_database_submit(bz_datasets,database_name)
% Submit dataset to the Buzsakilab metadata-database
%
% v1.1
%
% INPUT
% bz_datasets :
% A structure with size n x 1, where n is number of sessions to submit.
%
% database_name :
% Name of database to submit to. Default 'buzsakid_submitdataset'
%
%
% By Peter Petersen
% petersen.peter@gmail.com

if nargin == 1
    database_name = 'buzsakid_submitdataset';
end

fprintf(['\nSubmitting datasets to database: ''', database_name, '''\n'])

% Make connection to database
bz_mySQL = bz_database_credentials; % Credentials loaded

conn = database(database_name,bz_mySQL.userid,bz_mySQL.password,'Vendor','MYSQL','Server',bz_mySQL.address,'PortNumber',3306);
curs = exec(conn,['SELECT * FROM ' database_name '.' bz_mySQL.table]);
curs = fetch(curs);

if ~strcmp(curs.Message,'Invalid connection.')
    bz_database = table2struct(curs.Data);
    % attributes = attr(curs);
    close(curs);

    % Execute query and fetch results
    if isfield(bz_datasets, 'Id')
        bz_datasets = rmfield(bz_datasets, 'Id');
    end

    for k = 1:size(bz_datasets,1)
        if sum(strcmp(bz_datasets(k).Session,{bz_database.Session}) & strcmp(bz_datasets(k).Animal,{bz_database.Animal}) & strcmp(bz_datasets(k).Investigator,{bz_database.Investigator}))
            bz_datasets(k).NewDataset = 0;
        else
            bz_datasets(k).NewDataset = 1;
        end
    end
    clear bz_database;
    n_datasets = size(bz_datasets,1);
    bz_datasets = bz_datasets(find([bz_datasets.NewDataset]))';

    if size(bz_datasets,2) > 0
        bz_datasets = rmfield(bz_datasets,{'NewDataset'});
        for j = 1:length(bz_datasets)
            % Checking for existing datasets in the database
            disp([num2str(j) '. dataset: ' bz_datasets(j).Investigator, ', ', bz_datasets(j).Animal,', ', bz_datasets(j).Session])
            if any(structfun(@isempty, bz_datasets(j)))
                idx = find(structfun(@isempty, bz_datasets(j)));
                names = fieldnames(bz_datasets);
                for field = 1:length(idx)
                    warning([names{idx(field)} ' was empty, filling with null.'])
                    bz_datasets(j) = setfield(bz_datasets(j),names{idx(field)},'null');
                end
            end
            % Data to insert
            colnames = fieldnames(bz_datasets(j))'; %
            data_table = struct2table(bz_datasets(j));
            % data_table = buzsakilab_database_verify_fieldtypes_and_dataformat(data_table,attributes)

            insert(conn,bz_mySQL.table,colnames,data_table);
        end
        disp([num2str(size(bz_datasets,2)), '/' num2str(n_datasets), ' dataset(s) submitted to database. ', num2str(n_datasets-size(bz_datasets,2)) ' dataset(s) already exist in database'])
    else
        disp('Datasets already exist in database')
    end
else
    disp('Connection failed. mySQL server not available')
    disp(curs.Message)
end
close(conn) % Close connection to database
clear conn curs % Clear variables


%ProcessTestCase
% Base class for whole cell process test classes. This purposes of this
% class are to provide functions for
% - loading the process test fixture prior to the execution of each test.
%   This is automatically called by the setUp function.
% - testing for run-time errors in these process methods:
%   - allocateMemoryForState
%   - computeBiomassProductionByproducts
%   - computeMinimumEnzymeExpression
%   - resourceRequirement
%   - initializeState
%   - evolveState
% - testing that the process obeys mass balance
% - testing gene essentiality with regard to the process's primary function
%
% Author: Jonathan Karr, jkarr@stanford.edu
% Affiliation: Covert Lab, Department of Bioengineering, Stanford University
% Last updated: 9/10/2010
classdef ProcessTestCase < TestCase
    properties
        process               %process object
        processClass          %full class name of process
        processClassName      %short class name of process
        processBaseLocation   %base directory of process
    end

    %constructor
    methods
        function this = ProcessTestCase(name)
            %parent class constructor
            this = this@TestCase(name);

            %get class name of process
            this.processClass     = edu.stanford.covert.util.substr(class(this), 1, -5);
            this.processClassName = this.processClass(find(this.processClass == '.', 1, 'last') + 1:end);

            %find parent folder of process -- used to store test fixture and
            %expected outputs
            locationParts = strfind(this.Location, filesep);
            if this.Location(locationParts(end - 1) + 1) == '@'
                this.processBaseLocation = this.Location(1:locationParts(end-1));
            else
                this.processBaseLocation = this.Location(1:locationParts(end));
            end
        end
    end

    %setUp and tearDown
    methods
        %Called prior to the execution of each test case.
        %Performs three functions:
        % - instantiate process
        % - initialize process using test fixture
        % - seed random number generator
        function setUp(this)
            constructor = str2func(this.processClass);
            this.process = constructor([], this.processClassName);
            this.loadTestFixture();
            this.process.verbosity = 0;
            this.process.seed = 1;
            this.process.seedRandStream();
        end

        %Called following the execution of each test case.
        function tearDown(this)
            this.process = []; %free memory
        end

        %Loads knowledge base test fixture into process. Called by setUp prior
        %to the execution of each test. You may wish to override this method to
        %set additional properties.
        function loadTestFixture(this)
            this.process = edu.stanford.covert.cell.sim.ProcessFixture.load(this.process);
        end
    end

    %Suggested tests for subclasses to implement:
    % - Demonstrate main purpose of process using minimal, hard-coded fake data.
    % - Withhold one or all stimuli.
    % - Withhold one or all substrates.
    % - Withhold one or all enzymes.
    % - Predict which genes are essential to the process (testGeneEssentiality).

    %tests for absence of run-time errors
    methods (Sealed)
        function testAllocateMemoryForState(this)
            this.process.allocateMemoryForState(1);  %no run-time errors
        end

        function testCalcResourceRequirements_LifeCycle(this)
            %references
            sim = edu.stanford.covert.cell.sim.SimulationFixture.load([], true);
            m = sim.process(this.process.wholeCellModelID(9:end));
            g = sim.gene;
            t = sim.state('Time');
            r = sim.state('Rna');
            pm = sim.state('ProteinMonomer');
            pc = sim.state('ProteinComplex');

            %process local state
            m.stimuli = this.process.stimuli;
            m.substrates = this.process.substrates;
            m.enzymes = this.process.enzymes;
            m.boundEnzymes = this.process.boundEnzymes;
            this.process = m;

            %constants
            constants = edu.stanford.covert.util.StructUtil.catstruct(...
                sim.getFixedConstants(), sim.getFittedConstants());
            constants.processes.DNADamage.substrateGlobalIndexs = sim.process('DNADamage').substrateGlobalIndexs;
            constants.processes.DNADamage.substrateStimulusGlobalIndexs = sim.process('DNADamage').substrateStimulusGlobalIndexs;
            constants.processes.DNADamage.substrateStimulusCompartmentIndexs = sim.process('DNADamage').substrateStimulusCompartmentIndexs;
            constants.processes.DNADamage.substrateStimulusGlobalCompartmentIndexs = sim.process('DNADamage').substrateStimulusGlobalCompartmentIndexs;
            constants.processes.DNADamage.substrateStimulusLocalIndexs = sim.process('DNADamage').substrateStimulusLocalIndexs;

            %RNA and protein
            rnas = sum( r.counts( r.matureIndexs, :), 2) + ...
                sum(r.counts(r.boundIndexs, :), 2) + ...
                sum(r.counts(r.aminoacylatedIndexs, :), 2) + ...
                sum(r.counts(r.damagedIndexs, :), 2);
            mons = ...
                sum(pm.counts(pm.matureIndexs, :), 2) + ...
                sum(pm.counts(pm.boundIndexs, :), 2) + ...
                sum(pm.counts(pm.damagedIndexs, :), 2) + ...
                sum(pm.counts(pm.inactivatedIndexs, :), 2);
            cpxs = sum(pc.counts(pc.matureIndexs, :), 2) + ...
                sum(pc.counts(pc.boundIndexs, :), 2) + ...
                sum(pc.counts(pc.damagedIndexs, :), 2) + ...
                sum(pc.counts(pc.inactivatedIndexs, :), 2);

            rnaInCpxs = zeros(size(rnas));
            rnaInCpxs(setdiff(1:end, r.matureMRNAIndexs)) = ...
                sum(pc.proteinComplexComposition(setdiff(1:end, g.mRNAIndexs), :, :), 3) * cpxs;
            monInCpxs = sum(pc.proteinComplexComposition(g.mRNAIndexs, :, :), 3) * cpxs;

            cpxDecays = cpxs .* pc.decayRates(pc.matureIndexs) * t.cellCycleLength / log(2);
            rnaDecays = rnas .*  r.decayRates( r.matureIndexs) * t.cellCycleLength / log(2);
            rnaDecays(setdiff(1:end, r.matureMRNAIndexs)) = ...
                rnaDecays(setdiff(1:end, r.matureMRNAIndexs)) + ...
                sum(pc.proteinComplexComposition(setdiff(1:end, g.mRNAIndexs), :, :), 3) * cpxDecays;
            monDecays = mons .* pm.decayRates(pm.matureIndexs) * t.cellCycleLength / log(2) + ...
                sum(pc.proteinComplexComposition(g.mRNAIndexs, :, :), 3) * cpxDecays;

            states = struct;

            states.rnas0               = rnas;
            states.rnas                = rnas * t.cellCycleLength / log(2);
            states.rnaDecays           = rnaDecays;
            states.rnaDecays0          = rnaDecays * log(2) / t.cellCycleLength;
            states.rnaProductions      = rnas + rnaInCpxs + rnaDecays;
            states.rnaProductions0     = (rnas + rnaInCpxs) * t.cellCycleLength / log(2) + (rnas .* r.decayRates(r.matureIndexs));
            states.rnaProductions0(setdiff(1:end, r.matureMRNAIndexs)) = ...
                states.rnaProductions0(setdiff(1:end, r.matureMRNAIndexs)) + ...
                sum(pc.proteinComplexComposition(setdiff(1:end, g.mRNAIndexs), :, :), 3) * (cpxs .* pc.decayRates(pc.matureIndexs));

            states.monomers0           = mons;
            states.monomers            = mons * t.cellCycleLength / log(2);
            states.monomerDecays       = monDecays;
            states.monomerDecays0      = monDecays * log(2) / t.cellCycleLength;
            states.monomerProductions  = mons + monInCpxs + monDecays;
            states.monomerProductions0 = (mons + monInCpxs) * t.cellCycleLength / log(2) + (mons .* pm.decayRates(pm.matureIndexs));
            states.monomerProductions0 = ...
                states.monomerProductions0 + ...
                sum(pc.proteinComplexComposition(g.mRNAIndexs, :, :), 3) * (cpxs .* pc.decayRates(pc.matureIndexs));

            states.complexs0           = cpxs;
            states.complexs            = cpxs * t.cellCycleLength / log(2);
            states.complexDecays       = cpxDecays;
            states.complexDecays0      = cpxDecays * log(2) / t.cellCycleLength;
            states.complexProductions  = cpxs + cpxDecays;
            states.complexProductions0 = cpxs * t.cellCycleLength / log(2) + (cpxs .* pc.decayRates(pc.matureIndexs));

            %calculate life cycle resource requirements
            [bmProd, byProd, minEnzExp, maxEnzExp] = m.calcResourceRequirements_LifeCycle(constants, states);

            %assertions
            assertEqual(size(m.substrateWholeCellModelIDs), size(bmProd));
            assertEqual(size(m.substrateWholeCellModelIDs), size(byProd));
            assertEqual(size(m.enzymeWholeCellModelIDs), size(minEnzExp));
            assertEqual(size(m.enzymeWholeCellModelIDs), size(maxEnzExp));
        end

        function testCalcResourceRequirements_Current(this)
            this.process.calcResourceRequirements_Current();  %no run-time errors
        end

        function testInitializeState(this)
            this.process.initializeState();  %no run-time errors
        end

        function testEvolveState(this)
            m = this.process;
            m.evolveState();  %no run-time errors
            validateattributes(m.substrates(setdiff(1:end, m.substrateStimulusLocalIndexs), :), {'numeric'}, {'real', 'nonnegative', 'integer'});
            validateattributes(m.enzymes, {'numeric'}, {'real', 'nonnegative', 'integer'});
            validateattributes(m.boundEnzymes, {'numeric'}, {'real', 'nonnegative', 'integer'});
        end
    end

    %other tests provided by this class
    methods (Sealed)
        %Test that process obeys mass balance
        function testMassBalance(this)
            import edu.stanford.covert.util.ConstantUtil;
            m = this.process;
            initialWeight = sum(m.dryWeight);

            m.evolveState();
            assertElementsAlmostEqual(...
                initialWeight * ConstantUtil.nAvogadro, sum(m.dryWeight) * ConstantUtil.nAvogadro, ...
                'relative', 1e-8);
        end

        %Test that process uses rand streams correctly
        %- only changes state of process (and possibly state) rand
        %  stream(s)
        %- doesn't change state of global rand stream
        function testRandStreamUsage(this)
            %% hold onto state of default stream
            defaultStream = RandStream.getDefaultStream;
            defaultStreamType = defaultStream.Type;
            defaultStreamSeed = defaultStream.Seed;
            defaultStreamNumStreams = defaultStream.NumStreams;
            defaultStreamStreamIndex = defaultStream.StreamIndex;
            defaultStreamState = defaultStream.State;
            defaultStreamSubstram = defaultStream.Substream;
            defaultStreamRandnAlg = defaultStream.RandnAlg;
            defaultStreamAntithetic = defaultStream.Antithetic;
            defaultStreamFullPrecision = defaultStream.FullPrecision;

            %% simulate
            %run-1
            this.setUp();
            m = this.process;
            m.evolveState();
            streams1 = cell(1+numel(m.states), 1);
            streams1{1} = this.process.randStream;
            for i = 1:numel(m.states)
                streams1{i+1} = m.states{i}.randStream;
            end
            this.tearDown();

            %run-2
            this.setUp();
            m = this.process;
            m.evolveState();
            streams2 = cell(1+numel(m.states), 1);
            streams2{1} = this.process.randStream;
            for i = 1:numel(m.states)
                streams2{i+1} = m.states{i}.randStream;
            end
            this.tearDown();

            %% assert state of process and state random streams updated the same in both runs
            for i = 1:numel(streams1)
                assertTrue(streams1{i} == streams2{i});
            end

            %% assert state of default stream unchanged
            defaultStream = RandStream.getDefaultStream;
            assertEqual(defaultStreamType, defaultStream.Type);
            assertEqual(defaultStreamSeed, defaultStream.Seed);
            assertEqual(defaultStreamNumStreams, defaultStream.NumStreams);
            assertEqual(defaultStreamStreamIndex, defaultStream.StreamIndex);
            assertEqual(defaultStreamState, defaultStream.State);
            assertEqual(defaultStreamSubstram, defaultStream.Substream);
            assertEqual(defaultStreamRandnAlg, defaultStream.RandnAlg);
            assertEqual(defaultStreamAntithetic, defaultStream.Antithetic);
            assertEqual(defaultStreamFullPrecision, defaultStream.FullPrecision);
        end
    end

    %test helpers
    methods (Sealed)
        function helpTestGeneEssentiality(...
                this, expectedEssentialGenes, isFunctioningOkay, options)
            %process options
            if ~exist('options','var')
                options = struct;
            end
            lengthSec = this.optionVal(options, 'lengthSec', 1);
            knockoutStimuli = this.optionVal(options, 'knockoutStimuli', 1);
            knockoutSubstrates = this.optionVal(options, 'knockoutSubstrates', 1);
            knockoutEnzymes = this.optionVal(options, 'knockoutEnzymes', 1);

            m = this.process;

            base_globalStates = struct;
            for i = 1:numel(m.states)
                base_globalStates.(m.states{i}.wholeCellModelID) = struct;
                for j = 1:numel(m.states{i}.stateNames)
                    base_globalStates.(m.states{i}.wholeCellModelID).(m.states{i}.stateNames{j}) = ...
                        m.states{i}.(m.states{i}.stateNames{j});
                end
            end

            base_localStates = struct;
            for i = 1:numel(m.localStateNames)
                base_localStates.(m.localStateNames{i}) = m.(m.localStateNames{i});
            end

            baseStates = edu.stanford.covert.util.StructUtil.catstruct(base_globalStates, base_localStates);

            %compute gene composition of stimuli, substrates, enzymes
            geneWholeCellModelIDs    = m.gene.wholeCellModelIDs;
            stimuliGeneComposition   = m.stimuliGeneComposition();
            substrateGeneComposition = m.substrateGeneComposition();
            enzymeGeneComposition    = m.enzymeGeneComposition();

            includedGenes = false(size(geneWholeCellModelIDs));
            if knockoutStimuli
                includedGenes = includedGenes | any(stimuliGeneComposition,2);
            end
            if knockoutSubstrates
                includedGenes = includedGenes | any(substrateGeneComposition,2);
            end
            if knockoutEnzymes
                includedGenes = includedGenes | any(enzymeGeneComposition,2);
            end
            includedGenes = find(includedGenes);

            geneWholeCellModelIDs    = geneWholeCellModelIDs(includedGenes);
            stimuliGeneComposition   = stimuliGeneComposition(includedGenes, :);
            substrateGeneComposition = substrateGeneComposition(includedGenes, :);
            enzymeGeneComposition    = enzymeGeneComposition(includedGenes, :);

            %verify all expected essential genes included in test
            assertTrue(...
                all(ismember(expectedEssentialGenes, geneWholeCellModelIDs)), ...
                'not all expected essential genes are included in this test');

            %verify first that the process functions properly with all genes intact
            for j = 1:lengthSec
                m.evolveState();
                if isFunctioningOkay(m, baseStates)
                    break;
                end
            end
            assertTrue(...
                isFunctioningOkay(m, baseStates),...
                'process is not functioning properly with all genes intact');

            mispredictedAsEssential = false(size(geneWholeCellModelIDs));
            mispredictedAsNotEssential = false(size(geneWholeCellModelIDs));

            %for each gene
            for i = 1:length(geneWholeCellModelIDs)
                %reset process state
                m.seedRandStream();

                for j = 1:numel(m.states)
                    for k = 1:numel(m.states{j}.stateNames)
                        m.states{j}.(m.states{j}.stateNames{k}) = ...
                            base_globalStates.(m.states{j}.wholeCellModelID).(m.states{j}.stateNames{k});
                    end
                end

                for j = 1:numel(m.localStateNames)
                    m.(m.localStateNames{j}) = base_localStates.(m.localStateNames{j});
                end

                %knock out gene
                m.stimuli(     stimuliGeneComposition(i,:) >0, :) = 0;
                m.substrates(substrateGeneComposition(i,:) >0, :) = 0;
                m.enzymes(      enzymeGeneComposition(i,:) >0, :) = 0;
                m.boundEnzymes( enzymeGeneComposition(i,:) >0, :) = 0;

                this.helpKnockoutGene(geneWholeCellModelIDs{i});

                initial_localStates = base_localStates;
                initial_localStates.stimuli      = m.stimuli;
                initial_localStates.substrates   = m.substrates;
                initial_localStates.enzymes      = m.enzymes;
                initial_localStates.boundEnzymes = m.boundEnzymes;

                initialStates = edu.stanford.covert.util.StructUtil.catstruct(base_globalStates, initial_localStates);

                expectedEssential = ...
                    any(strcmp(geneWholeCellModelIDs{i}, expectedEssentialGenes));

                %execute process
                for j = 1:lengthSec
                    m.evolveState();
                    if isFunctioningOkay(m, initialStates)
                        if expectedEssential
                            mispredictedAsEssential(i) = true;
                        end
                        break;
                    end
                end

                %determine whether process correctly predicted gene essentiality
                if isFunctioningOkay(m, initialStates)
                    if expectedEssential
                        mispredictedAsEssential(i) = true;
                    end
                else
                    if ~expectedEssential
                        mispredictedAsNotEssential(i) = true;
                    end
                end
            end

            assertTrue(all(~mispredictedAsEssential), ...
                ['genes are not essential: '...
                strjoin(', ', geneWholeCellModelIDs{mispredictedAsEssential})]);
            assertTrue(all(~mispredictedAsNotEssential), ...
                ['genes are essential: '...
                strjoin(', ', geneWholeCellModelIDs{mispredictedAsNotEssential})]);
        end

        function result = optionVal(~, options, name, defaultValue)
            if isfield(options, name)
                result = options.(name);
            else
                result = defaultValue;
            end
        end
    end

    %helper methods optionally overridden by subclasses
    methods
        function helpKnockoutGene(this, geneWholeCellModelID) %#ok<MANU,INUSD>
        end
    end
end


function ExecFlag=DoSpinWatchExec(handles)

global VObj;
global VMag;
global VVar;
global VSeq;
global VCtl;
global VSig;
global VCoi;

%preserve VObj VMag & VCoi
VTmpObj=VObj;
VTmpMag=VMag;
VTmpCoi=VCoi;

handles.Simuh=guidata(handles.Simuh.SimuPanel_figure);
DoDisableButton([],[],handles.Simuh);
%% Do spin execution
try
    % Read tab parameters
    fieldname=fieldnames(handles.Attrh1);
    for i=1:length(fieldname)/2
        try
            eval(['SW.' fieldname{i*2} '=[' get(handles.Attrh1.(fieldname{i*2}),'String') '];']);
        catch me
            TAttr=get(handles.Attrh1.(fieldname{i*2}),'String');
            eval(['SW.' fieldname{i*2} '=''' TAttr{get(handles.Attrh1.(fieldname{i*2}),'Value')}  ''';']);
        end
    end

    % Prescan config
    DoPreScan(handles.Simuh);

    % Create SpinWatcher VOsw, VMsw & VCsw
    VOsw=VObj;
    VOsw.Gyro=SW.Gyro;
    VOsw.ChemShift=SW.ChemShift;
    VOsw.SpinNum=SW.SpinPerVoxel;
    VOsw.TypeNum=SW.TypeNum;
    VOsw.XDim=1;
    VOsw.YDim=1;
    VOsw.ZDim=1;
    VOsw=rmfield(VOsw, 'Rho');
    VOsw=rmfield(VOsw, 'T1');
    VOsw=rmfield(VOsw, 'T2');
    VOsw=rmfield(VOsw, 'T2Star');
    VOsw=rmfield(VOsw, 'Type');
    for i=1:VOsw.TypeNum
        VOsw.Rho(:,:,:,i)=SW.Rho(i);
        VOsw.T1(:,:,:,i)=SW.T1(i);
        VOsw.T2(:,:,:,i)=SW.T2(i);
        VOsw.T2Star(:,:,:,i)=SW.T2Star(i);
    end

    if isfield(VCtl, 'MT_Flag')
        if strcmp(VCtl.MT_Flag, 'on')
            K = VOsw.K;
            VOsw=rmfield(VOsw, 'K');
            VOsw.K=K(SW.LocY,SW.LocX,SW.LocZ,:);
        end
    end

    if isfield(VCtl, 'ME_Flag')
        if strcmp(VCtl.ME_Flag, 'on')
            K = VOsw.K;
            VOsw=rmfield(VOsw, 'K');
            VOsw.K=K(SW.LocY,SW.LocX,SW.LocZ,:);
        end
    end

    if isfield(VCtl, 'CEST_Flag')
        if strcmp(VCtl.CEST_Flag, 'on')
            K = VOsw.K;
            VOsw=rmfield(VOsw, 'K');
            VOsw.K=K(SW.LocY,SW.LocX,SW.LocZ,:);
        end
    end

    if isfield(VCtl, 'GM_Flag')
        if strcmp(VCtl.GM_Flag, 'on')
            K = VOsw.K;
            VOsw=rmfield(VOsw, 'K');
            VOsw.K=K(SW.LocY,SW.LocX,SW.LocZ,:);
        end
    end

    VOsw=rmfield(VOsw, 'Mx');
    VOsw=rmfield(VOsw, 'My');
    VOsw=rmfield(VOsw, 'Mz');
    VOsw.Mx= zeros(1,1,1,VOsw.SpinNum,VOsw.TypeNum);
    VOsw.My= zeros(1,1,1,VOsw.SpinNum,VOsw.TypeNum);
    for i=1:VOsw.TypeNum
        VOsw.Mz(:,:,:,:,i)= ones(1,1,1,VOsw.SpinNum,1).*(VOsw.Rho(i)./VOsw.SpinNum);
    end

    % Gradient Grid
    VMsw=VMag;
    VMsw=rmfield(VMsw, 'Gxgrid');
    VMsw=rmfield(VMsw, 'Gygrid');
    VMsw=rmfield(VMsw, 'Gzgrid');
    VMsw=rmfield(VMsw, 'dB0');
    VMsw=rmfield(VMsw, 'dWRnd');
    VMsw=rmfield(VMsw, 'FRange');
    VMsw.Gxgrid=VMag.Gxgrid(SW.LocY,SW.LocX,SW.LocZ);
    VMsw.Gygrid=VMag.Gygrid(SW.LocY,SW.LocX,SW.LocZ);
    VMsw.Gzgrid=VMag.Gzgrid(SW.LocY,SW.LocX,SW.LocZ);
    if VMag.dB0==0
        VMsw.dB0=0;
    else
        VMsw.dB0=VMag.dB0(SW.LocY,SW.LocX,SW.LocZ);
    end
    if VOsw.SpinNum > 1
        InddWRnd=linspace(0.01,0.99,VOsw.SpinNum);
        for j=1:VOsw.TypeNum
            for i=1:VOsw.SpinNum
                VMsw.dWRnd(:,:,:,i,j)=(1./VOsw.T2Star(:,:,:,j)-1./VOsw.T2(:,:,:,j)).*tan(pi.*(InddWRnd(i)-1/2));
                % need large number of spins for stimulating T2* effect,
                % insufficient number of spins may cause in-accurate simulation
            end
        end
    else
        for j=1:VOsw.TypeNum
            for i=1:VOsw.SpinNum
                VMsw.dWRnd(:,:,:,i,j)= 0;
            end
        end
    end
    VMsw.FRange=1;

    % Coil
    VCsw=VCoi;
    if isfield(handles.Simuh,'CoilTxXMLFile')
        VCsw.TxCoilmg=VCoi.TxCoilmg(SW.LocY - max(1, str2num(get(handles.Simuh.AP1_text,'String'))) + 1, ...   % Align postion with respect to FOV
                                    SW.LocX - max(1, str2num(get(handles.Simuh.LR1_text,'String'))) + 1, ...
                                    SW.LocZ - max(1, str2num(get(handles.Simuh.SI1_text,'String'))) + 1, :);
        VCsw.TxCoilpe=VCoi.TxCoilpe(SW.LocY - max(1, str2num(get(handles.Simuh.AP1_text,'String'))) + 1, ...   % Align postion with respect to FOV
                                    SW.LocX - max(1, str2num(get(handles.Simuh.LR1_text,'String'))) + 1, ...
                                    SW.LocZ - max(1, str2num(get(handles.Simuh.SI1_text,'String'))) + 1, :);
    else
        VCsw.TxCoilmg=VCoi.TxCoilmg(SW.LocY,SW.LocX,SW.LocZ,:);
        VCsw.TxCoilpe=VCoi.TxCoilpe(SW.LocY,SW.LocX,SW.LocZ,:);
    end
    %% Spin execution
    VObj=VOsw;
    VMag=VMsw;
    VCoi=VCsw;

    % Generate Pulse line
    DoPulseGen(handles.Simuh);

    % Initialize Mx, My, Mz, Muts in VSig
    VSig.Mx= zeros(1,1,1,VOsw.SpinNum,VOsw.TypeNum, max(size(VSeq.utsLine)));
    VSig.My= zeros(1,1,1,VOsw.SpinNum,VOsw.TypeNum, max(size(VSeq.utsLine)));
    VSig.Mz= zeros(1,1,1,VOsw.SpinNum,VOsw.TypeNum, max(size(VSeq.utsLine)));
    VSig.Muts=zeros(max(size(VSeq.utsLine)),1);

    VSig.Mx(:,:,:,:,:,1)=VOsw.Mx;
    VSig.My(:,:,:,:,:,1)=VOsw.My;
    VSig.Mz(:,:,:,:,:,1)=VOsw.Mz;
    VSig.Muts(1)=0;

    % Simulation Process
    try
        VCtl.CS=double(VObj.ChemShift*VCtl.B0);
        VCtl.RunMode=int32(1); % Spin scan
        VCtl.MaxThreadNum=int32(handles.Simuh.CPUInfo.NumThreads);
        VCtl.ActiveThreadNum=int32(0);
        DoDataTypeConv(handles.Simuh);

        if isfield(VCtl, 'MT_Flag')
            if strcmp(VCtl.MT_Flag, 'on')
                VCtl.GPUIndex=int32(0);
                DoMTScanAtGPU;  % beta MT kernel, only support two-pool MT
            else
                DoScanAtCPU;  % global (VSeq,VObj,VCtl,VMag,VCoi,VVar,VSig) are needed
            end
        elseif isfield(VCtl, 'ME_Flag')
            if strcmp(VCtl.ME_Flag, 'on')
                VCtl.GPUIndex=int32(0);
                DoMEScanAtGPU;  % beta ME kernel
            else
                DoScanAtCPU;  % global (VSeq,VObj,VCtl,VMag,VCoi,VVar,VSig) are needed
            end
        elseif isfield(VCtl, 'CEST_Flag')
            if strcmp(VCtl.CEST_Flag, 'on')
                VCtl.GPUIndex=int32(0);
                DoCESTScanAtGPU;  % beta CEST kernel
            else
                DoScanAtCPU;  % global (VSeq,VObj,VCtl,VMag,VCoi,VVar,VSig) are needed
            end
        elseif isfield(VCtl, 'GM_Flag')
            if strcmp(VCtl.GM_Flag, 'on')
                VCtl.GPUIndex=int32(0);
                DoGMScanAtGPU;  % beta GM kernel
            else
                DoScanAtCPU;  % global (VSeq,VObj,VCtl,VMag,VCoi,VVar,VSig) are needed
            end
        else
            DoScanAtCPU;  % global (VSeq,VObj,VCtl,VMag,VCoi,VVar,VSig) are needed
        end

    catch me
        error_msg{1,1}='ERROR!!! Spin execution process aborted.';
        error_msg{2,1}=me.message;
        errordlg(error_msg);
        ExecFlag=0;
        %recover VObj
        VObj=VTmpObj;
        VMag=VTmpMag;
        VCoi=VTmpCoi;
        return;
    end

    % Convert VSig.Mx, VSig.My, VSig.Mz
    VSig.Mx = double(VSig.Mx);
    VSig.My = double(VSig.My);
    VSig.Mz = double(VSig.Mz);

    handles.Mx=VSig.Mx.*double(VObj.SpinNum);
    handles.My=VSig.My.*double(VObj.SpinNum);
    handles.Mz=VSig.Mz.*double(VObj.SpinNum);
    handles.Muts=VSig.Muts;
    handles.MutsStep=length(VSig.Muts);

    Mx=sum(reshape(VSig.Mx,[VObj.SpinNum,VObj.TypeNum, max(size(VSeq.utsLine))]),1);
    My=sum(reshape(VSig.My,[VObj.SpinNum,VObj.TypeNum, max(size(VSeq.utsLine))]),1);
    Mz=sum(reshape(VSig.Mz,[VObj.SpinNum,VObj.TypeNum, max(size(VSeq.utsLine))]),1);
    Mx=reshape(Mx,[VObj.TypeNum max(size(VSeq.utsLine))]);
    My=reshape(My,[VObj.TypeNum max(size(VSeq.utsLine))]);
    Mz=reshape(Mz,[VObj.TypeNum max(size(VSeq.utsLine))]);
    Mx=Mx';
    My=My';
    Mz=Mz';
    handles.MxySum=sqrt(Mx.^2+My.^2);
    handles.MzSum=Mz;
    handles.MaxMxySum=max(max(handles.MxySum));
    handles.MaxMzSum=max(max(handles.MzSum));
    handles.MinMxySum=min(min(handles.MxySum));
    handles.MinMzSum=min(min(handles.MzSum));
    handles.Gzgrid=zeros(1,1,1,VObj.SpinNum);
    handles.Gygrid=zeros(1,1,1,VObj.SpinNum);
    handles.Gxgrid=zeros(1,1,1,VObj.SpinNum);
catch me
    error_msg{1,1}='ERROR!!! Spin execution process aborted.';
    error_msg{2,1}=me.message;
    errordlg(error_msg);
    ExecFlag=0;
    %recover VObj
    VObj=VTmpObj;
    VMag=VTmpMag;
    VCoi=VTmpCoi;
    return;
end

%recover VObj
VObj=VTmpObj;
VMag=VTmpMag;
VCoi=VTmpCoi;

guidata(handles.SpinWatcherPanel_figure, handles);
ExecFlag=1;

end


%% sparseSphericalMask
% Below is a demonstration of the features of the |sparseSphericalMask| function

%%
clear; close all; clc;

%% Syntax
% |[subIndMask,patchDataOut]=sparseSphericalMask(rSphere,nSubTri,optPatchOut);|

%% Description
% UNDOCUMENTED
%% Examples
%
%%
%
% <<gibbVerySmall.gif>>
%
% _*GIBBON*_
% <www.gibboncode.org>
%
% _Kevin Mattheus Moerman_, <gibbon.toolbox@gmail.com>
%%
% _*GIBBON footer text*_
%
% License: <https://github.com/gibbonCode/GIBBON/blob/master/LICENSE>
%
% GIBBON: The Geometry and Image-based Bioengineering add-On. A toolbox for
% image segmentation, image-based modeling, meshing, and finite element
% analysis.
%
% Copyright (C) 2006-2022 Kevin Mattheus Moerman and the GIBBON contributors
%
% This program is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% This program is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with this program.  If not, see <http://www.gnu.org/licenses/>.