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
@@ -0,0 +1,253 @@
% ALIGN_PROJECTIONS_TO_LOWRES_TOMOGRAM align projection from e.g. local tomography to low resolution
%
% [phase_diff_interior, phase_diff_lres_model, weight, tomogram_lowres] = align_projections_to_lowres_tomogram(stack_object, lres_tomo_path, par)
%
% estimate alignement for the interior tomogram. Resulting alignment is close to optimal
% but it should be further refined using an self-consistent method
%
% Inputs:
% **stack_object - complex valued projection from ptychography
% **lres_tomo_path - (string) path to nearfield low resolution
% tomogram saved as a .mat file (saved by function tomo.save_tomogram)
% The low resolution tomogram assumed to be saved as delta
% (real part of refractive index)
% and it has to include "par" structure with pixel scale
% and a conversion factor from delta to phase
% **par - tomography parameter structure
%
% *returns*:
% ++stack_object - aligned complex valued projections of the high resolution sinogram
% ++phase_diff_lres_model - phase difference fot the low resolution preview
% ++weight - weights between phase_diff_interior and phase_diff_lres_model, weights are provided as uint8 to save memory
% ++shifts - shifts that need to be applied on the phase_diff_interior to be aligned with the phase_diff_lres_model
% ++resolution_ratio - low resolution pixel size divided by the interior pixel size
% ++tomogram_lowres - low resolution tomogram
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 [stack_object, tomo_lres, win_small, shift, resolution_ratio, Nw_lres] = ...
align_projections_to_lowres_tomogram(stack_object, lres_tomo_path, theta, par)
import utils.*
import math.*
utils.verbose(struct('prefix', 'prealign'))
gpu = gpuDevice();
if ~isempty(par.GPU_list) && gpu.Index ~= par.GPU_list(1)
% switch and !! reset !! GPU
gpu = gpuDevice(par.GPU_list(1));
end
%% load low resolution tomogram
d = load(lres_tomo_path);
tomo_lres = -d.tomogram_delta ./ d.par.factor ; % revert delta back to phase
T = -graythresh(-tomo_lres(tomo_lres<0));
% calculate the maximal diamter of the local tomogram;
D = max(sum(radon(max(tomo_lres < T,[],3), 0:180)>0));
% calculate resolution ratio between low/ high res tomogram
resolution_ratio = d.par.pixel_size / par.pixel_size;
clear d
[Nx,Ny,Nangles] = size(stack_object);
Npix_lres = size(tomo_lres);
% size of the low resolution projections to be generated
Nw_lres = ceil([Npix_lres(3), 1.1*D]); % add 10% extra to the maximal diameter
Nw_full = ceil(Nw_lres*resolution_ratio); % get the corresponding size of the low res tomogram would be measured in full resolution
%% %%%%%%%% Get computed sinogram
% get rough and rather emptirical estimation of the reliability of the interior tomograms
win = Garray(single(par.illum_sum));
win = utils.imgaussfilt2_fft(sqrt(win), par.asize(1)/20);
win = 2-2./(1+ win.^2 / max(win(:).^2)); % limit the weights to 0-1 range
win = win .* tukeywin(Nx) .* tukeywin(Ny)';
gtomo_lres = Garray(tomo_lres);
%% center properly the reconstruction
for ii = 1:5
[x,y,mass] = center(sqrt(max(0,-gtomo_lres))+eps); % abs seems to be more stable than max(0,x) even for missing wedge or laminography
% more robust estimation of center
rec_center(1) = gather(mean(x.*mass)./mean(mass));
rec_center(2) = gather(mean(y.*mass)./mean(mass));
% avoid drifts of the reconstructed volume
gtomo_lres = tomo.block_fun(@imshift_fft, gtomo_lres, -rec_center(1), -rec_center(2));
end
tomo_lres = gather(gtomo_lres);
verbose(-1,'Aligning projections to a low resolution tomogram')
% get block size to work roughly with 0.2GB arrays
Nblocks = ceil((prod(Nw_full)*Nangles*4*2*12)/gpu.AvailableMemory);
% prepare the local tomo object downsampled to resolution of the low resolution tomogram
win_small = interpolate_linear(win,ceil([Nx,Ny]/resolution_ratio));
win_small = gather(uint8(win_small*255));
[stack_object, shift] = ...
tomo.block_fun(@find_alignment,stack_object, gtomo_lres,theta', par, resolution_ratio, win_small, Nw_lres, struct('Nblocks', Nblocks));
verbose(-1,'Pre-alignment done')
shift = gather(shift);
%% REPORT ALIGNMENT RESULTS
figure()
range = [round(Ny-par.asize(2))/2, round(Nx-par.asize(1))/2];
range = repmat(range, Nangles,1);
subplot(1,2,1)
errorbar(theta, shift(:,1)*par.pixel_size*1e6, range(:,1)*par.pixel_size*1e6 , '.')
title('Horizontal shift and FOV')
axis tight
ylabel('Estimated shift [um]')
xlabel('Angle [deg]')
grid on
subplot(1,2,2)
errorbar(theta, shift(:,2)*par.pixel_size*1e6, range(:,2)*par.pixel_size*1e6 , '.')
title('Vertical shift and FOV')
axis tight
ylabel('Estimated shift [um]')
xlabel('Angle [deg]')
grid on
plotting.suptitle('Shifts estimated from initial low-res tomogram')
drawnow
utils.verbose(struct('prefix', 'template'))
end
function [stack_object, total_shift] = find_alignment(stack_object, tomo_lres,theta, par, resolution_ratio, win_small, Nw_lres)
import utils.*
import math.*
Npix_lres = size(tomo_lres);
Nw_local = [size(stack_object,1), size(stack_object,2)];
[cfg_lres, vectors_lres] = astra.ASTRA_initialize(Npix_lres,Nw_lres,theta, par.lamino_angle, 0, 1);
% find optimal split of the dataset for given GPU
split = astra.ASTRA_find_optimal_split(cfg_lres);
% forward projection model
model = astra.Ax_partial(tomo_lres,cfg_lres, vectors_lres,split,'verbose', 0);
model = exp(1i*model);
% get phase difference
diff_model = math.get_phase_gradient_1D(model, 2);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% FIND RELATIVE SHIFT BETWEEN THE DOWNSCALED MODEL AND THE LOW RES RECONSTRUCTION %%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
win_small = single(win_small)/255;
object_small = interpolateFT_centered(stack_object,ceil(Nw_local/resolution_ratio), -1);
object_small = crop_pad(object_small ./ (abs(object_small) + 1e-3) .* win_small,Nw_lres);
% get at least some initial phase ramp removal
[object_small, gamma_tot, gamma_tot_x, gamma_tot_y] = stabilize_phase(object_small, model, 'weights', abs(object_small));
% find relative shift of the patch and low resolution model
total_shift = 0;
W = abs(object_small);
% perform crosscorrelation between phase derivatives to find
% the optimal shift
shift = find_shift_fast_2D( W.* diff_model, W.* math.get_phase_gradient_1D(object_small, 2));
% shift to the estimated position
object_small = imshift_fft(object_small, shift);
total_shift = total_shift + shift;
%% remove phase ramp using the low res tomogram
for ii = 1:5
% refine the phase ramp, we need high precision -> do several iterations
[object_small, gamma, gamma_x, gamma_y] = stabilize_phase(object_small, model,'weights', abs(object_small), 'fourier_guess', false);
gamma_tot = gamma_tot .* gamma;
gamma_tot_x = gamma_tot_x + gamma_x;
gamma_tot_y = gamma_tot_y + gamma_y;
end
% apply results from low resolution to the full resolution object
stack_object = apply_ramp(stack_object,gamma_tot, (gamma_tot_x)/resolution_ratio, (gamma_tot_y)/resolution_ratio );
total_shift = total_shift * resolution_ratio;
% weight = uint8(255*win_small);
% weight = uint8(255*real(abs(object_small)));
% weight(weight<10) = 0; % remove interpolation artefacts from regions far from measured array
end
function object_full = apply_ramp(object_full,gamma, gamma_x, gamma_y )
[M,N,~] = size(object_full);
xramp = pi*(linspace(-1,1,M))';
yramp = pi*(linspace(-1,1,N));
if ~isa(object_full, 'gpuArray')
object_full = bsxfun(@times,object_full , gamma);
object_full = bsxfun(@times,object_full , exp(1i*bsxfun(@times,xramp, M*gamma_x)));
object_full = bsxfun(@times,object_full , exp(1i*bsxfun(@times,yramp, N*gamma_y)));
else
% use inplace GPU calculation
object_full = arrayfun(@auxfun, object_full, gamma, M*gamma_x, N*gamma_y, xramp, yramp);
end
end
function object = auxfun(object, gamma, gamma_x, gamma_y, xramp, yramp)
object = object .* gamma;
object = object .* exp(1i*xramp*gamma_x);
object = object .* exp(1i*yramp*gamma_y);
end
+199
View File
@@ -0,0 +1,199 @@
% MERGE_WEIGHTS_INTERIORED_ARRAYS auxiliar function for interior tomography for block processing by tomo.block_fun
% that adds up two arrays as X*W + (1-W)*Y, and unwrap resulting phase difference and
% return the phase
%
% phase_diff = merge_weights_interiored_arrays(stack_object, phase_diff_lowres, weights_interior,shift, ROI, param, exterior_weights_interior)
%
% Inputs:
% **stack_object - complex valued projections
% **phase_diff_lowres - phase gradient for the low resolution tomogram
% **weights_interior - relative weights_interiors of each region of the tomogram (denotes interior region )
% **shift - shifts applied to the phase gradient
% **ROI - region to be unwrapped
% **param - tomography parameter structure
% **exterior_weights_interior - scalar, relative weights_interior of the low res region during alignment
% Outputs:
% ++phase - unwrapped phase of the low + high resolution sinogram together
% ++weights_interiors - importance weights_interiors used for alignment, ie gives much smaller
% weights_interior to the low resolution region compared to the interion projection part
% based on the "exterior_weights_interior" input value
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 [phase_diff, weights_interiors] = merge_and_unwrap_sinograms(stack_object, tomo_lres, weights_interior,shift, theta, ROI, Nw_lres, param)
Npix_lres = size(tomo_lres);
Npix_full = ceil(Nw_lres*param.resolution_ratio);
% get phase difference of the interior tomo
phase_diff_interior = math.get_phase_gradient_1D(stack_object,2,0);
avg_shift = round(mean(shift));
Npix_partial = [size(stack_object,1), size(stack_object,2)]+2*ceil(max(abs(shift-avg_shift)));
Npix_partial = min(Npix_partial, Npix_full);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% (1) UPSAMPLE WEIGHTS TO THE FULL RESOLUTION %%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if isa(weights_interior, 'uint8') || ( isa(weights_interior, 'gpuArray') && strcmpi(classUnderlying(weights_interior),'uint8'))
% convert to single precision
weights_interior = single(weights_interior-1)/255;
end
if size(weights_interior,1) ~= size(stack_object,1) || size(weights_interior,2) ~= size(stack_object,2)
% assume that the weights_interior are stored downsampled to same memory
weights_interior = utils.interpolate_linear(weights_interior, size(stack_object));
end
% pad arrays with enough space for save shifting
phase_diff_interior = utils.crop_pad(phase_diff_interior , Npix_partial);
weights_interior = utils.crop_pad(weights_interior , Npix_partial);
% apply (shifts-avg_shift) on the interior tomogram and its weights_interiors
[phase_diff_interior, weights_interior] = shift_interior_sinograms(phase_diff_interior, weights_interior, shift-avg_shift);
[cfg_lres, vectors_lres] = astra.ASTRA_initialize(Npix_lres,Nw_lres,theta, 90, 0, 1);
% find optimal split of the dataset for given GPU
split = astra.ASTRA_find_optimal_split(cfg_lres);
% forward projection model
model = astra.Ax_partial(tomo_lres,cfg_lres, vectors_lres,split,'verbose', 0);
% get phase difference
phase_diff_lowres = math.get_phase_gradient_1D( exp(1i*model), 2, 0);
clear model
% use FFT centred upsampling to keep it accurate
%phase_diff_lowres = utils.interpolateFT_centered(phase_diff_lowres / param.resolution_ratio, Npix_full,1);
% linear interpolation is much faster but it introduces bias
bias = 1-1/param.resolution_ratio;
phase_diff_lowres = utils.imshift_fft(phase_diff_lowres,-[bias, bias]);
phase_diff_lowres = utils.interpolate_linear(phase_diff_lowres / param.resolution_ratio, Npix_full);
% pad arrays to full size and apply the average shift
phase_diff_interior = utils.imshift_fast(phase_diff_interior ,-avg_shift(1),-avg_shift(2), Npix_full);
weights_interior = utils.imshift_fast(weights_interior ,-avg_shift(1),-avg_shift(2), Npix_full);
alpha = 0.05; % avoid effects from weak regions of the weights
weights_interior = max(0, weights_interior-alpha)/(1-alpha); % remove artefacts from the FFT shift
% merge tomogram based on provided weights_interior
phase_diff = arrayfun(@merge_arrays,phase_diff_interior, phase_diff_lowres, weights_interior);
if ~isempty(param.vert_range)
% find optimal vertical range
Nlayers = length(ROI{1});
vrange0 = param.vert_range([1,end]);
vrange(2) = min(vrange0(2),Nlayers);
vrange(1) = max(1,vrange0(1));
% help with splitting in ASTRA (GPU memory limit)
vrange_center = ceil(mean(vrange));
estimated_split_factor = 4;
Nvert = floor((vrange(2)-vrange(1)+1) / estimated_split_factor)*estimated_split_factor;
vrange(1) = ceil(vrange_center - Nvert/2);
vrange(2) = floor(vrange_center + Nvert/2-1);
vrange = vrange(1):vrange(2);
else
vrange = ':';
end
% select only object_ROI -> make it easily splitable to GPU and avoid
% edge artefacts
phase_diff = phase_diff(ROI{1}(vrange),ROI{2},:);
weights_interior = weights_interior(ROI{1}(vrange),ROI{2},:);
% apply binning
if param.binning > 1
phase_diff = utils.interpolateFT_centered(phase_diff,ceil(size(phase_diff)/param.binning/2)*2, 1); % accurate interpolation using FFT
end
if strcmpi(param.unwrap_data_method, 'fft_1d')
phase_diff = math.unwrap2D_fft(phase_diff,2,param.air_gap,0);
end
% provide weights_interiors to be used for alignment
weights_interiors = param.exterior_weight + (1-param.exterior_weight)*weights_interior;
weights_interiors = utils.interpolate_linear(weights_interiors, ceil(size(weights_interiors)/10));
% keep in uint8 to save memory
weights_interiors = uint8(weights_interiors*255);
end
function C = merge_arrays(A, B, W)
W = max(0, min(1,W));
C = A.*W + (1-W).*B;
end
% FUNCTION [phase_diff_interior,weights_interior ] = shift_interior_sinograms(phase_diff_interior, weights_interior, shift )
% auxiliar function for interior tomography
% Inputs:
% phase_diff_interior - phase gradient for the interior tomo
% weights_interior - relative weights_interiors of each region of the tomogram (denotes interior region )
% shift - shifts applied to the phase gradient
% Outputs:
% phase_diff_interior - phase difference for only the interior part
% weights_interior - reliability weights_interiors estimated from the original field of view and shifted accordingly
function [phase_diff_interior,weights_interior ] = shift_interior_sinograms(phase_diff_interior, weights_interior, shift )
% shift phase and weights_interiors in parallel to save time and avoid numerical problems at the edges
phase_diff_interior = utils.imshift_fft(weights_interior .* exp(1i*phase_diff_interior),shift);
% get amplitude (weights_interiors)
weights_interior = abs(phase_diff_interior);
% get phase = phase diff
phase_diff_interior = angle(phase_diff_interior);
end