function out = gtForwardSimulate_v2(first, last, workingdirectory, phaseID, varargin)
% GTFORWARDSIMULATE_V2  Finds missing diffraction spots and updates grain structure
%     out = gtForwardSimulate_v2(first, last, workingdirectory, [phaseID], varargin)
%     ------------------------------------------------------------------------------
%
%     INPUT:
%       first            = <int>     first grainid
%       last             = <int>     last grainid
%       workingdirectory = <string>  working directory
%       phaseID          = <int>     phase ID
%       varargin         =           parameters.fsim plus some additional
%                                    options as in gtAppFsimExtraPars():
%
%     OPTIONAL INPUT (p/v pairs):
%       updateSample     = <logical> true/false to update sample.mat and
%                                    spot2grain.mat {true}
%       updateDBtables   = <logical> true/false to update DB table with
%                                    'grainID', 'phaseID', 'flag' {false}
%       area_factor      = <double>  Weight for area criterion {1}
%       position_factor  = <double>  Weight for UV position criterion {1}
%       image_factor     = <double>  Weight for image criterion {1}
%       int_factor       = <double>  Weight for intensity criterion {0}
%
%     OPTIONAL INPUT for twin variants calculation (p/v pairs):
%       variants         = <logical> Use fsim relative to twin variants
%                                    {false}
%       plot_variants    = <logical> Plot the variants orientation wrt to
%                                    the parent orientation as XYZ crystal
%                                    axes {false}
%       num_variant      = <double>  Variant number to forward simulate
%                                    {[]}
%       twin_angle       = <double>  twin angle {[]}
%       twin_axis        = <double>  twin axis {[]}
%       twin_center      = <double>  alternative center for the twin (mm)
%                                    or the twinID if available from the
%                                    same indexed dataset {[]}
%       twin_grain       = <struct>  twin grain structure for common spot
%                                    properties and full image {[]}
%
%     OUTPUT:
%       out  = <struct>   updated grain structure. Fields:
%          .bl             = <struct N> stack of normalized difblobs, along with metadata
%          .proj.geom      = <double>   input for grain reconstruction
%                                        / forward projection using ROI
%                                        (hsize, vsize) (Nx12)
%          .proj.geom_full = <double>   input for grain reconstruction
%                                        / forward projection (full images) (Nx12)
%          .proj.geom_abs  = <double>   input for grain reconstruction
%                                        / forward projection of extinction spots (Nx12)
%          .proj.difstack  = <double>   stack of normalized difspots (XxYxN)
%          .ondet          = <double>   indices of those spots (from the list in allblobs)
%                                       which are expected on the detector
%          .uvw            = <double>   spot positions on the detector (Nx3)
%          .hklsp          = <double>   signed hk(i)l of reflection (Nx3(4))
%          .hkl            = <double>   hk(i)l family for each reflection (Nx3(4))
%          .flag           = <int>      status flag indicating result of forward simulation
%          .allblobs       = <struct>   forward simulation output produced by gtIndexFwdSimGrains
%          .used_fam       = <double>   List of unique HKL families used
%                                       (theoretical)
%          .used_famsp     = <double>   List of all the HKL families used
%                                       (theoretical)
%
%       We will classify spots with a flag  [1...6]:
%        - 1 : No intensity at expected spot position
%        - 2 : Found intensity in FULL images at predicted position
%        - 3 : Found segmented difspot at expected position - but position
%              / size do not match strict criteria
%        - 4 : Found segmented difspot in database matching the strict
%              search criteria - we will use theoretically predicted
%              angles for reconstruction
%        - 5 : Difspot is part of a pair identified by INDEXTER - use
%              Friedelpair information for reconstruction
%        - 6 : Conflict difspot (if existing)
%
%     Version 010 21-07-2014 by LNervo
%       Extracted the ASTRA geometry and the gtFwdSimFindCandidates routines

