Skip to content
Snippets Groups Projects
gtForwardSimulate_v2.m 40.57 KiB
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_angles       = <double>  twin angle {[]}
%       twin_axes        = <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();
if (~isfield(parameters, 'detgeo') || isempty(parameters.detgeo))
    parameters.detgeo = gtGeoConvertLegacyLabgeo2Detgeo(parameters.labgeo);
end
detgeo = parameters.detgeo;
if (~isfield(parameters.labgeo, 'omstep') || isempty(parameters.labgeo.omstep))
    parameters.labgeo.omstep = gtGetOmegaStepDeg(parameters);
end
labgeo = parameters.labgeo;
samgeo = parameters.samgeo;

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
conf = struct( ...
    'det_index', [], ...
    'twin_angles', zeros(0, 1), ...
    'twin_axes', zeros(0, 3) );
if (~exist('rej_pars', 'var'))
    rej_pars = {};
end
[conf, rej_pars] = parse_pv_pairs(conf, rej_pars);

if (isempty(conf.det_index))
    conf.det_index = 1:numel(detgeo);
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

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

    % Storing information about the grain in sample.mat
    sample.phases{phaseID}.setR_vector(n, gr.R_vector);
    sample.phases{phaseID}.setCenter(n, gr.center);

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

    proj = gtFwdSimProjDefinition('fwd_sim', numel(conf.det_index));
    fwd_sim = repmat(gtFwdSimDataStructureDefinition(0), [numel(conf.det_index), 1]);

    for ii_d = conf.det_index
        uvw = gr.allblobs.detector(ii_d).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, parameters, ii_d);

        %%% 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, ii_d);

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

        if (parameters.fsim.assemble_figure)
            full = zeros(detgeo(ii_d).detsizev, detgeo(ii_d).detsizeu);
        else
            full = [];
        end

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

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

        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
        [fwd_sim(ii_d), bl] = forward_simulate_grain(gr, ondet, ...
            spotsCommProps, segmentedSpots, proj_bls, parameters, ii_d);

        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %%% 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(fwd_sim(ii_d).flag == 1);
            for ii = reshape(toBeChecked, 1, []);
                [found_intensity, spotInfo] = gtFwdSimCheckSpotInRawImages(...
                    uvw(ondet(ii), :), spotsCommProps, parameters);

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

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

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

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

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

        if (parameters.fsim.assemble_figure)
            % no padded spots
            full = sfBuildFull(fwd_sim(ii_d).spotid(included), ...
                parameters, spotsCommProps, fwd_sim(ii_d).bb(included, :));
        end

        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %%% We now build the Geometry
        fprintf(' (Done).\n - Building geometry..');

        %%% Diffraction geometry
        % diff_beam_dirs: direction of diffracted beam used in ASTRA
        identif_indexed = fwd_sim(ii_d).flag(included) == 5;
        incl_indx = included(identif_indexed);
        incl_not_indx = included(~identif_indexed);

        if (isempty(incl_indx))
            warning('gtForwardSimulate_v2:wrong_grain', ...
                ['Grain "%d" is probably a badly indexed grain.' ...
                ' It will be deselected'], n)
            sample.phases{phaseID}.setSelected(n, false);
            continue;
        end

        % we found a segmented spot - can use the theoretically
        % predicted direction or apparent direction (based on CoM of
        % grain and spot) for backprojection

        % If available, we want to use the angles as determined from
        % the Friedel pairs - interrogate spotpair table
        [diff_beam_dirs(incl_indx, :), fwd_sim(ii_d).uvw(incl_indx, 3)] = sfGetFriedelPair( ...
            pairsTable, fwd_sim(ii_d).spotid(incl_indx), fwd_sim(ii_d).spot_type(incl_indx) == 'a');

        % Otherwise we have two choices
        if (parameters.fsim.use_th)
            % use theoretically predited directions (default)
            fwd_sim(ii_d).uvw(incl_not_indx, 3) = gr.allblobs.omega(ondet(incl_not_indx));
            % theoretically predicted beam direction (in sample
            % coordinate system)
            diff_beam_dirs(incl_not_indx, :) = gr.allblobs.dvecsam(ondet(incl_not_indx), :);
        else
            % use experimentally determined omega angles (which we find in: 
            % fwd_sim(ii_d).uvw(incl_not_indx, 3))
            % projection direction as inferred from grain / spot center
            % positions
            diff_beam_dirs(incl_not_indx, :) = gtGeoDiffVecInSample( ...
                fwd_sim(ii_d).uvw(incl_not_indx, 1:2), ...
                fwd_sim(ii_d).uvw(incl_not_indx, 3), ...
                gr.center, detgeo(ii_d), labgeo, samgeo);
        end

        %%% Selection of the spots
        [selected, goodness] = gtFwdSimSelectSpots( ...
            fwd_sim(ii_d).likelihood, fwd_sim(ii_d).intensity, ...
            fwd_sim(ii_d).avg_pixel_int, fwd_sim(ii_d).flag, ...
            included, parameters);

        %%% We now Build ASTRA's geometry
        [proj(ii_d), fwd_sim(ii_d).gv_verts] = gtFwdSimBuildProjGeometry( ...
            diff_beam_dirs(included, :), ...
            gr.center, fwd_sim(ii_d).uvw(included, 3), ...
            fwd_sim(ii_d).bb(included, :), parameters, ...
            spotsCommProps.stackUSize, spotsCommProps.stackVSize, ...
            selected);
        % Selections
        proj(ii_d).ondet    = ondet;
        proj(ii_d).included = included;
        % selected: logicals with pre-selected active spots
        proj(ii_d).selected = selected;
        % blob/spot stacks
        proj(ii_d).bl    = bl(included);
        proj(ii_d).stack = difstack(:, included, :);

        %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
        %%% We now look for possible twins if asked
        if (~isempty(conf.twin_angles))
            fprintf(' (Done).\n - Searching twins..');

            fp_twins = forward_simulate_twin(gr, fwd_sim(ii_d), proj, ...
                conf, spotsCommProps, segmentedSpots, parameters, ii_d);

            fprintf(' (Done).\n - Num spots per twin:\n')
            for ii_t = 1:numel(fp_twins)
                fprintf('   %d) %d\n', ii_t, numel(fp_twins(ii_t).proj.included));
            end
            fprintf(' - Assembling final data-structure..')
        else
            fprintf(' (Done).\n - Assembling final data-structure..')
        end
    end

    %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
    %%% 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.allblobs  = gr.allblobs;
    out.fwd_sim   = fwd_sim;

    out.check     = fwd_sim(1).check;
    out.flag      = fwd_sim(1).flag;
    out.pairid    = gr.pairid;
    out.spotid    = fwd_sim(1).spotid;

    % only included spots info
    out.cands     = fwd_sim(1).cands(included);
    out.likelihood = fwd_sim(1).likelihood(included);
    out.difspotID = fwd_sim(1).spotid(included);
    out.uv_exp    = fwd_sim(1).uvw(included, 1:2);
    out.om_exp    = fwd_sim(1).uvw(included, 3);
    out.bb_exp    = fwd_sim(1).bb(included, :);
    out.intensity = fwd_sim(1).intensity(included);

    % save full image
    out.full      = full;

    % check spot info
    out.checkSpots = fwd_sim(1).check_spots;

    % diffraction geometry
    out.proj = proj;

    if (~isempty(conf.twin_angles))
        out.twin_vars = fp_twins;
    else
        out.twin_vars = [];
    end

    found_reflections = numel(find(fwd_sim(1).flag > 1));
    out.completeness  = found_reflections / numspots;
    out.completeness_incl = numel(find(fwd_sim(1).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..');

        gtSaveGrain(phaseID, gr.id, 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}.setCompleteness(n, out.completeness);

    if (isempty(out.proj.selected))
        warning('gtForwardSimulate_v2:bad_value', ...
            'No spot was selected! This will make reconstruction impossible')
    elseif (numel(out.proj.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(fwd_sim(1).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.proj.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
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Forward Simulation

function fwd_sim = gtFwdSimDataStructureDefinition(numspots)
    fwd_sim = struct( ...
        'flag', ones(numspots, 1), ... % see above
    	'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), ... % Blob likelihood
        '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) ), ...
        'uvw', zeros(numspots, 3), ... % CoM positions of difspots from difspottable
        'spot_type', char(zeros(numspots, 1)), ...
        'check_spots', struct('info', cell(numspots, 1)), ...
        'gv_verts', [] );
end

function [fwd_sim, bl] = forward_simulate_grain(gr, onDet, ...
        spotsCommProps, segmentedSpots, proj_bls, parameters, det_index)

    uvw = gr.allblobs.detector(det_index).uvw;

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

    % stack of zero padded diffraction spots (projections used for 3D reconstruction)
    bl(1:numspots) = gtFwdSimBlobDefinition();
    fwd_sim = gtFwdSimDataStructureDefinition(numspots);

    % 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, gr, onDet(ii), segmentedSpots, parameters, det_index);

        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
                fwd_sim.flag(ii) = 2;
            end

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

        bls = gtFwdSimBuildDifstackBlobs(candidateIDs, [], parameters, ...
            spotsCommProps.stackUSize, 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 * parameters.labgeo.omstep;
        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);

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

        fwd_sim.spotid(ii) = candidateIDs(cand_inc_index);

        bl(ii) = bls(cand_inc_index);

        fwd_sim.uvw(ii, :) = [ ...
            spotsCands.CentroidX(cand_inc_index), ...
            spotsCands.CentroidY(cand_inc_index), ...
            spotsCands.CentroidImage(cand_inc_index) * parameters.labgeo.omstep ];
        fwd_sim.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
%         fwd_sim.bb(ii, :) = bls(cand_inc_index).mbbsize([1 2 4 5]);
        fwd_sim.intensity(ii) = bls(cand_inc_index).intensity;
        fwd_sim.avg_pixel_int(ii) = fwd_sim.intensity(ii) / sum(bls(cand_inc_index).mask(:));
        fwd_sim.likelihood(ii) = ls(cand_inc_index);

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

        isSpotA = any(gr.difspotidA == fwd_sim.spotid(ii));
        isSpotB = any(gr.difspotidB == fwd_sim.spotid(ii));

        if (isSpotA || isSpotB)
            % This spot is part of a pair and has been used by INDEXTER
            fwd_sim.flag(ii) = 5;
            fwd_sim.spot_type(ii) = char('a' * isSpotA + 'b' * isSpotB);
        elseif all(fwd_sim.check(ii, :))
            fwd_sim.flag(ii) = 4; % This flag is kinda obsolete
        else
            fwd_sim.flag(ii) = 3;
        end
    end
end

function g_twins = forward_simulate_twin(gr, fwd_sim, proj, twin_params, ...
        spotsCommProps, segmentedSpots, parameters, det_index)
    spacegroup = parameters.cryst(gr.phaseid).spacegroup;
    lp = parameters.cryst(gr.phaseid).latticepar;

    g_twins = gtTwinOrientations(gr.R_vector, ...
        twin_params.twin_angles, twin_params.twin_axes, spacegroup, lp);

    spotsCommProps.XsizeMin = 5;
    spotsCommProps.YsizeMin = 5;
    spotsCommProps.max_w_offset_im = 2.5 / parameters.labgeo.omstep;
    spotsCommProps.max_n_offset_deg = 2.5;
    spotsCommProps.max_t_offset_deg = 1;

    num_twins = numel(g_twins);
    fprintf('\b\b, variant: ')
    for ii = 1:num_twins
        num_chars = fprintf('%d/%d', ii, num_twins);

        twin = struct('id', gr.id, 'phaseid', gr.phaseid, ...
            'R_vector', g_twins(ii).R_vector, 'center', gr.center);
        twin = gtCalculateGrain(twin, parameters);

        twin.stat = gr.stat;
        twin.stat.bbxsstd = twin.stat.bbxsstd * 0.1;
        twin.stat.bbysstd = twin.stat.bbysstd * 0.1;
        twin.difspotidA = [];
        twin.difspotidB = [];

        uvw = twin.allblobs.detector(det_index).uvw;
        ondet_twin = gtFwdSimFindSpotsOnDetector(spotsCommProps, uvw, parameters);

        proj_bls = predict_spot_masks_from_fwdsimgrain(twin, gr, proj, fwd_sim, ondet_twin, parameters, det_index);

        [twin.fwd_sim, bl] = forward_simulate_grain(...
            twin, ondet_twin, spotsCommProps, segmentedSpots, proj_bls, parameters, det_index);

        included = find(twin.fwd_sim.flag >= 3);

        twin.proj(det_index).bl = bl(included);
        if (~isempty(twin.proj(det_index).bl))
            spots = arrayfun(@(x){sum(x.intm, 3)}, bl(included));
            twin.proj(det_index).stack = permute(cat(3, spots{:}), [1 3 2]);
        end

        twin.proj(det_index).ondet = ondet_twin;
        twin.proj(det_index).included = included;
        twin.proj(det_index).selected = false(numel(included), 1);

        g_twins(ii).allblobs = twin.allblobs;
        g_twins(ii).fwd_sim = clean_fwd_sim(twin.fwd_sim, included);
        g_twins(ii).proj = twin.proj;

        % Just to make gtGuiGrainMontage happy
        g_twins(ii).id = gr.id;
        g_twins(ii).phaseid = gr.phaseid;

        fprintf(repmat('\b', [1 num_chars]))
    end

    fprintf('%d/%d', num_twins, num_twins)
end

%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% Support 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.detgeo.detsizev, parameters.detgeo.detsizeu, 'single');

    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)

    for ii = numel(spotID):-1:1
        if (isSpotA(ii))
            idx = find(pairsTable.difAID == spotID(ii));
            omega(ii) = pairsTable.omega(idx);
            diff_beam_dir(ii, :) = [pairsTable.ldirX(idx), pairsTable.ldirY(idx), pairsTable.ldirZ(idx)];
        else
            idx = find(pairsTable.difBID == spotID(ii));
            omega(ii) = pairsTable.omegaB(idx);
            % in this case we have to reverse the line direction
            diff_beam_dir(ii, :) = -[pairsTable.ldirX(idx), pairsTable.ldirY(idx), pairsTable.ldirZ(idx)];
        end
    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, det_ind)

    samgeo = parameters.samgeo;
    labgeo = parameters.labgeo;
    detgeo = parameters.detgeo(det_ind);
    omstep = gtGetOmegaStepDeg(parameters, det_ind);

    % 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.stackUSize, spotsCommProps.stackVSize);
    % INDEXTER Data
    BBus = cat(1, bls_INDXTR(:).mbbu);
    BBvs = cat(1, bls_INDXTR(:).mbbv);
    BBsizes = cat(1, bls_INDXTR(:).mbbsize);
    BBs = [BBus(:, 1), BBvs(:, 1), BBsizes(:, 1:2)];

    Ws = segmentedSpots.CentroidImage(difspotID_INDXTR) .* omstep;

    gr_center = gr.center(ones(numel(difspotID_INDXTR), 1), :);

    projvecs = [segmentedSpots.CentroidX(difspotID_INDXTR), segmentedSpots.CentroidY(difspotID_INDXTR)];
    projvecs = gtGeoDet2Lab(projvecs, detgeo, false);
    projvecs = gtGeoLab2Sam(projvecs, Ws, labgeo, samgeo, 0, 0) - gr_center;

    verts_rec = gtFwdSimComputeCircumscribingPolyhedron(gr.center, projvecs, Ws, BBs, parameters, det_ind, 'convhull');

    proj_bls = gtFwdSimProjectVertices2Det(gr, verts_rec, ondet, parameters, det_ind);

    % 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) ];

    verts_rec = gtFwdSimComputeCircumscribingPolyhedron(gr.center, projvecs, Ws, BBs, parameters, det_ind, 'convhull');

    proj_bls_original = gtFwdSimProjectVertices2Det(gr, verts_rec, ondet, parameters, det_ind);

    for ii_b = 1:numel(proj_bls)
        proj_bls(ii_b).mbbsize = proj_bls_original(ii_b).bbsize;
        proj_bls(ii_b).mbbu = proj_bls_original(ii_b).bbuim;
        proj_bls(ii_b).mbbv = proj_bls_original(ii_b).bbvim;
        proj_bls(ii_b).mbbw = proj_bls_original(ii_b).bbwim;
        proj_bls(ii_b).mask = proj_bls_original(ii_b).mask;
    end
