initial commit

This commit is contained in:
2026-08-07 15:56:42 +09:00
commit 91ad25aca9
1012 changed files with 159314 additions and 0 deletions
+311
View File
@@ -0,0 +1,311 @@
% PHASE_RAMP_REMOVAL_TOMO Use tomography consistency between measured and
% reconstructed phase to remove phase ramp from data
% This function uses mask in volume space to accuratelly find regions of
% air in the projection space. These regions are iterativelly forced
% towards zero
%
% Several iterations are performed to further improve precision
%
% object_full = phase_ramp_removal_tomo(object_full,object_ROI, theta, Npix, total_shift, par, varargin)
%
% Inputs:
% **object_full - complex-valued projections
% **object_ROI - reliable region used for reconstruction
% **theta - tomography angles
% **Npix - size of reconstruction
% **par - tomography parameter structure
% *optional* (or use values from par structure as default if provided)
% **binning = 4 - bin data to make reconstruction faster & more robust
% **positivity = true - apply positivity constaint
% **auto_weighting = true - give less weight to thic regions of the sample
% **fourier_guess = true - calculate FFT to find phase ramp, important if the phase ramp is more than 2pi per frame
% **Niter = 3 - number of iterations for phase removal
% **unwrap_data_method = 'fft_2d' - fft_2d , fft_1d
% **sino_weights = 1 - importance weights
% **CoR_offset = [] - offset of the center of rotation, default is center of projection
% **inplace_processing = false - process data inplace to save memory
%
% *returns*
% ++object_full - complex-valued projections after phase ramp removal
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  Except where otherwise noted, this work is licensed under a          |
%|  Creative Commons Attribution-NonCommercial-ShareAlike 4.0            |
%|  International (CC BY-NC-SA 4.0) license.                             |
%|                                                                       |
%|  Copyright (c) 2017 by Paul Scherrer Institute (http://www.psi.ch)    |
%|                                                                       |
%|      Author: CXS group, PSI  |
%*-----------------------------------------------------------------------*
% You may use this code with the following provisions:
%
% If the code is fully or partially redistributed, or rewritten in another
% computing language this notice should be included in the redistribution.
%
% If this code, or subfunctions or parts of it, is used for research in a
% publication or if it is fully or partially rewritten for another
% computing language the authors and institution should be acknowledged
% in written form in the publication: Data processing was carried out
% using the cSAXS matlab package developed by the CXS group,
% Paul Scherrer Institut, Switzerland.
% Variations on the latter text can be incorporated upon discussion with
% the CXS group if needed to more specifically reflect the use of the package
% for the published work.
%
% A publication that focuses on describing features, or parameters, that
% are already existing in the code should be first discussed with the
% authors.
%
% This code and subroutines are part of a continuous development, they
% are provided as they are without guarantees or liability on part
% of PSI or the authors. It is the user responsibility to ensure its
% proper use and the correctness of the results.
function [object_full, W] = phase_ramp_removal_tomo(object_full,object_ROI, theta, Npix,total_shift, par, varargin)
import utils.*
verbose(struct('prefix', 'phase_ramp_remove'))
parser = inputParser;
parser.addParameter('binning', 4 , @isnumeric ) % bin data to make reconstruction faster & more robust
parser.addParameter('positivity', true , @islogical ) % apply positivity constaint
parser.addParameter('auto_weighting', true , @islogical ) % give less weight to thic regions of the sample
parser.addParameter('fourier_guess', true , @islogical ) % calculate FFT to find phase ramp, important if the phase ramp is more than 2pi per frame
parser.addParameter('Niter', 3 , @isnumeric ) % number of iterations for phase removal
parser.addParameter('unwrap_data_method', 'fft_2d' , @isstr ) % fft_2d , fft_1d
parser.addParameter('sino_weights', 1, @isnumeric ) % importance weights
parser.addParameter('CoR_offset', [] , @isnumeric ) % offset of the center of rotation, default is center of projection
parser.addParameter('CoR_offset_v', [] , @isnumeric ) % added by YJ. vertical offset of the center of rotation, default is center of projection
parser.addParameter('inplace_processing', false, @islogical ) % process data inplace to save memory
parser.parse(varargin{:})
r = parser.Results;
% load all varargins to the param structure
for name = fieldnames(r)'
if ~isfield(par, name{1}) || ~ismember(name, parser.UsingDefaults) % prefer values in param structure if parsers returns default value
par.(name{1}) = r.(name{1});
end
end
verbose(0,'Calculating phase ramp + amplitude correction')
Np_full = size(object_full);
verbose(1,'Binning: %i', par.binning)
if ismember(lower(par.unwrap_data_method), {'none', 'fft_2d'})
interp_sign = -1 ;
else
interp_sign = 1 ;
end
% use symmetrically expanded ROI, get more region around sample
for ii = 1:2
object_ROI{ii} = max(1, object_ROI{ii}(1)-ceil(par.asize(ii)/4)):min(Np_full(ii), ceil(object_ROI{ii}(end)+par.asize(ii)/4));
end
% shift the projections back to the "after loading" positions -> avoid boundary problems when
% the phase ramp removal is applied
% !! high accuracy downsampling and shift is not needed in this function !!
object = tomo.block_fun(@imshift_generic,object_full, -total_shift, [], [], 1, object_ROI, par.binning, 'fft', interp_sign,struct('use_fp16', false));
Npix = ceil(Npix / par.binning);
if isscalar(Npix)
Nlayers = size(object,1);
Npix = [Npix,Npix,Nlayers];
end
Ngpu = max(1,length(par.GPU_list));
if ~isscalar(par.sino_weights) && ~isempty(par.sino_weights)
sino_weights = tomo.block_fun(@imshift_generic,par.sino_weights, -total_shift, Np_full(1:2), [], 1, object_ROI, par.binning, 'linear', ...
struct('use_GPU', true, 'full_block_size', Np_full));
else
sino_weights = 1;
end
if all(mean(mean(abs(sino_weights-mean(mean(sino_weights))))) < 1e-2)
sino_weights = 1;
else
sino_weights = real(sino_weights ./ max(max(sino_weights)));
end
% if ismatrix(par.illum_sum)
% sino_weights = sino_weights .* imshift_generic(par.illum_sum,[0,0],Np_full(1:2), [], 1, object_ROI, par.binning, 'linear');
% end
gamma_tot = 1;
gamma_x_tot = 0;
gamma_y_tot = 0;
[~,circulo] = apply_3D_apodization(ones(Npix), 0, 0, 10);
for ii = 1:par.Niter
progressbar(ii, par.Niter)
phase = tomo.block_fun(@unwrap_object,object,sino_weights, par, struct('use_fp16', false, 'verbose_level', 0));
if par.positivity
% "positivity" constraint, useful for normal tomo but it has to be false for laminography
phase = min(0, phase);
end
[Nlayers,width_sinogram,~]=size(phase);
% find rotation center so that it stays consistent after binning
par.rotation_center = [Nlayers, width_sinogram]/2;
if ~isempty(par.CoR_offset) % important for laminography
par.rotation_center(2) = par.rotation_center(2) + par.CoR_offset/par.binning;
end
%added by YJ
if ~isempty(par.CoR_offset_v) % important for laminography
par.rotation_center(1) = par.rotation_center(1) + par.CoR_offset_v/par.binning;
end
par.rotation_center = par.rotation_center - total_shift(:,[2,1])/par.binning;
[cfg, vectors] = astra.ASTRA_initialize(Npix,[Nlayers,width_sinogram],theta,par.lamino_angle,par.tilt_angle, [par.horizontal_scale ; par.vertical_scale]', par.rotation_center);
split = astra.ASTRA_find_optimal_split(cfg,Ngpu,1,'back');
% get FBP reconstruction from the initial guess
rec = -tomo.FBP(phase, cfg, vectors, [1,1,Ngpu], 'GPU', par.GPU_list, 'split_sub', split, 'verbose',0);
clear phase
rec = rec .* circulo; % remove effect of unmeasured regions around sample
if par.positivity
% positivity constraint
rec = max(0, rec);
end
% find model projections for given reconstruction
split = astra.ASTRA_find_optimal_split(cfg,Ngpu,1,'fwd');
proj = tomo.Ax_sup_partial(rec, cfg, vectors, [1,1,Ngpu], 'GPU', par.GPU_list, 'split_sub', split ,'verbose',0);
if par.auto_weighting
%% zero weights to regions with sample compared to air regions
Thresh = graythresh(rec(:));
% find roughly region where is only air
mask = single(rec < Thresh);
% find the corresponding region in the projection space
proj_mask = tomo.Ax_sup_partial(mask, cfg, vectors, [1,1,Ngpu], 'GPU', par.GPU_list, 'split_sub', split ,'verbose',0);
proj_blank = astra.Ax_partial(ones(Npix,'single'), cfg, vectors, [1,1,Ngpu], 'GPU', par.GPU_list, 'split_sub', split ,'verbose',0);
% define corresponding mask
W = ((abs(proj_mask - proj_blank) ./ proj_blank) < 1e-2) .* sino_weights;
%size(proj_blank)
% try to estimate weights direclty from the projections -> just to account for
% case when mask == 0 everywhere
W = W + 1e-1*exp(-abs(proj).^2 / mean(abs(proj(:))).^2 );
else
W = sino_weights;
end
W([1,end],:,:) = 0; % avoid boundary effects
% find phase ramp so that the masked regions are zero, if not possible, just enforce
% consistency between the object and projection
[object, gamma, gamma_x, gamma_y] = stabilize_phase(object, exp(-1i*proj.* (1-W)), W, 'fourier_guess', false);
gamma_tot = gamma_tot .* gamma;
gamma_x_tot = gamma_x_tot + gamma_x;
gamma_y_tot = gamma_y_tot + gamma_y;
end
if any(isnan(gamma_tot)) || any(isnan(gamma_x_tot)) || any(isnan(gamma_y_tot))
error('Phase removal would result in NaNs')
end
if par.auto_weighting
% store the produced mask -> false for regions of air
projection_mask = ((abs(proj_mask - proj_blank) ./ proj_blank) < 1e-2) & (sino_weights > 0);
end
%% calculate amplitude correction
%use median of the masked regions to estimate amplitude correction factor
% use of median means the mask needs to be correct only in > 50% of the area
aobject = abs(object);
if par.auto_weighting
aobject(~projection_mask) = nan;
end
amp_correction = reshape(nanmedian(reshape(aobject,[],Np_full(3))),1,1,[]);
% just to be sure that there is some mask everywhere
amp_correction(isnan(amp_correction)) = mean(mean(abs(object(:,:,isnan(amp_correction)))));
%% apply the phase and amplitude correction to the original stack_object array
verbose(0,'Applying phase ramp + amplitude correction')
% Run locally on CPU , too slow GPU upload / download
cfg = struct('verbose_level',1,'inplace', par.inplace_processing, 'use_GPU', true);
object_full = tomo.block_fun(@apply_ramp_shifted,object_full, gather(gamma_tot), gather(gamma_x_tot)/par.binning, gather(gamma_y_tot)/par.binning,total_shift,amp_correction, cfg);
verbose(0,'Done')
verbose(struct('prefix', 'template'))
end
%%% AUXILIARY FUNCTION FOR FAST PROCESSING ON GPU
function phase = unwrap_object(object,sino_weights, par)
% get initial guess
switch lower(par.unwrap_data_method)
case 'none' %added by YJ
phase = angle(object);
case 'fft_1d'
phase = math.unwrap2D_fft(object,2,par.air_gap/par.binning);
case 'fft_2d'
phase = math.unwrap2D_fft2(object,par.air_gap/par.binning,0,sino_weights,1);
otherwise
error('Undefined unwrapping method')
end
end
%%%% AUXILIARY FUNCTION FOR PARALLEL GPU PROCESSING
function object_full = apply_ramp_shifted(object_full,gamma, gamma_x, gamma_y, total_shift, amp_correction)
% shift the projection to the original (ie after loading) positions to around ramp artefacts
% around edges if the projection was shifted too much
% it needs 2D circular shift (is nearest neighbor interpolation), FFT is not needed
object_full = utils.imshift_linear(object_full, -total_shift(:,1),-total_shift(:,2), 'circ');
[M,N,~] = size(object_full);
xramp = pi*(linspace(-1,1,M))';
yramp = pi*(linspace(-1,1,N));
if ~isa(object_full, 'gpuArray')
object_full = auxfun(object_full, gamma, gamma_x, gamma_y, xramp, yramp, amp_correction);
else
% use inplace GPU calculation
object_full = arrayfun(@auxfun, object_full, gamma, M*gamma_x, N*gamma_y, xramp, yramp, amp_correction);
end
object_full = utils.imshift_linear(object_full, total_shift(:,1),total_shift(:,2), 'circ');
end
function object = auxfun(object, gamma, gamma_x, gamma_y, xramp, yramp, amp_correction)
object = object .* (gamma./ amp_correction); % correct global phase and also amplitude
object = object .* exp(1i*xramp.*gamma_x);
object = object .* exp(1i*yramp.*gamma_y);
end