%     Version 009 14-05-2014 by LNervo
%       Added check of used hkl families
%
%     Version 008 25-04-2014 by LNervo
%       Adapted to use twin forward simulation
%
%     Version 007 Beginning-2014 by LNervo
%       Modulized, simplified, added application parameters to update
%       sample.mat and DB difspot table
%
%     Version 006 18-12-2013 by LNervo
%       Moduling a bit more...
%
%     Version 005 05/03/2013 by Nicola Vigano (nicola.vigano@esrf.fr)
%       Re-structured completely, to be more clear and easy to debug.
%
%     Version 004 21/06/2012 by AKing
%       Trivial change to make segbb zeros(1, 6)
%
%     Version 003 14-06-2012 by LNervo
%       Use gtFsimDefaultParameters


if (isdeployed)
    global GT_DB %#ok<NUSED,TLEV>
    global GT_MATLAB_HOME %#ok<NUSED,TLEV>
    load('workspaceGlobal.mat');
end

cd(workingdirectory);
gtDBConnect();

parameters = gtLoadParameters();
labgeo = parameters.labgeo;
samgeo = parameters.samgeo;
recgeo = parameters.recgeo;

if (~isfield(parameters.seg, 'writehdf5') || ~parameters.seg.writehdf5)
    error('gtForwardSimulate_v2:wrong_parameter', ...
        [ 'Segmentation has to write HDF5 blobs in order for ' ...
          'ForwardSimulation to work properly. You can now create them ' ...
          'using some of the conversion functions like "gtConvertRawImages2Blobs"' ])
end

if (~isfield(parameters.fsim, 'save_grain'))
    parameters.fsim.save_grain = true;
end
if (~exist('phaseID', 'var') || isempty(phaseID))
    phaseID = 1;
end

if (isdeployed)
    first   = str2double(first);
    last    = str2double(last);
    phaseID = str2double(phaseID);
    parameters.fsim.display_figure = false;
elseif (~isempty(varargin))
    % in interactive mode we can modify parameters
    [parameters.fsim, rej_pars] = parse_pv_pairs(parameters.fsim, varargin);
end
if (~exist('rej_pars', 'var'))
    rej_pars = {};
end

indexgrainModel = fullfile(parameters.acq.dir, '4_grains', 'phase_%02d', 'index.mat');
fprintf('\nLoading indexing results...\n');
grain = [];
load(sprintf(indexgrainModel, phaseID), 'grain');
totGrains = length(grain);

if (last > totGrains)
    warning('gtForwardSimulate_v2:wrong_parameter', ...
        'Last was bigger than the total number of grains, setting it to max')
    last = totGrains;
end

difspotfile = fullfile(parameters.acq.dir, '2_difspot', '00000', 'difspot00001.edf');
if (exist(difspotfile, 'file'))
    info = edf_info(difspotfile);
else
    info = [];
end

%%% this section should be replaced with a function dealing with different
% versions of parameters names:  fed, v1, v2...

% !!! using a mean pixel size is uncertain; ideally fwd simulation should
% use mm-s

% detector reference position in pixels
detrefpos_p = gtGeoLab2Sam(labgeo.detrefpos, eye(3), labgeo, recgeo, true);

fprintf('\nLoading reflections from database..');
% Initialization of spot table and info about pairs
segmentedSpots = gtDBLoadTable([parameters.acq.name 'difspot'], 'difspotID');
pairsTable     = gtDBLoadTable(parameters.acq.pair_tablename, 'pairID');
fprintf('(Done).\n')

difspot_num = numel(segmentedSpots.difspotID); % number of difspots in database
spot2grain  = cell(difspot_num, 1); % cell array linking difspots with grainids
spot2phase  = cell(difspot_num, 1);

filetable   = [parameters.acq.name '_filetable'];
filename    = fullfile(parameters.acq.dir, '4_grains', 'spot2grain.mat');
spotTable   = GtMergeTable(filetable, filename, {'spot2grain', 'spot2phase'});

samplefile  = fullfile(parameters.acq.dir, '4_grains', 'sample.mat');
sample      = GtSample.loadFromLockedFile(filetable, samplefile);

% initialise sample structure
phase = GtPhase(parameters.cryst(phaseID).name, totGrains);
sample.phases{phaseID} = phase;

% initialize output
out = [];

