Skip to content
Snippets Groups Projects
GtGrainODFuvwSolver.m 25.4 KiB
Newer Older
classdef GtGrainODFuvwSolver < GtGrainODFwSolver
    properties
        size_rspace_volume = [];

        voxel_centers = [];

        rspace_oversampling = 1;
        rspace_downscaling = 1;
    end

    methods (Access = public)
        function self = GtGrainODFuvwSolver(parameters, varargin)
            self = self@GtGrainODFwSolver(parameters, varargin{:});
        end
    end

    methods (Access = public) % Low Level API
        function build_orientation_sampling(self, ref_gr, det_index)
            self.build_orientation_sampling@GtGrainODFwSolver(ref_gr, det_index);

            self.chose_voxel_centers();

            num_vox_centers = size(self.voxel_centers, 1);
            recgeo = self.parameters.recgeo(self.sampler.detector_index);
            switch (self.shape_functions_type)
                    sf_nw = gtDefShapeFunctionsCreateNW(self.sampler, 2, ...
                        'data_type', self.data_type);

                    for ii_v = 1:num_vox_centers
                        fprintf('(%03d/%03d) ', ii_v, num_vox_centers);
                        disp_sampler = self.sampler.regenerate_displaced(self.voxel_centers(ii_v, :));

                        self.shape_functions{ii_v} = gtDefShapeFunctionsNW2UVW( ...
                            disp_sampler, sf_nw, 'recenter_sf', false, ...
                            'rspace_oversampling', self.rspace_downscaling, ...
                            'rspace_voxel_size', recgeo.voxsize * self.rspace_downscaling);
                    end
                case 'uvw'
                    shape_functions_name = sprintf('tmp_shape_functions_uvw_%d_%d', ...
                        size(self.voxel_centers, 1), self.sampler.get_total_num_orientations());

                    if (evalin('base', ['exist(''' shape_functions_name ''', ''var'')']))
                        fprintf('Loading shape functions..')
                        c = tic();
                        self.shape_functions = evalin('base', shape_functions_name);
                        fprintf('\b\b: Done in %g seconds\n', toc(c));
                    elseif (~exist([shape_functions_name '.mat'], 'file'))
                        for ii_v = 1:num_vox_centers
                            fprintf('(%3d/%03d) ', ii_v, num_vox_centers);
                            disp_sampler = self.sampler.regenerate_displaced(self.voxel_centers(ii_v, :));

                            self.shape_functions{ii_v} = gtDefShapeFunctionsFwdProj( ...
                                disp_sampler, 'recenter_sf', false, ...
                                'data_type', self.data_type, ...
                                'rspace_voxel_size', recgeo.voxsize * self.rspace_downscaling);
                        end
                        sfs = self.shape_functions;
                        save([shape_functions_name '.mat'], 'sfs', '-v7.3')
                        assignin('base', shape_functions_name, sfs);
                    else
                        fprintf('Loading shape functions..')
                        c = tic();
                        sfs_file = load([shape_functions_name '.mat']);
                        self.shape_functions = sfs_file.sfs;
                        assignin('base', shape_functions_name, sfs_file.sfs);
                        fprintf('\b\b: Done in %g seconds\n', toc(c));
                    end
            if (self.verbose)
                fprintf('Building sinogram..')
                c = tic();
            end
            ref_sel = self.sampler.selected;
            bls = self.sampler.bl(ref_sel);

            ref_inc = self.sampler.ondet(self.sampler.included);
            ref_gr = self.sampler.get_reference_grain();
            ref_ws = ref_gr.allblobs.detector(self.sampler.detector_index).uvw(ref_inc(ref_sel), 3);
            num_blobs = numel(bls);
            blob_sizes = cat(1, bls(:).bbsize);

            blob_u_lims = cat(1, bls(:).bbuim);
            blob_v_lims = cat(1, bls(:).bbvim);
            blob_w_lims = cat(1, bls(:).bbwim);
            blob_uvw_lims = permute(cat(3, blob_u_lims, blob_v_lims, blob_w_lims), [1 3 2]);

            with_shape_functions = ~isempty(self.shape_functions);
            if (with_shape_functions)
                if (any(strcmpi(self.shape_functions_type, {'uvw', 'nw2uvw'})))
                    for ii_o = numel(self.shape_functions{1}):-1:1
                        proj_w_lims(:, :, ii_o) = cat(1, self.shape_functions{1}{ii_o}(:).bbwim);
                    end
                    for ii_o = numel(self.shape_functions):-1:1
                        proj_w_lims(:, :, ii_o) = cat(1, self.shape_functions{ii_o}(:).bbwim);
                proj_w_mins = reshape(proj_w_lims(:, 1, :), num_blobs, []);
                proj_w_maxs = reshape(proj_w_lims(:, 2, :), num_blobs, []);

                proj_w_mins = gtGrainAnglesTabularFix360deg(proj_w_mins, ref_ws, self.parameters);
                proj_w_maxs = gtGrainAnglesTabularFix360deg(proj_w_maxs, ref_ws, self.parameters);

                proj_w_lims = [min(proj_w_mins, [], 2), max(proj_w_maxs, [], 2)];
                delta_ws = proj_w_lims(:, 2) - proj_w_lims(:, 1) + 1;
                [delta_ws, proj_w_lims] = self.sampler.get_omega_deviations(with_shape_functions);
                delta_ws = delta_ws(self.sampler.selected);
                proj_w_lims = proj_w_lims(self.sampler.selected, :);
            num_rspace_voxels = size(self.voxel_centers, 1);

            % Building UV information
            if (with_shape_functions && any(strcmpi(self.shape_functions_type, {'uvw', 'nw2uvw'})))
                for ii_v = num_rspace_voxels:-1:1
                    for ii_o = numel(self.shape_functions{1}):-1:1
                        proj_u_lims(:, :, ii_o, ii_v) = cat(1, self.shape_functions{ii_v}{ii_o}(:).bbuim);
                        proj_v_lims(:, :, ii_o, ii_v) = cat(1, self.shape_functions{ii_v}{ii_o}(:).bbvim);
                    end
                end

                % By expressing the third index with the column :, and
                % not expressing the fourth at all, we do an implicit
                % reshape from 4 imentions to 3
                proj_u_lims = [min(proj_u_lims(:, 1, :), [], 3), max(proj_u_lims(:, 2, :), [], 3)];
                proj_v_lims = [min(proj_v_lims(:, 1, :), [], 3), max(proj_v_lims(:, 2, :), [], 3)];
            else
                for ii_v = num_rspace_voxels:-1:1
                    disp_sampler = self.sampler.regenerate_displaced(self.voxel_centers(ii_v, :));

                    [~, proj_uv_lims(:, :, :, ii_v)] = disp_sampler.get_uv_deviations();
                end
                proj_uv_lims = proj_uv_lims(self.sampler.selected, :, :);
                proj_u_lims = [min(proj_uv_lims(:, 1, 1, :), [], 4), max(proj_uv_lims(:, 2, 1, :), [], 4)];
                proj_v_lims = [min(proj_uv_lims(:, 1, 2, :), [], 4), max(proj_uv_lims(:, 2, 2, :), [], 4)];
            proj_uv_lims = cat(3, proj_u_lims, proj_v_lims);
            delta_uvs = reshape(proj_uv_lims(:, 2, :) - proj_uv_lims(:, 1, :), [], 2);

            chosen_sizes = max(blob_sizes, [delta_uvs, delta_ws]);

            proj_uvw_lims = permute(cat(3, proj_uv_lims, proj_w_lims), [1 3 2]);
            num_uvws = max(chosen_sizes, [], 1) + 2;
            self.size_sino = [num_uvws, num_blobs];
            self.sino = zeros(self.size_sino, self.data_type);
            % In the case that the sampled orientations determine the
            % shift, we have to take it into account!
            align_uvw_shift = blob_uvw_lims(:, :, 1) - proj_uvw_lims(:, :, 1);
            align_uvw_shift(align_uvw_shift < 0) = 0;
            self.pre_paddings = floor((num_uvws(ones(num_blobs, 1), :) - chosen_sizes) / 2) + 1 + align_uvw_shift;

            for ii_b = 1:num_blobs
                ints_u_interval = self.pre_paddings(ii_b, 1):(self.pre_paddings(ii_b, 1) + blob_sizes(ii_b, 1) -1);
                ints_v_interval = self.pre_paddings(ii_b, 2):(self.pre_paddings(ii_b, 2) + blob_sizes(ii_b, 2) -1);
                ints_w_interval = self.pre_paddings(ii_b, 3):(self.pre_paddings(ii_b, 3) + blob_sizes(ii_b, 3) -1);
                masked_blob = bls(ii_b).intm;
                masked_blob(~bls(ii_b).mask) = 0;
                self.sino(ints_u_interval, ints_v_interval, ints_w_interval, ii_b) = masked_blob;
            end
            self.sino = reshape(self.sino, [], 1);
                fprintf('\b\b: Done in %g seconds.\n - Sino size: [%d, %d, %d, %d]\n', toc(c), self.size_sino);
        function chose_voxel_centers(self)
            recgeo = self.parameters.recgeo(self.sampler.detector_index);
            ref_gr = self.sampler.get_reference_grain();
            proj = ref_gr.proj(self.sampler.detector_index);
            rspace_vol_dims = ceil([proj.vol_size_y, proj.vol_size_x, proj.vol_size_z] / self.rspace_downscaling);
            max_dists_from_center = (rspace_vol_dims - 1 ./ self.rspace_oversampling) / 2;
            self.size_rspace_volume = rspace_vol_dims .* self.rspace_oversampling;
            tot_voxels = prod(self.size_rspace_volume);
            self.voxel_centers = zeros(tot_voxels, 3);
            for ss_z = linspace(-max_dists_from_center(3), max_dists_from_center(3), self.size_rspace_volume(3))
                for ss_y = linspace(-max_dists_from_center(2), max_dists_from_center(2), self.size_rspace_volume(2))
                    for ss_x = linspace(-max_dists_from_center(1), max_dists_from_center(1), self.size_rspace_volume(1))
                        offset_voxel = [ss_x, ss_y, ss_z] .* recgeo.voxsize * self.rspace_downscaling;
                        self.voxel_centers(counter, :) = ref_gr.center + offset_voxel;
        function build_projection_matrices_sf_none(self)
            ref_sel = self.sampler.selected;

            det_ind = self.sampler.detector_index;

            detgeo = self.parameters.detgeo(det_ind);
            labgeo = self.parameters.labgeo;
            samgeo = self.parameters.samgeo;

            om_step = gtGetOmegaStepDeg(self.parameters, det_ind);

            bls = self.sampler.bl(ref_sel);
            num_uvws = self.get_num_uvws();
            num_blobs = self.get_num_blobs();

            bls_bbus = cat(1, bls(:).bbuim);
            bls_bbvs = cat(1, bls(:).bbvim);
            bls_bbws = cat(1, bls(:).bbwim);
            min_uvw_conds = [bls_bbus(:, 1), bls_bbvs(:, 1), bls_bbws(:, 1)] - self.pre_paddings + 1;
            max_uvw_conds = min_uvw_conds + num_uvws(ones(num_blobs, 1), :) - 1;
            grid_gr = self.sampler.get_orientations();
            num_orients = numel(grid_gr);

            num_voxels = size(self.voxel_centers, 1);
            self.S = cell(num_voxels, 1);
            self.St = cell(num_voxels, 1);

            rot_l2s = cell(num_orients, 1);
            for ii_o = 1:num_orients
                rot_l2s{ii_o} = grid_gr{ii_o}.allblobs.srot(:, :, ref_sel);
            rot_l2s = cat(3, rot_l2s{:});
            rot_s2l = permute(rot_l2s, [2 1 3]);
            dveclab = cell(num_orients, 1);
            for ii_o = 1:num_orients
                dveclab{ii_o} = grid_gr{ii_o}.allblobs.dvec(ref_sel, :);
            end
            dveclab = cat(1, dveclab{:});

            for ii_v = 1:num_voxels
                b_us = [];
                b_vs = [];
                b_ws = [];
                b_cs = [];
                b_is = [];
                b_os = [];

                num_chars = fprintf('%03d/%03d', ii_v, num_voxels);

                gcsam_v = self.voxel_centers(ii_v, :);
                gcsam_v = gcsam_v(ones(1, size(rot_s2l, 3)), :);
                gclab_v = gtGeoSam2Lab(gcsam_v, rot_s2l, labgeo, samgeo, false);

                uv_tot = gtFedPredictUVMultiple([], dveclab', gclab_v', ...
                    detgeo.detrefpos', detgeo.detnorm', detgeo.Qdet, ...
                    [detgeo.detrefu, detgeo.detrefv]');
                uv_tot = reshape(uv_tot, 2, [], num_orients);

                    ws = grid_gr{ii_o}.allblobs.omega(ref_sel) / om_step;
                    uvw = [uv_tot(:, :, ii_o)', ws];
                    num_pos = size(uvw, 1);
                    min_uvws = floor(uvw);
                    max_uvws = min_uvws + 1;
                    max_uvw_cs = uvw - min_uvws;
                    min_uvw_cs = 1 - max_uvw_cs;
                    ok_uvw_mins = (min_uvws >= min_uvw_conds) & (min_uvws <= max_uvw_conds);
                    ok_uvw_maxs = (max_uvws >= min_uvw_conds) & (max_uvws <= max_uvw_conds) & (max_uvw_cs > eps('single'));
                    u_oks = [ok_uvw_mins(:, 1), ok_uvw_maxs(:, 1)];
                    v_oks = [ok_uvw_mins(:, 2), ok_uvw_maxs(:, 2)];
                    w_oks = [ok_uvw_mins(:, 3), ok_uvw_maxs(:, 3)];
                    u_cs = [min_uvw_cs(:, 1), max_uvw_cs(:, 1)];
                    v_cs = [min_uvw_cs(:, 2), max_uvw_cs(:, 2)];
                    w_cs = [min_uvw_cs(:, 3), max_uvw_cs(:, 3)];
                    pos_us = [min_uvws(:, 1), max_uvws(:, 1)] - bls_bbus(:, [1 1]) + self.pre_paddings(:, [1 1]);
                    pos_vs = [min_uvws(:, 2), max_uvws(:, 2)] - bls_bbvs(:, [1 1]) + self.pre_paddings(:, [2 2]);
                    pos_ws = [min_uvws(:, 3), max_uvws(:, 3)] - bls_bbws(:, [1 1]) + self.pre_paddings(:, [3 3]);
                    uvw_oks = u_oks(:, [1 1 1 1 2 2 2 2]) & v_oks(:, [1 1 2 2 1 1 2 2]) & w_oks(:, [1 2 1 2 1 2 1 2]);
                    uvw_cs  = u_cs(:, [1 1 1 1 2 2 2 2]) .* v_cs(:, [1 1 2 2 1 1 2 2]) .* w_cs(:, [1 2 1 2 1 2 1 2]);
                    pos_us = pos_us(:, [1 1 1 1 2 2 2 2]);
                    pos_vs = pos_vs(:, [1 1 2 2 1 1 2 2]);
                    pos_ws = pos_ws(:, [1 2 1 2 1 2 1 2]);
                    indx = find(uvw_oks);
                    indx_mod = mod(indx - 1, num_pos) + 1;

                    b_us = [b_us; pos_us(indx)];
                    b_vs = [b_vs; pos_vs(indx)];
                    b_ws = [b_ws; pos_ws(indx)];

                    b_cs = [b_cs; uvw_cs(indx)];

                    b_is = [b_is; indx_mod];
                    b_os = [b_os; ii_o(ones(numel(indx), 1))];
%                 sino_indx = sub2ind(self.size_sino, b_us, b_vs, b_ws, b_is);
                sino_indx = b_us ...
                    + num_uvws(1) * (b_vs - 1 ...
                    + num_uvws(2) * (b_ws - 1 ...
                    + num_uvws(3) * (b_is - 1)));
                self.S{ii_v} = sparse( ...
                        sino_indx, b_os, b_cs, ...
                        numel(self.sino), num_orients);
                self.St{ii_v} = self.S{ii_v}';

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

        function build_projection_matrices_sf_w(self)
            ref_sel = self.sampler.selected;

            det_ind = self.sampler.detector_index;

            detgeo = self.parameters.detgeo(det_ind);
            labgeo = self.parameters.labgeo;
            samgeo = self.parameters.samgeo;

            bls = self.sampler.bl(self.sampler.selected);

            num_uvws = self.get_num_uvws();
            num_blobs = self.get_num_blobs();
            bls_bbws = cat(1, bls(:).bbwim);
            bls_bbus = cat(1, bls(:).bbuim);
            bls_bbvs = cat(1, bls(:).bbvim);

            min_uvw_conds = [bls_bbus(:, 1), bls_bbvs(:, 1), bls_bbws(:, 1)] - self.pre_paddings + 1;
            max_uvw_conds = min_uvw_conds + num_uvws(ones(num_blobs, 1), :) - 1;

            grid_gr = self.sampler.get_orientations();
            num_orients = numel(grid_gr);

            num_voxels = size(self.voxel_centers, 1);
            self.S = cell(num_voxels, 1);
            self.St = cell(num_voxels, 1);

            % Extracting orientations' W shape functions
            oris_w_cs = cell(num_orients, 1);
            oris_ws = cell(num_orients, 1);
            oris_w_is = cell(num_orients, 1);

            for ii_o = 1:num_orients
                sf = self.shape_functions{ii_o};

                w_cs = cat(1, sf(:).intm);
                oris_w_cs{ii_o} = reshape(w_cs, [], 1);

                ws_sizes = [sf(:).bbsize];
                bbwims = cat(1, sf(:).bbwim);

                ws = cell(num_blobs, 1);
                w_is = cell(num_blobs, 1);
                for ii_b = 1:num_blobs
                    ws{ii_b} = bbwims(ii_b, 1):bbwims(ii_b, 2);
                    w_is{ii_b} = ii_b(ones(ws_sizes(ii_b), 1));
                end

                oris_ws{ii_o} = reshape([ws{:}], [], 1);
                oris_w_is{ii_o} = cat(1, w_is{:});
            end

            % Preparing for computing the UV deviations due to the XYZ
            % positions of the real-space voxels
            rot_l2s = cell(num_orients, 1);
            for ii_o = 1:num_orients
                rot_l2s{ii_o} = grid_gr{ii_o}.allblobs.srot(:, :, ref_sel);
            rot_l2s = cat(3, rot_l2s{:});
            rot_s2l = permute(rot_l2s, [2 1 3]);
            dveclab = cell(num_orients, 1);
            for ii_o = 1:num_orients
                dveclab{ii_o} = grid_gr{ii_o}.allblobs.dvec(ref_sel, :);
            end
            dveclab = cat(1, dveclab{:});

            for ii_v = 1:num_voxels
                b_us = [];
                b_vs = [];
                b_ws = [];
                b_cs = [];
                b_is = [];
                b_os = [];

                num_chars = fprintf('%03d/%03d', ii_v, num_voxels);

                gcsam_v = self.voxel_centers(ii_v, :);
                gcsam_v = gcsam_v(ones(1, size(rot_s2l, 3)), :);
                gclab_v = gtGeoSam2Lab(gcsam_v, rot_s2l, labgeo, samgeo, false);

                uv_tot = gtFedPredictUVMultiple([], dveclab', gclab_v', ...
                    detgeo.detrefpos', detgeo.detnorm', detgeo.Qdet, ...
                    [detgeo.detrefu, detgeo.detrefv]');
                uv_tot = reshape(uv_tot, 2, [], num_orients);

                for ii_o = 1:num_orients
                    w_cs = oris_w_cs{ii_o};
                    ws = oris_ws{ii_o};
                    w_is = oris_w_is{ii_o};
                    uv = uv_tot(:, :, ii_o)';

                    min_uvs = floor(uv);
                    max_uvs = min_uvs + 1;

                    max_uv_cs = uv - min_uvs;
                    min_uv_cs = 1 - max_uv_cs;

                    % Acceptance criteria
                    ok_uv_mins = (min_uvs >= min_uvw_conds(:, 1:2)) & (min_uvs <= max_uvw_conds(:, 1:2)) & (min_uv_cs > eps('single'));
                    ok_uv_maxs = (max_uvs >= min_uvw_conds(:, 1:2)) & (max_uvs <= max_uvw_conds(:, 1:2)) & (max_uv_cs > eps('single'));
                    u_oks = [ok_uv_mins(:, 1), ok_uv_maxs(:, 1)];
                    v_oks = [ok_uv_mins(:, 2), ok_uv_maxs(:, 2)];

                    u_cs = [min_uv_cs(:, 1), max_uv_cs(:, 1)];
                    v_cs = [min_uv_cs(:, 2), max_uv_cs(:, 2)];

                    pos_us = [(min_uvs(:, 1) - min_uvw_conds(:, 1) + 1), (max_uvs(:, 1) - min_uvw_conds(:, 1) + 1)];
                    pos_vs = [(min_uvs(:, 2) - min_uvw_conds(:, 2) + 1), (max_uvs(:, 2) - min_uvw_conds(:, 2) + 1)];
                    pos_ws = ws - min_uvw_conds(w_is, 3) + 1;

                    uvw_oks = u_oks(w_is, [1 1 2 2]) & v_oks(w_is, [1 2 1 2]);
                    uvw_cs  = u_cs(w_is, [1 1 2 2]) .* v_cs(w_is, [1 2 1 2]) .* w_cs(:, [1 1 1 1]);

                    pos_us = pos_us(w_is, [1 1 2 2]);
                    pos_vs = pos_vs(w_is, [1 2 1 2]);
                    pos_ws = pos_ws(:, [1 1 1 1]);

                    indx = find(uvw_oks);
                    indx_mod = mod(indx - 1, numel(w_is)) + 1;

                    wrong_w = ws < min_uvw_conds(w_is, 3) | ws > max_uvw_conds(w_is, 3);
                    if (any(wrong_w))
                        ii_o
                        find(wrong_w)
                        [min_uvw_conds(w_is(wrong_w), 3), ws(wrong_w), max_uvw_conds(w_is(wrong_w), 3), ...
                            (max_uvw_conds(w_is(wrong_w), 3) - min_uvw_conds(w_is(wrong_w), 3) + 1), ...
                            (ws(wrong_w) - min_uvw_conds(w_is(wrong_w), 3) + 1) ]
                    end

                    b_us = [b_us; pos_us(indx)];
                    b_vs = [b_vs; pos_vs(indx)];
                    b_ws = [b_ws; pos_ws(indx)];

                    b_cs = [b_cs; uvw_cs(indx)];

                    b_is = [b_is; w_is(indx_mod)];
                    b_os = [b_os; ii_o(ones(numel(indx), 1))];
                end

%                 sino_indx = sub2ind(self.size_sino, b_us, b_vs, b_ws, b_is);
                sino_indx = b_us ...
                    + self.size_sino(1) * (b_vs - 1 ...
                    + self.size_sino(2) * (b_ws - 1 ...
                    + self.size_sino(3) * (b_is - 1)));

                self.S{ii_v} = sparse( ...
                        sino_indx, b_os, b_cs, ...
                        numel(self.sino), num_orients);

                self.St{ii_v} = self.S{ii_v}';

                fprintf(repmat('\b', [1 num_chars]));
            end
        function build_projection_matrices_sf_uvw(self)
            ref_sel = self.sampler.selected;

            bls = self.sampler.bl(ref_sel);
            num_blobs = self.get_num_blobs();

            num_ws = self.get_num_ws();
            bls_bbws = cat(1, bls(:).bbwim);
            min_w_conds = bls_bbws(:, 1) - self.pre_paddings(:, 3) + 1;
            max_w_conds = min_w_conds + num_ws - 1;

            bls_bbus = cat(1, bls(:).bbuim);
            bls_bbvs = cat(1, bls(:).bbvim);

            grid_gr = self.sampler.get_orientations();
            num_orients = numel(grid_gr);

            num_voxels = size(self.voxel_centers, 1);
            self.S = cell(num_voxels, 1);
            self.St = cell(num_voxels, 1);

            bls_uvw_orig = cat(1, bls(:).bbuim, bls(:).bbvim, bls(:).bbwim);
            bls_uvw_orig = reshape(bls_uvw_orig, [], 3, 2);
            bls_uvw_orig = bls_uvw_orig(:, :, 1) - self.pre_paddings + 1;

            for ii_v = 1:num_voxels
                b_us = [];
                b_vs = [];
                b_ws = [];
                b_cs = [];
                b_is = [];
                b_os = [];

                num_chars = fprintf('%03d/%03d', ii_v, num_voxels);

                for ii_o = 1:num_orients
                    sf = self.shape_functions{ii_v}{ii_o};
                    sf_uvw_shift = cat(1, sf(:).bbuim, sf(:).bbvim, sf(:).bbwim);
                    sf_uvw_shift = reshape(sf_uvw_shift, [], 3, 2);
                    sf_uvw_shift = sf_uvw_shift(:, :, 1) - bls_uvw_orig;

                    uvw_cs = cell(num_blobs, 1);
                    bl_is = cell(num_blobs, 1);
                    bl_shifts = cell(num_blobs, 1);

                    for ii_b = 1:num_blobs
                        inds = find(sf(ii_b).intm);
                        uvw_cs{ii_b} = sf(ii_b).intm(inds);

%                         [inds_u, inds_v, inds_w] = ind2sub(sf(ii_b).bbsize, inds);
                        inds_u = mod(inds, sf(ii_b).bbsize(1));
                        inds_v = mod(floor(inds / sf(ii_b).bbsize(1)) + 1, sf(ii_b).bbsize(2));
                        inds_w = floor(inds / prod(sf(ii_b).bbsize(1:2))) + 1;

                        bl_is{ii_b} = ii_b(ones(numel(inds), 1));
                        bl_shifts{ii_b} = [inds_u, inds_v, inds_w] + sf_uvw_shift(bl_is{ii_b}, :);
                    end

                    uvw_cs = cat(1, uvw_cs{:});
                    bl_is = cat(1, bl_is{:});
                    bl_shifts = cat(1, bl_shifts{:});

                    b_us = [b_us; bl_shifts(:, 1)];
                    b_vs = [b_vs; bl_shifts(:, 2)];
                    b_ws = [b_ws; bl_shifts(:, 3)];

                    b_cs = [b_cs; uvw_cs];

                    b_is = [b_is; bl_is];
                    b_os = [b_os; ii_o(ones(numel(bl_is), 1))];
                end

%                 sino_indx = sub2ind(self.size_sino, b_us, b_vs, b_ws, b_is);
                sino_indx = b_us ...
                    + self.size_sino(1) * (b_vs - 1 ...
                    + self.size_sino(2) * (b_ws - 1 ...
                    + self.size_sino(3) * (b_is - 1)));

                self.S{ii_v} = sparse( ...
                        sino_indx, b_os, double(b_cs), ...
                        numel(self.sino), num_orients);

                self.St{ii_v} = self.S{ii_v}';

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

        function vol = get_volume(self)
            vol = reshape(sum(self.volume, 2), self.size_volume);

        function vol = get_volume_6D(self)
            vol = reshape(self.volume, [self.size_volume, self.size_rspace_volume]);
        end
    end

    methods (Access = protected)
        function y = fp(self, x)
            y = self.S{1} * x(:, 1);
            for ii_s = 2:numel(self.S)
                y = y + self.S{ii_s} * x(:, ii_s);
            end
        end

        function y = bp(self, x)
            for ii_s = numel(self.St):-1:1
                y(:, ii_s) = self.St{ii_s} * x;
            end
        end

        function num_uvws = get_num_uvws(self)
            num_uvws = self.size_sino(1:3);
        end

        function num_ws = get_num_ws(self)
            num_ws = self.size_sino(3);
        end

        function num_bs = get_num_blobs(self)
            num_bs = self.size_sino(4);
        end