From fc1d01092b3983ec102cf87dd82c235a325b4d2b Mon Sep 17 00:00:00 2001
From: Zheheng Liu <zheheng.liu@esrf.fr>
Date: Thu, 30 Jan 2025 17:50:43 +0100
Subject: [PATCH 1/3] create forward prjections for twin cluster.

Signed-off-by: Zheheng Liu <zheheng.liu@esrf.fr>
---
 ...gt6DCreateProjDataFromTwinClusterFwdProj.m | 940 ++++++++++++++++++
 .../gtTwinClusterFwdProjUpdateSelected.m      |  38 +
 2 files changed, 978 insertions(+)
 create mode 100755 zUtil_Twins/gt6DCreateProjDataFromTwinClusterFwdProj.m
 create mode 100755 zUtil_Twins/gtTwinClusterFwdProjUpdateSelected.m

diff --git a/zUtil_Twins/gt6DCreateProjDataFromTwinClusterFwdProj.m b/zUtil_Twins/gt6DCreateProjDataFromTwinClusterFwdProj.m
new file mode 100755
index 00000000..5d87dc93
--- /dev/null
+++ b/zUtil_Twins/gt6DCreateProjDataFromTwinClusterFwdProj.m
@@ -0,0 +1,940 @@
+function [cl, estim_space_bbox_pix, estim_orient_bbox_rod] = gt6DCreateProjDataFromTwinClusterFwdProj(grs_list, phase_id, p, varargin)
+% FUNCTION cl = gt6DCreateProjDataFromTwinClusterFwdProj(parent_id, variants, phase_id, varargin)
+% INPUT:
+%   parent_id: Parent grain ID (it could be the parent grain structure)
+%   variants: an array of structures defined like the following:
+%             struct('type', {'gr_id' | 'gr_str' | 'var_num'}, 'data', {...} )
+% OUTPUT:
+%   samp_ors: is an array of grain structures containing the parent and its
+%             twins
+
+    if (~exist('phase_id', 'var') || isempty(phase_id))
+        phase_id = 1;
+    end
+
+    conf = struct( ...
+        'verbose', false, ...
+        'det_index', 1, ...
+        'min_eta', 15, ...
+        'angular_tol', 1, ...
+        'rspace_oversize', 1, ...
+        'ospace_oversize', 1, ...
+        'check_spots', false, ...
+        'stack_oversize', 1.4, ...
+        'merge_dist_deg', 1, ...
+        'use_raw_images', 1, ...
+        'include_all', true, ...
+        'select_all', true, ...
+        'save', false, ...
+        'desel_global_pos', [], ...
+        'sel_global_pos', []);
+    conf = parse_pv_pairs(conf, varargin);
+
+    if (~exist('p', 'var') || isempty(p))
+        p = gtLoadParameters();
+    end
+    symm = gtCrystGetSymmetryOperators(p.cryst(phase_id).crystal_system);
+
+    if isstruct(grs_list)
+        grs = grs_list;
+        grs_list = cat(2, grs(:).id);
+        [grs_list, tmp_ind] = unique(grs_list);
+        grs = grs(tmp_ind);
+    else
+        fprintf('Loading grains: ')
+        grs_list = unique(grs_list(:))';
+        num_grains = numel(grs_list);
+        grs(1) = gtLoadGrain(phase_id, grs_list(1));
+        grs(1) = gtCalculateGrain(grs(1), p);
+        for ii_g = num_grains:-1:2
+            grs(ii_g) = gtLoadGrain(phase_id, grs_list(ii_g));
+            grs(ii_g) = gtCalculateGrain(grs(ii_g), p);
+        end
+    end
+
+    num_grains = numel(grs_list);
+
+    if (conf.verbose)
+        fprintf('R_vectors:\n')
+        cat(1, grs(:).R_vector)
+        fprintf('Reciprocal disorientations:\n')
+        for ii_g_1 = 1:num_grains
+            grs_to_check = (ii_g_1+1):num_grains;
+            if (numel(grs_to_check) > 0)
+                fprintf(' - Disorientations from grain: %d\n', grs(ii_g_1).id)
+                for ii_g_2 = grs_to_check
+                    dis_angle = gtDisorientation(grs(ii_g_1).R_vector', grs(ii_g_2).R_vector', symm);
+                    fprintf('   + Grain %d: %f\n', grs(ii_g_2).id, dis_angle);
+                end
+            end
+        end
+    end
+
+    refgr = grs(1);
+
+    refgr_omind = refgr.allblobs(conf.det_index).omind;
+    for ii_g = num_grains:-1:2
+        grs(ii_g) = gtGrainAllblobsFilterOrder(grs(ii_g), refgr_omind, conf.det_index);
+    end
+    size_refl_list = size(grs(1).allblobs(conf.det_index).omega);
+
+    if (numel(conf.use_raw_images) == 1)
+        conf.use_raw_images = conf.use_raw_images(ones(num_grains, 1));
+    end
+
+    % % % deselect the spots that are shared by the grains not belonging to
+    % the cluster
+    % Remove the other phases
+    spots_shared_by_externel_gr_to_desel = sfDetermineTheBlobsSharedByExternalGrainsToDeselect(p.acq(1).dir, phase_id, grs_list, grs, size_refl_list(1), num_grains, conf.det_index);
+
+    fprintf('Determining real-space bounding box..')
+    c = tic();
+
+    % Determining real-space bounding box. It is assumed to be contiguos.
+    [gr_center_pix, bbox_size_pix, estim_space_bbox_pix] = gt6DMergeRealSpaceVolumes(grs, conf.det_index, conf.rspace_oversize);
+    gr_center_mm = gtGeoSam2Sam(gr_center_pix, p.recgeo(conf.det_index), p.samgeo, false, false);
+
+    estim_space_bbox_mm = [ ...
+        gtGeoSam2Sam(estim_space_bbox_pix(1:3), p.recgeo(conf.det_index), p.samgeo, false, false), ...
+        gtGeoSam2Sam(estim_space_bbox_pix(4:6), p.recgeo(conf.det_index), p.samgeo, false, false) ];
+    bbox_size_mm = estim_space_bbox_mm(4:6) - estim_space_bbox_mm(1:3);
+
+    fprintf('\b\b: Done in %g seconds.\n', toc(c))
+
+    fprintf(' - Estimated extent: [%3g, %3d, %3d] -> [%3d, %3d, %3d]\n', estim_space_bbox_pix);
+    fprintf('          BBox size: %3d, %3d, %3d (%g, %g, %g mm)\n', bbox_size_pix, bbox_size_mm);
+
+    estim_orient_bbox_rod = zeros(num_grains, 6);
+    bbox_size_rod = zeros(num_grains, 3);
+
+    % Determining orientation-space bounding boxes. They are assumed to be
+    % detached from each others.
+    for ii_g = 1:num_grains
+        fprintf('Determining orientation-space region: %d/%d\n', ii_g, num_grains)
+
+        sampler = GtOrientationSampling(p, grs(ii_g), ...
+            'detector_index', conf.det_index, 'verbose', conf.verbose);
+        r_vecs = sampler.guess_ODF_BB()';
+
+        estim_orient_bbox_rod(ii_g, :) = [min(r_vecs, [], 1), max(r_vecs, [], 1)];
+        bbox_size_rod(ii_g, :) = estim_orient_bbox_rod(ii_g, 4:6) - estim_orient_bbox_rod(ii_g, 1:3);
+
+        % oversizing the orientation a bit
+        delta_bbox_size_rod = bbox_size_rod(ii_g, :) .* (conf.ospace_oversize - 1) / 2;
+        estim_orient_bbox_rod(ii_g, :) = estim_orient_bbox_rod(ii_g, :) + [-delta_bbox_size_rod, delta_bbox_size_rod];
+        bbox_size_rod(ii_g, :) = estim_orient_bbox_rod(ii_g, 4:6) - estim_orient_bbox_rod(ii_g, 1:3);
+
+        bbox_size_deg = 2 * atand(bbox_size_rod(ii_g, :));
+
+        fprintf(' - Estimated extent: [%g, %g, %g] -> [%g, %g, %g]\n', estim_orient_bbox_rod(ii_g, :));
+        fprintf('          BBox size: %3.3f, %3.3f, %3.3f (deg)\n', bbox_size_deg);
+    end
+
+    % include_all and select_all
+    grs = sfIncludeAllAndSelectAllAndUniformBlStruct(grs, conf);
+
+    % Determining the regions to be merged!
+    merge_lists = determine_merge_list(estim_orient_bbox_rod, num_grains, conf);
+    num_grains_new = numel(merge_lists);
+    new_grs_list_indices = zeros(1, num_grains_new);
+    num_created_grains = 0;
+    for ii_m = 1:num_grains_new
+        if (numel(merge_lists{ii_m}) > 1)
+            num_created_grains = num_created_grains + 1;
+            new_grs_list_indices(ii_m) = num_grains + num_created_grains;
+        else
+            new_grs_list_indices(ii_m) = merge_lists{ii_m};
+        end
+    end
+
+    samp_ors = cell(num_grains+num_created_grains, 1);
+
+    max_img_sizes = zeros(num_grains+num_created_grains, 2);
+
+    inconvenient_etas = cell(num_grains_new, 1);
+
+    if (num_grains_new < num_grains)
+        conf.use_raw_images = cellfun(@(x) max(conf.use_raw_images(x)), merge_lists);
+        % Merging regions
+        new_grs = cell(num_created_grains, 1);
+        for ii_m = 1:num_grains_new
+            if (new_grs_list_indices(ii_m) > num_grains)
+                new_gr = grs(merge_lists{ii_m}(end));
+                for ii_g = merge_lists{ii_m}(end-1:-1:1)
+                    new_gr = gtGrainMerge(new_gr, grs(ii_g), p, [], 1 + (conf.use_raw_images(ii_m) == 1));
+                end
+                new_grs{new_grs_list_indices(ii_m) - num_grains} = new_gr;
+                grs(merge_lists{ii_m}) = sfUniformProj(grs(merge_lists{ii_m}), new_gr, conf.det_index, size_refl_list(1));
+            end
+        end
+        grs(num_grains+1:num_grains+num_created_grains) = [new_grs{:}];
+
+        estim_orient_bbox_rod = [estim_orient_bbox_rod; zeros(num_created_grains, 6)];
+        for ii_m = 1:num_grains_new
+            if (new_grs_list_indices(ii_m) > num_grains)
+                estim_orient_bbox_rod(new_grs_list_indices(ii_m), :) = [ ...
+                    min(estim_orient_bbox_rod(merge_lists{ii_m}, 1:3), [], 1), ...
+                    max(estim_orient_bbox_rod(merge_lists{ii_m}, 4:6), [], 1) ...
+                    ];
+            end
+        end
+        bbox_size_rod = estim_orient_bbox_rod(:, 4:6) - estim_orient_bbox_rod(:, 1:3);
+        fprintf('New orientation-space regions:\n')
+        for ii_g = 1:num_grains+num_created_grains
+            bbox_size_deg = 2 * atand(bbox_size_rod(ii_g, :));
+            fprintf(' - Estimated extent: [%g, %g, %g] -> [%g, %g, %g]\n', estim_orient_bbox_rod(ii_g, :));
+            fprintf('          BBox size: %3.3f, %3.3f, %3.3f (deg)\n', bbox_size_deg);
+        end
+    end
+
+    % Computing the new grain structures
+    for ii_g = 1:num_grains+num_created_grains
+        refor = struct( ...
+            'id', grs(ii_g).id, 'phaseid', grs(ii_g).phaseid, ...
+            'center', gr_center_mm, 'R_vector', grs(ii_g).R_vector );
+        refor = gtCalculateGrain(refor, p, 'ref_omind', refgr_omind);
+
+        refor.bb_ors = gt6DCalculateFullSpaceCorners(estim_space_bbox_mm, ...
+            bbox_size_mm, estim_orient_bbox_rod(ii_g, :), ...
+            bbox_size_rod(ii_g, :), refgr, p, conf.det_index);
+
+        samp_ors{ii_g} = refor;
+    end
+
+    samp_ors = [samp_ors{:}];
+
+    matches_ref_to_twin = cell(num_grains_new-1, 1);
+    matches_twin_to_ref = cell(num_grains_new-1, 1);
+
+    for ii_g = 1:num_grains_new-1
+        % refl_matches links the positions (i.e the hklsp type) of shared reflections
+        % i.e. refl_matches= [2, 0; 0, 1]  indicates that the reflection in
+        % twin(1).allblobs(2) shares the spot with parent.allblobs(1) and
+        % twin(2).allblobs(1) shares the spot with parent.allblobs(1)
+        mis_angle_diff = zeros(1, num_grains_new-ii_g);
+        for ii_g_2 = (ii_g+1):num_grains_new
+            twin_info = gtCrystTwinTest(...
+                samp_ors(new_grs_list_indices(ii_g)).R_vector, ...
+                samp_ors(new_grs_list_indices(ii_g_2)).R_vector, phase_id);
+            if (twin_info.sigmaAnd > 0)
+                mis_angle_diff(ii_g_2-ii_g) = min(abs(twin_info.mis_angle - twin_info.theo_mis_angle));
+            end
+        end
+        angular_tolerance = max(conf.angular_tol, mis_angle_diff * 1.05);
+        if (ii_g == 1)
+            fprintf(' - Determining matching master reflections with other regions..')
+        else
+            angular_tolerance = 2 * angular_tolerance;
+            fprintf(' - Determining matching twin (%d) reflections with other regions..', ii_g)
+        end
+        [matches_ref_to_twin{ii_g}, matches_twin_to_ref{ii_g}] = gt6DGetMatchingReflections(...
+            samp_ors(new_grs_list_indices(ii_g)), ...
+            samp_ors(new_grs_list_indices((ii_g+1):end)), angular_tolerance, conf.det_index);
+        fprintf('\b\b: Done.\n')
+
+        if (conf.verbose > 1)
+            produce_matching_reflections_table(samp_ors(new_grs_list_indices(ii_g:end)), conf, angular_tolerance, matches_ref_to_twin{ii_g})
+        end
+    end
+
+    extended_projs = find_matches_with_refor(...
+        grs(new_grs_list_indices), matches_ref_to_twin, matches_twin_to_ref, ...
+        size_refl_list(1), num_grains_new, conf);
+
+    % updating grs
+    for ii_m = num_grains_new:-1:1
+        for ii_g = unique([merge_lists{ii_m}, new_grs_list_indices(ii_m)])
+            gr_proj = grs(ii_g).proj(conf.det_index);
+            gr_proj.included = extended_projs(ii_m).included;
+            gr_proj.selected = extended_projs(ii_m).selected;
+            gr_proj.bl = gtFwdSimBlobDefinition('blob', numel(gr_proj.included));
+            gr_proj.bl(extended_projs(ii_m).included_old_inc) = grs(ii_g).proj(conf.det_index).bl;
+            grs(ii_g).proj(conf.det_index) = gr_proj;
+        end
+    end
+
+    for ii_g = 1:num_grains_new
+        local_ondet = extended_projs(ii_g).ondet;
+        local_included = extended_projs(ii_g).included;
+        refor_ns = refor.allblobs(conf.det_index).eta(local_ondet(local_included));
+
+        % We avoid the vertical spots for convenience
+        inconvenient_etas{ii_g} = acosd(abs(cosd(refor_ns))) < conf.min_eta;
+    end
+
+    fprintf('Determining UVW bounding boxes..')
+    c = tic();
+
+    if any(conf.use_raw_images)
+        uvw_tab = cell(num_grains+num_created_grains, 1);
+        img_bboxes = cell(num_grains+num_created_grains, 1);
+        img_sizes = cell(num_grains+num_created_grains, 1);
+    end
+
+    for ii_m = 1:num_grains_new
+        if conf.use_raw_images(ii_m)
+            local_ondet = extended_projs(ii_m).ondet;
+            local_included = extended_projs(ii_m).included;
+            local_inc_refl = local_ondet(local_included);
+
+            for ii_g = unique([merge_lists{ii_m}, new_grs_list_indices(ii_m)])
+                uvw_tab{ii_g} = zeros(numel(local_inc_refl), numel(samp_ors(ii_g).bb_ors), 3);
+                for ii_o = 1:numel(samp_ors(ii_g).bb_ors)
+                    uvw_tab{ii_g}(:, ii_o, :) ...
+                        = samp_ors(ii_g).bb_ors(ii_o).allblobs(conf.det_index).detector.uvw(local_inc_refl, :);
+                end
+
+                % Let's treat those blobs at the w edge 360->0
+                % (from the sampled orientations perspective)
+                or_ws = samp_ors(ii_g).allblobs(conf.det_index).omega(local_inc_refl) / gtAcqGetOmegaStep(p, conf.det_index);
+                uvw_tab{ii_g}(:, :, 3) = gtGrainAnglesTabularFix360deg(uvw_tab{ii_g}(:, :, 3), or_ws, p);
+
+                img_bboxes{ii_g} = [ ...
+                    reshape(floor(min(uvw_tab{ii_g}, [], 2)), [], 3), ...
+                    reshape(ceil(max(uvw_tab{ii_g}, [], 2)), [], 3) ];
+                img_sizes{ii_g} = img_bboxes{ii_g}(:, 4:6) - img_bboxes{ii_g}(:, 1:3) + 1;
+
+                if conf.use_raw_images(ii_m) == 2
+                    tmp_bbsizes = img_sizes{ii_g};
+                else
+                    tmp_bbsizes = cat(1, img_sizes{ii_g}, grs(ii_g).proj(conf.det_index).bl.bbsize);
+                end
+                max_img_sizes(ii_g, :) = max(tmp_bbsizes(:, 1:2), [], 1);
+            end
+
+            if (conf.verbose || true)
+                or_ns = samp_ors(new_grs_list_indices(ii_m)).allblobs(conf.det_index).eta(local_inc_refl);
+                
+                tbl_out_format = ['%02d)du %8d, dv %8d, dw %8d, eta: %7.3f, omega: %7.3f ' ...
+                    '<- used: %d, u: [%4d %4d], v: [%4d %4d], w: [%4d %4d], sel: %d'];
+                tbl_output = [(1:numel(or_ns))', ...
+                    img_sizes{new_grs_list_indices(ii_m)}, or_ns, or_ws * gtAcqGetOmegaStep(p, conf.det_index), ...
+                    ~inconvenient_etas{ii_m}, ...
+                    img_bboxes{new_grs_list_indices(ii_m)}(:, [1 4 2 5 3 6]), ...
+                    extended_projs(ii_m).selected ];
+                
+                fprintf('\nImages for orientation %d:\n', ii_m)
+                if (ii_m == 1)
+                    fprintf([tbl_out_format '\n'], ...
+                        tbl_output');
+                else
+                    pos_of_shared_blobs = zeros(size(extended_projs(ii_m).shared_refl_prevgrs_inc, 1), 2);
+                    for ii_prevg = ii_m - 1:-1:1 % this way or loop for blobs?
+                        pos_of_shared_blobs(extended_projs(ii_m).shared_refl_prevgrs_inc(:, ii_prevg), 1) = extended_projs(ii_m).shared_refl_pos_in_prevgrs_inc{ii_prevg};
+                        pos_of_shared_blobs(extended_projs(ii_m).shared_refl_prevgrs_inc(:, ii_prevg), 2) = ii_prevg;
+                    end
+                    fprintf([tbl_out_format ', shared: %d in grain %d\n'], ...
+                        [tbl_output(),  pos_of_shared_blobs]');
+                end
+            end
+        else
+            for ii_g = unique([merge_lists{ii_m}, new_grs_list_indices(ii_m)])
+                tmp_bbsizes = cat(1, grs(ii_g).proj(conf.det_index).bl.bbsize);
+                max_img_sizes(ii_g, :) = max(tmp_bbsizes(:, 1:2), [], 1);
+            end
+        end
+    end
+
+    [stackUSize, stackVSize] = get_stack_UV_size(max_img_sizes, p, conf);
+
+    fprintf('\b\b: Done in %g seconds.\n', toc(c))
+
+    for ii_m = 1:num_grains_new
+        fprintf('Processing orientation-space region: %d/%d\n', ii_m, num_grains_new)
+
+        num_blobs = numel(extended_projs(ii_m).included);
+
+        c = tic();
+        switch conf.use_raw_images(ii_m)
+            case 2
+                fprintf(' - Loading raw images: ')
+                blobs = gtFwdSimBlobDefinition('blob', num_blobs);
+                for ii_b = 1:num_blobs
+                    num_chars = fprintf('%03d/%03d', ii_b, num_blobs);
+                    tmp_blob = blobs(ii_b * ones(1, numel(merge_lists{ii_m})));
+                    for ii_subg = 1:numel(tmp_blob)
+                        tmp_blob(ii_subg) = load_blob( ...
+                            img_bboxes{merge_lists{ii_m}(ii_subg)}(ii_b, :), img_sizes{merge_lists{ii_m}(ii_subg)}(ii_b, :), ...
+                            stackUSize, stackVSize, p, conf);
+                    end
+                    tmp_blob = gtGrainMergeProjBl(tmp_blob);
+                    blobs(ii_b) = tmp_blob;
+
+                    fprintf(repmat('\b', [1, num_chars]));
+                end
+            case 1
+                fprintf(' - Loading raw images for missing blobs: ')
+                blobs = grs(new_grs_list_indices(ii_m)).proj(conf.det_index).bl;
+                for ii_b = 1:num_blobs
+                    num_chars = fprintf('%03d/%03d', ii_b, num_blobs);
+                    if isempty(blobs(ii_b).intm)
+                        for ii_subg = 1:numel(merge_lists{ii_m})
+                            tmp_blob = grs(merge_lists{ii_m}(ii_subg)).proj(conf.det_index).bl(ii_b);
+                            if isempty(tmp_blob.intm)
+                                tmp_blob = load_blob( ...
+                                    img_bboxes{merge_lists{ii_m}(ii_subg)}(ii_b, :), img_sizes{merge_lists{ii_m}(ii_subg)}(ii_b, :), ...
+                                    stackUSize, stackVSize, p, conf);
+                            end
+                            blobs(ii_b) = gtGrainMergeProjBl([blobs(ii_b), tmp_blob]);
+                        end
+                    end
+                    fprintf(repmat('\b', [1, num_chars]));
+                end
+        end
+        grs(new_grs_list_indices(ii_m)).proj(conf.det_index).bl = blobs;
+
+        fprintf('\b\b: Done in %g seconds.\n - Producing proj data-structure..', toc(c))
+
+        proj = gtFwdSimProjDefinition();
+
+        proj.centerpix = gr_center_pix;
+        vol_size = bbox_size_pix;
+%         vol_size = bbox_size_pix + mean(bbox_size_pix) * 0.3;
+        proj.vol_size_x = vol_size(2);
+        proj.vol_size_y = vol_size(1);
+        proj.vol_size_z = vol_size(3);
+
+        proj.ondet = extended_projs(ii_m).ondet;
+        proj.included = extended_projs(ii_m).included;
+        proj.selected = extended_projs(ii_m).selected;
+
+        if (ii_m > 1)
+            shared_inc = extended_projs(ii_m).shared_refl_prevgrs_inc;
+            shared_inc = any(shared_inc, 2);
+            non_shared_blobs = blobs(~shared_inc);
+            shared_inc = find(shared_inc);
+            proj.global_pos = extended_projs(ii_m).refl_pos_inc_global(proj.ondet(proj.included));
+            for ii_b = 1:numel(shared_inc)
+                all_blobs_list(proj.global_pos(shared_inc(ii_b))) = gtGrainMergeProjBl(...
+                    [all_blobs_list(proj.global_pos(shared_inc(ii_b))), blobs(shared_inc(ii_b))]);
+            end
+            all_blobs_list = cat(1, all_blobs_list(:), non_shared_blobs(:));
+        else
+            proj.global_pos = (1:numel(blobs));
+            all_blobs_list = blobs;
+        end
+
+        samp_ors(new_grs_list_indices(ii_m)).proj(conf.det_index) = proj;
+
+        fprintf('\b\b: Done in %g seconds.\n', toc(c))
+    end
+    samp_ors = samp_ors(new_grs_list_indices);
+    fprintf('\n - Equalizing blob sizes: ')
+    c = tic;
+    tot_num_blobs = numel(all_blobs_list);
+    for ii_b = 1:tot_num_blobs
+        if (stackUSize < size(all_blobs_list(ii_b).intm, 1))
+            stackUSize = size(all_blobs_list(ii_b).intm, 1);
+        end
+        if (stackVSize < size(all_blobs_list(ii_b).intm, 2))
+            stackVSize = size(all_blobs_list(ii_b).intm, 2);
+        end
+    end
+    for ii_b = 1:tot_num_blobs
+        num_chars = fprintf('%02d/%02d', ii_b, tot_num_blobs);
+        all_blobs_list(ii_b) = equalize_blob_size(all_blobs_list(ii_b), stackUSize, stackVSize);
+        fprintf(repmat('\b', [1 num_chars]));
+        fprintf(repmat(' ', [1 num_chars]));
+        fprintf(repmat('\b', [1 num_chars]));
+    end
+    fprintf('\b\b: Done in %g seconds.\n', toc(c))
+    for ii_m = 1:num_grains_new
+        proj = samp_ors(ii_m).proj(conf.det_index);
+        proj.bl = all_blobs_list(proj.global_pos(:));
+        spots = arrayfun(@(x){sum(x.intm, 3)}, proj.bl);
+        spots = permute(cat(3, spots{:}), [1 3 2]);
+        proj.stack = spots;
+        samp_ors(ii_m).proj(conf.det_index) = proj;
+
+        if (conf.check_spots && ii_m > 1)
+            samp_ors(ii_m).proj.selected = gtGuiGrainMontage(samp_ors(ii_m), [], true, conf.det_index);
+        end
+    end
+
+    if (conf.verbose)
+        f = figure();
+        ax = axes('parent', f);
+        hold(ax, 'on');
+        for ii_g = 1:num_grains_new
+            gt6DPlotOrientationBBox(ax, cat(1, samp_ors(ii_g).bb_ors(:).R_vector));
+            scatter3(ax, samp_ors(ii_g).R_vector(1), samp_ors(ii_g).R_vector(2), samp_ors(ii_g).R_vector(3), 30);
+        end
+        hold(ax, 'off');
+
+        drawnow();
+    end
+
+    cl = struct(...
+        'samp_ors', samp_ors, ...
+        'blobs', {all_blobs_list}, ...
+        'conf', conf);
+
+    % % % deselect and select the spots % % %
+    c = tic;
+    fprintf('Select and deselect the spots..')
+    for ii_g = 1:num_grains_new
+        tmp_ind_desel = any(bsxfun(@eq, samp_ors(ii_g).id(:), spots_shared_by_externel_gr_to_desel.grs_list(:)'), 1);
+        tmp_ind_desel = any(spots_shared_by_externel_gr_to_desel.bool_deselect(:, tmp_ind_desel), 2);
+        tmp_ind_desel = tmp_ind_desel(samp_ors(ii_g).proj(conf.det_index).ondet(samp_ors(ii_g).proj(conf.det_index).included));
+        tmp_ind_desel = samp_ors(ii_g).proj(conf.det_index).global_pos(tmp_ind_desel);
+        spots_shared_by_externel_gr_to_desel.global_pos = unique([spots_shared_by_externel_gr_to_desel.global_pos(:); tmp_ind_desel]);
+    end
+    sel_desel_global_pos = {spots_shared_by_externel_gr_to_desel.global_pos(:), conf.sel_global_pos(:), conf.desel_global_pos(:)};
+    cl = gtTwinClusterFwdProjUpdateSelected(cl, conf.det_index, sel_desel_global_pos{2}, [sel_desel_global_pos{1}; sel_desel_global_pos{3}], 'flag_selected_all', false, 'deselect_eta_tol', conf.min_eta);
+    fprintf('\b\b: Done in %g seconds.\n', toc(c))
+
+    if (conf.save)
+        str_ids = sprintf('_%04d', sort(grs_list));
+        grain_filename = fullfile(p.acq(conf.det_index).dir, '4_grains', ...
+            sprintf('phase_%02d', phase_id), ...
+            sprintf('grain_cluster%s.mat', str_ids));
+        fprintf('Saving the cluster file "%s"..', grain_filename)
+        save(grain_filename, '-struct', 'cl', '-v7.3');
+        fprintf('\b\b: Done.\n')
+
+%         fprintf('Saving to sample.mat..')
+%         sample = GtSample.loadFromFile();
+%         cl_info = GtPhase.makeCluster(grs_list, ...
+%             cat(1, samp_ors(:).R_vector), ...
+%             estim_space_bbox_pix, estim_orient_bbox_rod, 2);
+%         sample.phases{phase_id}.setClusterInfo(grs_list, cl_info);
+%         sample.saveToFile();
+%         fprintf('\b\b: Done.\n')
+    end
+end
+
+function grs = sfIncludeAllAndSelectAllAndUniformBlStruct(grs, conf)
+    blob_struct = gtFwdSimBlobDefinition('blob', 1);
+    for ii_g = numel(grs):-1:1
+        if (conf.use_raw_images(ii_g) && conf.include_all)
+            ond_ind = grs(ii_g).proj(conf.det_index).ondet;
+            inc_ind = grs(ii_g).proj(conf.det_index).included;
+            sel_bool = grs(ii_g).proj(conf.det_index).selected;
+            gr_proj_bl = gtFwdSimBlobDefinition('blob', numel(ond_ind));
+            for ii_b = 1:numel(inc_ind)
+                gr_proj_bl(inc_ind(ii_b)) = gtAddMatFile(blob_struct, grs(ii_g).proj(conf.det_index).bl(ii_b), true, false, false, false, false);
+                gr_proj_bl(inc_ind(ii_b)).nvar = [];
+            end
+            gr_selected = false(size(ond_ind));
+            gr_selected(inc_ind(sel_bool)) = true;
+            grs(ii_g).proj(conf.det_index).selected = gr_selected;
+            grs(ii_g).proj(conf.det_index).included = 1:numel(ond_ind);
+            grs(ii_g).proj(conf.det_index).bl = gr_proj_bl;
+        else
+            gr_proj_bl = gtFwdSimBlobDefinition('blob', numel(grs(ii_g).proj(conf.det_index).included));
+            for ii_b = 1:numel(gr_proj_bl)
+                gr_proj_bl(ii_b) = gtAddMatFile(blob_struct, grs(ii_g).proj(conf.det_index).bl(ii_b), true, false, false, false, false);
+                gr_proj_bl(ii_b).nvar = [];
+            end
+            grs(ii_g).proj(conf.det_index).bl = gr_proj_bl;
+        end
+    end
+    if conf.select_all
+        for ii_g = numel(grs):-1:1
+            grs(ii_g).proj(conf.det_index).selected = true(size(grs(ii_g).proj(conf.det_index).included));
+        end
+    end
+end
+
+function spots_shared_by_externel_gr_to_desel = sfDetermineTheBlobsSharedByExternalGrainsToDeselect(dir, phase_id, grs_list, grs, height_refl_list, num_grains, det_ind)
+    % % % deselect the spots that are shared by the grains not belonging to
+    % the cluster
+    % Remove the other phases
+    spot2grainfilename = fullfile(dir,'4_grains','spot2grain.mat');
+    spots_shared_by_externel_gr_to_desel = struct('grs_list', grs_list, ...
+        'bool_deselect', false(height_refl_list, num_grains), ...
+        'global_pos', []);
+    if exist(spot2grainfilename, 'file')
+        c = tic;
+        fprintf('Find the spots shared by the grain not belonging to the cluster..')
+        spot2grain = load(spot2grainfilename);
+        for ii_s = 1:numel(spot2grain.spot2phase)
+            tmp_ind_spot2grain = (spot2grain.spot2phase{ii_s} ~= phase_id);
+            if ~isempty(tmp_ind_spot2grain)
+                spot2grain.spot2phase{ii_s}(tmp_ind_spot2grain) = [];
+                spot2grain.spot2grain{ii_s}(tmp_ind_spot2grain) = [];
+            end
+        end
+        spot2grain = spot2grain.spot2grain;
+        % find the spots to deselect
+        for ii_g = num_grains:-1:1
+            inc_ind = grs(ii_g).proj(det_ind).ondet(grs(ii_g).proj(det_ind).included);
+            spot2grain_i = spot2grain(grs(ii_g).fwd_sim(det_ind).difspotID(:));
+            for ii_spot = 1:numel(spot2grain_i)
+                check_existing_other_grain = any(~any(bsxfun(@eq, spot2grain_i{ii_spot}(:), grs_list(:)'), 2));
+                if check_existing_other_grain
+                    spots_shared_by_externel_gr_to_desel.bool_deselect(inc_ind(ii_spot), ii_g) = true;
+                end
+            end
+        end
+        fprintf('\b\b: Done in %g seconds. %d spots found.\n', toc(c), sum(spots_shared_by_externel_gr_to_desel.bool_deselect(:)))
+    end
+end
+
+function grs = sfUniformProj(grs, refgr, det_ind, totnum)
+    for ii_g = numel(grs):-1:1
+        proj = grs(ii_g).proj(det_ind);
+        proj.bl = gtFwdSimBlobDefinition('blob', numel(refgr.proj(det_ind).included));
+        bool_inc = false(totnum, 1);
+        bool_inc(proj.ondet(proj.included)) = true;
+        bool_inc_ondet = false(totnum, 1);
+        bool_inc_ondet(refgr.proj(det_ind).ondet) = true;
+        bool_inc_ondet = bool_inc_ondet & bool_inc;
+        bool_included_old = bool_inc(refgr.proj(det_ind).ondet(refgr.proj(det_ind).included));
+        proj.bl(bool_included_old) = grs(ii_g).proj(det_ind).bl(bool_inc_ondet(proj.ondet(proj.included)));
+        proj.ondet = refgr.proj(det_ind).ondet;
+        proj.included = refgr.proj(det_ind).included;
+        proj.selected = refgr.proj(det_ind).selected;
+        grs(ii_g).proj(det_ind) = proj;
+    end
+end
+
+function [u_size, v_size] = get_stack_UV_size(img_sizes, p, conf)
+
+    max_img_sizes = [max(img_sizes(:, 1)), max(img_sizes(:, 2))];
+    if any(conf.use_raw_images)
+        % Because in this case we saturate the volume field of view from
+        % the spots already, and we only make up for what will probably be
+        % used as oversize later on for the volume
+        stack_imgs_oversize = p.rec.grains.options.rspace_oversize;
+    else
+        stack_imgs_oversize = min(p.fsim.oversize, conf.stack_oversize);
+    end
+    u_size = round(max_img_sizes(1) * stack_imgs_oversize);
+    v_size = round(max_img_sizes(2) * stack_imgs_oversize);
+
+    fprintf('\n');
+    fprintf('               Maximum images size: [%3d, %3d]\n', max_img_sizes);
+    fprintf('Stack images size (oversize: %1.2f): [%3d, %3d]\n', stack_imgs_oversize, u_size, v_size);
+    fprintf('\n');
+end
+
+function blob = equalize_blob_size(blob, stackUSize, stackVSize)
+    new_bbsize = [stackUSize, stackVSize, blob.bbsize(3)];
+    shift = floor((new_bbsize - blob.bbsize) / 2);
+
+    new_bbuim = blob.bbuim - shift(1);
+    new_bbuim(2) = new_bbuim(1) + new_bbsize(1) - 1;
+
+    new_bbvim = blob.bbvim - shift(2);
+    new_bbvim(2) = new_bbvim(1) + new_bbsize(2) - 1;
+
+    blob.bbuim = new_bbuim;
+    blob.bbvim = new_bbvim;
+    blob.bbsize = new_bbsize;
+
+    blob.intm = gtPlaceSubVolume(zeros(new_bbsize, 'like', blob.intm), blob.intm, shift);
+    blob.mask = logical(gtPlaceSubVolume(zeros(new_bbsize, 'uint8'), blob.mask, shift));
+end
+
+function produce_matching_reflections_table(grs, conf, tols, refl_matches)
+
+    for ii = 1 : size(refl_matches, 2)
+        vis_sel = find(refl_matches(:, ii));
+        w_diffs = abs(mod(grs(1).allblobs(conf.det_index).omega(vis_sel) - grs(ii+1).allblobs(conf.det_index).omega(refl_matches(vis_sel, ii)) + 180, 360) - 180);
+        n_diffs = abs(mod(grs(1).allblobs(conf.det_index).eta(vis_sel) - grs(ii+1).allblobs(conf.det_index).eta(refl_matches(vis_sel, ii)) + 180, 360) - 180);
+        % pre-allocate
+        hklsp = zeros([size(grs(1).allblobs(conf.det_index).hklsp, 1), 3, numel(grs)]);
+
+        if (size(grs(1).allblobs(conf.det_index).hklsp, 2) == 4)
+            for jj = 1 : numel(grs)
+                hklsp(:,:,jj) = grs(jj).allblobs(conf.det_index).hklsp(:, [1 2 4]);
+            end
+        else
+            for jj = 1 : numel(grs)
+                hklsp(:,:,jj) = grs(jj).allblobs(conf.det_index).hklsp(:, :);
+            end
+        end
+
+        fprintf('Reflections shared by reference grain and twin number %d :\n', ii)
+        fprintf(['%02d) w1 %6.2f, w2 %6.2f, n1 %6.2f, n2 %6.2f, w_ind %d, hkl [%2d %2d %2d] ' ...
+            '<- matches w_ind %2d, [%2d %2d %2d], diff: %5.2f, %5.2f (!! %d, L %g) [bad n %d]\n'], ...
+            [(1:numel(vis_sel))', ...
+            grs(1).allblobs(conf.det_index).omega(vis_sel), grs(ii+1).allblobs(conf.det_index).omega(refl_matches(vis_sel, ii)), ...
+            grs(1).allblobs(conf.det_index).eta(vis_sel), grs(ii+1).allblobs(conf.det_index).eta(refl_matches(vis_sel, ii)), ...
+            grs(1).allblobs(conf.det_index).omind(vis_sel), hklsp(vis_sel, :, 1), refl_matches(vis_sel, ii), ...
+            hklsp(refl_matches(vis_sel, ii), :, ii+1), ...
+            w_diffs, n_diffs, ...
+            n_diffs < tols(ii) & w_diffs < tols(ii) * grs(1).allblobs(conf.det_index).lorentzfac(vis_sel), ...
+            grs(1).allblobs(conf.det_index).lorentzfac(vis_sel), ...
+            acosd(abs(cosd(grs(1).allblobs(conf.det_index).eta(vis_sel)))) < conf.min_eta, ...
+            ]');
+    end
+end
+
+function eproj = get6DExtendedProjDefinition(num_structs)
+    if (~exist('num_structs', 'var') || isempty(num_structs))
+        num_structs = 1;
+    end
+
+    repl_cell = cell(num_structs, 1);
+
+    eproj = struct(...
+        'ondet', repl_cell, ...
+        'included', repl_cell, ...
+        'selected', repl_cell, ...
+        'allreflbool_ondet', repl_cell, ...
+        'allreflbool_included', repl_cell, ...
+        'allreflbool_selected', repl_cell, ...
+        'included_old_to_keep', repl_cell, ...
+        'included_replaced', repl_cell, ...
+        'included_old_inc', repl_cell, ...
+        'shared_refl_prevgrs_inc', repl_cell, ...
+        'shared_refl_nextgrs_inc', repl_cell, ...
+        'shared_refl_pos_in_prevgrs_inc', repl_cell, ...
+        'shared_refl_pos_in_nextgrs_inc', repl_cell, ...
+        'refl_pos_inc_global', repl_cell);
+end
+
+function eproj = find_matches_with_refor(grs, matches_ref_to_twin, matches_twin_to_ref, length_allbls_refl_list, num_grains_new, conf)
+    eproj = get6DExtendedProjDefinition(num_grains_new);
+    for ii_g = 1:num_grains_new
+        gr_proj = grs(ii_g).proj(conf.det_index);
+        gr_ondet = gr_proj.ondet;
+        gr_included = gr_proj.included;
+        gr_selected = gr_proj.selected;
+        gr_allreflbool_ondet = false(length_allbls_refl_list, 1);
+        gr_allreflbool_included = false(length_allbls_refl_list, 1);
+        gr_allreflbool_selected = false(length_allbls_refl_list, 1);
+        gr_allreflbool_ondet(gr_ondet) = true;
+        gr_allreflbool_included(gr_ondet(gr_included)) = true;
+        gr_allreflbool_selected(gr_ondet(gr_included(gr_selected))) = true;
+        eproj(ii_g).ondet = gr_ondet;
+        eproj(ii_g).included = gr_included;
+        eproj(ii_g).selected = gr_selected;
+        eproj(ii_g).allreflbool_ondet = gr_allreflbool_ondet;
+        eproj(ii_g).allreflbool_included = gr_allreflbool_included;
+        eproj(ii_g).allreflbool_selected = gr_allreflbool_selected;
+    end
+    matches_othergrs_to_twin = zeros(length_allbls_refl_list, num_grains_new - 1);
+    matches_twin_to_othergrs = zeros(length_allbls_refl_list, num_grains_new - 1);
+
+    for ii_g = 1:num_grains_new
+        for ii_otherg = 1:ii_g - 1
+            matches_othergrs_to_twin(:, ii_otherg) = matches_ref_to_twin{ii_otherg}(:, ii_g - ii_otherg);
+            matches_twin_to_othergrs(:, ii_otherg) = matches_twin_to_ref{ii_otherg}(:, ii_g - ii_otherg);
+        end
+        if ii_g < num_grains_new
+            matches_othergrs_to_twin(:, ii_g:num_grains_new - 1) = matches_twin_to_ref{ii_g};
+            matches_twin_to_othergrs(:, ii_g:num_grains_new - 1) = matches_ref_to_twin{ii_g};
+        end
+
+        othergrs_eprojs = [eproj(1:ii_g - 1); eproj(ii_g + 1:num_grains_new)];
+        othergrs_ondet = cat(2, {othergrs_eprojs.ondet});
+        othergrs_included = cat(2, {othergrs_eprojs.included});
+
+        % bool_* vectors are all of length numel(gr.allblobs(conf.det_index).omega)
+        bool_othergrs_inc = cat(2, zeros(numel(eproj(ii_g).allreflbool_included), 0), othergrs_eprojs.allreflbool_included);
+        bool_othergrs_sel = cat(2, zeros(numel(eproj(ii_g).allreflbool_selected), 0), othergrs_eprojs.allreflbool_selected);
+        % list of twin reflections for which a shared spot has been predicted
+        bool_twin_shared = false(length_allbls_refl_list, num_grains_new - 1);
+        bool_twin_shared(matches_twin_to_othergrs > 0) = true;
+
+        twin_ondet = eproj(ii_g).ondet;
+        twin_inc = eproj(ii_g).included;
+        twin_sel = eproj(ii_g).selected;
+
+        % Conversion of original ondet, included, and shared to bool list
+        bool_twin_ondet = false(length_allbls_refl_list, 1);
+        bool_twin_ondet(twin_ondet) = true;
+        bool_twin_inc = false(length_allbls_refl_list, 1);
+        bool_twin_inc(twin_ondet(twin_inc)) = true;
+        bool_twin_sel = false(length_allbls_refl_list, 1);
+        bool_twin_sel(twin_ondet(twin_inc(twin_sel))) = true;
+
+        % Other grains' indices of shared reflections that are ondet for the twin
+        othergrs_shared_ondet = subsubfunc_bool_arrays_indices_mapping(...
+            matches_twin_to_othergrs, bsxfun(@and, bool_twin_shared, bool_twin_ondet), ...
+            [length_allbls_refl_list, num_grains_new - 1]);
+        % ...and which were included for the previous grains
+        othergrs_shared_ondet_inc = bool_othergrs_inc & othergrs_shared_ondet;
+        othergrs_shared_selected = othergrs_shared_ondet_inc & bool_othergrs_sel;
+
+        % translate this back into twin indices
+        twin_shared_othergrs_inc = subsubfunc_bool_arrays_indices_mapping(...
+            matches_othergrs_to_twin, othergrs_shared_ondet_inc, ...
+            [length_allbls_refl_list, num_grains_new - 1]);
+% % % 
+% % %         % These will tell which ones to keep from the fwd-simulation
+% % %         bool_twin_inc_keep = bsxfun(@and, bool_twin_inc, ~any(twin_shared_othergrs_inc(:, 1:ii_g - 1), 2));
+% % %         eproj(ii_g).included_old_to_keep = bool_twin_inc_keep(twin_ondet(twin_inc));
+        % These wll be the ones that will be included from the other grains
+        bool_twin_inc_old = bool_twin_inc;
+        bool_twin_inc_import = bsxfun(@and, bool_twin_ondet, twin_shared_othergrs_inc);
+
+        bool_twin_inc(any(bool_twin_inc_import, 2)) = true;
+        twin_inc = find(bool_twin_inc(twin_ondet));
+        % These are the ones that were included
+        eproj(ii_g).included_old_inc = bool_twin_inc_old(bool_twin_inc);
+
+        % If a shared & twin included spot is selected in the other grains, select
+        % it for the twin, as well
+        for ii_otherg = 1:num_grains_new - 1
+            othergr_sel_mapping_to_twin = matches_othergrs_to_twin(othergrs_shared_selected(:, ii_otherg), ii_otherg);
+            bool_twin_sel(othergr_sel_mapping_to_twin) = bool_twin_sel(othergr_sel_mapping_to_twin) | bool_twin_inc(othergr_sel_mapping_to_twin);
+        end
+        twin_sel = bool_twin_sel(twin_ondet(twin_inc));
+
+        % We now find the blobs in the old twin bl structure that are
+        % shared with the previous grains
+        eproj(ii_g).shared_refl_prevgrs_inc = twin_shared_othergrs_inc(twin_ondet(twin_inc), 1:ii_g - 1);
+        eproj(ii_g).shared_refl_nextgrs_inc = twin_shared_othergrs_inc(twin_ondet(twin_inc), ii_g:num_grains_new - 1);
+        % We have to find the included local and global IDs of the corresponding
+        % shared and non shared reflections
+        twin_refl_pos_inc_global = zeros(length_allbls_refl_list, 1);
+        twin_shared_refl_pos_in_prevgrs_inc = cell(ii_g - 1, 1);
+        twin_shared_refl_pos_in_nextgrs_inc = cell(num_grains_new - ii_g, 1);
+        if ii_g == 1
+            twin_refl_pos_inc_global(twin_ondet(twin_inc)) = (1:numel(twin_inc));
+            twin_index_of_last_non_shared_blob = numel(twin_inc);
+        else
+            twin_non_shared_refl_prevgrs_inc = ~any(eproj(ii_g).shared_refl_prevgrs_inc, 2);
+            twin_refl_pos_inc_global(twin_ondet(twin_inc(twin_non_shared_refl_prevgrs_inc))) = twin_index_of_last_non_shared_blob + (1:sum(twin_non_shared_refl_prevgrs_inc));
+            twin_index_of_last_non_shared_blob = twin_index_of_last_non_shared_blob + sum(twin_non_shared_refl_prevgrs_inc);
+            for ii_prevg = 1:ii_g - 1
+                twin_inc_import_mapping_to_othergr = matches_twin_to_othergrs(bool_twin_inc_import(:, ii_prevg), ii_prevg);
+                twin_refl_pos_inc_global(bool_twin_inc_import(:, ii_prevg)) = eproj(ii_prevg).refl_pos_inc_global(twin_inc_import_mapping_to_othergr);
+                refl_pos_in_othergr_inc = zeros(length_allbls_refl_list, 1);
+                refl_pos_in_othergr_inc(othergrs_ondet{ii_prevg}(othergrs_included{ii_prevg})) = (1:numel(othergrs_included{ii_prevg}));
+                twin_shared_refl_pos_in_prevgrs_inc{ii_prevg} = refl_pos_in_othergr_inc(twin_inc_import_mapping_to_othergr);
+            end
+        end
+        for ii_nextg = 1:num_grains_new - ii_g
+            ii_nextg_plus_ii_g_minus_one = ii_nextg + ii_g - 1;
+            twin_inc_import_mapping_to_othergr = matches_twin_to_othergrs(bool_twin_inc_import(:, ii_nextg_plus_ii_g_minus_one), ii_nextg_plus_ii_g_minus_one);
+            refl_pos_in_othergr_inc = zeros(length_allbls_refl_list, 1);
+            refl_pos_in_othergr_inc(othergrs_ondet{ii_nextg_plus_ii_g_minus_one}(othergrs_included{ii_nextg_plus_ii_g_minus_one})) = (1:numel(othergrs_included{ii_nextg_plus_ii_g_minus_one}));
+            twin_shared_refl_pos_in_nextgrs_inc{ii_nextg} = refl_pos_in_othergr_inc(twin_inc_import_mapping_to_othergr);
+        end
+        eproj(ii_g).refl_pos_inc_global = twin_refl_pos_inc_global;
+        eproj(ii_g).shared_refl_pos_in_prevgrs_inc = twin_shared_refl_pos_in_prevgrs_inc;
+        eproj(ii_g).shared_refl_pos_in_nextgrs_inc = twin_shared_refl_pos_in_nextgrs_inc;
+
+        eproj(ii_g).included = twin_inc;
+        eproj(ii_g).selected = twin_sel;
+        eproj(ii_g).allreflbool_included = bool_twin_inc;
+        eproj(ii_g).allreflbool_selected = bool_twin_sel;
+    end
+end
+function out = subsubfunc_bool_arrays_indices_mapping(indices_mapping_matrix, input_bool_arrays, arrays_size)
+    out = false(arrays_size);
+    for ii_otherg = 1:arrays_size(2)
+        out(indices_mapping_matrix(input_bool_arrays(:, ii_otherg), ii_otherg), ii_otherg) = true;
+    end
+end
+
+% function [problematic_bls, t2t_shr_bls] = detect_twin_only_shared_reflections(matches_ref_to_twin, matches_twin_to_ref, ii_g)
+%     % Suboptimal but it allows at least to detect and deselect them
+% 
+%     % The tables correspond only to the twins (so they have one fewer entry
+%     % than the number of grains), and inside the tables again, we only have
+%     % indices from the next grain and up. This means that the tables shrink
+%     % for successive grains, and the indeces have to adust for that
+%     shared_with_parent = matches_twin_to_ref{1}(:, ii_g-1) > 0;
+% 
+%     % There will be a 0 column (which corresponds to the considered twin
+%     % itself)
+%     t2t_shr_bls = false(size(shared_with_parent, 1), ii_g-1);
+%     for ii_g_prev = 2:ii_g-1
+%         shared_with_prev_twin = matches_twin_to_ref{ii_g_prev}(:, ii_g-ii_g_prev);
+%         t2t_shr_bls(:, ii_g_prev) = shared_with_prev_twin;
+%     end
+% 
+%     if (ii_g <= numel(matches_ref_to_twin))
+%         shared_with_next_twins = matches_ref_to_twin{ii_g};
+%         t2t_shr_bls = [t2t_shr_bls, shared_with_next_twins];
+%     end
+% 
+%     problematic_bls = any(t2t_shr_bls, 2) & ~shared_with_parent;
+% end
+
+function blob = load_blob(img_bboxes, img_sizes, stackUSize, stackVSize, p, conf)
+    blob = gtFwdSimBlobDefinition('blob');
+
+    bb = [img_bboxes(1, 1:2), img_sizes(1, 1:2)];
+    blob_vol = gtGetRawRoi(img_bboxes(3), img_bboxes(6), p.acq, bb, conf.det_index);
+    blob_vol(blob_vol < 0) = 0;
+    blob_vol(isnan(blob_vol)) = 0;
+    blob_bb = [img_bboxes(1, 1:3), img_sizes(1, :)];
+
+    % Transposing to keep the same convention as spots
+    blob_vol = permute(blob_vol, [2 1 3]);
+
+    blob.mbbsize = blob_bb(4:6);
+    blob.mbbu = [blob_bb(1), blob_bb(1) + blob_bb(4) + 1];
+    blob.mbbv = [blob_bb(2), blob_bb(2) + blob_bb(5) + 1];
+    blob.mbbw = [blob_bb(3), blob_bb(3) + blob_bb(6) + 1];
+
+    blob_size_im = [stackUSize, stackVSize, blob_bb(6)+2];
+
+    shifts_blob = gtFwdSimGetStackShifts(stackUSize, stackVSize, blob_bb, false);
+    shifts_blob = [shifts_blob.u, shifts_blob.v, 1];
+
+    blob.intm = gtPlaceSubVolume( ...
+        zeros(blob_size_im, 'single'), single(blob_vol), shifts_blob);
+
+    blob.mask = gtPlaceSubVolume( ...
+        false(blob_size_im), true(size(blob_vol)), shifts_blob);
+
+    blob.bbsize = blob_size_im;
+
+    blob_bb_im = [blob_bb(1, 1:3) - shifts_blob, blob_size_im];
+
+    blob.bbuim = [blob_bb_im(1), blob_bb_im(1) + blob_bb_im(4) - 1];
+    blob.bbvim = [blob_bb_im(2), blob_bb_im(2) + blob_bb_im(5) - 1];
+    blob.bbwim = [blob_bb_im(3), blob_bb_im(3) + blob_bb_im(6) - 1];
+
+    blob_int = sum(blob.intm(blob.mask));
+    blob.intm = blob.intm / blob_int;
+
+    blob.intensity = blob_int;
+end
+
+function merge_lists = determine_merge_list(estim_orient_bbox_rod, num_grains, conf)
+    merge_lists = cell(num_grains, 1);
+
+    bbsizes = (estim_orient_bbox_rod(:, 4:6) - estim_orient_bbox_rod(:, 1:3)) / 2;
+    centers = (estim_orient_bbox_rod(:, 4:6) + estim_orient_bbox_rod(:, 1:3)) / 2;
+
+    % Find overlapping regions (excluding master)
+    for ii_g = 1:num_grains
+        gr_inds = ii_g+1:num_grains;
+
+        overlaps = false(num_grains-ii_g, 1);
+        for ii_g_2 = gr_inds
+            distances = abs(centers(ii_g, :) - centers(ii_g_2, :)) ...
+                - (bbsizes(ii_g, :) + bbsizes(ii_g_2, :));
+            overlaps(ii_g_2-ii_g) = all(2 * atand(distances) <= conf.merge_dist_deg);
+        end
+
+        if (any(overlaps))
+            fprintf('Orientation box: %d overlaps with orientation boxes:%s. They will be merged...\n', ...
+                ii_g, sprintf(' %d', gr_inds(overlaps)));
+            merge_lists{ii_g} = gr_inds(overlaps);
+        end
+    end
+
+    merge_table = false(num_grains, num_grains);
+    for ii_g = 1:num_grains
+        merge_table([ii_g, merge_lists{ii_g}], [ii_g, merge_lists{ii_g}]) = true;
+    end
+    to_keep = true(num_grains, 1);
+    for ii_g = 1:num_grains
+        if to_keep(ii_g)
+            flag_incomplete_merge = true;
+            while(flag_incomplete_merge)
+                sub_table = any(merge_table(:, [ii_g, merge_lists{ii_g}]), 2);
+                sub_table([ii_g, merge_lists{ii_g}]) = false;
+                if any(sub_table)
+                    merge_lists{ii_g} = [find(sub_table'), merge_lists{ii_g}];
+                    merge_table([ii_g, merge_lists{ii_g}], [ii_g, merge_lists{ii_g}]) = true;
+                else
+                    flag_incomplete_merge = false;
+                    to_keep(merge_lists{ii_g}) = false;
+                    merge_lists{ii_g} = [ii_g, sort(merge_lists{ii_g}, 'ascend')];
+                end
+            end
+        end
+    end
+    merge_lists = merge_lists(to_keep);
+end
diff --git a/zUtil_Twins/gtTwinClusterFwdProjUpdateSelected.m b/zUtil_Twins/gtTwinClusterFwdProjUpdateSelected.m
new file mode 100755
index 00000000..b94e0535
--- /dev/null
+++ b/zUtil_Twins/gtTwinClusterFwdProjUpdateSelected.m
@@ -0,0 +1,38 @@
+function cl = gtTwinClusterFwdProjUpdateSelected(cl, det_ind, global_pos_to_selected, global_pos_to_deselected, varargin)
+    if (nargin < 2 || isempty(det_ind))
+        det_ind = 1;
+    end
+    if (nargin < 3)
+        global_pos_to_selected = [];
+    end
+    if (nargin < 4)
+        global_pos_to_deselected = [];
+    end
+    conf = struct(...
+        'flag_selected_all', false, ...
+        'deselect_eta_tol', 0);
+    conf = parse_pv_pairs(conf, varargin);
+    if conf.flag_selected_all
+        for ii_g = 1:numel(cl.samp_ors)
+            cl.samp_ors(ii_g).proj(det_ind).selected = true(size(cl.samp_ors(ii_g).proj(det_ind).selected));
+        end
+    end
+    if conf.deselect_eta_tol
+        for ii_g = 1:numel(cl.samp_ors)
+            tmp_proj = cl.samp_ors(ii_g).proj(det_ind);
+            tmp_eta = cl.samp_ors(ii_g).allblobs.eta(tmp_proj.ondet(tmp_proj.included));
+            tmp_eta = 90 - abs(abs(tmp_eta - 180) - 90);
+            tmp_ind = (tmp_eta < conf.deselect_eta_tol);
+            tmp_ind = tmp_proj.global_pos(tmp_ind);
+            global_pos_to_deselected = [global_pos_to_deselected(:); tmp_ind(:)];
+        end
+    end
+    global_pos_to_selected = unique(global_pos_to_selected);
+    global_pos_to_deselected = unique(global_pos_to_deselected);
+    for ii_g = 1:numel(cl.samp_ors)
+        ind_bool = any(bsxfun(@eq, cl.samp_ors(ii_g).proj(det_ind).global_pos(:), global_pos_to_selected(:)'), 2);
+        cl.samp_ors(ii_g).proj(det_ind).selected(ind_bool) = true;
+        ind_bool = any(bsxfun(@eq, cl.samp_ors(ii_g).proj(det_ind).global_pos(:), global_pos_to_deselected(:)'), 2);
+        cl.samp_ors(ii_g).proj(det_ind).selected(ind_bool) = false;
+    end
+end
\ No newline at end of file
-- 
GitLab


From 73b5cb463e3655066ddf6f1c3f33a85bb6544c0d Mon Sep 17 00:00:00 2001
From: Zheheng Liu <zheheng.liu@esrf.fr>
Date: Thu, 30 Jan 2025 17:51:03 +0100
Subject: [PATCH 2/3] some changes for gtGrainMerge

Signed-off-by: Zheheng Liu <zheheng.liu@esrf.fr>
---
 4_grains/gtGrainMerge.m       | 18 ++++++++-----
 4_grains/gtGrainMergeProjBl.m | 49 ++++++++++++-----------------------
 2 files changed, 28 insertions(+), 39 deletions(-)

diff --git a/4_grains/gtGrainMerge.m b/4_grains/gtGrainMerge.m
index 9723d20c..5d1fc0be 100644
--- a/4_grains/gtGrainMerge.m
+++ b/4_grains/gtGrainMerge.m
@@ -79,15 +79,19 @@ function grn = gtGrainMerge(gr1, gr2, p, mode, flag_merge_bl)
                 gr2_bl_to_merge = gr2.proj(ii_d).bl(merge_blob_ind_on_gr2_bl_list);
                 if ~isempty(gr1_bl_to_merge) && ~isempty(gr2_bl_to_merge)
                     tmp_bl_merge = gtGrainMergeProjBl([gr1_bl_to_merge, gr2_bl_to_merge]);
-                elseif ~isempty(gr1_bl_to_merge)
-                    tmp_bl_merge = gr1_bl_to_merge;
-                elseif ~isempty(gr2_bl_to_merge)
-                    tmp_bl_merge = gr2_bl_to_merge;
+                elseif flag_merge_bl == 1
+                    if ~isempty(gr1_bl_to_merge)
+                        tmp_bl_merge = gr1_bl_to_merge;
+                    elseif ~isempty(gr2_bl_to_merge)
+                        tmp_bl_merge = gr2_bl_to_merge;
+                    else
+                        error('gtGrainMerge:missing_blob', ...
+                            'Both gr1.proj(%d).bl(%d) and gr2.proj(%d).bl(%d) are empty', ii_d, merge_blob_ind_on_gr1_bl_list, ii_d, merge_blob_ind_on_gr2_bl_list)
+                    end
                 else
-                    error('gtGrainMerge:missing_blob', ...
-                        'Both gr1.proj(%d).bl(%d) and gr2.proj(%d).bl(%d) are empty', ii_d, merge_blob_ind_on_gr1_bl_list, ii_d, merge_blob_ind_on_gr2_bl_list)
+                    tmp_bl_merge = grn.proj(ii_d).bl(ii_merge_bl);
                 end
-                grn.proj(ii_d).bl(ii_merge_bl) = tmp_bl_merge;
+                grn.proj(ii_d).bl(ii_merge_bl) = gtAddMatFile(grn.proj(ii_d).bl(ii_merge_bl), tmp_bl_merge, true, false, false, false, false);
             end
         end
 
diff --git a/4_grains/gtGrainMergeProjBl.m b/4_grains/gtGrainMergeProjBl.m
index 348f40b0..70de07eb 100644
--- a/4_grains/gtGrainMergeProjBl.m
+++ b/4_grains/gtGrainMergeProjBl.m
@@ -14,7 +14,11 @@ function bl_merge = gtGrainMergeProjBl(bl_array, difspotID_array)
         end
     end
     % Initialize the merged bl
-    bl_merge = bl_array(1);
+    if isempty(bl_array)
+        bl_merge = gtFwdSimBlobDefinition('blob');
+    else
+        bl_merge = bl_array(1);
+    end
 
     if numel(bl_array(:)) > 1
         % Merge bounds and sizes
@@ -39,38 +43,19 @@ function bl_merge = gtGrainMergeProjBl(bl_array, difspotID_array)
         if exist('difspotID_array','var') && numel(difspotID_array(:)) == numel(bl_array(:))
             [~, tmp_ind] = unique(difspotID_array(:));
             bl_array = bl_array(tmp_ind);
-
-            % Merge intensity
-            intensity_array = cat(1, bl_array(:).intensity);
-            tmp_intensity = sum(intensity_array);
-            intensity_array = intensity_array/tmp_intensity;
-
-            % Merge mask and intm
-            tmp_mask = false(bl_merge.bbsize);
-            tmp_intm = single(zeros(bl_merge.bbsize));
-            for i_bl = 1:numel(bl_array(:))
-                tmp_vol_ind = sfVolInd(bound(:, :, i_bl), bound_merge, bl_merge.bbsize);
-                tmp_mask(tmp_vol_ind) = tmp_mask(tmp_vol_ind) + bl_array(i_bl).mask(:);
-                tmp_intm(tmp_vol_ind) = tmp_intm(tmp_vol_ind) + intensity_array(i_bl) * bl_array(i_bl).intm(:);
-            end
-        else
-            % Merge mask, intm and intensity.
-            intensity_array = cat(1, bl_array(:).intensity);
-            tmp_mask = false(bl_merge.bbsize);
-            tmp_intm = single(zeros(bl_merge.bbsize));
-            for i_bl = 1:numel(bl_array(:))
-                tmp_vol_ind = sfVolInd(bound(:, :, i_bl), bound_merge, bl_merge.bbsize);
-                % Ignore the bl whose mask overlaps the ones of previous bls
-                if any(bl_array(i_bl).mask(:) & tmp_mask(tmp_vol_ind))
-                    intensity_array(i_bl) = 0;
-                else
-                    tmp_mask(tmp_vol_ind) = tmp_mask(tmp_vol_ind) + bl_array(i_bl).mask(:);
-                    tmp_intm(tmp_vol_ind) = tmp_intm(tmp_vol_ind) + intensity_array(i_bl) * bl_array(i_bl).intm(:);
-                end
-            end
-            tmp_intensity = sum(intensity_array(:));
-            tmp_intm = tmp_intm/tmp_intensity;
         end
+        % Merge mask, intm and intensity.
+        tmp_mask = false(bl_merge.bbsize);
+        tmp_intm = single(zeros(bl_merge.bbsize));
+        for i_bl = 1:numel(bl_array(:))
+            tmp_vol_ind = sfVolInd(bound(:, :, i_bl), bound_merge, bl_merge.bbsize);
+            % Remove the overlaps
+            tmp_not_overlapped_vol = (bl_array(i_bl).mask(:) & ~tmp_mask(tmp_vol_ind)) .* bl_array(i_bl).intm(:) .* bl_array(i_bl).intensity;
+            tmp_mask(tmp_vol_ind) = tmp_mask(tmp_vol_ind) | bl_array(i_bl).mask(:);
+            tmp_intm(tmp_vol_ind) = tmp_intm(tmp_vol_ind) + tmp_not_overlapped_vol;
+        end
+        tmp_intensity = sum(tmp_intm(:));
+        tmp_intm = tmp_intm/tmp_intensity;
 
         bl_merge.intensity = tmp_intensity;
         bl_merge.mask = tmp_mask;
-- 
GitLab


From eaced50124b7924b1777915c46da3e969b881b1b Mon Sep 17 00:00:00 2001
From: Zheheng Liu <zheheng.liu@esrf.fr>
Date: Thu, 30 Jan 2025 18:23:02 +0100
Subject: [PATCH 3/3] Add noise variances to cluster forward projection
 structure

Signed-off-by: Zheheng Liu <zheheng.liu@esrf.fr>
---
 zUtil_Twins/GtGrainClusterCalcBlobNoiseVars.m | 540 ++++++++++++++++++
 1 file changed, 540 insertions(+)
 create mode 100644 zUtil_Twins/GtGrainClusterCalcBlobNoiseVars.m

diff --git a/zUtil_Twins/GtGrainClusterCalcBlobNoiseVars.m b/zUtil_Twins/GtGrainClusterCalcBlobNoiseVars.m
new file mode 100644
index 00000000..9c7bb010
--- /dev/null
+++ b/zUtil_Twins/GtGrainClusterCalcBlobNoiseVars.m
@@ -0,0 +1,540 @@
+classdef GtGrainClusterCalcBlobNoiseVars < handle
+    properties
+        cluster;
+        parameters;
+        invalid_pixel_nvar = 3e38;
+        detector_margin = 1;
+        use_detector_mask = [];
+    end
+
+    properties (Access = protected)
+        bbuims = [];
+        bbvims = [];
+        bbwims = [];
+        data_type;
+        det_ind;
+        detector_mask;
+    end
+
+    methods (Access = public)
+        function [self, cl] = GtGrainClusterCalcBlobNoiseVars(cl_ids, phase_id, p, detinds, varargin)
+            % GtGrainClusterCalcBlobNoiseVars(cl, phase_id, p, detinds)
+            % The function reads the blobs as the estimate of
+            % Poisson noise variance. Next, upon the parameter
+            % detector_weights.nvar_images, the background images are read
+            % to get more accurate Poisson noise. An alternative method is
+            % to load raw images as noise variances. The parameters are
+            % located at parameters.rec.grains.options.detector_weights.
+            % The optional input 'detector_margin' will inactivate the
+            % pixels at the edges of detector and default number is 1; the
+            % default value of 'use_detector_mask' is true, which uses the
+            % detector mask saved in 'detector%d_mask.edf' (same convention
+            % as dark.edf).
+            % If we have got the object det_weights =
+            % GtGrainClusterCalcBlobNoiseVars(gr, phase_id, p, detinds_1),
+            % we can read the noise variance again by
+            % det_weights.loadNoiseVarianceForIndicatedDet(detinds_2)
+
+            conf = struct(...
+                'detector_margin', 1, ...
+                'use_detector_mask', true);
+            conf = parse_pv_pairs(conf, varargin);
+
+            self.detector_margin = conf.detector_margin;
+            self.use_detector_mask = conf.use_detector_mask;
+            if (~exist('phase_id', 'var') || isempty(phase_id))
+                phase_id = 1;
+            end
+            if (~exist('p', 'var') || isempty(p))
+                self.parameters = gtLoadParameters;
+            else
+                self.parameters = p;
+            end
+            if isnumeric(cl_ids)
+                cl_ids = sort(unique(cl_ids), 'ascend');
+                self.cluster = gtLoadCluster(phase_id, cl_ids);
+            else
+                self.cluster = cl_ids;
+                cl_ids = sort(unique(cat(2, cl_ids.samp_ors.id)), 'ascend');
+            end
+            self.set_unused_bl_to_empty();
+            if isstruct(self.cluster.blobs)
+                self.cluster.blobs = {self.cluster.blobs};
+            end
+            self.normalize_blobs();
+            if (~exist('detinds', 'var') || isempty(detinds))
+                detinds = 1:numel(self.cluster.blobs);
+            end
+            self.loadNoiseVarianceForIndicatedDet(detinds)
+            if nargout > 1
+                cl = self.cluster;
+            end
+        end
+
+        function gr = loadNoiseVarianceForIndicatedDet(self, detinds)
+            % this function is used to read the noise variance for
+            % indicated detector indices.
+            % For example, as we have got the object det_weights =
+            % GtGrainClusterCalcBlobNoiseVars(gr, phase_id, p, detinds_1),
+            % we can read the noise variance again by
+            % det_weights.loadNoiseVarianceForIndicatedDet(detinds_2)
+            for ii_d = 1:numel(detinds)
+                if ~isempty(self.cluster.blobs{detinds(ii_d)})
+                    self.det_ind = detinds(ii_d);
+                    self.get_bboxes_and_datatype();
+                    self.get_noise_var_into_grain();
+                end
+            end
+            if nargout
+                gr = self.grain;
+            end
+        end
+
+        function saveClusterToFile(self, phase_id)
+            cl_ids = sort(unique(cat(2, self.cluster.samp_ors.id)), 'ascend');
+            gtSaveCluster(phase_id, cl_ids, self.cluster)
+        end
+
+        function applyDetectorMask(self, detinds)
+            if (~exist('detinds', 'var')) || isempty(detinds)
+                detinds = 1;
+            end
+
+            % fill the missing use_detector_mask for the detectors.
+            nof_missing_flags = max(detinds) - numel(self.use_detector_mask);
+            if (nof_missing_flags > 0)
+                self.use_detector_mask = [self.use_detector_mask(:)', self.use_detector_mask(ones(1, nof_missing_flags))];
+            end
+
+            for ii_d = 1:numel(detinds)
+                if isempty(self.cluster.blobs{detinds(ii_d)})
+                    self.use_detector_mask(detinds(ii_d)) = false;
+                else
+                    if (self.use_detector_mask(detind))
+                        self.load_detector_mask(detinds(ii_d)); % load detector mask from detector%d_mask.edf
+                    end
+                    if (self.use_detector_mask(detind))
+                        self.apply_detector_mask(detinds(ii_d)); % apply the detector mask
+                    end
+                end
+            end
+        end
+
+        function inactivatePixelsOutsideDetector(self, detinds)
+            if (~exist('detinds', 'var')) || isempty(detinds)
+                detinds = 1;
+            end
+            for ii_d = 1:numel(detinds)
+                detbbmin = 1 + self.detector_margin;
+                detbbumax = self.parameters.acq(detinds(ii_d)).xdet - self.detector_margin;
+                detbbvmax = self.parameters.acq(detinds(ii_d)).ydet - self.detector_margin;
+                for ii_b = 1:numel(self.cluster.blobs{detinds(ii_d)})
+                    bbuim = self.cluster.blobs{detinds(ii_d)}(ii_b).bbuim;
+                    bbvim = self.cluster.blobs{detinds(ii_d)}(ii_b).bbvim;
+                    bbuim = bbuim(1):bbuim(2);
+                    bbvim = bbvim(1):bbvim(2);
+                    bbuim = (bbuim > detbbumax) & (bbuim < detbbmin);
+                    bbvim = (bbvim > detbbvmax) & (bbvim < detbbmin);
+                    if any(bbuim)
+                        self.cluster.blobs{detinds(ii_d)}(ii_b).nvar(bbuim, :, :) = self.invalid_pixel_nvar;
+                    end
+                    if any(bbvim)
+                        self.cluster.blobs{detinds(ii_d)}(ii_b).nvar(:, bbvim, :) = self.invalid_pixel_nvar;
+                    end
+                end
+            end
+        end
+
+        function deselectBlobCoveredByInvalidPixels(self, detinds)
+            nof_ors = numel(self.cluster.samp_ors);
+            if iscell(self.cluster.blobs)
+                if (~exist('detinds', 'var')) || isempty(detinds)
+                    detinds = 1;
+                end
+                for ii_d = 1:numel(detinds)
+                    for ii_ors = 1:nof_ors
+                        global_pos = self.cluster.samp_ors(ii_ors).proj(detinds(ii_d)).global_pos;
+                        for ii_b = 1:numel(global_pos)
+                            exist_valid_pixel = self.cluster.blobs{detinds(ii_d)}(global_pos(ii_b)).mask > 0.1;
+                            exist_valid_pixel = self.cluster.blobs{detinds(ii_d)}(global_pos(ii_b)).nvar(exist_valid_pixel);
+                            exist_valid_pixel = exist_valid_pixel < (self.invalid_pixel_nvar * 0.9);
+                            exist_valid_pixel = any(exist_valid_pixel(:));
+                            if ~exist_valid_pixel
+                                self.cluster.samp_ors(ii_ors).proj(detinds(ii_d)).selected(ii_b) = false;
+                            end
+                        end
+                    end
+                end
+            else
+                for ii_ors = 1:nof_ors
+                    global_pos = self.cluster.samp_ors(ii_ors).proj(1).global_pos;
+                    for ii_b = 1:numel(global_pos)
+                        exist_valid_pixel = self.cluster.blobs(global_pos(ii_b)).mask > 0.1;
+                        exist_valid_pixel = self.cluster.blobs(global_pos(ii_b)).nvar(exist_valid_pixel);
+                        exist_valid_pixel = exist_valid_pixel < (self.invalid_pixel_nvar * 0.9);
+                        exist_valid_pixel = any(exist_valid_pixel(:));
+                        if ~exist_valid_pixel
+                            self.cluster.samp_ors(ii_ors).proj(1).selected(ii_b) = false;
+                        end
+                    end
+                end
+            end
+        end
+
+        function showNoiseVariance2DStack(self, detind, lim_max)
+            if (~exist('detind', 'var')) || isempty(detind)
+                detind = 1;
+            end
+            if (~exist('lim_max', 'var')) || isempty(lim_max)
+                lim_max = 1e4;
+            end
+            stack = arrayfun(@(x) sum(x.nvar, 3), self.cluster.blobs{detind}, 'UniformOutput', false);
+            stack = cat(3, stack{:});
+            GtVolView(min(stack, lim_max));
+        end
+    end
+
+    methods (Access = protected)
+        function set_unused_bl_to_empty(self)
+            for ii_ors = 1:numel(self.cluster.samp_ors)
+                for ii_det = 1:numel(self.cluster.samp_ors(ii_ors).proj)
+                    for ii_b = 1:numel(self.cluster.samp_ors(ii_ors).proj(ii_det).bl)
+                        self.cluster.samp_ors(ii_ors).proj(ii_det).bl(ii_b).intm = [];
+                        self.cluster.samp_ors(ii_ors).proj(ii_det).bl(ii_b).mask = [];
+                    end
+                end
+            end
+        end
+
+        function normalize_blobs(self)
+            num_det = numel(self.cluster.blobs);
+            for ii_d = 1:num_det
+                nof_bl = numel(self.cluster.blobs{ii_d});
+                for ii_b = 1:nof_bl
+                    sum_intm = sum(abs(self.cluster.blobs{ii_d}(ii_b).intm(:)));
+                    if ((~isempty(sum_intm)) && (abs(sum_intm) > 1e-5) && (abs(sum_intm - 1) > 1e-5))
+                        self.cluster.blobs{ii_d}(ii_b).intm = self.cluster.blobs{ii_d}(ii_b).intm / sum_intm;
+                    end
+                end
+            end
+        end
+
+        function get_bboxes_and_datatype(self)
+            detind = self.det_ind;
+            self.data_type = class(self.cluster.blobs{detind}(1).intm);
+            self.bbuims = cat(1, self.cluster.blobs{detind}(:).bbuim);
+            self.bbvims = cat(1, self.cluster.blobs{detind}(:).bbvim);
+
+            % The bbwim is only used to read the median images, so TT
+            % does not need it as there is no median images for TT.
+            if isfield(self.cluster.blobs{detind}, 'bbwim')
+                self.bbwims = cat(1, self.cluster.blobs{detind}(:).bbwim);
+            end
+        end
+
+        function load_detector_mask(self, detind)
+            % load detector mask from detector_mask.edf under
+            % p.acq(detind).dir (currently only work for dct detector 1)
+            detmaskdir = sprintf('detector%d_mask.edf', detind);
+            detmaskdir = fullfile(self.parameters.acq(detind).dir, detmaskdir);
+            if exist(detmaskdir, 'file')
+                det_mask = edf_read(detmaskdir);
+                self.detector_mask{detind} = det_mask' > 0;
+            else
+                self.use_detector_mask(detind) = false;
+            end
+        end
+
+        function get_noise_var_into_grain(self)
+            % get parameters
+            nvar_param = self.get_parameters_of_nvar_generation();
+
+            % get the Poisson noise variance
+            if isempty(nvar_param.nvar_images)
+                nvar_param.nvar_images = 'blob';
+            end
+            fprintf('Loading %s images ...\n', image_type)
+            var_dark = [];
+            dark = [];
+            c = tic;
+            switch nvar_param.nvar_images
+                case 'median' % read background noise from median images. The total noise variance is blob + median image
+                    self.copy_bl_int_to_nvar()
+                    self.load_bg_from_median_imgs();
+                case 'raw' % directly use raw images as noise variance
+                    [var_dark, dark] = GtGrainCalcBlobNoiseVars.calcDarkImageVariance(self.parameters.acq(self.det_ind));
+                    self.load_nvar_from_raw_imgs(dark);
+                otherwise % only use blob as noise variance
+                    self.copy_bl_int_to_nvar()
+            end
+            fprintf('Done in %.2f s.\n', toc(c));
+
+            % get the noise variance of dark images
+            if (nvar_param.use_dark_image_variance)
+                c = tic;
+                fprintf('Calculating background noise variance from dark images ... ')
+                self.add_noise_of_dark_images(var_dark, dark) % Calculate the noise in dark images
+                fprintf('Done in %.2f s.\n', toc(c));
+            end
+        end
+
+        function nvar_param = get_parameters_of_nvar_generation(self)
+            nvar_param = struct(...
+                'nvar_images', [], ...
+                'use_dark_image_variance', false, ...
+                'save_cluster', false);
+            rec_opt = self.parameters.rec.grains.options;
+            if (isfield(rec_opt, 'detector_weights') && ...
+                    ~isempty(rec_opt.detector_weights))
+                weight_opt = rec_opt.detector_weights;
+                if (isfield(weight_opt, 'nvar_images') && ...
+                        ischar(weight_opt.nvar_images))
+                    nvar_param.nvar_images = lower(weight_opt.nvar_images);
+                end
+                if (isfield(weight_opt, 'use_dark_image_variance') && ...
+                        ~isempty(weight_opt.use_dark_image_variance))
+                    nvar_param.use_dark_image_variance = weight_opt.use_dark_image_variance;
+                end
+                if (isfield(weight_opt, 'save_cluster') && ...
+                        ~isempty(weight_opt.save_cluster))
+                    nvar_param.save_cluster = weight_opt.save_cluster;
+                end
+            end
+
+            % As for TT, we can only use the blob as the noise variance.
+            if (~strcmpi(self.parameters.acq(self.det_ind).type, '360degree'))
+                nvar_param.nvar_images = [];
+                nvar_param.use_dark_image_variance = false;
+            end
+        end
+
+        function copy_bl_int_to_nvar(self)
+            switch lower(self.parameters.acq(self.det_ind).type)
+                case {'180degree', '360degree'}
+                    blob_type = 'blob';
+                case 'topotomo'
+                    blob_type = 'blob_topo';
+            end
+            nof_bl = numel(self.cluster.blobs{self.det_ind});
+            if ~isfield(self.cluster.blobs{self.det_ind}(1), 'nvar')
+                tmpbl = gtFwdSimBlobDefinition(blob_type, nof_bl);
+                for ii_b = nof_bl:-1:1
+                    tmpbl(ii_b) = gtAddMatFile(tmpbl(ii_b), self.cluster.blobs{self.det_ind}(ii_b), true, false, false, false, false);
+                end
+                self.cluster.blobs{self.det_ind} = tmpbl;
+            end
+            for ii_b = nof_bl:-1:1
+                self.cluster.blobs{self.det_ind}(ii_b).nvar = abs(self.cluster.blobs{self.det_ind}(ii_b).intm) * self.cluster.blobs{self.det_ind}(ii_b).intensity;
+            end
+        end
+
+        function load_bg_from_median_imgs(self)
+            % According to gtPreprocessing, the median image is regarded as
+            % the background of next parameters.prep.fullint projection
+            % images.
+            acq = self.parameters.acq(self.det_ind);
+            outdir = fullfile(acq.dir, '1_preprocessing', 'full');
+            fullint = self.parameters.prep.fullint;
+            totproj = 2 * acq.nproj;
+            nof_med = ceil(totproj/fullint);
+            gauge = GtGauge(nof_med, 'loading background noise from median images ... ');
+            % loop for median images
+            for fullint_lb = 0:fullint:(fullint * (nof_med - 1))
+                fullint_ub = min(fullint_lb + fullint, totproj);
+                fname = fullfile(outdir, sprintf('med%04d.edf', fullint_lb));
+                gauge.incrementAndDisplay();
+                self.add_median_image_to_spots(fname, fullint_lb, fullint_ub, totproj, acq.xdet, acq.ydet);
+            end
+        end
+
+        function add_median_image_to_spots(self, fname, fullint_lb, fullint_ub, totproj, xdet, ydet)
+            % This function finds the blobs (partially) use this median
+            % image as background and adds this median image to (the part of)
+            % the noise variance of the blobs.
+
+            % truncate the blobs that are partially outside the range of
+            % the median image, and finds the blobs (partially) in the range
+            [bbwims_modified, bool_inint] = self.modify_bbwims_and_check_bl_exist_med(fullint_lb, fullint_ub, totproj);
+
+            if any(bool_inint)
+                % find the blob regions inside the detector.
+                bbuim_inint = self.bbuims(bool_inint, :);
+                bbuim_inint_ondet = GtGrainCalcBlobNoiseVars.calcOndetBbim(bbuim_inint, 1 + self.detector_margin, xdet - self.detector_margin);
+                subbbu = bsxfun(@minus, bbuim_inint_ondet, bbuim_inint(:, 1) - 1);
+                bbvim_inint = self.bbvims(bool_inint, :);
+                bbvim_inint_ondet = GtGrainCalcBlobNoiseVars.calcOndetBbim(bbvim_inint, 1 + self.detector_margin, ydet - self.detector_margin);
+                subbbv = bsxfun(@minus, bbvim_inint_ondet, bbvim_inint(:, 1) - 1);
+                inds_inint = find(bool_inint);
+
+                % The information helping to read the median image
+                info = edf_info(fname);
+
+                % loop for the blobs: read the median image and add it to the blob
+                for ii_inint = 1:numel(inds_inint)
+                    ind_b = inds_inint(ii_inint);
+
+                    % determine the available bounding box
+                    bbwim = bbwims_modified(ind_b, :);
+                    bbwlow = max(fullint_lb, bbwim(1)) - bbwim(1) + 1;
+                    bbwup = min(fullint_ub - 1, bbwim(2)) - bbwim(1) + 1;
+                    bb_ii = [bbuim_inint_ondet(ii_inint, :); bbvim_inint_ondet(ii_inint, :)];
+                    bb_ii(:, 2) = bb_ii(:, 2) - bb_ii(:, 1) + 1;
+
+                    % read median image in the bounding box
+                    medimg_crop = cast(edf_read(fname, bb_ii(:)', [], info), self.data_type)';
+                    medimg_crop(medimg_crop <= 0) = self.invalid_pixel_nvar;
+
+                    % add the median image to the blob noise variance
+                    medbg = medimg_crop(:, :, ones(1, bbwup - bbwlow + 1));
+                    subu = subbbu(ii_inint, :);
+                    subv = subbbv(ii_inint, :);
+                    spotaddbg = self.cluster.blobs{self.det_ind}(ind_b).nvar(subu(1):subu(2), subv(1):subv(2), bbwlow:bbwup) + medbg;
+                    self.cluster.blobs{self.det_ind}(ind_b).nvar(:, :, bbwlow:bbwup) = cast(self.invalid_pixel_nvar, self.data_type);
+                    self.cluster.blobs{self.det_ind}(ind_b).nvar(subu(1):subu(2), subv(1):subv(2), bbwlow:bbwup) = cast(spotaddbg, self.data_type);
+                end
+            end
+        end
+
+        function [bbwims_modified, bool_inint] = modify_bbwims_and_check_bl_exist_med(self, fullint_lb, fullint_ub, totproj)
+            % truncate the blobs that are partially outside the range of
+            % the median image, and finds the blobs (partially) in the range
+            bbwims_modified = self.bbwims;
+            bool_inint = any(bbwims_modified < (fullint_ub - totproj), 2) & any(bbwims_modified >= (fullint_lb - totproj), 2);
+            if any(bool_inint)
+                bbwims_modified(bool_inint, :) = bbwims_modified(bool_inint, :) - totproj;
+            end
+            bool_inint = any(bbwims_modified < (fullint_ub + totproj), 2) & any(bbwims_modified >= (fullint_lb + totproj), 2);
+            if any(bool_inint)
+                bbwims_modified(bool_inint, :) = bbwims_modified(bool_inint, :) + totproj;
+            end
+            bool_inint = any(bbwims_modified < fullint_ub, 2) & any(bbwims_modified >= fullint_lb, 2);
+        end
+
+        function load_nvar_from_raw_imgs(self, dark)
+            acq = self.parameters.acq(self.det_ind);
+            dir = fullfile(acq.dir, '0_rawdata', acq.name);
+            if (~exist('dark', 'var') || isempty(dark))
+                dark = edf_read(fullfile(dir, 'dark.edf'));
+            end
+            dark = cast(dark', self.data_type); % dark image needs substracting from raw images
+            totproj = 2 * acq.nproj;
+            gauge = GtGauge(totproj, 'loading raw images as noise ... ');
+            self.set_bl_nvar_to_inf(); % initialize the noise variance to a very large value
+            fname = fullfile(acq.dir, '0_rawdata', acq.name, sprintf('%s0000.edf', acq.name));
+            info = edf_info(fname); % The information helping to read the median image
+
+            % find the blob regions inside the detector.
+            bbuims_ondet = GtGrainCalcBlobNoiseVars.calcOndetBbim(self.bbuims, 1 + self.detector_margin, acq.xdet - self.detector_margin);
+            subbbu = bsxfun(@minus, bbuims_ondet, self.bbuims(:, 1) - 1);
+            bbvims_ondet = GtGrainCalcBlobNoiseVars.calcOndetBbim(self.bbvims, 1 + self.detector_margin, acq.ydet - self.detector_margin);
+            subbbv = bsxfun(@minus, bbvims_ondet, self.bbvims(:, 1) - 1);
+
+            % loop for raw images
+            for ii_proj = 0:(totproj-1)
+                gauge.incrementAndDisplay();
+                [bbwims_modified, bool_inproj] = self.modify_bbwims_and_check_bl_exist_raw(ii_proj, totproj);
+                if any(bool_inproj)
+                    fname = fullfile(acq.dir, '0_rawdata', acq.name, sprintf('%s%04d.edf', acq.name, ii_proj));
+                    inds_inproj = find(bool_inproj);
+
+                    % loop for the blobs: read the raw image and add it to the blob
+                    for ind_b = inds_inproj(:)'
+                        bbwim = bbwims_modified(ind_b, :);
+                        bbw = ii_proj - bbwim(1) + 1;
+                        bb_ii = [bbuims_ondet(ind_b, :); bbvims_ondet(ind_b, :)];
+                        dark_crop = dark(bb_ii(1, 1):bb_ii(1, 2), bb_ii(2, 1):bb_ii(2, 2)); % dark image needs substracting from raw images
+                        bb_ii(:, 2) = bb_ii(:, 2) - bb_ii(:, 1) + 1;
+                        rawimg_crop = cast(edf_read(fname, bb_ii(:)', [], info), self.data_type)'; % read raw image
+                        rawimg_crop(rawimg_crop <= 0) = self.invalid_pixel_nvar;
+                        rawimg_crop = rawimg_crop - dark_crop; % dark image needs substracting from raw images
+                        subu = subbbu(ind_b, :);
+                        subv = subbbv(ind_b, :);
+                        self.cluster.blobs{self.det_ind}(ind_b).nvar(subu(1):subu(2), subv(1):subv(2), bbw) = cast(rawimg_crop, self.data_type);
+                    end
+                end
+            end
+        end
+
+        function [bbwims_modified, bool_inproj] = modify_bbwims_and_check_bl_exist_raw(self, ii_proj, totproj)
+            bbwims_modified = self.bbwims;
+            bool_inproj = any(bbwims_modified <= (ii_proj - totproj), 2) & any(bbwims_modified >= (ii_proj - totproj), 2);
+            if any(bool_inproj)
+                bbwims_modified(bool_inproj, :) = bbwims_modified(bool_inproj, :) - totproj;
+            end
+            bool_inproj = any(bbwims_modified <= (ii_proj + totproj), 2) & any(bbwims_modified >= (ii_proj + totproj), 2);
+            if any(bool_inproj)
+                bbwims_modified(bool_inproj, :) = bbwims_modified(bool_inproj, :) + totproj;
+            end
+            bool_inproj = any(bbwims_modified <= ii_proj, 2) & any(bbwims_modified >= ii_proj, 2);
+        end
+
+        function set_bl_nvar_to_inf(self)
+            % initialize the noise variance to a very large value
+            switch lower(self.parameters.acq(self.det_ind).type)
+                case {'180degree', '360degree'}
+                    blob_type = 'blob';
+                case 'topotomo'
+                    blob_type = 'blob_topo';
+            end
+            nof_bl = numel(self.cluster.blobs{self.det_ind});
+            if ~isfield(self.cluster.blobs{self.det_ind}(1), 'nvar')
+                tmpbl = gtFwdSimBlobDefinition(blob_type, nof_bl);
+                for ii_b = nof_bl:-1:1
+                    tmpbl(ii_b) = gtAddMatFile(tmpbl(ii_b), self.cluster.blobs{self.det_ind}(ii_b), true, false, false, false, false);
+                end
+                self.cluster.blobs{self.det_ind} = tmpbl;
+            end
+            for ii_b = nof_bl:-1:1
+                self.cluster.blobs{self.det_ind}(ii_b).nvar = cast(ones(size(self.cluster.blobs{self.det_ind}(ii_b).intm)) * self.invalid_pixel_nvar, self.data_type);
+            end
+        end
+
+        function add_noise_of_dark_images(self, var_n_dark, dark)
+            bbuim = self.bbuims;
+            bbvim = self.bbvims;
+
+            % find the range of the detector
+            bbuim_ondet = GtGrainCalcBlobNoiseVars.calcOndetBbim(bbuim, 1 + self.detector_margin, self.parameters.acq(self.det_ind).xdet - self.detector_margin);
+            subbbu = bsxfun(@minus, bbuim_ondet, bbuim(:, 1) - 1);
+            bbvim_ondet = GtGrainCalcBlobNoiseVars.calcOndetBbim(bbvim, 1 + self.detector_margin, self.parameters.acq(self.det_ind).ydet - self.detector_margin);
+            subbbv = bsxfun(@minus, bbvim_ondet, bbvim(:, 1) - 1);
+            bbwsize = abs(self.bbwims(:,2) - self.bbwims(:,1)) + 1;
+
+            detind = self.det_ind;
+            if ~exist('var_n_dark', 'var') || isempty(var_n_dark)
+                [var_n_dark, dark] = GtGrainCalcBlobNoiseVars.calcDarkImageVariance(self.parameters.acq(detind));
+            end
+            var_n_dark(~dark) = self.invalid_pixel_nvar; % set the noise variance of bad pixels to a very large number.
+            var_n_dark = cast(var_n_dark', self.data_type);
+            nof_blobs = numel(self.cluster.blobs{detind});
+            for n = 1:nof_blobs
+                tmp_dark_var = var_n_dark(bbuim_ondet(n, 1):bbuim_ondet(n, 2), bbvim_ondet(n, 1):bbvim_ondet(n, 2));
+                tmp_dark_var = tmp_dark_var(:, :, ones(1, bbwsize(n)));
+                spotaddbg = self.cluster.blobs{detind}(n).nvar(subbbu(n, 1):subbbu(n, 2), subbbv(n, 1):subbbv(n, 2), :) + tmp_dark_var;
+
+                % set the noise variance out of the detector to a very large number
+                self.cluster.blobs{detind}(n).nvar = cast(ones(size(self.cluster.blobs{detind}(n).nvar)) * self.invalid_pixel_nvar, self.data_type);
+
+                self.cluster.blobs{detind}(n).nvar(subbbu(n, 1):subbbu(n, 2), subbbv(n, 1):subbbv(n, 2), :) = cast(spotaddbg, self.data_type);
+            end
+        end
+
+        function apply_detector_mask(self, detind)
+            nof_bl = numel(self.cluster.blobs{detind});
+            size_detmask = size(self.detector_mask);
+            for ii_b = 1:nof_bl
+                size_nvar = size(self.cluster.blobs{detind}(ii_b).nvar);
+                nvarinactmask = false(size_nvar(1), size_nvar(2));
+                bbim = self.cluster.blobs{detind}(ii_b).bbuim;
+                bbuinds = bbim(1):bbim(2);
+                bool_bbuindsondet = ~((bbuinds < 1) | (bbuinds > size_detmask(1)));
+                bbim = self.cluster.blobs{detind}(ii_b).bbvim;
+                bbvinds = bbim(1):bbim(2);
+                bool_bbvindsondet = ~((bbvinds < 1) | (bbvinds > size_detmask(2)));
+                nvarinactmask(bool_bbuindsondet, bool_bbvindsondet) = ~self.detector_mask(bbuinds(bool_bbuindsondet), bbvinds(bool_bbvindsondet));
+                nvarinactmask = nvarinactmask(:, :, ones(1, size_nvar(3)));
+                self.cluster.blobs{detind}(ii_b).nvar(nvarinactmask) = self.invalid_pixel_nvar;
+            end
+        end
+    end
+end
\ No newline at end of file
-- 
GitLab