if (parameters.fsim.verbose)
    disp('FwdSim parameters:')
    print_structure(parameters.fsim, 'parameters.fsim', false, true)
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
for n = first : last
    fprintf('\nRunning forward simulation for grain %d:\n', n);
    fprintf(' - Computing grain properties..');

    gr = grain{n}; % output from INDEXTER

    %%% Predict spot positions
    gr = gtCalculateGrain(gr, parameters);
    uvw = gr.allblobs.uvw;

    %%% Get:
    % - average difspot X and Y sizes for the current spots in the grain
    % - theshold values used during the database search
    % - offset in possible lateral grain orientation
    % - diffstack Vertical and Horizontal sizes
    spotsCommProps = gtFwdSimGetDiffspotsCommonProperties(gr, detrefpos_p, parameters);

    %%% Get the indices of all spots which fully intersect the active
    % region of the detector
    onDet = gtFwdSimFindSpotsOnDetector(spotsCommProps, uvw, parameters);

    fprintf(' (Done).\n - Producing blob masks..')

    proj_bls = predict_spot_masks_from_indexed( ...
        gr, grain, segmentedSpots, spotsCommProps, onDet, parameters);

    fprintf(' (Done).\n - Preallocating structs..')

    % number of exspected diffraction spots
    numspots = numel(onDet);

    flag      = ones(numspots, 1); % see above
    % stack of zero padded diffraction spots (projections used for 3D reconstruction)
    bl(1:numspots) = struct( ...
        'intm', [], ...         % The normalized blob
        'mask', [], ...         % The segmentation mask
        'bbuim', [], ...        % Image BB on U
        'bbvim', [], ...        % Image BB on V
        'bbwim', [], ...        % Image BB on W <- Not strictly w
        'bbsize', [], ...       % Image BoundingBox (includes margins)
        'mbbsize', [], ...      % Real (Measured) Blob Bounding Box
        'intensity', [] );      % Blob original intensity
    checkSpots = struct('info', cell(numspots, 1));

    bb        = zeros(numspots, 4); % difspot BoundingBox from database
    cent      = zeros(numspots, 1); % centroid distance from database added 06-02-2014 LNervo
    spotid    = zeros(numspots, 1); % Mysql difspottable difspotID
    check     = false(numspots, 2);
    intensity = zeros(numspots, 1); % Integrated blob intensity
    avg_pixel_int = zeros(numspots, 1); % Average pixel intensity in blob
    likelihood = zeros(numspots, 1); % Integrated blob intensity
    cands     = struct(...
        'id', cell(numspots, 1), ...
        'likelihood_tot', cell(numspots, 1), ...
        'likelihood_proj_mask', cell(numspots, 1), ...
        'likelihood_dev_uv', cell(numspots, 1), ...
        'likelihood_dev_w', cell(numspots, 1) );

    % Diffraction vectors directions
    diff_beam_dirs  = zeros(numspots, 3);

    U = zeros(numspots, 1); % CoM u positions of difspots from difspottable
    V = zeros(numspots, 1); % CoM v positions of difspots from difspottable
    W = zeros(numspots, 1); % CoM w positions of difspots from difspottable

    if (parameters.fsim.assemble_figure)
        full = zeros(parameters.acq.ydet, parameters.acq.xdet);
    else
        full = [];
    end

    tempIsSpotA = false(numspots, 1);

    fprintf(' (Done).\n - Matching reflections and spots..');

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %%% Forward simulation:
    % We try to match the reflections with the segmented spots, so we
    % examine all hklsp and search corresponding spots / intensity on the
    % detector..
    %%% But also build the diffraction blob stack
    for ii = 1:numspots

        % Producing predicted spot's properties
        spot_props = predict_spot_properties(spotsCommProps, proj_bls(ii), gr.stat, parameters);

        % Look for difspots appying loose search criteria - is there any intensity at the predicted spot
        % position ?  Choose the one which is closest to the expected size
        [candidateIDs, spotsCands] = gtFwdSimFindCandidates(spot_props, uvw(onDet(ii), :), segmentedSpots);

        if (isempty(candidateIDs))
            % NO CANDIDATES FOUND:
            %   segmentation / overlap / intensity / ... problem ?

            if (spotsCands.found_intensity)
                % A bigger spot was found in the region.. it may be an
                % overlap
                flag(ii) = 2;
            end

            % If requested, we will check into the raw images later.
            continue;
        end

        bls = gtFwdSimBuildDifstackBlobs(candidateIDs, [], parameters, ...
            spotsCommProps.stackHSize, spotsCommProps.stackVSize);

        proj_bl = proj_bls(ii * ones(numel(bls), 1));

        % Computing spot area/shape deviation/likelihood
        ls_pb = compute_proj_bbs_partial_likelihoods(bls, proj_bl);

        refl_indx = onDet(ii) * ones(numel(bls), 1);

        % Computing UV deviation/likelihood
        th_uv = uvw(refl_indx, 1:2);
        blobs_uv = [ spotsCands.CentroidX, spotsCands.CentroidY ];
        ls_uv = compute_uv_partial_likelihoods(blobs_uv, th_uv, proj_bl);

        % Computing W deviation/likelihood
        th_w = gr.allblobs.omega(refl_indx);
        blob_w = spotsCands.CentroidImage * gtGetOmegaStepDeg(parameters);
        ls_w = compute_w_partial_likelihoods(blob_w, th_w);

        % in case there are several candidates, take the one with best
        % likelihood in terms of: shape, UV position, and W position.
        ls = ls_pb .* ls_uv .* ls_w;
        [~, cand_inc_index] = max(ls);

        cands(ii).id = candidateIDs';
        cands(ii).likelihood_tot = ls';
        cands(ii).likelihood_proj_mask = ls_pb';
        cands(ii).likelihood_dev_uv = ls_uv';
        cands(ii).likelihood_dev_w = ls_w';

        spotid(ii) = candidateIDs(cand_inc_index);

        bl(ii) = bls(cand_inc_index); %#ok<AGROW>

        U(ii) = spotsCands.CentroidX(cand_inc_index);
        V(ii) = spotsCands.CentroidY(cand_inc_index);
        W(ii) = spotsCands.CentroidImage(cand_inc_index) * gtGetOmegaStepDeg(parameters);
        bb(ii, :) = [...
            spotsCands.BoundingBoxXorigin(cand_inc_index), ...
            spotsCands.BoundingBoxYorigin(cand_inc_index), ...
            spotsCands.BoundingBoxXsize(cand_inc_index), ...
            spotsCands.BoundingBoxYsize(cand_inc_index) ];
        % The following should be beter than the previous, but it seems to
        % geneterate problems
