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
+317
View File
@@ -0,0 +1,317 @@
% [U,S,V,rec_all, rec_all_0] = SVD_regularization(sinogram, theta, Niter_SVD, Npix, reconstruct_ind,max_projections,par)
% perform spectral SVD analysis and SART based reconstruction
% Inputs:
% sinogram - unwrapped sinogram
% theta - projection angles
% Niter_SVD - number of iteration of the SVD optimization
% Npix - (3x1 int), size of the output volume
% reconstruct_ind - (int array) list of angles that will be considered for reconstruction
% max_projections - maximal number of projections selected for each
% energy step. Set "inf" to ignore this limit. It is useful to
% equialize the weight for each energy step when the distribution is
% highly unequal
% par - tomogrpahy paramter structure
% Outputs:
% U,S,V - SVD vectors
% rec_all - regularized SART reconstructions for each energy
% rec_all_0 - oriignal FBP reocnstructions before SVD regularization
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 [U,S,V,rec_all, rec_all_0] = SVD_regularization(sinogram, theta, Niter_SVD, Npix, reconstruct_ind,max_projections,par)
% internal parameters
SART_grouping = 25;
Niter_SART = 10;
Nmodes = 2;
Nangles = length(theta);
E_all = unique(par.energy(ismember(1:Nangles, reconstruct_ind) ));
Nenergy = length(E_all);
tomogram_edensity = cell(Nenergy,1);
for ii = 1:Nenergy
utils.progressbar(ii,Nenergy)
% choose projections to process
rec_ind = find(par.energy == E_all(ii) & ismember(1:Nangles,reconstruct_ind)'); % use only some angles
% downsample to the requested "max_projections"
rec_ind = rec_ind(1:max(1,ceil(length(rec_ind)/max_projections)):end);
num_proj_all(ii) = length(rec_ind);
if isempty(rec_ind)
tomogram_edensity{ii} = zeros(Npix,Npix,size(sinogram,1),'single');
continue;
end
%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%
[Nlayers,width_sinogram,~] = size(sinogram);
CoR = [Nlayers,width_sinogram]/2; % there is 0.5px shift between fft_1d/fft_2d vs none
[cfg, vectors] = astra.ASTRA_initialize([Npix,Npix, Nlayers],[Nlayers,width_sinogram],theta,par.lamino_angle,par.tilt_angle,1,CoR);
% find optimal split of the dataset for given GPU
split = astra.ASTRA_find_optimal_split(cfg, length(par.GPU_list), 1);
% new FBP code
tomogram = tomo.FBP_zsplit(sinogram, cfg, vectors, split,'valid_angles',rec_ind,...
'determine_weights', true, ...
'GPU', par.GPU_list,'filter',par.filter_type, 'filter_value',par.freq_scale, 'verbose',0);
% Caclulate delta tomogram
par.lambda = 1.234e-9 / E_all(ii);
par.factor=par.lambda/(2*pi*par.pixel_size);
par.factor_edensity = 1e-30*2*pi/(par.lambda^2*2.81794e-15);
% get full reconstruction (for FBP is sum already final tomogram)
% calculate complex refractive index
tomogram_edensity{ii} = gather(tomogram*par.factor*par.factor_edensity);
end
% quantity = min(max_projections, hist(par.energy, E_all));
rec_all_0 = gather(cat(4, tomogram_edensity{:}));
% get a weighted average
mrec = max(0,mean(rec_all_0 .* reshape(num_proj_all,1,1,1,[]) ,4)) / mean(num_proj_all);
mask = mrec > graythresh(mrec);
%%
mask = imopen(mask, strel('disk',5));
mask = imdilate(mask, strel('disk',5));
mask = imfill(mask, 'holes');
mask = Garray(mask);
constraint_fnct = @(x)(max(0,x.*mask));
for ii = 1:Nenergy
tomogram_edensity{ii} = constraint_fnct(tomogram_edensity{ii});
end
gpu = gpuDevice;
for iter = 1:Niter_SVD
utils.verbose(1,' ====== Iteration %i ==== ', iter)
rec_all = cat(4, tomogram_edensity{:});
utils.verbose(2,'Available GPU memory = %3.1fGB', gpu.AvailableMemory/1e9)
plotting.smart_figure(4)
plotting.imagesc3D(cat(2, squeeze(rec_all(:,:,ceil(end/2),:)), squeeze(rec_all_0(:,:,ceil(end/2),:))));
axis off image, colormap bone
caxis(gather(math.sp_quantile(rec_all, [0.001, 0.9999], 10)));
title('Left - SVD refined , Right - original FBP')
rec_all = reshape(rec_all, [], Nenergy);
% add more modes progressivelly for higher iteration number
Nmodes_tmp = min(floor(iter^(1/3)), Nmodes);
%%%%%%%%%%%%%%%%%%%% APPLY SVD CONSTRAINT %%%%%
[U,S,V] = math.fsvd(rec_all, Nmodes_tmp);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% enforce smoothness of the first V vector
if iter > 1
% plot convergence progress
try
err_total(iter,:) = gather(sqrt(sum((U*S*V'-rec_all).^2)));
catch
keyboard
end
plotting.smart_figure(3)
loglog(mean(err_total'))
axis tight
grid on
xlabel('Iteration')
ylabel('Residuum between SVD model and reconstruction')
drawnow
end
% relax = 0.1;
% V(:,1) = V(:,1) *(1-relax) + relax * polyval(polyfit(1:Nenergy,V(:,1)',1), 1:Nenergy)';
%
% if Nmodes_tmp == 3
% keyboard
%
% end
if iter > 1
% plot SVD evolution
best_angle = fminsearch(@(x)get_rotation_score(x,U,S,V), zeros(3,1));
R = rotation_matrix_3D(best_angle(1),best_angle(2),best_angle(3));
R = R(1:Nmodes_tmp,1:Nmodes_tmp); % use only number of modes that is needed
Vplot = (R*V')'; % apply rotation
Uplot = U*S*inv(R); % apply inverse rotation
% flip sign to for convinince
sign_U = sign(mean(Uplot));
Uplot = Uplot .* sign_U;
Vplot = Vplot .* sign_U;
rec_all = gather(rec_all);
U_plot = Uplot ./ quantile(Uplot(1:32:end,:), 0.99);
U_plot = reshape(U_plot, [size(tomogram_edensity{1}),Nmodes_tmp]);
U_plot = reshape(permute(U_plot,[1,2,4,3]), [size(U_plot,1),size(U_plot,2)*size(U_plot,4),size(U_plot,3)]);
plotting.smart_figure(5)
subplot(2,1,1)
plotting.imagesc3D(U_plot, 'init_frame', size(U_plot,3)/2)
axis image off
colormap bone
caxis(gather(math.sp_quantile(U_plot, [0.001, 0.995], 10)));
title('Topos SVD vectors')
subplot(2,1,2)
plot(E_all,Vplot, '-o')
hold all
offset = min(min(Vplot ));
range = max(max(Vplot )) - offset;
bar(E_all,num_proj_all / max(num_proj_all)*range*0.2+ offset, 'facecolor', 'none', 'Basevalue', offset)
hold off
title('Chronos SVD vectors')
axis tight
grid on
xlabel('Energy [keV]')
ylabel('S*V''')
Energy = diag(S);
Energy = Energy / sum(Energy);
for kk = 1:Nmodes_tmp
legend_txt{kk} = sprintf('E=%3.2g%%', Energy(kk)*100);
end
legend(legend_txt ,'location','best')
clear U_plot
end
% ather from GPU to save memory
% Uout = gather(Uout);
% apply SART refinement
for ii = 1:Nenergy
utils.progressbar(ii,Nenergy)
% choose projections to process
rec_ind = find(par.energy == E_all(ii) & ismember(1:Nangles,reconstruct_ind)'); % use only some angles
% downsample to the requested "max_projections"
rec_ind = rec_ind(1:ceil(length(rec_ind)/max_projections):end);
if isempty(rec_ind); continue; end
[cache_SART,cfg_SART] = tomo.SART_prepare(cfg, vectors(rec_ind,:), SART_grouping, 'keep_on_GPU', true);
rec = U*S*V(ii,:)';
rec = reshape(rec,size(tomogram_edensity{1}));
par.lambda = 1.234e-9 / E_all(ii);
par.factor=par.lambda/(2*pi*par.pixel_size);
par.factor_edensity = 1e-30*2*pi/(par.lambda^2*2.81794e-15);
% get full reconstruction (for FBP is sum already final tomogram)
% calculate complex refractive index
rec = rec / (par.factor*par.factor_edensity);
rec = Garray(rec);
sino = Garray(sinogram(:,:,rec_ind));
clear err
for jj = 1:Niter_SART
[rec,err(jj,:)] = tomo.SART(rec, sino, cfg_SART, ...
vectors(rec_ind,:),cache_SART, 'relax', 0, 'constraint', constraint_fnct);
end
% apply some weak total variation to help agsints undersampling
% artefacts
rec = regularization.local_TV3D_chambolle(rec, 1e-6, 10);
tomogram_edensity{ii} = (rec*par.factor*par.factor_edensity);
% plotting.smart_figure(1)
% subplot(1,3,1)
% plotting.imagesc3D(tomogram_edensity{ii}); axis off image, colormap bone
% caxis([0,1])
% subplot(1,3,2)
% plot(theta(rec_ind), 'o-')
% title(['Nangles ', num2str(length(rec_ind)) ])
% subplot(1,3,3)
% loglog(mean(err'))
% drawnow
end
clear rec sino cache_SART
end
rec_all = reshape(rec_all, [size(tomogram_edensity{1}), Nenergy]);
% get recosntructions to RAM
U = gather(U);
S = gather(S);
V = gather(V);
rec_all = gather(rec_all);
end
function score = get_rotation_score(x,U,S,V)
R = rotation_matrix_3D(x(1),x(2),x(3));
Nmodes = size(S,1);
V = (R(1:Nmodes,1:Nmodes)*V')';
score = gather(norm(V(:,1) - mean(V(:,1))));
end