end

function proj_bls = predict_spot_masks_from_fwdsimgrain(new_gr, main_gr, proj, main_gr_fwd_sim, new_gr_ondet, parameters, det_ind)
% We here produce the expected bounding box using the main grain's proj
% datastructure, and then we produce the projected blobs at the predicted
% new grain's positions

    if (isempty(main_gr_fwd_sim.gv_verts))
        centroids_w = zeros(numel(proj.bl), 1);
        for ii_b = 1:numel(proj.bl)
            int_profile = reshape(sum(sum(proj.bl(ii_b).intm, 1), 2), 1, []);
            num_slices = numel(int_profile);
            centroids_w(ii_b) = sum((0:num_slices-1) .* int_profile) / num_slices;
        end
        Ws = cat(1, proj.bl(:).bbwim);
        Ws = (Ws(:, 1) + centroids_w) .* gtGetOmegaStepDeg(parameters, det_ind);

        % Producing image blobs' boundingboxes (as by blob size info)
        BBus = cat(1, proj.bl(:).bbuim);
        BBvs = cat(1, proj.bl(:).bbvim);
        BBsizes = cat(1, proj.bl(:).bbsize);
        BBs = [BBus(:, 1), BBvs(:, 1), BBsizes(:, 1:2)];

        projvecs = main_gr.allblobs.dvecsam(proj.ondet(proj.included), :);

        verts_rec = gtFwdSimComputeCircumscribingPolyhedron(main_gr.center, projvecs, Ws, BBs, parameters, det_ind, 'convhull');
    else
        verts_rec = main_gr_fwd_sim.gv_verts;
    end

    proj_bls = gtFwdSimProjectVertices2Det(new_gr, verts_rec, new_gr_ondet, parameters, det_ind);

    if (isempty(main_gr_fwd_sim.gv_verts))
        % Producing original blobs' boundingboxes (as by original segmentation
        % info)
        BBus = cat(1, proj.bl(:).mbbu);
        BBvs = cat(1, proj.bl(:).mbbv);
        BBsizes = cat(1, proj.bl(:).mbbsize);
        BBs = [BBus(:, 1), BBvs(:, 1), BBsizes(:, 1:2)];

        verts_rec = gtFwdSimComputeCircumscribingPolyhedron(main_gr.center, projvecs, Ws, BBs, parameters, det_ind, 'convhull');
    end

    proj_bls_original = gtFwdSimProjectVertices2Det(new_gr, verts_rec, new_gr_ondet, parameters, det_ind);

    for ii_b = 1:numel(proj_bls)
        proj_bls(ii_b).mbbsize = proj_bls_original(ii_b).bbsize;
        proj_bls(ii_b).mbbu = proj_bls_original(ii_b).bbuim;
        proj_bls(ii_b).mbbv = proj_bls_original(ii_b).bbvim;
        proj_bls(ii_b).mbbw = proj_bls_original(ii_b).bbwim;

        proj_bls(ii_b).mask = proj_bls_original(ii_b).mask;
    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.max_w_offset_im = spotsCommProps.max_w_offset_im;
    if (isfield(spotsCommProps, 'max_n_offset') && ~isempty(spotsCommProps.max_n_offset))
        spot_props.max_n_offset_deg = spotsCommProps.max_n_offset_deg;
        spot_props.max_t_offset_deg = spotsCommProps.max_t_offset_deg;
    end
end

function fwd_sim = clean_fwd_sim(fwd_sim, included)
%     fwd_sim.flag = fwd_sim.flag(included);
    fwd_sim.bb = fwd_sim.bb(included, :);
    fwd_sim.cent = fwd_sim.cent(included);
%     fwd_sim.spotid = fwd_sim.spotid(included);
    fwd_sim.difspotID = fwd_sim.spotid(included);
%     fwd_sim.check = fwd_sim.check(included, :);
    fwd_sim.intensity = fwd_sim.intensity(included);
    fwd_sim.avg_pixel_int = fwd_sim.avg_pixel_int(included);
    fwd_sim.likelihood = fwd_sim.likelihood(included);
    fwd_sim.cands = fwd_sim.cands(included);
    fwd_sim.uvw = fwd_sim.uvw(included, :);
    fwd_sim.spot_type = fwd_sim.spot_type(included);
    fwd_sim.check_spots = fwd_sim.check_spots(included);
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/4 makes up for the recent change to the use of a bigger estimate of
    % the grain volume
    ls = (1 .* mismatch_more + 3 / 4 .* mismatch_less) ./ proj_count;

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