%         bb(ii, :) = bls(cand_inc_index).mbbsize([1 2 4 5]);
        intensity(ii) = bls(cand_inc_index).intensity;
        avg_pixel_int(ii) = intensity(ii) / sum(bls(cand_inc_index).mask(:));
        likelihood(ii) = ls(cand_inc_index);

        % Found a segmented difspot at expected position:
        % we will check if it does match strict criteria
        uv_ok = cent(ii, :) <= spotsCommProps.Offset;
        bb_ok = all( [...
            between(bb(ii, 3), spotsCommProps.XsizeMin, spotsCommProps.XsizeMax), ...
            between(bb(ii, 4), spotsCommProps.YsizeMin, spotsCommProps.YsizeMax) ] );
        check(ii, :)  = [uv_ok, bb_ok];

        isSpotA = ~isempty(find(gr.difspotidA == spotid(ii), 1));
        isSpotB = ~isempty(find(gr.difspotidB == spotid(ii), 1));

        if (isSpotA || isSpotB)
            % This spot is part of a pair and has been used by INDEXTER
            flag(ii) = 5;
            tempIsSpotA(ii) = isSpotA;
        elseif all(check(ii, :))
            flag(ii) = 4;
        else
            flag(ii) = 3;
        end
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %%% Re-check flag == 1, to see if there's intensity at the predicted
    % position, if requested.
    if (parameters.fsim.check_spot)
        fprintf(' (Done).\n -  Checking spots in Raw images..');

        toBeChecked = find(flag == 1);
        for ii = reshape(toBeChecked, [1 numel(toBeChecked)]);
            [found_intensity, spotInfo] = gtFwdSimCheckSpotInRawImages(...
                uvw(onDet(ii), :), spotsCommProps, parameters);

            if (found_intensity)
                % Found intensity in FULL images at predicted position
                flag(ii) = 2;

                if (parameters.fsim.assemble_figure)
                    full = gtPlaceSubImage2(spotInfo.spot, full, spotInfo.bb(1), spotInfo.bb(2), 'summed');
                end
            end
            checkSpots(ii).info = spotInfo;
        end
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    % Reflections that have been matched to segmented spots
    included = find(flag >= 3);

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %%% We now build the diffraction stack
    fprintf(' (Done).\n - Building difstack..');

    difstack = gtFwdSimBuildDifstackSpots(spotid, included, parameters, ...
        spotsCommProps.stackHSize, spotsCommProps.stackVSize, ...
        spotsCommProps.Xsize, spotsCommProps.Ysize, ...
        bb, info);

    if (parameters.fsim.assemble_figure)
        % no padded spots
        full = sfBuildFull(spotid(reshape(included, 1, [])), ...
            parameters, spotsCommProps, bb(reshape(included, 1, []), :));
    end

    fprintf(' (Done).\n - Building geometry..');

    %%% Diffraction geometry
    for ii = reshape(included, 1, [])
        % we found a segmented spot - can use the theoretically
        % predicted direction or apparent direction (based on CoM of
        % grain and spot) for backprojection

        % theoretically prediceted omega angle
        if (flag(ii) == 5)
            % If available, we want to use the angles as determined from
            % the Friedel pairs - interrogate spotpair table
            %
            % diff_beam_dirs: direction of diffracted beam used in ASTRA
            [diff_beam_dirs(ii, :), W(ii)] = sfGetFriedelPair(pairsTable, spotid(ii), tempIsSpotA(ii));
        elseif (parameters.fsim.use_th)
            currentReflection = onDet(ii);
            % use theoretically predited directions (default)
            W(ii) = gr.allblobs.omega(currentReflection);
            % theoretically predicted beam direction (in sample
            % coordinate system)
            diff_beam_dirs(ii, :) = gr.allblobs.dvecsam(currentReflection, :);
        else
            % use experimentally determined omega angles
            W(ii) = segmentedSpots.CentroidImage(spotid(ii)) * gtGetOmegaStepDeg(parameters);
            % projection direction as inferred from grain / spot center
            % positions
            diff_beam_dirs(ii, :) = gtGeoDiffVecInSample([U(ii), V(ii)], W(ii), gr.center, labgeo, samgeo);
        end
    end

    [selected, goodness] = gtFwdSimSelectSpots(likelihood, intensity, ...
        avg_pixel_int, flag, included, parameters);

    %%% We now Build ASTRA's geometry
    proj = gtFwdSimBuildProjGeometry(...
        diff_beam_dirs(included, :), gr.center, W(included), ...
        bb(included, :), parameters, ...
        spotsCommProps.stackHSize, spotsCommProps.stackVSize, ...
        selected);

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %%% Now fill in the information for the output
    %  remove reflections for which no segmented spot has been found

    out.id        = gr.id;
    out.phaseid   = phaseID;
    out.R_vector  = gr.R_vector;
    out.center    = gr.center;

    out.selected  = selected; % vector of logicals with pre-selected spots active
    out.included  = included; % This is needed by Orientation Reconstruction
    out.ondet     = onDet;
    out.allblobs  = gr.allblobs;

    out.check     = check;
    out.flag      = flag;
    out.pairid    = gr.pairid;
    out.spotid    = spotid;

    % only included spots info
    out.cands     = cands(included);
    out.likelihood = likelihood(included);
    out.difspotID = spotid(included);
    out.uv_exp    = [U(included), V(included)];
    out.om_exp    = W(included);
    out.bb_exp    = bb(included, :);
    out.intensity = intensity(included);

    % save full image
    out.full      = full;

    % check spot info
    out.checkSpots = checkSpots;

    % diffraction geometry
    out.proj       = proj;
    % blob/spot stacks
    out.proj.bl    = bl(included);
    out.proj.stack = difstack(:, included, :);

    % recontruction and assemble stuff
    out.seg       = [];
    out.vol       = [];
    out.segbb     = zeros(1, 6);
    out.threshold = 0;

    found_reflections = length(find(flag > 1));
    out.completeness  = found_reflections / numspots;
    out.completeness_incl = length(find(flag > 3)) / numspots;
    out.goodness      = goodness;
    out.goodness_sel  = mean(out.likelihood(selected));
    out.confidence    = out.goodness * out.completeness_incl;

    if (parameters.fsim.save_grain)
        fprintf(' (Done).\n - Saving ouput..');

        grain_file_name = fullfile(parameters.acq.dir, '4_grains', 'phase_%02d', 'grain_%04d.mat');
        save(sprintf(grain_file_name, phaseID, gr.id), '-struct', 'out');
    end

    if (parameters.fsim.display_figure)
        fprintf(' (Done).\n - Displaying figure..');

        displayShowFsim(phaseID, out, rej_pars{:})
    end

    fprintf(' (Done).\n\n');

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %%% Now update spotTables and sample
    spot2grain(out.difspotID) = {n};
    spot2phase(out.difspotID) = {phaseID};

    sample.phases{phaseID}.setR_vector(n, gr.R_vector);
    sample.phases{phaseID}.setCompleteness(n, out.completeness);
    sample.phases{phaseID}.setCenter(n, out.center); % center in pixels

    if (isempty(out.selected))
        warning('gtForwardSimulate_v2:bad_value', ...
            'No spot was selected! This will make reconstruction impossible')
    elseif (numel(out.selected) < 10)
        warning('gtForwardSimulate_v2:bad_value', ...
            'Too few selected spots! This will give a very bad reconstruction')
    end

    if (out.goodness < 0.25)
        warning('gtForwardSimulate_v2:very_low_goodness', ...
            [ 'Very low goodness (included %f, selected %f) indicates a' ...
              ' probably poor quality of grain reconstruction' ], ...
            out.goodness, out.goodness_sel)
    elseif (out.goodness < 0.5)
        warning('gtForwardSimulate_v2:low_goodness', ...
            [ 'Low goodness (included %f, selected %f) indicates a' ...
              ' possibly poor quality of grain reconstruction' ], ...
            out.goodness, out.goodness_sel)
    end

    if (~parameters.fsim.check_spot)
        fprintf(['Completeness estimates may be too pessimistic - activate ' ...
                 'option ''check_spot'' for more reliable results\n'])
    end

    fprintf('\n Grain number %d:\n', n);

    newspots = length(find(flag == 4));
    fprintf(['Forward simulation found intensity at %d out of %d expected ' ...
             'spot positions \n'], found_reflections, numspots);
    fprintf('Completeness estimate:  %f\n', out.completeness);
    fprintf('Goodness estimate: included %f, selected %f\n', ...
        out.goodness, out.goodness_sel);
    fprintf('%d spots will be used for reconstruction\n', length(out.selected));
    fprintf(['%d new spots have been detected in addition to the %d ' ...
             'spotpairs identified by INDEXTER\n'], newspots, gr.nof_pairs);

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    if (parameters.fsim.save_grain)
        % update difspot table in the DB
        % keep list of difspots
        spotID = out.spotid;
        spotID(out.spotid == 0) = [];
        flags = out.flag;
        flags(out.spotid == 0) = [];

        gtDBFillUpdateColumn([parameters.acq.name 'difspot'], ...
            'grainID', 'int', 'integral', 'difspotID', {out.difspotID}, n)
        gtDBFillUpdateColumn([parameters.acq.name 'difspot'], ...
            'phaseID', 'int', 'integral', 'difspotID', {out.difspotID}, phaseID)
        gtDBFillUpdateColumn([parameters.acq.name 'difspot'], ...
            'flag', 'int', 'integral', 'difspotID', {spotID}, {flags});
    end
