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
+339
View File
@@ -0,0 +1,339 @@
% AX_PARTIAL backprojector that allows to split the full volume into smaller pieces
% composed tomography back-projector based on ASTRA toolbox
% can be used either for data in RAM or on GPU (automatically decided from class of volData)
% * volume is split based on "split" parameter, 1 == no splitting
% * Ax_partial tries to split data if GPU limits are exceeded (ie texture memory limits)
%
% vol_full = Atx_partial(projData, cfg, vectors,split,varargin)
%
% Inputs:
% **projData - array Nx x Ny x Nz of projected volume
% **cfg - config structure generated by ASTRA_initialize
% **vectors - orientation of projections generated by ASTRA_initialize
% **split - 3 or 4 elements vector, [split X, split Y, split Z, split angle ]
% *optional*
% **deformation_fields - 3x1 cell contaning 3D arrays of local deformation of the object
% **GPU - GPU id to be used for reconstruction
% **verbose - verbose = 0 : (default) quiet, verbose = 1: standard info , verbose = 2: debug
% **keep_on_GPU - if true keep reconstructed volume on GPU to make is faster, default == false (the safe option)
%
% *returns*
% ++vol_full - projection of volData
%
% recompile commands
% (Linux, GCC 4.8.5) mexcuda -outdir private ASTRA_GPU_wrapper/ASTRA_GPU_wrapper.cu ASTRA_GPU_wrapper/util3d.cu ASTRA_GPU_wrapper/par3d_fp.cu ASTRA_GPU_wrapper/par3d_bp.cu
% (Windows) mexcuda -outdir private ASTRA_GPU_wrapper\ASTRA_GPU_wrapper.cu ASTRA_GPU_wrapper\util3d.cu ASTRA_GPU_wrapper\par3d_fp.cu ASTRA_GPU_wrapper\par3d_bp.cu
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 vol_full = Atx_partial(projData, cfg, vectors,split,varargin)
import utils.*
import math.*
par = inputParser;
par.KeepUnmatched = true;
par.addOptional('deformation_fields', {}) % deformation_fields: 3x1 cell contaning 3D arrays of local deformation of the object
par.addOptional('GPU', []) % GPUs id to be used in reconstruction
par.addOptional('keep_on_GPU', false) % true - keep reconstructed volume on GPU to make is faster
par.addOptional('verbose', 0) %
par.parse(varargin{:})
r = par.Results;
if isempty(r.deformation_fields); r.deformation_fields = {}; end
%% check the inputs + check memory availibility on GPU
assert(gpuDeviceCount>0, 'No CUDA enabled GPU availible')
if ~( (isa(projData, 'gpuArray') && strcmp(classUnderlying(projData), 'single')) || ...
isa(projData, 'single') ) || ~isreal(projData)
error('Only single precision real input array supported')
end
if ~isempty(r.deformation_fields)
assert(any(numel(r.deformation_fields) == [3,6]), 'Deformation field expected as 3x1 or 6x1 cell array')
for i = 1:numel(r.deformation_fields)
if ~( (isa(r.deformation_fields{i}, 'gpuArray') && strcmp(classUnderlying(r.deformation_fields{i}), 'single')) || ...
isa(r.deformation_fields{i}, 'single'))
error('Only single precision for deformation fields is supported')
end
r.deformation_fields{i} = gpuArray(r.deformation_fields{i}); % move on GPU, they are usually small
end
r.deformation_fields = r.deformation_fields' ; % transpose to that array(:) results in sorted field
end
projSize = size(projData);
cfg.iProjAngles = size(vectors,1); % ignore value in config
gpu = gpuDevice();
if ~isempty(r.GPU) && gpu.Index ~= r.GPU(1)
% switch and !! reset !! GPU
if isa(projData, 'gpuArray'), error('Switching GPUs will reset content'); end
gpu = gpuDevice(r.GPU(1));
end
assert(cfg.iVolX*cfg.iVolY*cfg.iVolZ > 0, 'Volume is empty');
assert(numel(projSize) > 0, 'Projections are empty');
% input parameters check
if ismatrix(projData); projSize = [projSize,1]; end
assert(max(cfg.iProjU, cfg.iProjV) <= 4096, 'Sinogram exceed maximal size allowed by GPU (4096)')
assert(all(projSize==[cfg.iProjV,cfg.iProjU,cfg.iProjAngles]), 'Wrong inputs size')
assert(all(size(vectors)==[cfg.iProjAngles,12]), 'Wrong vectors size')
split_projections = cfg.iProjAngles > 1024 || ...
numel(projData)*4 > 1024e6 || ... ; % Data array is to large, try splitting
(length(split) > 3 && split(4) > 1); % split contains also angular split
% be sure that ASTRA wrapper is feeded by doubles !!
for i = fieldnames(cfg)'
cfg.(i{1}) = double(cfg.(i{1}));
end
vectors = double(vectors);
split=double(split);
if all(split == 1) && ~split_projections
assert(cfg.iVolX*cfg.iVolY*cfg.iVolZ*4 < min(4*double(intmax('int32')),gpu.AvailableMemory), 'Volume array is too large for GPU, use Atx_sup_partial');
%% in the simplest case call ASTRA_GPU_wrapper directly
if isa(projData, 'gpuArray'), r.keep_on_GPU = true; end % assume that the data should stay on GPU if provided
projData = matlab2astra(gpuArray(projData));
vol_full = astra.ASTRA_GPU_wrapper('bp',projData, cfg, vectors,[],r.deformation_fields{:});
if ~r.keep_on_GPU; vol_full = gather(vol_full); end
return
end
%% otherwise prepare data for split and call ASTRA_GPU_wrapper on subvolumes
split = ceil(max(1,split));
if isscalar(split)
split = split .* ones(3,1);
end
if length(split) < 4
split(4) = 1;
end
%% PREPARE VOLUME %%
Npix_full = [cfg.iVolX,cfg.iVolY,cfg.iVolZ];
Npix_small = Npix_full(:)./reshape(split(1:3),[],1);
cfg.iVolX = Npix_small(1);
cfg.iVolY = Npix_small(2);
cfg.iVolZ = Npix_small(3);
assert( all(mod(Npix_small,1)==0), sprintf('Volume array cannot be divided to %i %i %i cubes', split))
assert(prod(Npix_small)*4 < min(4*double(intmax('int32')),gpu.AvailableMemory), 'Volume array is too large for GPU, use Atx_sup_partial');
req_vol_memory = 4*(cfg.iVolX*cfg.iVolY*cfg.iVolZ) * (1+any(split(1:3)>1));
keep_volume_on_GPU = isa(projData, 'gpuArray') ||...
(gpu.AvailableMemory * 0.5 > req_vol_memory) && ...
( (cfg.iVolX*cfg.iVolY*cfg.iVolZ) < intmax('int32') );
% preallocate large array for results
if keep_volume_on_GPU || prod(split(1:3)) == 1
% transfer to GPU now
vol_full = gpuArray.zeros(Npix_full, 'single');
else
% keep the volume in RAM
vol_full = zeros(Npix_full, 'single');
end
%% PREPARE PROJECTIONS %%
% fix if the number of projections is > 1024, or arrays are too large
Nproj_groups = max(split(4),ceil(max([cfg.iProjAngles/1024, ...
cfg.iProjU*cfg.iProjV*cfg.iProjAngles*4 / min(1024e6,gpu.AvailableMemory-1.024e9), ...
cfg.iProjU * cfg.iProjV * cfg.iProjAngles / double(intmax('int32'))])));
% avoid some rounding issues during splitting
Nproj_groups = ceil(cfg.iProjAngles / floor(cfg.iProjAngles/Nproj_groups));
if r.verbose > 1
fprintf('Size of the full volume: %i %i %i\n', Npix_full)
fprintf('Size of the subvolume: %i %i %i\n', Npix_small)
fprintf('Size of the data: %i %i %i\n', size(projData))
fprintf('Free GPU memory: %3.2g%%\n', gpu.AvailableMemory/gpu.TotalMemory*100)
end
if Nproj_groups > 1
% fix if the number of projections is > 1024 (limitation of the ASTRA code )
% also if the dataset is too large (>1024MB), do automatic splitting along angles
for i = 1:Nproj_groups
ind = (1+(i-1)*ceil(cfg.iProjAngles/Nproj_groups)):i*ceil(cfg.iProjAngles/Nproj_groups);
ind = ind(ind <= cfg.iProjAngles);
proj_ind{i} = ind;
vectors_tmp{i} = vectors(ind,:);
cfg_tmp{i} = cfg;
cfg_tmp{i}.iProjAngles = length(ind);
end
cfg = cfg_tmp; vectors = vectors_tmp;
clear projData_tmp
else
Nproj_groups = 1;
cfg = {cfg};
vectors = {vectors};
end
if ~isempty(r.deformation_fields) && any(split(1:3) > 1)
error('Deformation field splitting not implemented')
end
if gpu.AvailableMemory > 4*(numel(projData)+ ...
cfg{1}.iProjU* cfg{1}.iProjV*cfg{1}.iProjAngles * (Nproj_groups>1) ...
+any(split>1)*prod(Npix_small)*(2 + ~isa(vol_full, 'gpuArray'))) && ...
numel(projData) < intmax('int32')
projData = gpuArray(projData); % move small blocks directly on GPU
end
inParpool = ~isempty(getCurrentTask());
iter = 1;
for k = 1:Nproj_groups
if Nproj_groups > 1
if length(proj_ind{k}) > 1 && ~inParpool && ~isa(projData, 'gpuArray') && exist('+tomo/get_from_array.m', 'file')
% use custom made MEX function from +tomo package, usually
% faster but use a lot of CPU
projData_small = tomo.get_from_array(projData, [], proj_ind{k}); % load subblock from projections
else
projData_small = projData(:,:,proj_ind{k}); % split the projections if needed
end
else
projData_small = projData;
end
if gpu.AvailableMemory < 2*4*numel(projData_small)
% in case of low GPU memory, transpose data in RAM
projData_small = gpuArray(matlab2astra(projData_small)); % transfer projections to GPU if not there yet
else
% move subblocks of the data on GPU
projData_small = gpuArray(projData_small);
projData_small = matlab2astra(projData_small);
end
if numel(projData_small) * 4 > 1024e6
error('Data exceeded maximal size of texture memory 1024MB, Increase "split" to reduce the projection size')
end
if gpu.AvailableMemory < prod(Npix_small)*4 && prod(split(1:3)) ~= 1
% memory needed to make CUDA array for texture memory
pause(0.1)
!nvidia-smi
whos
error('Too low GPU memory, avail: %3.2gGB / req: %3.2gGB, GPU %i/%i', gpu.AvailableMemory/1e9,prod(Npix_small)*4/1e9, gpu.Index, gpuDeviceCount)
end
for z = 1:split(3)
for x = 1:split(1)
for y =1:split(2)
if r.verbose > 0
progressbar(iter, prod(split(1:3))*Nproj_groups+1, 20);
end
pos = [x,y,z];
for n = 1:3
% find optimal shift of the subvolume
if mod(split(n),2)==1 %% odd
shift(n) = (pos(n) - ceil(split(n)/2))*Npix_small(n);
else
shift(n) = (pos(n) - ceil(split(n)/2)-1/2)*Npix_small(n);
end
end
vectors_tmp = vectors{k};
vectors_tmp(:,4:6) = bsxfun(@minus, vectors_tmp(:,4:6), shift);
if prod(split(1:3))== 1
req_mem = numel(projData_small)*4 ;
else
req_mem = 2*numel(projData_small)*4 ;
end
if gpu.AvailableMemory < req_mem
error('Too low GPU memory, avail: %3.2gGB / req: %3.2gGB, GPU %i/%i', gpu.AvailableMemory/1e9,req_mem/1e9, gpu.Index, gpuDeviceCount)
end
try
if prod(split(1:3))== 1
% no splitting, vol_small == vol_full -> write
% the backprojection directly to vol_full
% without copying -> no output arguments are needed
astra.ASTRA_GPU_wrapper('bp', projData_small, cfg{k}, vectors_tmp,vol_full,r.deformation_fields{:});
else
vol_small = astra.ASTRA_GPU_wrapper('bp', projData_small, cfg{k}, vectors_tmp,[],r.deformation_fields{:});
% if some volume split is needed, add the subvolume
% to the full volume
if ~keep_volume_on_GPU
vol_small = gather(vol_small); % return to RAM
end
if keep_volume_on_GPU || isa(vol_full, 'gpuArray') || ~exist('+tomo/add_to_3D_volume.m', 'file')
% return results to the large array
% use matlab to do the copying on GPU
vol_full =add_to_3D(vol_full, vol_small,([x,y,z]-1).*Npix_small');
else
% otherwise use paralelized CPU code
tomo.add_to_3D_volume(vol_full,vol_small, ([x,y,z]-1).*Npix_small', true);
end
end
catch err
if strcmpi(err.identifier,'parallel:gpu:array:OOM')
warning('Out of memory on GPU %i, try reset GPU or split the array onto smaller blocks', gpu.Index)
gpuDevice
end
reset(gpuDevice)
rethrow(err)
end
iter = iter+1;
if r.verbose > 0
progressbar(iter, prod(split(1:3))*Nproj_groups+1, 20);
end
end
end
end
end
if ~r.keep_on_GPU
vol_full = gather(vol_full);
end
end