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
+326
View File
@@ -0,0 +1,326 @@
% ALIGN_TOMO_GLOBAL_PARAMETERS find center of rotation or lamino angle or tilt of the projections
% plot various statistics that may (and may not) help to decided which
% parameter provides best reconstruction
%
% align_tomo_global_parameters(sinogram,angles, Npix, par, varargin )
%
% Inputs:
% **sinogram_0 - real value sinogram (ie not diff)
% **angles - angle in degress
% **Npix - size of the reconstructed field
% **par - parameter structure -> params, INPUTS DESCRIBED IN CODE
% Outputs:
% (none)
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1!!
% updates should be done manually by user if one is confident that
% the newly estimated geometry is definitelly leading to improved
% reconstruction
% !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
% it very useful for quick verification that the global geometry is ok
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 align_tomo_global_parameters(sinogram,angles, Npix, params, varargin )
import tomo.*
import utils.*
import math.*
utils.verbose(struct('prefix', 'align'))
parser = inputParser;
parser.addParameter('binning', 4 , @isint )
parser.addParameter('deformation_fields', []) % assume deformated sample and use these fielresid_sino
parser.addParameter('plot_results', true , @islogical ) % plot results
parser.addParameter('verbose', 1 , @isnumeric ) % change verbosity of the code
parser.addParameter('is_laminography', false , @isnumeric ) % change verbosity of the code
parser.addParameter('search_range', [-100,100] , @isnumeric ) % search range for the center of rotation
parser.addParameter('num_grid_points', 100 , @isnumeric ) % number of grid points
parser.addParameter('search_parameter', 'center_of_rotation' , @(x)(ismember(lower(x), {'center_of_rotation', 'center_of_rotation_y', 'lamino_angle', 'tilt_angle', 'rot_angle', 'shear_angle' })) )
parser.addParameter('CoR_offset', 0, @isnumeric);
parser.addParameter('CoR_offset_v', 0, @isnumeric);
parser.addParameter('lamino_angle_offset', 0, @isnumeric);
parser.addParameter('tilt_angle_offset', 0, @isnumeric);
parser.addParameter('rotation_angle_offset', 0, @isnumeric);
parser.addParameter('shear_angle_offset', 0, @isnumeric);
parser.addParameter('selected_roi', {}, @iscell);
parser.addParameter('usecircle', false, @islogical);
parser.addParameter('showed_layer_id', [], @isnumeric);
parser.KeepUnmatched = false;
parser.parse(varargin{:})
r = parser.Results;
% load all to the param structure
par = params;
for name = fieldnames(r)'
if ~isfield(par, name{1}) || ~ismember(name, parser.UsingDefaults) % prefer values in param structure
par.(name{1}) = r.(name{1});
end
end
% load all to the param structure
verbose(1,'Starting %s estimation', r.search_parameter)
verbose(1,['Binning: ', num2str(r.binning)])
sinogram = tomo.block_fun(@imreduce,sinogram,r.selected_roi,r.binning);
%% %%%%%%%%%%%%%%%% initialize astra %%%%%%%%%%%%%%%%
[Nlayers,width_sinogram,~] = size(sinogram);
%% %%%%%%%%%% initialize GPU %%%%%%%%%%%%%%%
gpu = gpuDevice();
if ~isempty(par.GPU_list) && gpu.Index ~= par.GPU_list(1)
% switch and !! reset !! GPU
gpu = gpuDevice(par.GPU_list(1));
end
% ASTRA needs the reconstruction to be dividable by 32 othewise there
% will be artefacts in left corner
Npix = ceil(Npix/r.binning);
if isscalar(Npix)
Npix = [Npix, Npix, Nlayers];
elseif length(Npix) == 2
Npix = [Npix, Nlayers];
end
if isempty(r.showed_layer_id)
r.showed_layer_id = ceil(Npix(3)/2);
end
% !! important for binning => account for additional shift of the center
% of rotation after binning, for binning == 1 the correction is zero
rotation_center = [Nlayers, width_sinogram]/2;
% rotation_center(2) = rotation_center(2) + 0.5*(1-1/r.binning) ;
if ~isempty(r.CoR_offset)
rotation_center(2) = rotation_center(2) + r.CoR_offset/r.binning;
end
if ~isempty(r.CoR_offset_v)
rotation_center(1) = rotation_center(1) + r.CoR_offset_v/r.binning;
end
% !! important for binning => account for additional shift of the center
% of rotation after binning, for binning == 1 the correction is zero
if par.is_laminography
padding = 'symmetric';
else % Im really not sure why it differs from normal tomo, but I have it empirically tested
padding = 0;
end
CoR_offsets_x = 0 ;
CoR_offsets_y = 0 ;
lamino_angles_offsets = 0 ;
tilt_angle_offsets = 0 ;
rot_angle_offsets = 0;
shear_angle_offsets = 0;
search_grid = linspace(r.search_range(1),r.search_range(2),r.num_grid_points);
switch lower(r.search_parameter)
case 'center_of_rotation'
CoR_offsets_x = search_grid;
case 'center_of_rotation_y'
CoR_offsets_y = search_grid;
case 'lamino_angle'
lamino_angles_offsets = search_grid;
case 'tilt_angle'
tilt_angle_offsets = search_grid;
case 'rot_angle'
rot_angle_offsets = search_grid;
case 'shear_angle'
shear_angle_offsets = search_grid;
otherwise
error('Missing option, choose from: center_of_rotation, lamino_angle, tilt_angle, rot_angle')
end
if par.usecircle && Npix(1) == Npix(2)
radial_smooth_apodize= 10;
apodize = 20;
[~,circulo] = apply_3D_apodization(ones(Npix(1:2)), apodize, 0, radial_smooth_apodize);
end
% generate dummy config
[cfg, vectors] = ...
astra.ASTRA_initialize(Npix, [Nlayers, width_sinogram],angles );
% use FBP function to provide already filtered sinogram
utils.verbose(0,'Filtering sinogram')
[~,sinogram_filtered] = FBP(sinogram, cfg, vectors, 1,...
'GPU', par.GPU_list, 'verbose', 0, 'keep_on_GPU', true, ...
'filter', par.filter_type, 'filter_value', par.freq_scale, ...
'padding', padding, 'only_filter_sinogram', true);
clear sinogram
% plotting.smart_figure(1)
clf
utils.verbose(0,'Parameter scan ... ')
for ii = 1:length(search_grid)
[cfg, vectors] = ...
astra.ASTRA_initialize(Npix, [Nlayers, width_sinogram],...
angles + r.rotation_angle_offset+rot_angle_offsets(min(ii,end)), ...
r.lamino_angle_offset + par.lamino_angle + lamino_angles_offsets(min(ii,end)),...
r.tilt_angle_offset + par.tilt_angle + tilt_angle_offsets(min(ii,end)), 1, ...
rotation_center + [(CoR_offsets_y(min(ii,end)))/r.binning,(CoR_offsets_x(min(ii,end)))/r.binning], ...
r.shear_angle_offset + par.skewness_angle + shear_angle_offsets(min(ii,end)) );
% find optimal split of the dataset for given GPU
split = astra.ASTRA_find_optimal_split(cfg, length(par.GPU_list),1,'back');
%% backproject the already filtered sinogram method
verbose(2,'FBP')
rec = tomo.Atx_sup_partial(sinogram_filtered, cfg, vectors, [1,1,length(par.GPU_list)],...
'GPU', par.GPU_list, 'verbose', 0, 'split_sub', split);
if par.usecircle && Npix(1) == Npix(2)
rec = rec .* circulo;
end
plotting.smart_figure(144)
plotting.imagesc3D(rec, 'init_frame', r.showed_layer_id)
axis image off
colormap bone
title(sprintf('Lamino global param search: step id %i/%i', ii, length(search_grid)))
drawnow
rec_preview_all(:,:,ii) = rec(:,:,max(1, min(end, r.showed_layer_id)));
[dX, dY] = math.get_img_grad(rec);
% estimate total variation
TV(ii) = gather(mean(mean2(abs(dX) + abs(dY))));
STD(ii) = gather(std(rec(:)));
SP(ii) = gather(sparseness(abs(dX) + abs(dY)));
utils.progressbar(ii, r.num_grid_points)
end
Nfine = 1e3;
fine_offsets = linspace(r.search_range(1),r.search_range(2),Nfine);
TV = (TV - mean(TV)) / std(TV);
STD = (STD - mean(STD)) / std(STD);
SP = (SP - mean(SP)) / std(SP);
spline_TV = interp1(search_grid, TV, fine_offsets, 'spline');
spline_STD = interp1(search_grid, STD, fine_offsets, 'spline');
spline_SP = interp1(search_grid, SP, fine_offsets, 'spline');
figure()
hold all
plot(fine_offsets, spline_TV, '-r')
plot(fine_offsets, spline_STD, '-b')
plot(fine_offsets, spline_SP, '-G')
plot(search_grid, TV, 'or')
plot(search_grid, STD, 'ob')
plot(search_grid, SP, 'oG')
hold off
xlabel(['Required additional correction of ', r.search_parameter], 'Interpreter', 'none')
ylabel('Value')
legend({'Total variation', 'Standard deviation', 'Sparsity'})
grid on
title(sprintf('Final score for global parameter search: %s', r.search_parameter), 'interpreter', 'none')
drawnow
figure
plotting.imagesc3D(rec_preview_all)
axis image off
colormap bone
title(sprintf('Preview of reconstruction for all param steps: step id %i/%i', ii, length(search_grid)))
drawnow
end
%
% function sinogram = unwrap_data(sinogram, method, boundary)
% switch lower(method)
% case 'fft_1d'
% % unwrap the data by fft along slices
% sinogram = -math.unwrap2D_fft(sinogram, 2, boundary);
% % case 'fft_2d'
% % % unwrap the data by 2D fft along slices
% % sinogram = -math.unwrap2D_fft_split(sinogram, boundary);
% case {'none', 'diff'}
%
% otherwise
% error('Missing method')
% end
% end
function spars = sparseness(x)
%Hoyer's measure of sparsity for a vector
% from scipy.linalg import norm
order_1 = 1;
order_2 = 2;
x = x(:);
sqrt_n = sqrt(length(x));
spars = (sqrt_n - norm(x, order_1) / norm(x, order_2)) / (sqrt_n - order_1);
end
function img = imreduce(img, ROI, binning)
import math.*
import utils.*
isReal = isreal(img);
% crop the FOV after shift and before "binning"
if ~isempty(ROI)
img = img(ROI{:},:); % crop to smaller ROI if provided
% apply crop after imshift_fft
end
Np = size(img);
% perform FT interpolation instead of binning
img = interpolateFT_centered(img, ceil(Np(1:2)/binning/2)*2, -1);
if isReal; img = real(img); end
end