end

if (parameters.fsim.save_grain)
    % fill spotTables
    spotTable.fillTable(spot2grain, 'spot2grain');
    spotTable.fillTable(spot2phase, 'spot2phase');
    % save sample.mat
    sample.mergeToFile(filetable, samplefile, 'sample');
end

end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% sub-functions
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

function full = sfBuildFull(spotid, parameters, spotsCommProps, bb)
% full = sfBuildFull(spotid, parameters, spotsCommProps, bb)
%
% spotid         : spotid from grain
% parameters     : parameters.mat
% spotsCommProps : spots common properties
% bb             : bounding boxes from DB

    full = zeros(parameters.acq.ydet, parameters.acq.xdet);

    for ii = 1:numel(spotid)
        [spot, ~] = gtFwdSimBuildSpot(spotid(ii), parameters, spotsCommProps.Xsize, spotsCommProps.Ysize, bb(ii, :));

        full = gtPlaceSubImage2(spot, full, bb(ii, 1), bb(ii, 2), 'summed');
    end
end

function [diff_beam_dir, omega] = sfGetFriedelPair(pairsTable, spotID, isSpotA)
% [diff_beam_dirs, omega] = sfGetFriedelPair(pairsTable, spotID, isSpotA)

    if (isSpotA)
        idx = find(pairsTable.difAID == spotID);
        omega = pairsTable.omega(idx);
        diff_beam_dir = [pairsTable.ldirX(idx), pairsTable.ldirY(idx), pairsTable.ldirZ(idx)];
    else
        idx = find(pairsTable.difBID == spotID);
        omega = pairsTable.omegaB(idx);
        % in this case we have to reverse the line direction
        diff_beam_dir = -[pairsTable.ldirX(idx), pairsTable.ldirY(idx), pairsTable.ldirZ(idx)];
    end
end

function displayShowFsim(phaseID, grain, varargin)
% displayShowFsim(phaseID, grain, varargin)

    if isempty(varargin)
        [vars_pars, vars_list] = gtAppFsimDefaultPars();
        ind = findValueIntoCell(fieldnames(vars_pars), {'fsimtype', 'f_title'});
        vars_list(ind(:, 1), 4) = {2};

        vars_pars = gtModifyStructure(vars_pars, vars_list, 1, 'Options for displaying the Forward Sim:');
        varargin = struct2pv(vars_pars);
    end
    gtShowFsim(grain, phaseID, varargin{:});
end

function proj_bls = predict_spot_masks_from_indexed(gr, grain_INDX, ...
    segmentedSpots, spotsCommProps, onDet, parameters)

    % First argument: included_INDXTR, might be needed in the future for
    % some reason...
    [~, difspotID_INDXTR] = gtFwdSimGetIndexterReflections(gr, grain_INDX{gr.id}, segmentedSpots);

    % Producing blobs' boundingboxes (as by dilated segmentation info)
    bls_INDXTR = gtFwdSimBuildDifstackBlobs(difspotID_INDXTR, [], ...
        parameters, spotsCommProps.stackHSize, spotsCommProps.stackVSize);
    % INDEXTER Data
    BBs = cat(1, bls_INDXTR(:).mbbsize);
    BBs = BBs(:, [1 2 4 5]);

    Ws = segmentedSpots.CentroidImage(difspotID_INDXTR) .* gtGetOmegaStepDeg(parameters);

    proj_bls = gtFwdSimPredictProjectedGrainBBox(gr, BBs, Ws, onDet, parameters);

    % Producing original blobs' boundingboxes (as by original segmentation
    % info)
    BBs = [...
        segmentedSpots.BoundingBoxXorigin(difspotID_INDXTR), ...
        segmentedSpots.BoundingBoxYorigin(difspotID_INDXTR), ...
        segmentedSpots.BoundingBoxXsize(difspotID_INDXTR), ...
        segmentedSpots.BoundingBoxYsize(difspotID_INDXTR) ];

    proj_bls_original = gtFwdSimPredictProjectedGrainBBox(gr, BBs, Ws, onDet, parameters);

    for ii_b = 1:numel(proj_bls)
        proj_bls(ii_b).mbbsize = proj_bls_original(ii_b).bbsize;
    end
end

function spot_props = predict_spot_properties(spotsCommProps, proj_bl, gr_stats, parameters)
    bb_size_factor = parameters.fsim.bbsize_factor;

    % Minimums could be wrong by up to 2 pixels (roundings)
    spot_props.XsizeMin = max(5, proj_bl.mbbsize(1) - bb_size_factor * gr_stats.bbxsstd - 2);
    spot_props.YsizeMin = max(5, proj_bl.mbbsize(2) - bb_size_factor * gr_stats.bbysstd - 2);

    % Allowing for matching spots, even if the convex shape of the grain
    % computed from indexter's selection had a limited angle view on the
    % grain volume
    spot_props.XsizeMin = min(spot_props.XsizeMin, spotsCommProps.XsizeMin);
    spot_props.YsizeMin = min(spot_props.YsizeMin, spotsCommProps.YsizeMin);

    spot_props.XsizeMax = proj_bl.mbbsize(1) + bb_size_factor * gr_stats.bbxsstd;
    spot_props.YsizeMax = proj_bl.mbbsize(2) + bb_size_factor * gr_stats.bbysstd;

    spot_props.thr_max_offset = spotsCommProps.thr_max_offset;
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Likelihood computation

function ls = compute_uv_partial_likelihoods(blob_uv, th_uv, proj_bls)
    proj_bbox(:, [1 3]) = cat(1, proj_bls(:).bbuim);
    proj_bbox(:, [2 4]) = cat(1, proj_bls(:).bbvim);

    blob_expected_sizes = proj_bbox(:, [3 4]) - proj_bbox(:, [1 2]) + 1;

    ls = (th_uv - blob_uv) ./ max(blob_expected_sizes ./ 10, 10);
    ls = exp(- (ls .^ 2) );
    ls = ls(:, 1) .* ls(:, 2);
end

function ls = compute_w_partial_likelihoods(blob_w, th_w)
    ls = 2 .* (th_w - blob_w);
    ls = exp(- (ls .^ 2));
end

function ls = compute_proj_bbs_partial_likelihoods(bls, proj_bls)
    bls_bboxes(:, [1, 3]) = cat(1, bls(:).bbuim);
    bls_bboxes(:, [2, 4]) = cat(1, bls(:).bbvim);

    proj_bls_bboxes(:, [1, 3]) = cat(1, proj_bls(:).bbuim);
    proj_bls_bboxes(:, [2, 4]) = cat(1, proj_bls(:).bbvim);

    cont_bbxes = [ ...
        min(bls_bboxes(:, 1:2), proj_bls_bboxes(:, 1:2)), ...
        max(bls_bboxes(:, 3:4), proj_bls_bboxes(:, 3:4)) ...
        ];

    cont_bbxes = [cont_bbxes(:, 1:2), (cont_bbxes(:, 3:4) - cont_bbxes(:, 1:2) + 1)];

    bls_shifts = bls_bboxes(:, 1:2) - cont_bbxes(:, 1:2);
    bls_shifts(:, 3) = 0;
    proj_bls_shifts = proj_bls_bboxes(:, 1:2) - cont_bbxes(:, 1:2);
    proj_bls_shifts(:, 3) = 0;

    num_bls = numel(proj_bls);
    mismatch_more = zeros(num_bls, 1);
    mismatch_less = zeros(num_bls, 1);
    proj_count = zeros(num_bls, 1);

    for ii = 1:num_bls
        cont_size = cont_bbxes(ii, 3:4);

        proj_count(ii) = sum(proj_bls(ii).mask(:));

        bl_mask = bls(ii).mask;
        bl_mask = sum(bl_mask, 3);
        bl_mask = bl_mask > 0;

        proj_bl_mask = logical(proj_bls(ii).mask);

        bl_mask = gtPlaceSubVolume( ...
            zeros(cont_size, 'uint8'), bl_mask, bls_shifts(ii, :));
        proj_bl_mask = gtPlaceSubVolume( ...
            zeros(cont_size, 'uint8'), proj_bl_mask, proj_bls_shifts(ii, :));

        overlap = bl_mask & proj_bl_mask;

        bl_mask(overlap) = 0;
        proj_bl_mask(overlap) = 0;

        mismatch_more(ii) = sum(bl_mask(:));
        mismatch_less(ii) = sum(proj_bl_mask(:));
    end

    % 3 and 2 are temporary
    ls = (1 .* mismatch_more + 1 .* mismatch_less) ./ proj_count;

    ls = exp(- (ls .^ 2));
end

