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
+626
View File
@@ -0,0 +1,626 @@
% ATX_SUP_PARTIAL distributed (multiGPU) backprojector that allows to split the full volume into smaller pieces
% this allows to solve datasets much larger than memory of used GPU or
% spread calculations over several GPUs
%
% volData = Atx_sup_partial(projData, cfg, vectors, split, varargin)
%
% Inputs:
% **projData - array Nlayers x width_sinogram x Nangles of back-projected data
% **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 ]
% **deformation_fields: 3x1 cell contaning 3D arrays of local deformation of the object
% **verbose - verbose <= 0 : quiet, verbose : standard info , verbose = 2: debug
% **use_shared_memory - true - share data between processed by shared memory, false = use matlab parfor distribution
% **max_memory_blocks - maximal size of used share memory memory
% **varargin - for additional parameters see the code and als the astra.Axt_partial function
% *returns*
% ++volData - backprojected volume
%
% recompile commands
% (Linux, GCC 4.8.5) mexcuda -outdir private +astra/ASTRA_GPU_wrapper/ASTRA_GPU_wrapper.cu +astra/ASTRA_GPU_wrapper/util3d.cu +astra/ASTRA_GPU_wrapper/par3d_fp.cu +astra/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 volData = Atx_sup_partial(projData, cfg, vectors, split, varargin)
import utils.*
import math.*
par = inputParser;
par.addOptional('deformation_fields', {}) % deformation_fields: 3x1 cell contaning 3D arrays of local deformation of the object
par.addOptional('GPU', []) % list of GPUs to be used in reconstruction
par.addOptional('split_sub', 1) % splitting of the sub block on smaller tasks in the Atx_partial method , 1 == no splitting , otherwise [split_x,split_y,split_z,split_angles]
par.addOptional('verbose', 1) % verbose = 0 : quiet, verbose : standard info , verbose = 2: debug
par.addOptional('use_shared_memory', []) % true - share data between processed by shared memory, false = use matlab parfor distribution
par.addOptional('max_memory_blocks', min(50e9, utils.check_available_memory*1e6/4)) % maximal size of used share memory memory
par.parse(varargin{:})
r = par.Results;
if isscalar(split)
split = split .* ones(1,3);
end
split_orig = split;
if isempty(r.use_shared_memory)
r.use_shared_memory = length(r.GPU) > 1;
end
if isscalar(r.split_sub)
r.split_sub = r.split_sub .* ones(1,3);
end
gpu = gpuDevice;
if isempty(r.GPU)
r.GPU = gpu.Index;
end
N_GPU = length(r.GPU);
if ~isempty(r.deformation_fields)
% deformation field splitting not implemneted for split_sub;
split(1:3) = split(1:3) .* r.split_sub(1:3);
r.split_sub = 1;
end
%% if not splitting on this level is requirested, continue to tomo.Atx_partial
if all(split(1:3) == 1) && N_GPU == 1 && gpu.AvailableMemory > 4*(numel(projData)/10+cfg.iVolX*cfg.iVolY*cfg.iVolZ)
volData = astra.Atx_partial(projData, cfg, vectors, r.split_sub, ...
'GPU', r.GPU, 'deformation_fields', r.deformation_fields, 'verbose', r.verbose);
return
elseif all(split(1:2) == 1) && isempty(r.deformation_fields) && ...
utils.check_available_memory*1e6 > 4*(cfg.iVolX*cfg.iVolY*cfg.iVolZ*N_GPU+numel(projData)) && ...
cfg.iVolX*cfg.iVolY*cfg.iVolZ*4 < min(4*double(intmax('int32')),gpu.TotalMemory/2)
%% if no splitting is needed and the volume is small enough then at least split the data on multiple GPUs by angles
volData = Atx_angle_split(projData, cfg, vectors, r.split_sub, r.GPU, r.verbose);
return
end
%% otherwise do proper checking of all inputs before splitting
if ~(isa(projData, 'gpuArray') && strcmp(classUnderlying(projData), 'single')) && ...
~isa(projData, 'single')
error('Only single precision input array supported')
end
projData = gather(projData);
if any(cfg.pixel_scale ~= 1)
error('Non integer pixel size is not implemented, try to use Atx_partial')
end
if cfg.skewness_angle ~= 0
error('Skewness is not working well with sup-split')
end
if length(cfg.pixel_scale) == 2 && cfg.pixel_scale(1) ~= cfg.pixel_scale(2)
error('Variable pixel size for each axis is not implemented')
end
Nvol_orig = [cfg.iVolX,cfg.iVolY,cfg.iVolZ];
split(3) = max(split(3), ceil(split(3) * ( 4*(numel(projData)/5+cfg.iVolX*cfg.iVolY*cfg.iVolZ*(1+any(r.split_sub>1)) ) / gpu.AvailableMemory / prod(split) )));
split(3) = max(split(3), ceil(split(3) * ( ( cfg.iVolX*cfg.iVolY*cfg.iVolZ)/prod(split(1:3)) / double(intmax('int32')) )));
% make it equaly splitable among the GPUs
split(3) = ceil(max(split(3), ceil(prod(split(1:3)) / N_GPU) * N_GPU) / prod(split(1:2)));
split(1:3) = min(split(1:3), Nvol_orig(1:3));
% adjust splitting to make get equal blocks of the volume
for i = 1:100
split(1:3) = ceil(Nvol_orig ./ floor(Nvol_orig ./ split(1:3)));
end
if prod(r.split_sub) > 1
% reduce the split_sub if possible
r.split_sub(3) = ceil(r.split_sub(3) / 2^nextpow2(prod(split) / prod(split_orig)) );
end
if length(split) < 4
split(4) = 1;
end
% initial parameters check
Nvol_sub = Nvol_orig./split(1:3);
Nproj_full = size(projData);
assert(all(mod(Nvol_sub,1)==0), sprintf('Volume array cannot be divided to %i %i %i cubes', split(1:3)))
assert(all(Nproj_full==[cfg.iProjV,cfg.iProjU,cfg.iProjAngles]), 'Wrong inputs size')
assert(all(size(vectors)==[cfg.iProjAngles,12]), 'Wrong vectors size')
% size of the subprojection of single subvolume
Nproj_sub = [ (Nvol_sub(3)* sind(cfg.lamino_angle) + sqrt(sum(Nvol_sub(1:2).^2))*cosd(cfg.lamino_angle)), ...
sqrt(sum(Nvol_sub(1:2).^2))];
% adjust sub projection size to account for inplane rotation of the geometry
if cfg.tilt_angle ~= 0
Nproj_rot = [cosd(cfg.tilt_angle), -sind(cfg.tilt_angle); +sind(cfg.tilt_angle), cosd(cfg.tilt_angle)] * [0,0; Nproj_sub(1:2)];
% calculate projection window size after rotation
Nproj_rot = max(Nproj_rot) - min(Nproj_rot);
% add some extra padding
Nproj_sub(1:2) = 2*(Nproj_rot - Nproj_sub(1:2)) + Nproj_sub(1:2);
end
% provide extra space for subpixel (linear) interpolation at the borders of the split volumes, needed only for noninteger CoR_offset
Nproj_sub = Nproj_sub + 2*([split(3), max(split([1,2]))] - 1);
Nproj_sub = ceil(Nproj_sub/16)*16; % make it easier splitable for ASTRA
if cfg.lamino_angle == 90
Nproj_sub(1) = min(Nproj_sub(1), Nproj_full(1));
end
% only if there is not split in the horizontal dimension
if all(split(1:2) == 1)
% do not take larger than size of the inputs
Nproj_sub = min(Nproj_sub, Nproj_full(1:2));
end
% avoid low RAM issues due to paralelization
split(4) = max(split(4), ceil(4*prod(Nproj_sub)*cfg.iProjAngles/split(4)/(r.max_memory_blocks/N_GPU)));
Nproj_sub(3) = ceil(cfg.iProjAngles/split(4));
cfg_small = cfg;
cfg_small.iVolX = Nvol_sub(1);
cfg_small.iVolY = Nvol_sub(2);
cfg_small.iVolZ = Nvol_sub(3);
% get new size of projections
cfg_small.iProjU = Nproj_sub(2);
cfg_small.iProjV = Nproj_sub(1);
% calculate and store offset of the center of rotation, it will be used later
offset = vectors(:,4:6) +(vectors(:,10:12).*cfg.iProjV/2+vectors(:,7:9).*cfg.iProjU/2 );
CoR_offset = -[dot(offset', vectors(:,10:12)') ./ dot(vectors(:,10:12)', vectors(:,10:12)');
dot(offset', vectors(:,7:9)') ./ dot(vectors(:,7:9)', vectors(:,7:9)')]' ;
% remove centering offset && apply new offset
shift_vec = vectors(:,10:12)*(cfg.iProjV/2-cfg_small.iProjV/2)+vectors(:,7:9)*(cfg.iProjU/2-cfg_small.iProjU/2);
vectors(:,4:6) = vectors(:,4:6) + shift_vec;
%% keep the volume in RAM , transfer only sub-blocks
Nblocks = prod(split);
%% write back to the preallocated shared array
volData = zeros(Nvol_orig, 'single');
if N_GPU > 1
poolobj = gcp('nocreate');
if isempty(poolobj) || poolobj.NumWorkers < N_GPU
delete(poolobj);
poolobj = parpool(N_GPU);
end
poolobj.IdleTimeout = 600; % set idle timeout to 10 hours
end
% when the function is finished, make sure to execute following code
global status
status = true;
if r.use_shared_memory
out = onCleanup(@()myCleanupFun());
end
% run blocks in series
% run sub-blocks on each GPU in parallel
t_total = tic();
clear output
%% START OF OUTER GPU LOOP
outputs_blocks = [];
%% unitialize one solver per GPU
for thread_id = 1:N_GPU
% parse inputs and try to split them if possible
[outputs_blocks, inputs_block{thread_id},cfg_all{thread_id}] = ...
submit_block(thread_id, thread_id, outputs_blocks, projData, cfg, cfg_small, vectors,CoR_offset, split, Nproj_sub,Nvol_sub, r, varargin{:} );
end
unprocessed_blocks = N_GPU+1:Nblocks;
%% merge blocks back from GPUs and write to the shared array volData
for ii = 1:Nblocks
if r.verbose; utils.progressbar(ii, Nblocks); end
% set values from the small blocks to the final output arrays
[thread_id, timing, id] = gather_block( outputs_blocks,volData, cfg_all, split(4) > 1);
if ~isempty(unprocessed_blocks)
block_id = unprocessed_blocks(1);
unprocessed_blocks(1) = [];
if isa(outputs_blocks, 'parallel.FevalFuture') && sum([outputs_blocks.Read]) ~= 1
outputs_blocks
keyboard
end
% submit a new job once the previous is finished
[outputs_blocks, inputs_block{thread_id},cfg_all{block_id}] = ...
submit_block(block_id, thread_id, outputs_blocks, projData, cfg, cfg_small, vectors, CoR_offset, split, Nproj_sub,Nvol_sub, r, varargin{:} );
if isa(outputs_blocks, 'parallel.FevalFuture') && any(cat(1,[outputs_blocks.Read]))
outputs_blocks
keyboard
end
end
end
if r.verbose > 1
fprintf('Timing system: GPU init %3.2gs shared_mem down %3.2gs upload on GPU %3.2gs tomo projection %3.2gs download from GPU %3.2gs shared_mem %3.2gs \n',sum(timing,2) )
if length(r.GPU) > 1
fprintf('Timing local: GPU init %3.2gs shared_mem down %3.2gs upload on GPU %3.2gs tomo projection %3.2gs download from GPU %3.2gs shared_mem %3.2gs \n ',sum(timing,2)/max(1,length(r.GPU)) )
fprintf('Total time %3.2fs, parfor overhead %3.2fs \n', t_total, t_total - sum(sum(timing,2)/max(1,length(r.GPU))) )
end
end
% finish GPU blocks
% everything was fine -> no cleaning needed
status = false;
end
function [outputs_blocks,inputs_block, cfg_out] = submit_block(block_id, thread_id, outputs_blocks, projData, cfg, cfg_small, vectors, CoR_offset,split, Nproj_sub,Nvol_sub, r, varargin )
% prepare blocks for asynchonous processing
try
inputs_block = prepare_block(block_id, projData, cfg, cfg_small, vectors,CoR_offset, split, Nproj_sub,Nvol_sub, r, varargin{:});
catch err
disp(getReport(err))
keyboard
end
N_GPU = length(r.GPU);
if isempty(outputs_blocks); clear outputs_blocks; end
cfg_out = inputs_block{2};
try
%% process preloaded data
% no paralel toolbox
if N_GPU <= 1
[outputs_blocks{thread_id}.volData_small,outputs_blocks{thread_id}.timing, outputs_blocks{thread_id}.id]=...
run_partial_projector(inputs_block, block_id, 1,r.GPU,0);
else
% run it asynchronously
if r.verbose > 3
ticBytes(gcp);
end
outputs_blocks(thread_id) = parfeval(@run_partial_projector, 3, inputs_block,block_id,thread_id, r.GPU,0);
if r.verbose > 3
try; tocBytes(gcp); end
end
end
catch err
disp(getReport(err))
utils.check_available_memory
keyboard
end
end
function myCleanupFun()
% destroy all shared memory that could have been left behind
!ipcs -m | cut -d' ' -f2 | grep '^[0-9]' | while read x; do ipcrm -m $x; done
end
function [thread_id, timing, id] = gather_block(output_package,volData, cfg_all, add_values )
if isa(output_package, 'parallel.FevalFuture')
% gather results from cluster , WAIT FOR CALCULATIONS TO BE FINISHED
% [~, outputs_block, id] = fetchNext(output_package);
%% my version of the fetchNext function, it seems faster
id = [];
assert(any(~[output_package.Read]), 'All blocks are already read')
while true
for thread_id =1:length(output_package)
if strcmpi(output_package(thread_id).State, 'finished') && output_package(thread_id).Read == 0
try
[volData_small,timing,id] = output_package(thread_id).fetchOutputs;
catch err
if strcmpi(err.identifier, 'parallel:fevalqueue:InvalidExecutionResult')
warning('Unknown error, trying to restart parpool')
delete(gcp('nocreate'));
end
if strcmpi(err.identifier, 'parallel:fevalqueue:InvalidExecutionResult')
warning('Unknown error, trying to restart parpool')
delete(gcp('nocreate'));
end
if ~isempty(output_package(thread_id).Diary)
fprintf('============ THREAD %i FAILED, OUTPUT: ============= \n', thread_id)
disp(output_package(thread_id).Diary)
end
fprintf('============ THREAD %i FAILED, ERROR: ============= \n', thread_id)
disp(getReport(output_package(thread_id).Error))
keyboard
output_package.cancel
rethrow(err)
end
break
end
end
if ~isempty(id); break; end
pause(0.01) % wait for the data to be prepared
end
elseif iscell(output_package)
thread_id = 1;
id = output_package{thread_id}.id;
volData_small = output_package{thread_id}.volData_small;
timing = output_package{thread_id}.timing;
else
disp('FAILED ?? ')
keyboard
end
if isempty(volData_small)
warning('ASTRA projection probably failed')
keyboard
end
if isa(volData_small, 'shm')
% load data from shared memory
[s,volData_small] = volData_small.attach;
s.protected = false; % release shared memory
elseif ~isnumeric(volData_small)
keyboard
end
% write back to the full array stored in RAM
positions = zeros(size(volData_small,3),2)+cfg_all{id}.volume_shift(1:2);
indices = (1:size(volData_small,3)) + cfg_all{id}.volume_shift(3);
% use a MEX code to speed it up
utils.add_to_3D_projection(volData_small, volData,positions,indices, true, false);
end
function prepared_block = prepare_block(id, projData, cfg, cfg_small, vectors,CoR_offset, split, Nproj_sub, Nvol_sub ,r, varargin)
[x,y,z,angle_block_id] = ind2sub(split,id);
block_pos = [x,y,z];
shift = zeros(3,1);
for n = 1:3
%% find optimal shift of the subvolume
if mod(split(n),2)==1 %% odd
shift(n) = (block_pos(n) - ceil(split(n)/2))*Nvol_sub(n);
else
shift(n) = (block_pos(n) - split(n)/2-1/2)*Nvol_sub(n);
end
end
Nangle_per_blocks = ceil(cfg.iProjAngles / split(4));
%% for splitting to angular blocks using close angles, it makes ASTRA faster
angle_ids = (1+(angle_block_id-1)*Nangle_per_blocks:min(cfg.iProjAngles, angle_block_id*Nangle_per_blocks));
cfg_small.iProjAngles = length(angle_ids);
Nproj_sub(3) = cfg_small.iProjAngles;
%% find optimal shift of the subvolume
deform_fields_small = get_subdeform_fields(block_pos, r.deformation_fields, split);
CoR_offset = [CoR_offset, zeros(cfg.iProjAngles,1)] ;
% shift the sub-projections off center to create single
% large projection after assembling, if shift == 0 =>
% projections will be rotationally centered
vec = vectors;
% apply optimal shift
projection_shift = cfg.pixel_scale(1)^2.*[vec(:,10:12)*shift, vec(:,7:9)*shift, zeros(cfg.iProjAngles,1)];
projection_shift = bsxfun(@plus, projection_shift, [cfg.iProjV/2-cfg_small.iProjV/2,cfg.iProjU/2-cfg_small.iProjU/2,0] + CoR_offset);
% calculate subpixel shifts
projection_shift_subpix = projection_shift - round(projection_shift) - CoR_offset;
projection_shift = round(projection_shift);
% apply subpixel shifts
vec(:,4:6) = vec(:,4:6) - ...
( bsxfun(@times,vectors(:,7:9),projection_shift_subpix(:,2))+ ...
bsxfun(@times,vectors(:,10:12),projection_shift_subpix(:,1)));
% just store shifts for later
cfg_small.volume_shift = (block_pos-1).*Nvol_sub;
cfg_small.projection_shift = projection_shift;
% move data using custom made MEX routine
% allocate a small sub array
projData_small = zeros(Nproj_sub, 'single');
if r.use_shared_memory
s = shm();
try
s.allocate(projData_small) % attach the shared memory
catch
keyboard
end
[s, projsmall_shm] = s.attach();
% === write data =====
% use self-made MEX OMP function to move the data s
utils.get_from_3D_projection(projsmall_shm, projData, projection_shift(angle_ids,1:2), angle_ids);
% detach the shared memory
projData_small = s;
s.detach;
else
% use custom made MEX OMP function to move the data
utils.get_from_3D_projection(projData_small, projData, projection_shift(angle_ids,1:2), angle_ids);
end
% return only vector for the use angles
vec = vec(angle_ids,:);
prepared_block = {projData_small, cfg_small, vec, r.split_sub, varargin{:}, ...
'deformation_fields', deform_fields_small, 'verbose', 0, 'GPU', []};
end
function [vol, timing,block_id] = run_partial_projector(prepared_block, block_id, thread_id, GPU_list, verbose)
t0 = tic;
gpu_id = GPU_list(thread_id);
% let matlab to choose which GPU use
gpu = gpuDevice();
if ~isempty(GPU_list) && gpu.Index ~= gpu_id
gpuDevice(gpu_id); % avoid unneeded initalization
end
t_init = toc(t0);
timing = [t_init, 0,0,0,0,0];
t = tic;
if isa(prepared_block{1}, 'shm')
% data are attached to shared memory
[s,projData_small] = prepared_block{1}.attach();
else
% data are given directly to the worker
projData_small = prepared_block{1};
end
timing(2) = toc(t);
t = tic;
timing(3) = toc(t);
% call the next level abstraction
t = tic;
is_remote = ~isempty(getCurrentTask());
% call the next level abstraction around ASTRA wrapper
try
for ii = 1:2
try
vol = astra.Atx_partial(projData_small, prepared_block{2:end},'verbose', max(is_remote, verbose));
break
catch err
if ii == 2
rethrow(err)
else
warning('Atx_partial failed, trying again ... ')
end
end
end
catch err
qkeyboard
gpu = gpuDevice
reset(gpu)
fprintf('Error on GPU %i / %i', gpu.Index, gpuDeviceCount)
disp( getReport(err, 'extended', 'hyperlinks', 'on'))
% projData_small = single([]);
rethrow(err)
end
%%%%%%%%%%%
timing(4) = toc(t);
t = tic;
vol = gather(vol); % move to RAM
timing(5) = toc(t);
t = tic;
if isscalar(prepared_block{1})
% data are distributed to shared memory
s.detach();
s = shm(true);
s.upload(vol);
vol = s;
end
timing(6) = toc(t);
end
function deformation_fields_sub = get_subdeform_fields(block_pos, deform_fields, split)
% crop the deformation field only int the region of interest
if isempty(deform_fields)
deformation_fields_sub = {};
return
end
if prod(split)==1
deformation_fields_sub = deform_fields;
return
end
for ii = 1:3
N_full = size(deform_fields{1,ii});
N_small = ceil( N_full ./ reshape(split(1:3),[],1)');
for kk = 1:3
ind_def{kk} = (1+(block_pos(kk)-1)*N_small(kk)) : min(N_full(kk), (split(kk))*N_small(kk));
end
for jj = 1:2
deformation_fields_sub{jj,ii} = deform_fields{jj,ii}(ind_def{:});
end
end
warning('Splitting of deformation field is not supported / recommended')
end
function volData = Atx_angle_split(projData, cfg, vectors, split, GPU, verbose)
%% simplified version for multiGPU reconstruction of volumes that are small enough
% simply split the projections by angles and apply each projection
% block on a separated GPU
Nangles = cfg.iProjAngles;
N_GPU = length(GPU);
Nblocks = ceil(Nangles / N_GPU);
if length(split) == 4
split(4) = max(1, split(4) / N_GPU);
end
if verbose; utils.progressbar(1,4); end
for id = 1:N_GPU
ind{id} = 1+(id-1)*Nblocks:min(id*Nblocks,Nangles);
projData_shm{id} = shm();
projData_shm{id} = tomo.get_from_array(projData, projData_shm{id}, ind{id});
volData_shm{id} = [];
end
if verbose; utils.progressbar(2,4); end
parfor(id = 1:N_GPU, N_GPU)
t = getCurrentTask();
gpu_id = GPU(1+mod(t.ID-1, N_GPU));
% let parfor to choose which GPU use
gpu = gpuDevice();
if ~isempty(GPU) && gpu.Index ~= gpu_id
gpuDevice(gpu_id); % avoid unneeded initalization
end
[s,projData_block] = projData_shm{id}.attach();
volData_block = astra.Atx_partial(projData_block, cfg, vectors(ind{id},:),split, 'keep_on_GPU', true, 'verbose',0);
volData_block = gather(volData_block);
s.upload(volData_block);
s.protected = true;
volData_shm{id} = s;
end
if verbose; utils.progressbar(3,4); end
volData = single(0);
for id = 1:N_GPU
[s,volData_block] = volData_shm{id}.attach();
volData = volData + volData_block;
s.free;
end
if verbose; utils.progressbar(4,4); end
end
+552
View File
@@ -0,0 +1,552 @@
% AX_SUP_PARTIAL distributed (multiGPU) forward projector that allows to split the full volume into smaller pieces
% this allows to solve datasets much larger than memory of used GPU
% or spread calculations over several GPUs
%
% projData_all = Ax_sup_partial(volData, cfg, vectors, split, varargin)
%
% Inputs:
% **volData - 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 ]
% **deformation_fields: 3x1 cell contaning 3D arrays of local deformation of the object
% **verbose - verbose <= 0 : quiet, verbose : standard info , verbose = 2: debug
% **use_shared_memory - true - share data between processed by shared memory, false = use matlab parfor distribution
% **max_memory_blocks - maximal size of used share memory memory
% **varargin - for additional parameters see the code and als the astra.Ax_partial function
% *returns*
% ++projData_all - projection of the volData
%
%
% recompile commands
% (Linux, GCC 4.8.5) mexcuda -outdir private +astra/ASTRA_GPU_wrapper/ASTRA_GPU_wrapper.cu +astra/ASTRA_GPU_wrapper/util3d.cu +astra/ASTRA_GPU_wrapper/par3d_fp.cu +astra/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 projData_all = Ax_sup_partial(volData, cfg, vectors, split, varargin)
import utils.*
import math.*
par = inputParser;
par.addOptional('deformation_fields', {}) % deformation_fields: 3x1 cell contaning 3D arrays of local deformation of the object
par.addOptional('GPU', []) % list of GPUs to be used in reconstruction
par.addOptional('split_sub', 1) % splitting of the sub block on smaller tasks in the Atx_partial method , 1 == no splitting , otherwise [split_x,split_y,split_z,split_angles]
par.addOptional('verbose', 1) % verbose <= 0 : quiet, verbose : standard info , verbose = 2: debug
par.addOptional('use_shared_memory', []) % true - share data between processed by shared memory, false = use matlab parfor distribution
par.addOptional('max_memory_blocks', utils.check_available_memory*1e6/4) % maximal size of used share memory memory
par.parse(varargin{:})
r = par.Results;
if isscalar(split)
split = split .* ones(1,3);
end
if isempty(r.use_shared_memory)
r.use_shared_memory = length(r.GPU) > 1;
end
%% if not splitting on this level is requirested, continue to tomo.Ax_partial
if all(split(1:3) == 1)
projData_all = astra.Ax_partial(volData, cfg, vectors, r.split_sub, ...
'GPU', r.GPU, 'deformation_fields', r.deformation_fields, 'verbose', r.verbose);
return
end
%% otherwise do proper checking of all inputs before splitting
if ~(isa(volData, 'gpuArray') && strcmp(classUnderlying(volData), 'single')) && ...
~isa(volData, 'single')
error('Only single precision input array supported')
end
volData = gather(volData);
if ismatrix(volData)
% only if volData is 2D array !!
split = split([1, min(2,end)]);
assert(all(size(volData)==[cfg.iVolX,cfg.iVolY]), 'Wrong inputs size')
else
assert(all(size(volData)==[cfg.iVolX,cfg.iVolY,cfg.iVolZ]), 'Wrong inputs size')
end
if isempty(r.GPU)
gpu = gpuDevice;
r.GPU = gpu.Index;
end
N_GPU = length(r.GPU);
% make it equaly splitable among the GPUs
split(3) = ceil(max(split(3), ceil(prod(split(1:3)) / N_GPU) * N_GPU) / prod(split(1:2)));
if length(split) == 3
split(4) = 1;
end
%% backprojector that allows to split the full volume into smaller pieces
cfg.iProjAngles = size(vectors,1);
assert(cfg.iProjAngles > 1, 'Not supported <=1 angles')
assert(all(size(vectors,2)==12), 'Wrong vectors size')
assert(~isempty(vectors), 'Wrong vectors size')
% final array that contains all data
Nproj_orig = [cfg.iProjV, cfg.iProjU,cfg.iProjAngles];
Nvol_orig = size(volData);
%disp(split)
%disp(Nvol_orig)
Nvol_sub = Nvol_orig./split(1:3);
assert(all(mod(Nvol_sub,1)==0), 'Volume cannot be split')
% size of the subprojection of single subvolume
Nproj_sub = [ cfg.pixel_scale(1) * (Nvol_sub(3)* sind(cfg.lamino_angle) + sqrt(sum(Nvol_sub(1:2).^2))*cosd(cfg.lamino_angle)), ...
cfg.pixel_scale(1) * sqrt(sum(Nvol_sub(1:2).^2)) , ...
cfg.iProjAngles];
% adjust sub projection size to account for inplane rotation of the geometry
if cfg.tilt_angle ~= 0
Nproj_rot = [cosd(cfg.tilt_angle), -sind(cfg.tilt_angle); +sind(cfg.tilt_angle), cosd(cfg.tilt_angle)] * [0,0; Nproj_sub(1:2)];
% calculate projection window size after rotation
Nproj_rot = max(Nproj_rot) - min(Nproj_rot);
% add some extra padding
Nproj_sub(1:2) = 2*(Nproj_rot - Nproj_sub(1:2)) + Nproj_sub(1:2);
end
% provide extra space for subpixel (linear) interpolation at the borders of the split volumes, needed only for noninteger CoR_offset
Nproj_sub = Nproj_sub + 2*([split(3), max(split([1,2])),0] - 1);
Nproj_sub(1:2) = ceil(Nproj_sub(1:2)/16)*16; % make it easier splitable for ASTRA
% only if there is not split in the horizontal dimension
if all(split(1:2) == 1)
% do not take larger than size of the Nproj_orig because the edges
% are not need anyway
Nproj_sub(2) = min(Nproj_sub(2), Nproj_orig(2));
end
split(4) = ceil(split(4) * 4*prod(Nproj_sub)/(r.max_memory_blocks/N_GPU));
cfg_small = cfg;
cfg_small.iVolX = cfg.iVolX/split(1);
cfg_small.iVolY = cfg.iVolY/split(2);
cfg_small.iVolZ = cfg.iVolZ/split(3);
% get new size of projections
cfg_small.iProjU = Nproj_sub(2);
cfg_small.iProjV = Nproj_sub(1);
% calculate and store offset of the center of rotation, it will be used later
offset = vectors(:,4:6) +(vectors(:,10:12).*cfg.iProjV/2+vectors(:,7:9).*cfg.iProjU/2 );
CoR_offset = -[dot(offset', vectors(:,10:12)') ./ dot(vectors(:,10:12)', vectors(:,10:12)');
dot(offset', vectors(:,7:9)') ./ dot(vectors(:,7:9)', vectors(:,7:9)')]' ;
% find vector that will shift subvolume into center of the new projection size
shift_vec = vectors(:,10:12)*(cfg.iProjV/2-cfg_small.iProjV/2)+vectors(:,7:9)*(cfg.iProjU/2-cfg_small.iProjU/2);
% remove centering offset && apply new offset
vectors(:,4:6) = vectors(:,4:6) + shift_vec;
if length(cfg.pixel_scale) == 2 && cfg.pixel_scale(1) ~= cfg.pixel_scale(2)
error('Variable pixel size for each axis is not implemented')
end
Nblocks = prod(split);
% split into volume cubes
projData_all = zeros(Nproj_orig, 'single');
if N_GPU > 1
poolobj = gcp('nocreate');
if isempty(poolobj) || poolobj.NumWorkers < N_GPU
delete(poolobj);
poolobj = parpool(N_GPU);
end
poolobj.IdleTimeout = 600; % set idle timeout to 10 hours
end
% when the function is finished, make sure to execute following code
global status
status = true;
if r.use_shared_memory
out = onCleanup(@()myCleanupFun());
end
% run blocks in series
% run sub-blocks on each GPU in parallel
t_total = tic();
clear output
%% START OF OUTER GPU LOOP
outputs_blocks = [];
%% unitialize one solver per GPU
for thread_id = 1:N_GPU
% parse inputs and try to split them if possible
[outputs_blocks, inputs_block{thread_id},cfg_all{thread_id}] = ...
submit_block(thread_id, thread_id, outputs_blocks, volData, cfg, cfg_small, vectors,CoR_offset, split, r, varargin{:} );
end
unprocessed_blocks = N_GPU+1:Nblocks;
% write back to the shared array projData_all
%% merge blocks back from GPUs and write to the shared array volData
for ii = 1:Nblocks
if r.verbose>0; utils.progressbar(ii, Nblocks); end
% set values from the small blocks to the final output arrays
[thread_id, timing, id] = gather_block( outputs_blocks,projData_all, cfg_all);
if ~isempty(unprocessed_blocks)
block_id = unprocessed_blocks(1);
unprocessed_blocks(1) = [];
if isa(outputs_blocks, 'parallel.FevalFuture') && sum([outputs_blocks.Read]) ~= 1
outputs_blocks
keyboard
end
% submit a new job once the previous is finished
[outputs_blocks, inputs_block{thread_id},cfg_all{block_id}] = ...
submit_block(block_id, thread_id, outputs_blocks, volData, cfg, cfg_small, vectors,CoR_offset, split, r, varargin{:} );
if isa(outputs_blocks, 'parallel.FevalFuture') && any(cat(1,[outputs_blocks.Read]))
outputs_blocks
keyboard
end
end
end
t_total = toc(t_total);
if r.verbose > 1
fprintf('Timing system: GPU init %3.2gs shared_mem down %3.2gs upload on GPU %3.2gs tomo projection %3.2gs download from GPU %3.2gs shared_mem %3.2gs \n',sum(timing,2) )
if length(r.GPU) > 1
fprintf('Timing local: GPU init %3.2gs shared_mem down %3.2gs upload on GPU %3.2gs tomo projection %3.2gs download from GPU %3.2gs shared_mem %3.2gs \n ',sum(timing,2)/max(1,length(r.GPU)) )
fprintf('Total time %3.2fs, parfor overhead %3.2fs \n', t_total, t_total - sum(sum(timing,2)/max(1,length(r.GPU))) )
end
end
% everything was fine -> no cleaning needed
status = false;
end
function [outputs_blocks,inputs_block, cfg_out] = submit_block(block_id, thread_id, outputs_blocks, volData, cfg, cfg_small, vectors, CoR_offset,split, r, varargin )
% prepare blocks for asynchonous processing
inputs_block = prepare_block(block_id, volData, cfg, cfg_small, vectors,CoR_offset, split, r, varargin{:});
N_GPU = length(r.GPU);
if isempty(outputs_blocks); clear outputs_blocks; end
cfg_out = inputs_block{2};
try
%% process preloaded data
% no parallel toolbox
if N_GPU <= 1
[outputs_blocks{thread_id}.projData_small,outputs_blocks{thread_id}.timing, outputs_blocks{thread_id}.id]=...
run_partial_projector(inputs_block, block_id, 1,r.GPU, 0);
else
% run it asynchronously
if r.verbose > 3
ticBytes(gcp);
end
outputs_blocks(thread_id) = parfeval(@run_partial_projector, 3, inputs_block,block_id,thread_id, r.GPU, 0);
if r.verbose > 3
try; tocBytes(gcp); end
end
end
catch err
disp(getReport(err))
utils.check_available_memory
keyboard
end
end
function [thread_id, timing, id] = gather_block(output_package,projData_all, cfg_all )
if isa(output_package, 'parallel.FevalFuture')
%% my version of the fetchNext function, it seems faster
id = [];
assert(any(~[output_package.Read]), 'All blocks are already read')
while true
for thread_id =1:length(output_package)
if strcmpi(output_package(thread_id).State, 'finished') && output_package(thread_id).Read == 0
try
[projData_small,timing,id] = output_package(thread_id).fetchOutputs;
catch err
if strcmpi(err.identifier, 'parallel:fevalqueue:InvalidExecutionResult')
warning('Unknown error, trying to restart parpool')
delete(gcp('nocreate'));
end
if strcmpi(err.identifier, 'parallel:fevalqueue:InvalidExecutionResult')
warning('Unknown error, trying to restart parpool')
delete(gcp('nocreate'));
end
if ~isempty(output_package(thread_id).Diary)
fprintf('============ THREAD %i FAILED, OUTPUT: ============= \n', thread_id)
disp(output_package(thread_id).Diary)
end
fprintf('============ THREAD %i FAILED, ERROR: ============= \n', thread_id)
disp(getReport(output_package(thread_id).Error))
keyboard
output_package.cancel
rethrow(err)
end
break
end
end
if ~isempty(id); break; end
pause(0.01) % wait for the data to be prepared
end
elseif iscell(output_package)
thread_id = 1;
id = output_package{thread_id}.id;
projData_small = output_package{thread_id}.projData_small;
timing = output_package{thread_id}.timing;
else
disp('FAILED ?? ')
keyboard
end
if isempty(projData_small)
warning('ASTRA projection probably failed')
keyboard
end
% write back to the full array stored in RAM
if isa(projData_small, 'shm')
% load data from shared memory
[s, projData_small] = projData_small.attach;
s.protected = false; % release shared memory
elseif ~isnumeric(projData_small)
keyboard
end
% write the obtained projections back to the full projection array (projData_all)
utils.add_to_3D_projection(projData_small, projData_all, cfg_all{id}.projection_shift(cfg_all{id}.angle_ids,1:2), cfg_all{id}.angle_ids, true, false);
end
function myCleanupFun()
global status
if status
% destroy all shared memory that could have been left behind
!ipcs -m | cut -d' ' -f2 | grep '^[0-9]' | while read x; do ipcrm -m $x; done
end
end
function prepared_block = prepare_block(id, volData, cfg, cfg_small, vectors,CoR_offset, split,r, varargin)
Nblocks = prod(split(1:3));
Nvol_orig = size(volData);
Nvol_sub = Nvol_orig./split(1:3);
volData_small = zeros(Nvol_sub, 'single');
[i,j,k,angle_block_id] = ind2sub(split,id);
pos = [i,j,k];
% iterate over X,Y,Z axis
ind = cell(3,1);
shift = zeros(3,1);
for n = 1:3
ind{n} = max(1, 1+(pos(n)-1)*Nvol_sub(n)):min(pos(n)*Nvol_sub(n),Nvol_orig(n)); %% find optimal shift of the subvolume
if mod(split(n),2)==1 %% odd
shift(n) = (pos(n) - ceil(split(n)/2))*Nvol_sub(n);
else
shift(n) = (pos(n) - ceil(split(n)/2)-1/2)*Nvol_sub(n);
end
end
cfg_small.iVolX = length(ind{1});
cfg_small.iVolY = length(ind{2});
cfg_small.iVolZ = length(ind{3});
%% for splitting to angular blocks
assert(split(4) == 1 || isempty(r.deformation_fields), 'Deformation fields with angular splitting not supported')
Nangle_per_blocks = ceil(cfg.iProjAngles / split(4));
angle_ids = 1+(angle_block_id-1)*Nangle_per_blocks:min(cfg.iProjAngles, angle_block_id*Nangle_per_blocks);
cfg_small.iProjAngles = length(angle_ids);
cfg_small.angle_ids = angle_ids;
if ~isempty(r.deformation_fields)
for ii = 1:3
N_full = size(r.deformation_fields{1,ii});
N_small = ceil( N_full ./ reshape(split(1:3),[],1)');
for kk = 1:3
ind_def{kk} = (1+(pos(kk)-1)*N_small(kk)) : min(N_full(kk), (split(kk))*N_small(kk));
end
for jj = 1:2
deformation_fields_sub{jj,ii} = r.deformation_fields{jj,ii}(ind_def{:});
end
end
else
deformation_fields_sub = {};
end
CoR_offset = [CoR_offset, zeros(cfg.iProjAngles,1)] ;
vec = vectors;
% shift the sub-projections off center to create single
% large projection after assembling, if shift == 0 =>
% projections will be rotationally centered
% find optimal shift of the projections in the projData_all matrix
projection_shift = cfg_small.pixel_scale(1).^2* [vec(:,10:12)*shift, vec(:,7:9)*shift, zeros(cfg.iProjAngles,1)];
% change offset of the detector center
projection_shift = bsxfun(@plus, projection_shift , [cfg.iProjV/2-cfg_small.iProjV/2,cfg.iProjU/2-cfg_small.iProjU/2,0] + CoR_offset);
% calculate subpixel shifts
projection_shift_subpix = projection_shift - round(projection_shift) - CoR_offset;
projection_shift = round(projection_shift);
% apply subpixel shifts
vec(:,4:6) = vec(:,4:6) - ...
( bsxfun(@times,vectors(:,7:9),projection_shift_subpix(:,2))+ ...
bsxfun(@times,vectors(:,10:12),projection_shift_subpix(:,1)));
% just store it for later
cfg_small.projection_shift = projection_shift; % + [20,0,0];
% return only vector for the use angles
vec = vec(angle_ids,:);
prepared_block = {[], cfg_small, vec, r.split_sub, varargin{:},...
'verbose',0, 'deformation_fields', deformation_fields_sub, 'GPU', [], 'keep_on_GPU', false};
% take only small subvolume, (unfortunatelly this is more than duplicate the needed RAM !! )
if Nblocks > 1
% copy data from full volume into smaller field
% volData_small = volData(ind{:}); % move using matlab !! slow !!
% move data using custom made MEX routine
if r.use_shared_memory
s = shm();
s.allocate(volData_small);
% attach the shared memory
[s, volsmall_shm] = s.attach();
% === write data =====
% use self-made MEX OMP function to move the data
positions = ones(Nvol_sub(3),2,'int32').*int32([ind{1}(1),ind{2}(1)]-1);
% !! fill the data direclty to the shared memory
utils.get_from_3D_projection(volsmall_shm, volData,positions , int32(ind{3})');
% detach the shared memory
prepared_block{1} = s;
s.detach;
else
volData_small = volData(ind{:});
prepared_block{1} = volData_small;
end
else
prepared_block{1} = volData; % avoid memory copy if possible
end
end
function [projData_small, timing,block_id] = run_partial_projector(prepared_block, block_id,thread_id, GPU_list, verbose)
try
t0 = tic;
gpu = gpuDevice();
gpu_id = GPU_list(thread_id);
% let parfor to choose which GPU use
if gpu.Index ~= gpu_id
gpuDevice(gpu_id); % avoid unneeded initalization
end
t_init = toc(t0);
timing = [t_init, 0,0,0,0,0];
t = tic;
if isa(prepared_block{1}, 'shm')
% data are downloaded from shared memory
[s,volData_small] = prepared_block{1}.attach();
else
% data are given directly to the worker
volData_small = prepared_block{1};
end
timing(2) = toc(t);
t = tic;
timing(3) = toc(t);
t = tic;
is_remote = ~isempty(getCurrentTask());
% call the next level abstraction around ASTRA wrapper
projData_small = astra.Ax_partial(volData_small, prepared_block{2:end}, 'keep_on_GPU', true, 'verbose', is_remote);
timing(4) = toc(t);
t = tic;
projData_small = gather(projData_small); % move to RAM
timing(5) = toc(t);
t = tic;
if isa(prepared_block{1}, 'shm')
tic
s.detach;
% data are distributed to shared memory
s = shm(true);
s.upload(projData_small)
projData_small = s;
toc
end
timing(6) = toc(t);
catch err
gpu = gpuDevice
reset(gpu);
fprintf('Error on GPU %i / %i', gpu.Index, gpuDeviceCount)
disp( getReport(err, 'extended', 'hyperlinks', 'on'))
rethrow(err)
end
end
+136
View File
@@ -0,0 +1,136 @@
% CGLS conjugate gradient tomo solver, solve tomography as least squares
% tasks
% Note: in contrast to SART, it does not accept additional constraints
%
% [rec] = CGLS(rec, sino, cfg, vectors, Niter, varargin)
% Inputs:
% **rec - initial guess of the reconstruction
% **sino - sinogram (Nlayers x width x Nangles)
% **cfg - config struct from ASTRA_initialize
% **vectors - vectors of projection rotation generated by ASTRA_initialize
% **Niter - number of iterations
% **varargin - see the code + parameters of tomo.Atx_sup_partial
% *returns*
% ++rec - tomography reconstruction
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 [rec] = CGLS(rec, sino, cfg, vectors, Niter, varargin)
par = inputParser;
par.addOptional('split', 1)
par.addParameter('valid_angles', [])
par.addParameter('deformation_fields', {} ) % cell 3x1 of deformation arrays
par.addOptional('GPU', []) % list of GPUs to be used in reconstruction
par.addOptional('split_sub', 1) % splitting of the sub block on smaller tasks in the Atx_partial method , 1 == no splitting
par.addOptional('verbose', 1) % verbose = 0 : quiet, verbose : standard info , verbose = 2: debug
par.parse(varargin{:})
res = par.Results;
if Niter == 0
return
end
if res.verbose
disp('====== CGLS ==========')
end
if ~isempty(res.valid_angles)
sino = sino(:,:,res.valid_angles);
vectors = vectors(res.valid_angles,:);
try cfg.lamino_angle = cfg.lamino_angle(res.valid_angles); end
end
[Nlayers,Nw,Nproj] = size(sino);
cfg.iProjAngles = Nproj;
assert(cfg.iProjU == Nw, 'Wrong sinogram width')
assert(cfg.iProjV == Nlayers, 'Wrong sinogram height')
import tomo.*
varargin = {'deformation_fields',res.deformation_fields,'GPU',res.GPU, 'split_sub', res.split_sub,'verbose', res.verbose};
% r = sino - A*x
r = sino - Ax_sup_partial(rec, cfg, vectors, res.split, varargin{:});
% p = A'*r
p = Atx_sup_partial(r, cfg, vectors,res.split, varargin{:});
norm_sino = sqrt(mean(sino(:).^2));
gamma_0 = sum(p(:).^2);
t0 = tic;
for i = 1:Niter
% progressbar(i, Niter)
fprintf('CGLS Iter %i/%i\n', i, Niter)
q = Ax_sup_partial(p, cfg, vectors, res.split, varargin{:});
alpha = gamma_0 / sum(q(:).^2);
rec = rec + alpha * p;
r = r - alpha * q;
err(i) = sqrt(mean(r(:).^2));
s = Atx_sup_partial(r, cfg, vectors, res.split, varargin{:});
gamma_1 = sum(s(:).^2);
beta = gamma_1 / gamma_0;
gamma_0 = gamma_1;
p = s + beta * p;
if toc(t0) >10 && res.verbose % plot every 5s
figure(239821)
subplot(1,2,1)
plotting.imagesc3D(-rec, 'init_frame', size(rec,3)/2 )
axis off image ;
colormap bone
title('CLGS reconstruction preview')
subplot(1,2,2)
loglog(err/norm_sino)
title('Relative data error')
axis tight
drawnow
t0 = tic;
end
if i > 1 && err(i) > err(i-1)
disp('Error increased, finishing')
break
end
end
end
+276
View File
@@ -0,0 +1,276 @@
% FBP filtered back projection - multiGPU FBP solver
%
% [rec,sinogram] = FBP(sinogram, cfg, vectors, varargin)
%
% Inputs:
% **sino - sinogram (Nlayers x width x Nangles)
% **cfg - config struct from ASTRA_initialize
% **vectors - vectors of projection rotation generated by ASTRA_initialize
% *optional*
% ** split =[1,1,1] - split the solved volume, split(3 is used to split in separated blocks, split(1:2) is used inside Atx_partial to du subplitting for ASTRA
% ** valid_angles = [] - list of valid angles, []==all are valid
% ** filter = 'ram-lak' - name of the FBP filter
% ** filter_value = 1 - fitlering value for the FBP filter
% ** deformation_fields = {} - cell 3x1 of deformation arrays
% ** GPU = [] - list of GPUs to be used in reconstruction
% ** split_sub = [1,1,1] - splitting of the sub block on smaller tasks in the Atx_partial method , 1 == no splitting
% ** verbose = 1 - verbose = 0 : quiet, verbose : standard info , verbose = 2: debug
% ** use_derivative = false - calculate reconstruction from the phase derivative
% ** extra_padding = false - surround the projection by void space to enforce zero around tomogram
% ** keep_on_GPU - if false, move the reconstruction back from GPU before returning
% ** determine_weights = true - reweight projections if the angles are not equidistant
% ** mask = [] - apply mask on reconstruction , inputs is 2D or 3D array
% ** padding = 0 - zero padding is improving standard tomography. 'symmetric' is good for lamino / local tomo
% ** only_filter_sinogram = false - return filtered sinogram, do not backproject
% *returns*
% ++tomogram - FBP reconstruction
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 [rec,sinogram, H] = FBP(sinogram, cfg, vectors, varargin)
par = inputParser;
par.addOptional('split', [1,1,1]) % split the solved volume, split(3) is used to split in separated blocks, split(1:2) is used inside Atx_partial to du subplitting for ASTRA
par.addParameter('valid_angles', [])
par.addParameter('filter', 'ram-lak' )
par.addParameter('filter_value', 1 )
par.addParameter('deformation_fields', {} ) % cell 3x1 of deformation arrays
par.addOptional('GPU', []) % list of GPUs to be used in reconstruction
par.addOptional('split_sub', [1,1,1]) % splitting of the sub block on smaller tasks in the Atx_partial method , 1 == no splitting
par.addOptional('verbose', 1) % verbose = 0 : quiet, verbose : standard info , verbose = 2: debug
par.addOptional('use_derivative', false) % calculate reconstruction from the phase derivative
par.addOptional('extra_padding', false) % surround the projection by void space to enforce zero around tomogram
par.addOptional('keep_on_GPU', isa(sinogram, 'gpuArray')) % if false, move the reconstruction back from GPU before returning
par.addOptional('determine_weights', true)% reweight projections if the angles are not equidistant
par.addOptional('mask', []) % apply mask on reconstruction , inputs is 2D or 3D array
par.addOptional('padding', 0) % zero padding is improving standard tomography. 'symmetric' is good for lamino / local tomo
par.addOptional('only_filter_sinogram', false) % return filtered sinogram, do not backproject
par.parse(varargin{:})
r = par.Results;
if r.verbose>0
disp('====== FBP ==========')
end
if ~isempty(r.valid_angles) && (~islogical(r.valid_angles) || any(~r.valid_angles))
sinogram = sinogram(:,:,r.valid_angles);
vectors = vectors(r.valid_angles,:);
end
[Nlayers,Nw,Nproj] = size(sinogram);
cfg.iProjAngles = Nproj;
assert(cfg.iProjU == Nw, 'Wrong sinogram width')
assert(cfg.iProjV == Nlayers, 'Wrong sinogram height')
assert(mod(Nw,2)==0, 'Only even width of sinogram is supported')
if ~isempty(r.mask)
assert(all(size(r.mask) == [cfg.iVolX, cfg.iVolY]), 'Wrong size of reconstruction mask')
end
if ~isreal(sinogram)
r.use_derivative = true;
sinogram = math.get_phase_gradient_1D(sinogram,2, 0.01);
end
% calculate the original angles
theta = pi-atan2(vectors(:,2),-vectors(:,1));
lamino_angle = pi/2-atan2(vectors(:,3), vectors(:,1)./cos(theta));
if ~strcmpi(r.filter, 'none')
%%% Determine weights for uneven angular sampling %%%
% if r.determine_weights && any(theta<-pi/Nproj)
% warning('There are some theta < 0 angles. Using constant angular sampling code.')
% r.determine_weights = false;
% end
% if r.determine_weights && any(theta>pi+pi/Nproj)
% warning('There are some theta >= 180 angles. Using constant angular sampling code.')
% r.determine_weights = false;
% end
% if r.determine_weights && abs(max(theta)-min(theta)-pi) > 5*mean(diff(sort(theta)))
% warning('Missing wedge is to large for weighting')
% r.determine_weights = false;
% end
if r.determine_weights
% determine weights in case of iregular fourier space sampling
theta = mod(theta - theta(1), pi) ; % assume the the first one is zero, assume that theta and theta+180 are the same projections
[theta_sort,ind_sort] = sort(theta); % sort the angles
weights = zeros(Nproj,1);
weights(2:end-1) = - theta_sort(1:end-2)/2 + theta_sort(3:end)/2;
weights(1) = theta_sort(2)-theta_sort(1);
weights(end) = theta_sort(end) - theta_sort(end-1);
weights(ind_sort) = weights; % sort it back as given in
if any(weights > 2*median(weights))
utils.verbose(2,'Too large angular jump for FBP weighting, assuming missing wedge tomo')
weights(weights > 2*median(weights)) = median(weights);
end
weights = weights / mean(weights);
else
weights = 1; % constant weighting
end
weights = weights .* (pi/2/Nproj) .* sin(lamino_angle);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Design the filter
H = designFilter(r.filter, Nw, r.filter_value, r.use_derivative);
% account for laminography tilt + unequal spacing of the tomo angles
H = bsxfun(@times, H', reshape(weights,1,1,[]));
Nelements = size(H,2)*cfg.iProjV*cfg.iProjAngles;
if gpuDeviceCount
% if possible, run in parallel on GPU
gpu = gpuDevice;
% manually define the block size because default calculation in block_fun is not valid for this function
Nblocks = ceil( (8*4* Nelements) / gpu.AvailableMemory) ;
Nblocks = max(Nblocks, Nelements/ double(intmax('int32')));
Nblocks = max(Nblocks, length(r.GPU));
else
% CPU processing
max_block_size = min(utils.check_available_memory*1e6, 20e9); %% work with 10GB blocks
Nblocks = ceil( (6*8* Nelements) / max_block_size) ;
end
sinogram = tomo.block_fun(@applyFilter,sinogram, H, Nw, r.padding, ...
struct('GPU_list', r.GPU, 'verbose_level', r.verbose, 'Nblocks', Nblocks, 'move_to_GPU', false));
end
% back-project the filtered arrays back to the volume space
if ~r.only_filter_sinogram
if isa(sinogram, 'gpuArray') || max(cfg.iProjU, cfg.iProjV) < 4096 && cfg.iVolX*cfg.iVolY*cfg.iVolZ < intmax('int32') && length(r.GPU) <= 1
rec = astra.Atx_partial(sinogram, cfg, vectors, r.split_sub, 'verbose', r.verbose, 'deformation_fields', r.deformation_fields );
else
sinogram = gather(sinogram);
rec = tomo.Atx_sup_partial(sinogram, cfg, vectors, r.split, 'GPU', r.GPU, 'split_sub', r.split_sub, 'verbose', r.verbose, 'deformation_fields', r.deformation_fields );
end
% apply apodization function if provided
if ~isempty(r.mask)
rec = tomo.block_fun(@(x)(x .* r.mask), rec, struct('use_GPU', false)); % run on CPU
end
else
rec = [];
end
if ~r.keep_on_GPU
rec = gather(rec);
end
end
function sinogram = applyFilter(sinogram, H, Nw, padding)
sinogram = utils.Garray(sinogram);
% Zero pad projections, important to avoid negative values in air around
sinogram = padarray(sinogram,double([0,(size(H,2) - Nw)/2]),padding, 'both');
% move directly to complex to include the expected memore requirements
sinogram = complex(sinogram);
sinogram = math.fft_partial(sinogram,2,1); % sinogram holds fft of projections
sinogram = sinogram.*H; % frequency domain filtering
sinogram = math.ifft_partial(sinogram,2,1);
sinogram = real(sinogram);
sinogram = sinogram(:,1+end/2-Nw/2:end/2+Nw/2,:); % Truncate the filtered projections
end
function filt = designFilter(filter, len, d, derivative)
% Returns the Fourier Transform of the filter which will be
% used to filter the projections
%
% INPUT ARGS: filter - either the string specifying the filter
% len - the length of the projections
% d - the fraction of frequencies below the nyquist
% which we want to pass
%
% OUTPUT ARGS: filt - the filter to use on the projections
order = max(64,2^nextpow2(2*len));
% order = len; % better for laminography
% First create a ramp filter - go up to the next highest
% power of 2.
if derivative
filt = 0*( 0:(order/2) )+1;
else
filt = 2*( 0:(order/2) )./order;
end
w = 2*pi*(0:size(filt,2)-1)/order; % frequency axis up to Nyquist
switch filter
case 'ram-lak'
% Do nothing
case 'shepp-logan'
% be careful not to divide by 0:
filt(2:end) = filt(2:end) .* (sin(w(2:end)/(2*d))./(w(2:end)/(2*d)));
case 'cosine'
filt(2:end) = filt(2:end) .* cos(w(2:end)/(2*d));
case 'hamming'
filt(2:end) = filt(2:end) .* (.54 + .46 * cos(w(2:end)/d));
case 'hann'
filt(2:end) = filt(2:end) .*(1+cos(w(2:end)./d)) / 2;
case 'parzen'
aux = parzenwin(round(2*size(filt,2)*d)-1)';
aux = aux(round(size(aux,2)/2):round(size(aux,2)));
filt(1:size(aux,2)) = filt(1:size(aux,2)).*aux;
filt(size(aux,2)+1:end) = 0;
otherwise
eid = sprintf('Images:%s:invalidFilter',mfilename);
msg = 'Invalid filter selected.';
error(eid,'%s',msg);
end
filt(w>pi*d) = 0; % Crop the frequency response
if derivative
filt = [filt' ; -filt(end-1:-1:2)']/(1i*pi); % Symmetry of the filter
else
filt = [filt' ; filt(end-1:-1:2)']; % Symmetry of the filter
end
end
+110
View File
@@ -0,0 +1,110 @@
% FBP_CPU wrapper around iradonfast_v3 that automatically splits the data into
% smaller blocks to avoid too large memory allocation and emulate GPU based
% function tomo.FBP
%
% tomogram = FBP_CPU(sinogram, cfg, vectors, varargin)
%
% Inputs:
% **sino - sinogram (Nlayers x width x Nangles)
% **cfg - config struct from ASTRA_initialize
% **vectors - vectors of projection rotation generated by ASTRA_initialize
% *optional*
% ** valid_angles = [] - list of valid angles, []==all are valid
% ** filter = 'ram-lak' - name of the FBP filter
% ** filter_value = 1 - fitlering value for the FBP filter
% ** verbose = 1 - verbose = 0 : quiet, verbose : standard info , verbose = 2: debug
% ** use_derivative = false - calculate reconstruction from the phase derivative
% *returns*
% ++tomogram - FBP reconstruction
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 tomogram = FBP_CPU(sinogram, cfg, vectors, varargin)
par = inputParser;
par.addParameter('valid_angles', [])
par.addParameter('filter', 'ram-lak' )
par.addParameter('filter_value', 1 )
par.addOptional('verbose', 1) % verbose = 0 : quiet, verbose : standard info , verbose = 2: debug
par.addOptional('determine_weights', true)% reweight projections if the angles are not equidistant
par.addOptional('mask', []) % apply mask on reconstruction
par.addOptional('use_derivative', false) % calculate reconstruction from the phase derivative
par.KeepUnmatched = true;
par.parse(varargin{:})
r = par.Results;
if r.verbose
disp('====== FBP ==========')
end
if r.use_derivative
extra_args = {'derivative'};
else
extra_args = {};
end
if ~isempty(r.valid_angles)
sinogram = sinogram(:,:,r.valid_angles);
vectors = vectors(r.valid_angles,:);
end
% calculate the original angles
theta = rad2deg(pi-atan2(vectors(:,2),-vectors(:,1)))';
lamino_angle = rad2deg(pi/2-atan2(vectors(:,3), vectors(:,1)./cos(theta)));
if abs(lamino_angle - 90) > 1e-2
error('CPU implementation of FBP does not support laminography')
end
[Nlayers,tomo_size,Nangles] = size(sinogram) ;
block_size = ceil(2e9/(tomo_size*Nangles*4)); % split the task into ~2GB blocks
Nblocks = ceil(Nlayers / block_size );
% preallocate array to store results
tomogram = zeros(tomo_size, tomo_size, Nlayers, 'single');
for ii = 1:Nblocks
ind = 1+(ii-1)*block_size:min(ii*block_size, Nlayers);
tomogram(:,:,ind) =tomo.iradonfast_v3(permute(sinogram(ind,:,:),[2,3,1]), theta, 'linear', r.filter, tomo_size, r.filter_value, extra_args{:}); % Calculate slice
end
% make the results equivalent to the GPU version
tomogram = utils.imshift_fft(tomogram, [0.5,0.5] );
tomogram = utils.crop_pad(tomogram, [cfg.iVolX, cfg.iVolY]);
if ~isempty(r.mask)
tomogram = tomogram .* r.mask;
end
end
+169
View File
@@ -0,0 +1,169 @@
% FBP_PROPAGATION filtered back propagation for diffraction tomography
%
% [rec] = FBP_propagation(sino, theta, variable, par, optimal_propagation)
%
% Inputs:
% **sino - sinogram (Nlayers x width x Nangles)
% **angles - projection angles
% **variable - 'phase' or 'amplitude'
% **par - parameter structure
% **optimal_propagation - position of center of focus
% *returns*
% ++rec - reconstructed volume
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 [rec_volume] = FBP_propagation(sino, theta, variable, par, optimal_propagation, thickness)
% Filtered backpropagation
% assert(~isreal(sino), 'Input sinogram has to be complex-valued')
[Nlayers, width_sinogram, Nangles] = size(sino);
assert(length(theta)==Nangles, 'Size of sinogram does not correspond to size of Nangles');
% create a matrix of propagation through entire sample
if nargin < 6
propag = -single(par.pixel_size*(-ceil(width_sinogram/2):floor(width_sinogram/2-1)));
else
propag = -single(linspace(-thickness/2,thickness/2, width_sinogram));
end
[~,H0] = utils.prop_free_nf(ones(Nlayers,width_sinogram,'single'), par.lambda, propag, par.pixel_size);
%% allocate shared memory
use_sharemem = false;
if use_sharemem
%% allocate reconstruction volume
rec_volume = zeros(width_sinogram, width_sinogram, Nlayers, 'single');
share_mem = shm(true);
share_mem.allocate(rec_volume);
share_mem.detach();
else
%% allocate reconstruction volume
rec_volume = gpuArray.zeros(width_sinogram, width_sinogram, Nlayers, 'single');
H0 = gpuArray(H0);
end
% create a support mask that limits extend of the reconstruction
[~,circle] = utils.apply_3D_apodization(rec_volume,50,0,0.1);
circle = single(circle);
%% solve the FBP task
for ii = 1:Nangles
utils.progressbar(ii, Nangles, 100);
rec_volume = calculate_filt_back_propagation(rec_volume, sino(:,:,ii), theta(ii),circle,H0, par, variable, optimal_propagation(min(ii,end)));
end
if use_sharemem
[share_mem, rec_shm] = share_mem.attach();
rec_volume(:) = rec_shm;
share_mem.detach();
end
rec_volume = gather(rec_volume);
end
function rec_volume = calculate_filt_back_propagation(rec_volume, sino_tmp, theta, circle, H0, par, variable, optimal_propagation, thickness)
[Nlayers, width_sinogram] = size(sino_tmp);
sino_tmp = propagate_sinogram(H0, sino_tmp, par,variable, optimal_propagation);
sino_tmp = permute(sino_tmp, [3,2,1]);
cfg.iProjV = size(sino_tmp,1);
cfg.iProjU = width_sinogram;
cfg.iProjAngles = Nlayers;
% apply filtering, dont do any backprojetion
[~,sino_filt] = tomo.FBP(sino_tmp, cfg, zeros(Nlayers,12), 1,'verbose',0, 'determine_weights', false, 'GPU', par.GPU_list, 'filter', 'ram-lak', 'only_filter_sinogram', true);
if isscalar(H0)
sino_filt = repmat(sino_filt,width_sinogram,1,1);
end
sino_filt = sino_filt .* circle;
%% rotate propagated projections
% sino_filt = utils.imrotate_ax_fft(sino_filt, theta(ii), 3); % subpixel precision inteprolation using FFT
sino_filt = utils.imrotate_ax(sino_filt, theta, 3); % common linear interpolation
%% add filtered update to the total reconstruction
if isa(rec_volume, 'shm')
[share_mem, rec_volume] = rec_volume.attach();
tomo.set_to_array(rec_volume, gather(sino_filt), 0, true); % write directly to the shared memory
share_mem.detach();
else
rec_volume = rec_volume + sino_filt;
end
end
function sinogram = propagate_sinogram(H0, sinogram, par,variable, optimal_propagation)
[Nlayers, width_sinogram, ~] = size(sinogram);
sinogram = gpuArray(sinogram);
%% FT interpolation + NF propagation
if any(optimal_propagation > 0)
H = gpuArray.ones(Nlayers,width_sinogram,'single');
[~,H] = utils.prop_free_nf(H, par.lambda, optimal_propagation, par.pixel_size);
else
H = 1;
end
if ~isscalar(H0) || ~isscalar(H)
% propagate along the beam
sinogram = ifft2(fft2(sinogram).*H0.* H);
end
switch variable
case 'amplitude'
%% get amplitude
sinogram = -log(abs(sinogram));
case 'phase'
%% get phase
sinogram = -math.unwrap2D_fft(sinogram,2, [10,10], 0);
otherwise
error('Wrong option')
end
end
+170
View File
@@ -0,0 +1,170 @@
% FBP_ZSPLIT filtered back projection with simple splitting along vertical direction -> works only for classical tomography, not for laminography
%
% [rec] = FBP_zsplit(sinogram, cfg, vectors, varargin)
%
% Inputs:
% **sino - sinogram (Nlayers x width x Nangles)
% **cfg - config struct from astra.ASTRA_initialize
% **vectors - vectors of projection rotation generated by astra.ASTRA_initialize
% *optional*
% ** split =[1,1,1] - split the solved volume, split(3 is used to split in separated blocks, split(1:2) is used inside Atx_partial to du subplitting for ASTRA
% ** valid_angles = [] - list of valid angles, []==all are valid
% ** filter = 'ram-lak' - name of the FBP filter
% ** filter_value = 1 - fitlering value for the FBP filter
% ** deformation_fields = {} - cell 3x1 of deformation arrays
% ** GPU = [] - list of GPUs to be used in reconstruction
% ** split_sub = [1,1,1] - splitting of the sub block on smaller tasks in the Atx_partial method , 1 == no splitting
% ** verbose = 1 - verbose = 0 : quiet, verbose : standard info , verbose = 2: debug
% ** use_derivative = false - calculate reconstruction from the phase derivative
% ** extra_padding = false - surround the projection by void space to enforce zero around tomogram
% ** keep_on_GPU - if false, move the reconstruction back from GPU before returning
% ** determine_weights = true - reweight projections if the angles are not equidistant
% ** mask = [] - apply mask on reconstruction , inputs is 2D or 3D array
% ** padding = 0 - zero padding is improving standard tomography. 'symmetric' is good for lamino / local tomo
% ** only_filter_sinogram = false - return filtered sinogram, do not backproject
%
% *returns*
% ++rec - reconstructed volume
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 [rec] = FBP_zsplit(sinogram, cfg0, vectors0, varargin)
par = inputParser;
par.KeepUnmatched = true;
par.addOptional('split', [1,1,1,1]) % split the solved volume, split(3) is used to split in separated blocks, split(1:2) is used inside Atx_partial to du subplitting for ASTRA
par.addOptional('keep_on_GPU', false) % keep results in GPU
par.addOptional('verbose', true) % verbosity level
par.addOptional('mask', []) % apply reconstruction mask, inputs is 2D or 3D array
par.addOptional('GPU', []) % ids of the used GPUs
par.addOptional('use_GPU', true, @islogical) % if false, use CPU based reconstruction
par.parse(varargin{:})
r = par.Results;
[Nlayers,Nw,Nproj] = size(sinogram);
cfg0.iProjAngles = Nproj;
assert(cfg0.iProjU == Nw, 'Wrong sinogram width')
assert(cfg0.iProjV == Nlayers, 'Wrong sinogram height')
assert(mod(Nw,2)==0, 'Only even width of sinogram is supported')
assert(length(r.GPU)<=1, 'For multiGPU use tomo.FBP function')
if r.verbose > 0; fprintf('====== FBP split ==========\n'); end
Nelements = numel(sinogram) ;
if gpuDeviceCount
Nblocks = r.split(3);
r.split(3) = 1;
gpu = gpuDevice;
% empirical condition, may be too pesimistic
Nblocks = max(Nblocks,ceil( (2*8*4* Nelements) / gpu.AvailableMemory)) ;
Nblocks = max(Nblocks, Nelements/ double(intmax('int32')));
Nblocks = max(Nblocks, length(r.GPU));
else
% CPU processing
max_block_size = min(utils.check_available_memory*1e6, 20e9); %% work with 10GB blocks
Nblocks = ceil( (6*8* Nelements) / max_block_size) ;
end
if ~r.use_GPU
rec = tomo.FBP_CPU(sinogram, cfg0, vectors0, varargin{:}, 'verbose', 0);
return
end
if Nblocks == 1 || cfg0.iProjU>4096 || cfg0.iProjV>4096
% for small datatsets process everthing in a single block
if Nblocks == 1
sinogram = utils.Garray(sinogram);
end
rec = tomo.FBP(sinogram, cfg0, vectors0, varargin{:}, 'verbose', 0);
if ~isempty(r.mask)
rec = rec .* r.mask;
end
if ~r.keep_on_GPU
rec = gather(rec);
end
else
%% split tomogram into vertical blocks, it is the most effecient way how to calculate it
% -> upload each block to GPU and keep it there for maximal speed
Nl_small = ceil(Nlayers / Nblocks);
% keep on GPU only if the volume is small enough !!
r.keep_on_GPU = r.keep_on_GPU && (4*cfg0.iVolX*cfg0.iVolY*cfg0.iVolZ) < gpu.AvailableMemory / 10;
if r.keep_on_GPU && gpuDeviceCount
rec = gpuArray.zeros(cfg0.iVolX, cfg0.iVolY, cfg0.iVolZ, 'single');
else
rec = zeros(cfg0.iVolX, cfg0.iVolY, cfg0.iVolZ, 'single');
end
for ii = 1:Nblocks
if r.verbose>0; utils.progressbar(ii, Nblocks); end
ind = 1+Nl_small*(ii-1) : min(Nlayers, Nl_small*ii);
if isempty(ind); continue; end
if isa(sinogram, 'gpuArray')
% if on GPU, use matlab memcpy
sinogram_small = sinogram(ind,:,:);
else
% fast MEX memory copy
pos = [Nl_small*(ii-1),0];
sinogram_small = zeros(length(ind),Nw,Nproj, 'like', sinogram);
sinogram_small = utils.get_from_3D_projection(sinogram_small, sinogram, repmat(pos,Nproj,1), 1:Nproj);
end
cfg = cfg0;
vectors = vectors0;
cfg.iProjV = size(sinogram_small,1);
cfg.iVolZ = cfg.iProjV;
vectors(:,4:6) = vectors(:,4:6) - vectors(:,10:12)*(cfg.iProjV - cfg0.iProjV)/2;
sinogram_small = utils.Garray(fp16.get(sinogram_small));
%% call standard FBP on the data that are on GPU
try
rec_tmp = tomo.FBP(sinogram_small, cfg, vectors,varargin{:}, 'split', r.split, 'verbose', 0, 'keep_on_GPU', r.keep_on_GPU);
catch err
if strcmp(err.identifier, 'parallel:gpu:array:OOM')
warning(err.message)
end
keyboard
end
rec(:,:,ind) = rec_tmp;
end
end
end
+174
View File
@@ -0,0 +1,174 @@
% SART tomography reconstruction code
%
% [rec] = SART(rec, sino, cfg, vectors, cache, varargin)
%
% Inputs:
% **rec - initial guess of the reconstruction
% **sino - sinogram (Nlayers x width x Nangles)
% **cfg - config struct from ASTRA_initialize
% **vectors - vectors of projection rotation generated by ASTRA_initialize
% **cache - precalcualated cache by SART_prepare
% *optional*
% ** split =[1,1,1] - split the solved volume, split(3 is used to split in separated blocks, split(1:2) is used inside Atx_partial to du subplitting for ASTRA
% ** valid_angles = [] - list of valid angles, []==all are valid
% ** filter = 'ram-lak' - name of the FBP filter
% ** filter_value = 1 - fitlering value for the FBP filter
% ** deformation_fields = {} - cell 3x1 of deformation arrays
% ** inv_deformation_fields={}-cell 3x1 of inverse deformation arrays
% ** GPU = [] - list of GPUs to be used in reconstruction
% ** split_sub = [1,1,1] - splitting of the sub block on smaller tasks in the Atx_partial method , 1 == no splitting
% ** verbose = 1 - verbose = 0 : quiet, verbose : standard info , verbose = 2: debug
% ** relax = 0 - relaxation constant, shorter steps , 1 == no relaxation
% ** constraint= [] - constraint function that will be applied after every step
%
% *returns*
% ++rec reconstruction
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 [rec, err] = SART(rec, sinogram, cfg,vectors,cache,varargin)
import tomo.*
import utils.*
import astra.*
G = cfg.Grouping;
cfg.iProjAngles = size(vectors,1);
Nsets = ceil(cfg.iProjAngles/G);
% try to optimize the group sizes
G = ceil(cfg.iProjAngles / Nsets);
Nsets = ceil(cfg.iProjAngles/G);
par = inputParser;
par.addOptional('split', 1)
par.addParameter('valid_angles', [])
par.addParameter('deformation_fields', cell(Nsets,1) ) % cell 3x1 of deformation arrays
par.addParameter('inv_deformation_fields', cell(Nsets,1) ) % cell 3x1 of deformation arrays
par.addParameter('GPU', []) % list of GPUs to be used in reconstruction
par.addParameter('split_sub', 1) % splitting of the sub block on smaller tasks in the Atx_partial method , 1 == no splitting
par.addParameter('verbose', 1) % verbose = 0 : quiet, verbose : standard info , verbose = 2: debug
par.addParameter('relax', 0, @(x)(x < 1 && x >= -1)) % relaxation constant, shorter steps , 1 == no relaxation
par.addParameter('constraint', []) % constraint function that will be applied after every step
par.addParameter('keep_on_GPU', true) % constraint function that will be applied after every step
par.parse(varargin{:})
res = par.Results;
if ~isempty(res.valid_angles)
sinogram = sinogram(:,:,res.valid_angles);
vectors = vectors(res.valid_angles,:);
cache.R = cache.R(:,:,res.valid_angles);
try cfg.lamino_angle = cfg.lamino_angle(res.valid_angles); end
end
if islogical(res.valid_angles); res.valid_angles = find(res.valid_angles); end
err = nan(Nsets,1);
rng('default')
if isempty(res.deformation_fields) && isempty(res.inv_deformation_fields)
indices = randperm(cfg.iProjAngles);
for i=randperm(Nsets)
ind{i} = indices((1+(i-1)*G):min(cfg.iProjAngles,i*G));
end
else
% for deformation tomography keep the blocks corresponding to the
% subtomograms
for i=1:Nsets
ind{i} = (1+(i-1)*G):min(cfg.iProjAngles,i*G);
end
end
if ~isempty(res.valid_angles)
for i=1:Nsets
ind{i} = intersect(ind{i}, res.valid_angles);
end
end
for i = 1:Nsets
if res.verbose > 0
utils.progressbar(i, Nsets);
end
cfg.iProjAngles = length(ind{i});
data = sinogram(:,:,ind{i});
R = cache.R(:,:,ind{i});
if ~isempty(res.deformation_fields)
R = R .* tukeywin(cfg.iProjV, 0.2); % avoid amplification of edge regions where deformation may be wrong
end
if res.keep_on_GPU
data = gpuArray(data);
end
if ~isa(data, 'gpuArray') && (isempty(res.deformation_fields) || isempty(res.deformation_fields{i}))
proj = Ax_sup_partial(rec, cfg, vectors(ind{i},:), res.split, 'GPU', res.GPU, 'split_sub', res.split_sub, 'verbose',0, 'deformation_fields',res.deformation_fields{i});
else
proj = Ax_partial(rec, cfg, vectors(ind{i},:), res.split_sub, 'verbose',0, 'deformation_fields', res.deformation_fields{i} );
end
D = data - proj;
D = D .* R;
if nargout > 1
%% get error before update
err(ind{i}) = gather(sqrt(mean(mean(D.^2))));
end
if ~isa(D, 'gpuArray') && (isempty(res.deformation_fields) || isempty(res.deformation_fields{i}))
rec_upd = Atx_sup_partial(D, cfg, vectors(ind{i},:), res.split, 'GPU', res.GPU, 'split_sub', res.split_sub, 'verbose', res.verbose, 'deformation_fields', res.inv_deformation_fields{i} );
else
rec_upd = Atx_partial(D, cfg, vectors(ind{i},:), 1, 'verbose', res.verbose, 'deformation_fields', res.inv_deformation_fields{i} );
end
rec_upd = ((1-res.relax)/cfg.iProjAngles)*rec_upd;
if ~isa(rec,'gpuArray')
rec_upd = gather(rec_upd);
end
rec = rec + rec_upd;
% apply constraints
if ~isempty(res.constraint)
rec = res.constraint(rec);
end
end
if ~isempty(res.constraint)
rec = res.constraint(rec);
end
end
+93
View File
@@ -0,0 +1,93 @@
% SART_PREPARE prepare cache and config for SART tomography reconstruction code
%
% [cache,cfg] = SART_prepare(cfg, vectors, grouping, varargin)
%
% Inputs:
% **cfg - config struct from ASTRA_initialize
% **vectors - vectors of projection rotation generated by ASTRA_initialize
% **split - split parameter for the ASTRA projector
% *optional*
% ** split =[1,1,1] - split the solved volume, split(3 is used to split in separated blocks, split(1:2) is used inside Atx_partial to du subplitting for ASTRA
% ** deformation_fields = {} - cell 3x1 of deformation arrays
% ** GPU = [] - list of GPUs to be used in reconstruction
% ** split_sub = [1,1,1] - splitting of the sub block on smaller tasks in the Atx_partial method , 1 == no splitting
% ** verbose = 1 - verbose = 0 : quiet, verbose : standard info , verbose = 2: debug
% ** keep_on_GPU = false - if true, do not gather arrays from GPU -> faster
% ** weights = [] - custom weights (3D array) that denote reliable region in the projection
%
% *returns*
% ++cache - precalcualted values needed for SART
% ++cfg - modified ASTRA config structure needed for SART
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 [cache,cfg] = SART_prepare(cfg, vectors, grouping, varargin)
par = inputParser;
par.addOptional('split', 1)
par.addParameter('deformation_fields', {} ) % cell 3x1 of deformation arrays
par.addParameter('GPU', []) % list of GPUs to be used in reconstruction
par.addParameter('split_sub', 1) % splitting of the sub block on smaller tasks in the Atx_partial method , 1 == no splitting
par.addParameter('verbose', 0) % verbose = 0 : quiet, verbose : standard info , verbose = 2: debug
par.addParameter('keep_on_gpu', false); % keep resuls on GPU or move to RAM
par.addParameter('weights', []); % custom weights (3D array) that denote reliable region in the projection
par.parse(varargin{:})
res = par.Results;
Nangles = length(vectors);
cfg.Grouping = min(Nangles, grouping);
cfg.iProjGroups = ceil(Nangles/cfg.Grouping);
if ~res.keep_on_gpu && gpuDeviceCount
empty = ones(cfg.iVolX, cfg.iVolY, cfg.iVolZ, 'single');
R = tomo.Ax_sup_partial(empty, cfg, vectors, res.split, 'GPU', res.GPU, 'split_sub', res.split_sub, 'verbose', res.verbose, 'deformation_fields', res.deformation_fields );
else
empty = gpuArray.ones(cfg.iVolX, cfg.iVolY, cfg.iVolZ, 'single');
R = astra.Ax_partial(empty, cfg, vectors, res.split_sub, 'verbose', res.verbose, 'deformation_fields', res.deformation_fields );
end
% some "representative" sample of R
R_val = R(ceil(end/2), ceil(end/2), ceil(end/2));
R = (R>0)./sqrt(R.^2+(1e-2 * R_val)^2 ); %% !! avoid division by 0
if ~isempty(res.weights)
R = R.*res.weights; % apply custom weights
end
cache.R = R;
cfg.iProjAngles = Nangles;
cache.Nbunches = cfg.iProjGroups;
end
+89
View File
@@ -0,0 +1,89 @@
% SART_ZSPLIT simple wrapper of the SART function that allows easy dataset
% splitting along the vertical axis
%
% rec=SART_zsplit(rec, sinogram, cfg, vectors, cache, relax )
%
% Inputs:
% **rec - initial guess of the reconstruction
% **sino - sinogram (Nlayers x width x Nangles)
% **cfg - config struct from ASTRA_initialize
% **vectors - vectors of projection rotation generated by ASTRA_initialize
% **Niter - number of iterations
% **relax - 0 == no relaxation, -> relax projection in presence of noise
% **split - split parameter for the ASTRA projector
% *optional*
% ** split =[1,1,1] - split the solved volume, split(3 is used to split in separated blocks, split(1:2) is used inside Atx_partial to du subplitting for ASTRA
% ** valid_angles = [] - list of valid angles, []==all are valid
% ** filter = 'ram-lak' - name of the FBP filter
% ** filter_value = 1 - fitlering value for the FBP filter
% ** deformation_fields = {} - cell 3x1 of deformation arrays
% ** inv_deformation_fields={}-cell 3x1 of inverse deformation arrays
% ** GPU = [] - list of GPUs to be used in reconstruction
% ** split_sub = [1,1,1] - splitting of the sub block on smaller tasks in the Atx_partial method , 1 == no splitting
% ** verbose = 1 - verbose = 0 : quiet, verbose : standard info , verbose = 2: debug
% ** relax = 0 - relaxation constant, shorter steps , 1 == no relaxation
% ** constraint= [] - constraint function that will be applied after every step
%
% *returns*
% ++rec - FBP reconstruction
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 rec=SART_zsplit(rec, sinogram, cfg, vectors, cache, relax )
[Nlayers, ~, ~] = size(sinogram);
Nvols = ceil(numel(rec)*4 / 1024e6);
Nlayers_on_GPU = ceil(Nlayers/Nvols);
for i = 1:ceil(Nlayers/Nlayers_on_GPU)
progressbar(i, ceil(Nlayers/Nlayers_on_GPU))
ind = (1+(i-1)*Nlayers_on_GPU):min(i*Nlayers_on_GPU, Nlayers);
%%%%%%%%%%%%%%%%%%%% SART SPLIT + MOVE TO GPU %%%%%%%%%%%%%%%%%%%%%%%
rec_tmp = gpuArray(rec(:,:,ind));
sinogram_tmp = gpuArray(sinogram(ind,:,:));
cfg_tmp = cfg;
cfg_tmp.iVolZ = length(ind);
cfg_tmp.iProjV = length(ind);
cache_tmp = cache;
cache_tmp.R = cache_tmp.R(ind,:,:);
%% call normal SART
rec_tmp = SART(rec_tmp, sinogram_tmp, cfg_tmp, vectors, cache_tmp, relax ) ;
rec(:,:,ind) = gather(rec_tmp);
end
end
+273
View File
@@ -0,0 +1,273 @@
% ALIGN_TOMO_XCORR Cross-correlation alignment
% [total_shift, variation, variation_aligned] = align_tomo_Xcorr(object, angles, par, varargin)
%
% Inputs:
% **object - complex valued projections to be aligned
% **angles - corresponding angles (used only for sorting the projections)
% **par - parameter structure with:
% *optional* (use value in param as default)
% **filter_pos - parameter for high pass filtering of the shifts
% **filter_data - highpass filtering of the data
% **max_iter - maximal number of iterations
% **binning - binning applied on the data
% **ROI - region of interest
% *returns*
% ++total_shift - optimal shift of the projections
% ++variation - local variation of the measured data
% ++variation_aligned - aligned variation of the measured data
%
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 [total_shift, variation, variation_aligned] = align_tomo_Xcorr(object_0, angles, par, varargin)
import utils.*
import math.*
utils.verbose(struct('prefix', 'align'))
parser = inputParser;
parser.addParameter('binning', 1 , @isnumeric )
parser.addParameter('max_iter', 1 , @isnumeric )
parser.addParameter('filter_pos', 50 , @isnumeric )
parser.addParameter('filter_data', 0.05 , @isnumeric )
parser.addParameter('ROI', {} , @iscell )
parser.parse(varargin{:})
r = parser.Results;
% load all to the param structure
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
if isreal(object_0)
error('Complex object expected')
end
% binning to speed up the calculation, anyway we need only low
% resolution guess
weights = par.illum_sum ./ (par.illum_sum+1e-1*max(par.illum_sum(:)));
variation = tomo.block_fun(@get_variation_field, object_0,par.binning, weights(par.ROI{:}), struct('use_GPU', true, 'ROI', {par.ROI}, 'use_fp16', false));
variation = real(variation); % the real() function must be applied outside of get_variation_field to get betetr performance, it seems like a bug in matlab
[~,ind_sort] = sort(angles);
[~,ind_sort_inv] = sort(ind_sort);
% get some initial guess of the relative shifts
[Nx, Ny, Nangles] = size(variation);
total_shift = zeros(length(angles),2);
if ~par.is_laminography
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% find center of rotation from comparison between 0 and 180 deg frame %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
ind = math.argmin(abs(angles(1)+180 - angles)); % find the opposite angle to 0 degrees
avg_step = 5*median(diff(sort(angles)));
if abs(angles(ind)-(angles(1)+180)) < avg_step
obj_tmp = variation(:,:,[1,ind]);
obj_tmp(:,:,2) = fliplr(obj_tmp(:,:,2));
fvar = filtered_FFT(obj_tmp,[0,0],par);
CoR_offset = 0.5 * find_shift_fast_2D(fvar(:,:,1),fvar(:,:,2),0, false);
% apply the estimated CoR offset to the total shift
total_shift(:,1) = total_shift(:,1) - CoR_offset(1);
else
utils.verbose('Missing mirror projection for initial projection, skipping CoR estimation')
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% apply crosscorrelation between subsequent frames to remove the relative shifts %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
for iter = 1:par.max_iter
utils.progressbar(iter, par.max_iter);
% apply the estimated shift to the data to get better next step
% AND
% use local variation in order to suppress issues
% caused by low spatial freq. errors in the ptychography
% reconstruction
fvar = tomo.block_fun(@filtered_FFT,variation,total_shift, par, struct('verbose_level', 0));
% compare subsequent slices
frame_ref = fvar(:,:,ind_sort);
frame_align = fvar(:,:,circshift(ind_sort,-1));
% align the first frame with flipped version of the last frame
if ~par.is_laminography && abs(mod(angles(1) - angles(Nangles)-180, 360)) < 2*median(abs(diff(angles)))
frame_ref(:,:,end) = fft2(fliplr(ifft2(frame_ref(:,:,end))));
end
% find the optimal relative shift between projections and the adjanced angles
relative_shifts = tomo.block_fun(@find_shift_fast_2D,frame_ref,frame_align,0, false,struct('verbose_level', 0));
relative_shifts = circshift(relative_shifts,1);
% avoid too fast jumps by limiting the maximal step size per iteration
max_shift = max(10 ,3*mad(relative_shifts));
relative_shifts = min(max_shift, abs(relative_shifts)) .* sign(relative_shifts);
% long term drifts cannot be trusted
cum_shift = cumsum(relative_shifts);
cum_shift = cum_shift - mean(cum_shift);
% minimize the shift amplitude that is needed
% long term drifts cannot be trusted
if ~isinf(par.filter_pos)
for i = 1:2
smooth = ceil(par.filter_pos/2)*2+1;
% get properly smoothed cumulative shift
smoothed_shift = conv(cum_shift(:,i),ones(smooth,1)/smooth, 'same') ./ conv(ones(Nangles,1),ones(smooth,1)/smooth, 'same');
% subtract it from cum_shift
cum_shift(:,i) = cum_shift(:,i) - smoothed_shift;
end
end
total_shift = total_shift + cum_shift(ind_sort_inv,:);
% limit the maximal shift to 3* mean absolute devition of all the
% position -> prevent outliers
total_shift = min(6*mad(total_shift,1), abs(total_shift)) .* sign(total_shift);
% draw position shifts
plotting.smart_figure(12)
clf()
plot(par.scanstomo, (total_shift(ind_sort,:)-mean(total_shift)) * par.binning,'.')
legend({'Horizontal', 'Vertical'})
xlabel('Scan number')
axis tight
grid on
title('Total shift estimated by initial cross-correlation')
drawnow
if 3*mad(cum_shift(:)) < par.precision && ~debug()
break
end
end
% return shifted projections for user to judge the alignment quality
variation_aligned = utils.imshift_fft(variation, total_shift);
total_shift = total_shift .* par.binning;
if ~debug()
total_shift = round(total_shift); % the accuracy is too low for subpixel shift
end
end
function variation = get_variation_field(object, binning, weights)
% just auxiliar function to move computations on GPU
import utils.*
import math.*
dX = convn(object,[-1,1], 'same');
dY = convn(object,[-1,1]', 'same');
if isa(dX, 'gpuArray')
variation = arrayfun(@aux_fun,dX, dY, object);
else
variation = aux_fun(dX, dY, object);
end
% remove phase ramp artefacts
variation([1,end],:,:) = variation([2,end-1],:,:);
variation(:,[1,end],:) = variation(:,[2,end-1],:) ;
% crop values exceeding limits, important mainly for laminography where
% the field of view can contain weakly illuminated (ie very noisy) regions
mean_variation = mean2(variation .* weights) ./ mean2(weights);
dev_variation = sqrt(mean2((variation-mean_variation).^2 .* weights) ./ mean2(weights));
variation = min(variation, mean_variation + 1*dev_variation);
% decimate data to lower resolution, smoothing needs to be applied before downsampling
% smoothed array works better than binning
variation = utils.imgaussfilt3_conv(variation, [2*binning,2*binning,0]);
boundary_correction = utils.imgaussfilt3_conv(ones(size(object,1), size(object,2), 'like', object), [2*binning,2*binning,0]);
variation = variation ./ boundary_correction;
variation = variation(1:binning:end,1:binning:end,:);
end
function variation = aux_fun(dX, dY, object)
% get total variation -> better results in alignment than raw
% object
variation = sqrt(abs(dX).^2 + abs(dY).^2);
% ignore regions with low amplitude
variation = variation .* abs(object);
end
function img = filtered_FFT(img, shift, par)
[nx, ny, ~] = size(img);
img = utils.imshift_fft(img ,shift);
% suppress edge effects of the registration procedure
spatial_filter = tukeywin(nx,0.3) * tukeywin(ny,0.3)';
img = img - mean(img(:));
img = img .* spatial_filter;
% precalculate FFT
img = fft2(img);
% remove low frequencies (e.g. phase ramp issues)
if par.filter_data > 0
[X,Y] = meshgrid( (-nx/2:nx/2-1), (-ny/2:ny/2-1));
spectral_filter = fftshift(exp(1./(-(X.^2+Y.^2)/(mean([nx,ny])*par.filter_data)^2)))';
img = img.* spectral_filter;
end
end
+390
View File
@@ -0,0 +1,390 @@
% ALIGN_TOMO_CONSISTENCY_XCORR cross-correlation of measured projections with the projected model
% is it much more robust to outliers than the optimization based methods,
% ! but in some cases it can make the reconstruction worse !
% also only model projections are shifted in contrast to align_tomo_consistency_linear where the measured projectins are shifted
% issues that majority of the points are correct
%
% xcorr_shift_total = align_tomo_consistency_Xcorr(sinogram_0, sino_weights, angles, shift_0, binning, Npix, par, varargin)
%
% Inputs:
% **sinogram_0 - unwrapped phase or phase difference
% **angles - corresponding angles (used only for sorting the projections)
% **shift_0 - initial guess of the sinogram shifts
% **binning - binning used for calculations to speed it up
% **Npix - size of the tomogram
% **par - tomography parameter structure ,
% **varargin - list of all paremters is described in code
% *returns*:
% ++xcorr_shift_total - additional shift that nees to be added to shift_0 input in order to maximize this self-consitency alignment method
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 xcorr_shift_total = align_tomo_consistency_Xcorr(sinogram_0, sino_weights, angles, shift_0, binning, Npix, par, varargin)
import tomo.*
import utils.*
import math.*
import plotting.*
utils.verbose(struct('prefix', 'align'))
parser = inputParser;
parser.addParameter('align_vertical', true , @islogical )
parser.addParameter('align_horizontal', false , @isnumeric )
parser.addParameter('apply_positivity', true , @islogical ) % apply_positivity , useful for tomography, should be avoided for laminography
parser.addParameter('high_pass_filter', 0.02 , @isnumeric ) % high pass filter applied on the xcorrelated model and data to remove effect of low spatial freq. errors, ie phase ramp , smaller value == less filtering
parser.addParameter('binning', 4 , @isint ) % binning applied on the projections to speed up shift estimation
parser.addParameter('lamino_angle', 90 , @isnumeric ) % laminography title (with respect to the beam ))
parser.addParameter('tilt_angle', 0 , @isnumeric ) % rotation of camera around axis of the beam
parser.addParameter('skewness_angle', 0 , @isnumeric ) % skewness of camera around axis of the beam
parser.addParameter('pixel_scale', [1,1] , @isnumeric ) % pixel scale along each axis
parser.addParameter('unwrap_data_method', 'fft_1D' , @isstr ) % assume that inputs is phase derivative, accepted values: 'fft_1d' for input phase difference, 'none' for already unwrapped input
parser.addParameter('verbose', 1 , @isnumeric ) % change verbosity of the code
parser.addParameter('filter_type', 'ram-lak' , @isstr ) % FBP settings
parser.addParameter('freq_scale', '1' , @isnumeric ) % FBP settings
parser.addParameter('plot_results', true , @islogical ) % plot results
parser.addParameter('is_laminography', false , @isnumeric ) % change default behavoir for laminography reconstructions
parser.addParameter('CoR_offset', [] , @isnumeric ) % offset of the center of rotation , empty == auto
parser.addParameter('Niter', 5 , @isnumeric ) % number of ierations of the Xcorr refinement
parser.addParameter('selected_roi', {} , @iscell ) % field of view considered for alignment for laminography
parser.addParameter('vert_range', [] , @isnumeric) % vertical range considered for alignment for standard tomo
parser.parse(varargin{:})
r = parser.Results;
% load all to the param structure
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
assert(isreal(sinogram_0), 'Input projections has to be real-valued, either unwrapped phase or phase difference')
verbose(1,'==== Starting self-consistent cross correlation ==== ')
relax = 0.8;
if ~isempty(par.vert_range) && isempty(par.selected_roi)
vrange = ceil(par.vert_range([1,end])/16)*16 + [0,-1]; % help with splitting in ASTRA (GPU memory)
if isempty(vrange(1):vrange(2))
error('Too small par.vert_range, extend the alignment range');
end
if length(vrange(1):vrange(2)) < 10*binning && par.align_vertical
error('Too small par.vert_range for vertical alignment, extend the alignment range');
end
par.selected_roi = {vrange(1):vrange(2),':'};
end
[Nlayers,width_sinogram,Nangles] = size(sinogram_0);
verbose(1,['Binning: ', num2str(binning)])
sinogram_0 = tomo.block_fun(@imreduce,sinogram_0, par.selected_roi, binning, [Nlayers,width_sinogram]);
if ~isempty(sino_weights)
if isa(sino_weights, 'uint8')
sino_weights = single(sino_weights)/255; % load the weights from uint8 format
end
assert(all(sum(sum(sino_weights)) > 0), sprintf('Provided "weights" contain %i projections with empty mask', sum(sum(sum(sino_weights))==0)))
sino_weights = tomo.block_fun(@imreduce,sino_weights, par.selected_roi, binning, [Nlayers,width_sinogram], struct('full_block_size', [Nlayers,width_sinogram,Nangles]));
else
sino_weights = 1 ;
end
[Nlayers,width_sinogram,Nangles] = size(sinogram_0);
if gpuDeviceCount
gpu = gpuDevice;
if isempty(par.GPU_list)
par.GPU_list = gpu.Index;
end
GPU_list = par.GPU_list(1);
gpu_split = 1;
% split it among multiple GPU only for larger datasets (>1000MB)
if numel(sinogram_0) * 4 > 1000e6 && length(par.GPU_list) > 1
gpu_split = max(1,length(par.GPU_list));
GPU_list = par.GPU_list;
end
if gpu.Index ~= GPU_list(1)
gpuDevice(GPU_list(1));
end
else
gpu_split = 1;
GPU_list = [];
end
Npix = ceil(Npix/binning);
if isscalar(Npix)
Npix = [Npix, Npix, Nlayers];
elseif length(Npix) == 2
Npix = [Npix, Nlayers];
end
shift_0 = shift_0 / binning;
%% unwrap complex data
if ~strcmpi( par.unwrap_data_method, 'none')
verbose(1,'Unwrapping')
sinogram_unwrapped = tomo.block_fun(@unwrap_data, sinogram_0, par.unwrap_data_method, par.air_gap/par.binning);
else
sinogram_unwrapped = sinogram_0;
end
%% get configuration for ASTRA
% !! important for binning => account for additional shift of the center
% of rotation after binning, for binning == 1 the correction is zero
if strcmpi(par.unwrap_data_method, 'none')
% direclty unwrapped phase
rotation_center = [Nlayers, width_sinogram]/2 + (1-1/par.binning);
else % phase difference
rotation_center = [Nlayers, width_sinogram]/2 - (1-1/par.binning);
end
if ~isempty(par.CoR_offset)
rotation_center = rotation_center + par.CoR_offset/par.binning;
end
[cfg, vectors_0] = ...
astra.ASTRA_initialize(Npix,[Nlayers, width_sinogram],angles,par.lamino_angle,par.tilt_angle,1, rotation_center);
%% find optimal split of the dataset for given GPU
split = astra.ASTRA_find_optimal_split(cfg, gpu_split);
vectors = vectors_0;
xcorr_shift_total = zeros(Nangles,2);
%% choose solver
if gpuDeviceCount == 0
tomo_solver = @FBP_CPU;
padding = [];
elseif ~par.is_laminography
% use simple z-axis splitting
tomo_solver = @FBP_zsplit ;
padding = 0;
else
% use more general solver, can be less efficient
tomo_solver = @FBP;
padding = 'symmetric';
end
win = tukeywin(width_sinogram, 0.2)'; % avoid edge issues
if Nlayers > 10 && (par.align_vertical ); win = tukeywin(Nlayers, 0.2)*win; end
sino_weights = sino_weights .* win;
for ii = 1:par.Niter
verbose(1,'Iter %i/%i ', ii, par.Niter)
%% apply initial shift to the geometry
vectors(:,4:6) = vectors_0(:,4:6) + ...
bsxfun(@times,vectors_0(:,10:12), shift_0(:,2) + xcorr_shift_total(:,2)) +...
bsxfun(@times,vectors_0(:,7:9), shift_0(:,1) + xcorr_shift_total(:,1));
%% FBP method
rec = tomo_solver(sinogram_unwrapped, cfg, vectors, [1,1,gpu_split],...
'valid_angles',par.valid_angles, ...
'GPU', GPU_list, 'split_sub', split, 'verbose', 0,...
'filter', par.filter_type, 'filter_value', par.freq_scale, 'padding', padding);
if par.apply_positivity
rec = max(0, rec);
end
% forward projection
if gpuDeviceCount
sinogram_corr = Ax_sup_partial(rec,cfg, vectors,[1,1,max(1,length(par.GPU_list))],...
'GPU', GPU_list, 'split_sub', split, 'verbose', 0);
else
sinogram_corr = radon_wrapper(rec, cfg, vectors);
end
% filter data to remove low freq. errors
if strcmpi(par.unwrap_data_method, 'fft_1d')
% fft_1d was used for unwrap -> input sinogram is phase_difference
sinogram_corr = - tomo.block_fun(@math.get_phase_gradient_1D,sinogram_corr);
% reduce effect of too sharp features
sinogram_corr = min(0.1, abs(sinogram_corr)) .* sign(sinogram_corr);
sinogram = min(0.1, abs(sinogram_0)) .* sign(sinogram_0);
elseif strcmpi(par.unwrap_data_method, 'none')
% suppress lowest spatial frequencies such as phase ramp from effecting alignment
sinogram_corr = sinogram_corr - tomo.block_fun(@utils.imgaussfilt2_fft, sinogram_corr, width_sinogram / 10);
sinogram = sinogram_unwrapped - tomo.block_fun(@utils.imgaussfilt2_fft, sinogram_unwrapped, width_sinogram / 10);
else
error('Unsupported unwrap_data_method')
end
% perform 2D cross-correlation to find the shift
xcorr_shift = utils.find_shift_fast_2D(sinogram_corr.*sino_weights,sinogram.*sino_weights, par.high_pass_filter, 'full_range');
xcorr_shift = gather(xcorr_shift);
% remove constant offsets
xcorr_shift = xcorr_shift - median(xcorr_shift);
% avoid too large jumps
xcorr_shift = xcorr_shift * relax;
xcorr_shift = min([width_sinogram,Nlayers] / 4, abs(xcorr_shift)) .* sign(xcorr_shift);
if any(isnan(xcorr_shift(:)))
warning('Crosscorrelation alignment resulted in NaN positions')
keyboard
end
if ~par.align_vertical
xcorr_shift(:,2) = 0;
end
if ~par.align_horizontal
xcorr_shift(:,1) = 0;
end
xcorr_shift_total = xcorr_shift_total + gather(xcorr_shift);
if any(xcorr_shift_total(:)~=0) && par.plot_results
[angles_sort, ang_sort] = sort(angles);
plotting.smart_figure(6464)
clf()
warning('off', 'MATLAB:legend:IgnoringExtraEntries')
if par.align_horizontal
subplot(1,2,1)
plot(angles_sort, xcorr_shift_total(ang_sort,1)*binning, '.r')
subplot(1,2,2)
plot(angles_sort, shift_0(ang_sort,1)*binning, '.r')
end
if par.align_vertical
subplot(1,2,1)
hold on
plot(angles_sort, xcorr_shift_total(ang_sort,2)*binning, '.b')
hold off
subplot(1,2,2)
hold on
plot(angles_sort, shift_0(ang_sort,2)*binning, '.b')
hold off
end
subplot(1,2,1)
legend({'Horizontal ', 'Vertical'});
title('Xcorr update shift')
xlim([min(angles), max(angles)])
xlabel('Angles')
ylabel('Shift [px]')
subplot(1,2,2)
legend({'Horizontal ', 'Vertical'});
title('Initial shifts')
xlim([min(angles), max(angles)])
xlabel('Angles')
ylabel('Shift [px]')
suptitle('Consitency-based cross-correlation position correction')
warning('on', 'MATLAB:legend:IgnoringExtraEntries')
drawnow
end
if max(abs(xcorr_shift))*binning < 1
% convergence reached
progressbar(par.Niter, par.Niter)
break
end
end
xcorr_shift_total = xcorr_shift_total * binning;
end
function img = imreduce(img, ROI, binning, Npix)
% auxiliary function that first upsamples the provided array to Npix
% size, then crops it down to ROI region and finally perform
% downsampling by interpolateFT that provides more precise results than
% binning
% !! merging all these operations into one function makes it much more efficient when processed on GPU via tomo.block_fun
%
% Inputs
% ** img - input image block
% ** ROI - cells of indices for selected ROI , ie. {10:500, 63:300}, ROI cropping is applied after first upsampling to Npix
% ** binning - integer or 2x1 vector, binning the img
% ** Npix - target size of img before binning - useful to save mask in low resolution and interpolate it when needed
% returns:
% ++img - processed image block
import math.*
import utils.*
isReal = isreal(img);
% if needed upsample to the size of the projection
img = utils.interpolate_linear(img, Npix);
% crop the FOV after shift and before "binning"
if ~isempty(ROI)
img = img(ROI{:},:); % crop to smaller ROI if provided
% apply crop aftetr imshift_fft
end
Np = size(img);
% perform FT interpolation instead of binning
img = interpolateFT(img, ceil(Np(1:2)/binning/2)*2);
if isReal; img = real(img); end
end
function sinogram = unwrap_data(sinogram, method, boundary)
% auxiliary function to perform data unwrapping, see
% math.unwrap2D_fft for detailed help
% ** sinogram - unwrapped arrays
% ** method - none or fft_1d unwrap method
% ** boundary - air region for zero boundary condtion
switch lower(method)
case 'fft_1d'
% unwrap the data by fft along slices
sinogram = -math.unwrap2D_fft(sinogram, 2, boundary);
case 'none'
% assume that data are already unwrapped
otherwise
error('Missing method')
end
end
File diff suppressed because it is too large Load Diff
+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
+537
View File
@@ -0,0 +1,537 @@
% ALIGN_TOMO_INITIAL Get fast initial guess of the vertical and horizontal alignment
%
% [optimal_shift] = align_tomo_initial(stack_object, shift_init, angles, param, varargin)
%
% Inputs:
% **stack_object - complex-valued input array that will be unwrapped and used for alignment
% **shift_init - initial guess of the shifts
% **angles - corresponding angles (used only for sorting the projections)
% *optional*: % if not provided, value from param is used as default
% **air_gap - empty region around sample where phase = 0 is assumed
% **vert_range - vertical range used for alignment , try to avoid highly
% scattering / residual features
% **phase_jumps_threshold - threshold above which the phase difference
% is assumed to be wrong and masked out
% **alignment_invariant - choose: phase_2D, phase_1D, phase_derivative, goldstein
% **use_vertical_xcorr_guess - if true, use crosscorrelation for initial guess
% **data_filter - high pass filter constant 0=none, 0.005-0.02 seems to be optimal
% OTHER INPUTS DESCRIBED IN CODE
%
% *returns*
% ++optimal_shift - (Nangles x 1 array) = vertical shift to be applied on the stack_object in order to minimize the vertical mass fluctuation
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 [optimal_shift] = align_tomo_initial(stack_object, shift_init, angles, ROI, param, varargin)
if nargin < 3
param = struct();
end
parser = inputParser;
parser.addParameter('vert_range', [] , @isnumeric ) % vertical range used for alignment
parser.addParameter('air_gap', [50, 50] , @isnumeric ) % rough estimate of air region
parser.addParameter('phase_jumps_threshold', 1 , @isnumeric ) % threshold above which the phase difference is assume to be wrong
parser.addParameter('alignment_invariant', 'phase_2D' , @isstr ) % name of the invariant used for alignment
parser.addParameter('showsorted', true , @islogical ) % if the projections should be plotted sorted by angle
parser.addParameter('use_vertical_xcorr_guess', true , @islogical ) % get an initial guess by Xcorr, -> avoid trapping in local minima
parser.addParameter('data_filter', 0.02 , @isnumeric ) % high pass filtering to remove low spatial freq. errors
%% internal variables, usually no need to change
parser.addParameter('outer_loops_refinement', 3 , @isnumeric ) % number of outer loops for linear refinement step
parser.addParameter('N_SVD_modes', 10 , @isnumeric ) % number of SVD modes used to fill empty gaps in phase invariant
parser.addParameter('weights', [] , @isnumeric ) % numeric of logical array contaning weights for each projection and each pixels for 2D unwrapping
parser.addParameter('windowautopos', true , @islogical ) % distribute the plots over screen autimatically
parser.parse(varargin{:})
r = parser.Results;
% load all to the param structure
for name = fieldnames(r)'
if ~isfield(param, name{1}) % prefer values in param structure
param.(name{1}) = r.(name{1});
end
end
import tomo.*
import utils.*
import math.*
utils.verbose(struct('prefix', 'align'))
if ~isempty(param.vert_range)
ROI{1} = ROI{1}(max(1,param.vert_range(1)):min(end,param.vert_range(end)));
end
Nlayers = length(ROI{1});
Nw = length(ROI{2});
Nangles = length(angles);
switch param.alignment_invariant
case 'phase_1D'
%% standard vertical mass fluctuation
utils.verbose(0,'Fast 1D FFT unwrapping')
[phase, phase_diff, residues] = tomo.block_fun(@unwrap2D_fft, stack_object, 2, param.air_gap, struct('ROI', {ROI}, 'use_fp16', false));
invar0 = max(0,squeeze(sum(-phase,2)));
jumps = abs(phase_diff) > param.phase_jumps_threshold;
mask_invar = squeeze(any(jumps,2)); %% relevance weights for each line
mask_residues = squeeze(sum(residues,2))>0;
mask_residues = conv2(mask_residues,ones(3,1),'same')>0;
mask_invar(2:end,:) = mask_invar(2:end,:) | mask_residues;
case 'phase_2D'
utils.verbose(0,'Fast 2D FFT unwrapping')
% standard vertical mass fluctuation
% fft-based unwrapping
[phase, residues] = unwrap2D_fft2_split(stack_object, param.air_gap,1,param.weights,param.GPU_list,ROI);
invar0 = squeeze(sum(phase,2));
mask_invar = squeeze(sum(residues,2))>0;
% mask_invar = conv2(mask_invar,ones(3,1),'same')>0;
case 'phase_derivative'
% vertical derivative fluctuation
utils.verbose(0,'Get phase gradient')
phase_diff = tomo.block_fun(@get_phase_gradient_1D,stack_object, 1,1, struct('ROI', {ROI}, 'use_fp16', false));
invar0 = squeeze(sum(phase_diff,2));
jumps = abs(phase_diff) > param.phase_jumps_threshold;
jumps(:,[1:2,end-1:end],:) = 0; % avoid jumps caused by phase ramp
mask_invar = squeeze(sum(jumps,2) > 1); %% relevance weights for each line
case 'phase_goldstein'
utils.verbose(0,'Estimating residua')
residues = abs(findresidues(stack_object)) > 0.1;
mask_invar = squeeze(sum(residues,2))>0;
if sum2(mask_invar) > 1
warning('Selected range contains %i residua', sum2(mask_invar))
end
phase = zeros(Nlayers, Nw, Nangles, 'single');
parfor ii = 1:Nangles
utils.progressbar(ii, Nangles)
o = stack_object(:,:,ii)
phase(:,:,ii) = utils.goldsteinunwrap2(angle(o(ROI{:})));
end
phase = utils.remove_sinogram_ramp(phase,param.air_gap, true);
invar0 = squeeze(sum(phase,2));
otherwise
error('Missing option %s', par.alignment_invariant)
end
clear jumps
% move to GPU, always assume that GPU is availible
invar0 = Garray(invar0);
if any(sum(invar0)==0)
error('Some projections are empty')
end
if ~exist('residues', 'var') && ~strcmpi(param.alignment_invariant, 'phase_derivative')
utils.verbose(0,'Estimating residua')
residues = tomo.block_fun(@(x)(abs(findresidues(x)) > 0.1), stack_object);
mask_invar = mask_invar | squeeze(sum(residues,2))>0;
end
if any(mean(mask_invar) > 0.9)
wrong = param.scanstomo(mean(mask_invar) > 0.9);
error(sprintf(['Too many phase jumps in %i angles, alignment will fail \n try to increase par.phase_jumps_threshold or change par.alignment_invariant\n Wrong scans: ', repmat('%i ',1,length(wrong)), ' \n quitting'], length(wrong), wrong ))
elseif any(mean(mask_invar) > 0.7)
wrong = param.scanstomo(mean(mask_invar) > 0.7);
warning(sprintf(['Too many phase jumps in %i angles, alignment will most likely fail \n try to increase par.phase_jumps_threshold or change par.alignment_invariant\n Wrong scans: ', repmat('%i ',1,length(wrong)), ], length(wrong), wrong ))
end
if isempty(shift_init)
shift_init = zeros(Nangles, 1);
end
Nplots = 2+param.use_vertical_xcorr_guess;
if param.showsorted
[sangles,plot_sort] = sort(angles);
x_axis = sangles;
x_label = 'Angled [deg]';
else
plot_sort = 1:Nangles;
x_axis = param.scanstomo;
x_label = 'Scan number';
end
weight = ~mask_invar;
% remove linear offset -> prevents boundary problems
invar = remove_linear_ramp(invar0);
% apply only integer shift
shift_Y = shift_init;
invar = imshift_fft_ax(invar, shift_Y, 1);
weight = imshift_linear_ax(weight, shift_Y, 1, 'nearest', 0);
% select range without boundary issues
offset= max(abs(shift_Y));
range = round(2+offset : Nlayers - offset-1);
if length(range) < 20; error('Too small range for vertical alignment'); end
invar = invar(range,:);
weight = weight(range,:);
Nlayers = length(range);
% remove linear offset -> prevents boundary problems
invar = remove_linear_ramp(invar);
%% plot initial alignment
fig_id = 5667;
if param.windowautopos && ~ishandle(fig_id) % autopositioning only if the figure does not exists yet
plotting.smart_figure(fig_id)
set(gcf,'units','normalized','outerposition',[0.2 0.2 0.8 0.8])
else
plotting.smart_figure(fig_id)
end
ax(1)=subplot(Nplots,3,1);
invar_tmp = invar;
invar_tmp = imfilter_high_pass_1d(invar_tmp, 1, param.data_filter, Nlayers/2);
imagesc(x_axis, 1:Nlayers, invar_tmp(:,plot_sort), quantile(invar_tmp(~isnan(invar_tmp)), [1e-2,1-1e-2]))
axis xy
grid on
title('No alignment, linear ramp removed')
ylabel('Vertical axis [pixels]')
xlabel(x_label)
subplot(Nplots,3,2)
invar_tmp(~weight) = nan ;
plot(invar_tmp)
xlabel('Vertical pixels')
axis tight
ax(4)=subplot(Nplots,3,3);
imagesc(x_axis, 1:Nlayers, 1-weight(:,plot_sort))
axis xy
title('Phase jumps / Residues mask')
utils.verbose(0,'Vertical alignment - initial guess')
utils.verbose(0,'Damaged pixels: %3.2g%%', mean2(~weight)*100)
%subtitle('Tomography invariant vertical alignment')
ylabel('Vertical axis [pixels]')
xlabel(x_label)
if param.use_vertical_xcorr_guess
%% use cross correlation as the first guess
[shift_Y,invar_filtered] = ...
cross_correlation_estimation(invar, weight, angles, param.N_SVD_modes, param.data_filter);
% try to be smart and avoid drastic jumps
% shift_Y = max(shift_Y, quantile(shift_Y, 1e-2));
% shift_Y = min(shift_Y, quantile(shift_Y, 1-1e-2));
% minimize the shift offset
shift_Y = shift_Y - (max(shift_Y)+min(shift_Y))/2;
%shift_Y = shift_Y - median(shift_Y);
% perform only nearest neighbor shift
invar_filtered = imshift_linear_ax(invar_filtered, shift_Y, 1, 'circ');
weight_shifted = imshift_linear_ax(weight, shift_Y, 1, 'nearest',0);
%% plot current estimation
ax(2)=subplot(Nplots,3,4);
imagesc(x_axis, 1:Nlayers, invar_filtered(:,plot_sort), quantile(invar_filtered(:), [1e-2,1-1e-2]))
title('X-corr based guess - highpass filtered')
axis xy
grid on
ylabel('Vertical axis [pixels]')
xlabel(x_label)
subplot(Nplots,3,5)
invar_filtered(~weight_shifted) = nan;
plot(invar_filtered)
axis tight
xlabel('Vertical pixels')
title('Line plot - highpass filtered')
subplot(Nplots,3,6)
plot(x_axis, shift_Y(plot_sort))
axis tight
title('Applied shift')
ylabel('Shift [pixels]')
grid on
utils.verbose(0,'Vertical alignment - iterative refinement')
xlabel(x_label)
else
shift_Y = zeros(Nangles,1);
end
%% iterative vertical position refinement
for ii = 1:param.outer_loops_refinement
progressbar(ii, param.outer_loops_refinement)
% shift sinograms
invar_shifted = imshift_fft_ax(invar, squeeze(shift_Y),1);
weights_shifted = imshift_linear_ax(weight, squeeze(shift_Y),1,'nearest',0);
% select range without boundary issues
offset= max(abs(shift_Y));
range = round(1+offset : Nlayers - offset);
assert(length(range) > 30, 'Too small vertical range for alignment')
% crop to the undamaged region by boundary issues
invar_shifted = invar_shifted(range,:);
weights_shifted = weights_shifted(range,:);
% perform alignment
[shift_update, invar_shifted,weights_shifted] = linear_iterative_refinement(invar_shifted, weights_shifted, param.data_filter);
shift_Y = shift_Y + shift_update;
end
if param.outer_loops_refinement > 0
%% plot results
ax(3)=subplot(Nplots,3,3*Nplots-2);
imagesc(x_axis, range , invar_shifted(:,plot_sort), quantile(invar_shifted(:), [1e-2,1-1e-2]))
axis xy
grid on
title('Iterative refinement')
xlabel(x_label)
ylabel('Vertical axis [pixels]')
%% plot results
subplot(Nplots,3,3*Nplots-1)
invar_shifted_plot = invar_shifted;
invar_shifted_plot(weights_shifted==0) = nan;
plot(invar_shifted_plot)
xlabel('Vertical pixels')
axis tight
title('Line plot - highpass filtered')
subplot(Nplots,3,3*Nplots)
plot(x_axis, shift_Y(plot_sort))
axis tight
title('Applied shift')
ylabel('Shift [pixels]')
grid on
xlabel(x_label)
end
try linkaxes(ax, 'xy'); end
drawnow
if exist(param.output_folder, 'file') && ~debug()
try
paths{1} = [param.output_folder, '/vertical_alignment.png'];
if param.online_tomo
paths{2} = [param.output_folder, '/vertical_alignment.png'];
end
for ii = 1:length(ii)
print(['-f', num2str(fig_id)],'-dpng', paths{ii} )
utils.verbose(0,['Plot saved to:', paths{ii}])
system(sprintf('convert -trim %s %s', paths{ii}, paths{ii}));
end
catch err
warning('vertical_alignment.png saving failed with error:\n "%s"', err.message)
end
end
optimal_shift = shift_Y + shift_init;
optimal_shift = optimal_shift - median(optimal_shift);
end
function [shift_Y, invar, weight] = cross_correlation_estimation(invar0, weight, angles, N_SVD_modes, data_filter)
%% cross-corelation based alignment guess
%% make a fast initial guess based on the tomography invariant and cross-correlation
% take several angles at the beginning to get some initial guess of the
% vertical fluctuation shape and use Xcorr to find the optimal shifts
import utils.Garray
% remove linear offset
[Nlayers, Nangles]= size(invar0);
[~,angle_sort] = sort(angles);
% move on GPU
invar0 = Garray(invar0);
weight = Garray(weight);
% helps a lot in case of golden ratio datasets
invar0 = invar0(:,angle_sort);
weight = weight(:,angle_sort);
% apply high pass filter
invar = imfilter_high_pass_1d(invar0, 1, data_filter, Nlayers/2);
% further suppress boundary effects
invar = invar.* tukeywin(Nlayers, 0.1);
% update weight of inreliable pixels
range = quantile(invar(:), [0.01 , 0.99]);
weight_invar = invar > range(1) & invar < range(2) & weight;
% crop to the limited range
invar = max(min(invar, range(2)), range(1));
% fill missing data
invar = fill_gaps_1D(invar,~weight_invar, N_SVD_modes, 20);
% find 5 of the most representative angles to be used as referene
[~, ~, ~, D] = kmeans(invar0',1); % using invar before highpass filter to find optimal cluster center seems to work better
[~,ind] = sort(D);
invar_reference = median(invar(:,ind(1:5)),2);
% find optimal shift using Xcorr method
shift_Y = -utils.find_shift_fast_1D(invar,invar_reference,1,0)';
% provide some extra robustness by using median filter -> assume that
% neighboring projections are quite well aligned
shift_Y = gather(shift_Y);
medfilt_win = 3;
mshift_Y = medfilt1(shift_Y,medfilt_win, 'truncate');
medfilt_resid = shift_Y - mshift_Y; % residuum betw
% avoid too large jumps with respect to rest of the shifts
range = 2*quantile(medfilt_resid, [0.001, 0.999]);
medfilt_resid = max(min(medfilt_resid, range(2)), range(1));
shift_Y = mshift_Y + medfilt_resid;
weight = weight & weight_invar;
%% resort to original order
[~,scan_sort] = sort(angle_sort);
shift_Y = shift_Y(scan_sort);
weight = weight(:,scan_sort);
invar = invar(:,scan_sort);
% move from GPU
invar = gather(invar);
weight = gather(weight);
end
function invar = fill_gaps_1D(invar0,mask_invar, N_SVD_modes, Niter)
% try to repair failed values , iterativelly replace them using SVD
% method by most propable value
import math.*
if ~any(mask_invar(:))
invar = invar0;
return;
end
invar = invar0;
range = quantile(invar(~mask_invar), [0.01, 0.99]);
for i = 1:Niter
% slowly increase complexity
[U,S,V]=fsvd(invar,N_SVD_modes);
invar_filt = U * S*V'; % %get smooth estimate from SVD
invar = invar.*~mask_invar + invar_filt .* mask_invar ; %% replace missing by a smooth curve
% avoid outliers
invar = max(min(invar, range(2)), range(1));
end
end
function array = remove_linear_ramp(array)
% auxiliary function to subtract linear ramp from sinogram
% it is important to avoid edge ringing and other artefacts when FFT
% filtering is applied on the 2D array
[Nlayers]= size(array,1);
Nedge = 5; % number of averaged edge layers
top = mean(array(1:Nedge,:));
bottom = mean(array(end-Nedge:end,:));
ramp = interp1([0,Nlayers]',[top;bottom], 1:Nlayers);
array = array - ramp;
end
function [total_shift_Y, invar,weights] = linear_iterative_refinement(invar_0, weights_0, data_filter)
%% ITERATIVE REFINEMENT OF VERTICAL ALIGNMENT METHOD
% method based on optical flow, it can deal better with the missing /
% damaged data compared to the Xcorr based methods -> it us used for
% refinement of the Xcorr guess
import utils.*
import math.*
[Nlayers,Nangles] = size(invar_0);
total_shift_Y = zeros(Nangles,1);
invar_0 = Garray(invar_0);
weights_0 = Garray(single(weights_0));
total_shift_Y = Garray(total_shift_Y);
% apply high pass filter
invar_0 = remove_linear_ramp(invar_0);
invar_0 = imfilter_high_pass_1d(invar_0, 1, data_filter, Nlayers/2);
% further suppress boundary effects
invar_0 = invar_0 .* tukeywin(Nlayers, 0.1);
% fill missing data
invar_0 = fill_gaps_1D(invar_0,~weights_0, 2, 20);
% img = imshift_2D(ones(10), randn(10,1)*2)
X = utils.Garray(1:Nlayers);
Y = utils.Garray(1:Nangles);
[X,Y] = meshgrid(X,Y);
relax_step = 0.9; % avoid too large steps
for i = 1:1e3
% run till convergence criterion is reached
invar = imshift_fft_ax(invar_0, total_shift_Y, 1);
weights = interp2(weights_0, Y',X'+total_shift_Y', 'nearest', 0);
% apply high pass filter => get rid of phase artefacts
invar = imfilter_high_pass_1d(invar,1,data_filter, Nlayers/2);
% further suppress boundary effects
invar = invar .* tukeywin(Nlayers, 0.2);
% take median over all the positions
m_invar = sum(invar .* weights,2) ./ (sum(weights,2)+1e-3);
% get gradient by convoolution to avoid edge issues when using fft
md_invar = math.get_img_grad_conv(m_invar,2,1);
% in vertical direction use shift of the invariant => more robust and faster
DY = m_invar-invar;
shift_Y = -squeeze(sum(weights .* (DY .* md_invar) ,1) ./...
sum(weights .* md_invar.^2,1));
% avoid too large steps where linear approximation is not valid anymore
shift_Y = relax_step * min(0.5,abs(shift_Y)) .* sign(shift_Y);
total_shift_Y = total_shift_Y + shift_Y';
err(i) = gather(mean2(weights .* DY.^2));
if i > 2 && err(i-1) < err(i) || max(abs(shift_Y)) < 1e-2
break % it will stop when alignment reaches the numerical precision
end
end
total_shift_Y = gather(total_shift_Y);
invar = gather(invar);
end
+596
View File
@@ -0,0 +1,596 @@
% [deltastack regstack] = alignprojections_v4(regstack,limsy,limsx,deltastack,pixtol,rembias,maxorder,disp,paramsalign)
% Function to align projections. It relies on having air on both sides of
% the sample. The code aligns in x using a center of mass and in y by
% aligning the projections (in x) of the sample. It performs a local search
% in y, so convergence issues can be addressed by giving an approximate initial
% guess for a possible drift.
% WARNING. The code aligns the center of rotation in the ROI defined by
% limsx so that it is consistent (within the window) with the center
% expected by iradon: ceil(size(R,1)/2).
%
%
% Inputs %
% restack Stack of projections
% limsy Limits of window of interest in y
% limsx Limits of window of interest in x
% deltastack Vectors [y;x] of initial estimates for object motion (2,n)
% pixtol Tolerance for change in registration
% rembias true -> removal of bias and lower order terms (for y
% registration). Default false
% maxorder if rembias is true specify the order of bias removal (e.g.
% = 1 mean, = 2 linear). Default = 0.
% disp Display = 0 no images
% = 1 Final diagnostic images
% = 2 Diagnostic images per iteration
%
% Extra optional parameters (in structure paramsalign)
% paramsalign.alignx = true - align x using center of mass (default),
% = false - align y only
% paramsalign.expshift = false - Shift images normally (default)
% = true - shift images in phasor space
% paramsalign.interpmeth = 'sinc' - Shift images with sinc interpolation (default)
% = 'linear' - Shift images with linear interpolation (default)
%
% Outputs %
% deltastack Object positions
% regstack2 Aligned stack of images (discrete sinc interpolation)
%
% Manuel Guizar 23 Sept 2010
% This code is provided as is and without guarantees on its performance
% The algorithm is not yet published. Do not distribute and contact
% (mguizar@gmail.com) prior to publication of results where this code plays
% a significant role, or to report a bug.
% Please acknowledge if used.
% v3 - Option to enable align y only - Guizar Feb 8, 2011
% v4 - Option to change from sinc to bilinear interpolation
% - Option to shift final images in phasor space
% - Fixed bug on computation of parabola for empty frame
% Guizar June 15, 2011
function [deltastack regstack] = alignprojections_v4(regstack,limsy,limsx,deltastack,pixtol,rembias,maxorder,disp,paramsalign)
import math.*
import utils.*
deltastack = round(deltastack);
maxit = 15;
if exist('paramsalign') == 0,
paramsalign = 0;
end
if isfield(paramsalign,'interpmeth') == 0,
paramsalign.interpmeth = 'sinc';
else
interpmeth = paramsalign.interpmeth;
if (strcmp(interpmeth,'sinc'))||(strcmp(interpmeth,'linear'))
else
error('Undefined interpolation method')
end
end
if isfield(paramsalign,'expshift') == 0,
expshift = false;
else
expshift = paramsalign.expshift;
end
if isfield(paramsalign,'alignx') == 0,
alignx = true;
else
alignx = paramsalign.alignx;
end
if exist('pixtol') == 0,
pixtol = 1;
end
if exist('deltastack') == 0,
deltastack = zeros(2,size(regstack,3));
end
if exist('rembias') == 0,
rembias = false;
end
if exist('maxorder') == 0,
maxorder = 0;
end
display([' Registration of projections - Single pixel'])
[Ny,Nx] = size(regstack(limsy(1):limsy(2),limsx(1):limsx(2),1));
Xp = [-fix(Nx/2 -0.5):ceil(Nx/2 + 0.5)-1]; % not right for an fft but right for
% iradonfast
Yp = ([-fix(Ny/2):ceil(Ny/2-1)]);
[Xp,Yp] = meshgrid(Xp,Yp);
figure(1);
imagesc(regstack(:,:,1));
axis xy equal tight
colormap bone
hold on
plot([limsx(1) limsx(1)],[limsy(1) limsy(2)],'r')
plot([limsx(2) limsx(2)],[limsy(1) limsy(2)],'r')
plot([limsx(1) limsx(2)],[limsy(1) limsy(1)],'r')
plot([limsx(1) limsx(2)],[limsy(2) limsy(2)],'r')
hold off
%%%%%%%%%%%%% Single pixel precision %%%%%%%%%%%%%%
%Center of mass x
% loop here
clear auxinit; % Before main loop
domain = 1;
count = 0;
while domain == 1,
count = count+1;
deltaprev = deltastack;
if alignx
for ii = 1:size(regstack,3),
mass(ii) = sum(sum(regstack([limsy(1):limsy(2)]+deltastack(1,ii),...
[limsx(1):limsx(2)]+deltastack(2,ii),ii),1),2);
center(ii) = squeeze(sum(sum(Xp.*regstack([limsy(1):limsy(2)]+deltastack(1,ii),...
[limsx(1):limsx(2)]+deltastack(2,ii),ii),1),2));
end
center = center./mass;
else
center = 0;
end
display(['Max correction, center of mass in x =' num2str(max(abs(center)))])
% Center for iradonfast = ceil(size(R,1)/2)
% Correction with mass center
deltastack(2,:) = deltastack(2,:) + round(center);
%%%%%% Mass distribution registration in y
clear aux,
for ii = 1:size(regstack,3)
%ii
aux(:,ii) = squeeze(sum(regstack([limsy(1):limsy(2)]+deltastack(1,ii),...
[limsx(1):limsx(2)]+deltastack(2,ii),ii),2));
if rembias,
[coeffs auxi] = projectleg1D_2(aux(:,ii),maxorder,Yp(:,1),1);
aux(:,ii) = auxi.';
end
end
meanaux = mean(aux,2);
if exist('auxinit')==0,
auxinit = aux;
meaninit = meanaux;
for ii = 1:size(aux,2)
erroryinit(ii) = sum(abs(aux(:,ii)-meanaux).^2);
end
display(['Initial error metric for y, E = ' num2str(sum(erroryinit))])
end
%%% Search for shifts with respect to mean
for ii = 1:size(regstack,3);
shift = 0;
%%% Looking bothways
% compute current shift error
auxshift = squeeze(sum(regstack([limsy(1):limsy(2)]-shift+deltastack(1,ii),...
[limsx(1):limsx(2)]+deltastack(2,ii),ii),2));
if rembias,
[coeffs auxshift] = projectleg1D_2(auxshift,maxorder,Yp(:,1),1);
end
currenterror = sum(abs(auxshift-meanaux).^2);
% compute shift forward error
auxshift = squeeze(sum(regstack([limsy(1):limsy(2)]-(shift+1)+deltastack(1,ii),...
[limsx(1):limsx(2)]+deltastack(2,ii),ii),2));
if rembias,
[coeffs auxshift] = projectleg1D_2(auxshift,maxorder,Yp(:,1),1);
end
forwarderror = sum(abs(auxshift-meanaux).^2);
% compute shift backward error
auxshift = squeeze(sum(regstack([limsy(1):limsy(2)]-(shift-1)+deltastack(1,ii),...
[limsx(1):limsx(2)]+deltastack(2,ii),ii),2));
if rembias,
[coeffs auxshift] = projectleg1D_2(auxshift,maxorder,Yp(:,1),1);
end
backwarderror = sum(abs(auxshift-meanaux).^2);
mini = min([currenterror backwarderror forwarderror]);
switch mini
case currenterror
deltastack(1,ii) = deltastack(1,ii) - shift;
auxtempreg(:,ii) = auxshift; %temporary for debugging
erroryreg(ii) = currenterror;
dir = 0;
continue;
case backwarderror % Looking backward
dir = -1;
case forwarderror % Looking forward
dir = 1;
end
if dir~=0,
shift = shift+dir;
currenterror = mini;
do = 1;
while do==1,
auxshift = squeeze(sum(regstack([limsy(1):limsy(2)]-(shift+dir)+deltastack(1,ii),...
[limsx(1):limsx(2)]+deltastack(2,ii),ii),2));
if rembias,
[coeffs auxshift] = projectleg1D_2(auxshift,maxorder,Yp(:,1),1);
end
nexterror = sum(abs(auxshift-meanaux).^2);
%shift,
if nexterror<=currenterror
shift = shift+dir;
currenterror = nexterror;
else
do = 0;
deltastack(1,ii) = deltastack(1,ii) - shift;
auxtempreg(:,ii) = auxshift;
erroryreg(ii) = currenterror;
end
end
end
end
deltastack(1,:) = deltastack(1,:) - round(mean(deltastack(1,:)));
display(['Final error mectric for y, E = ' num2str(sum(erroryreg))])
changey = abs(deltaprev(1,:) - deltastack(1,:));
display(['Max correction in y = ' num2str(max(abs(changey)))])
if (max(abs(changey))<max(pixtol,1))&&(max(abs(center))<max(pixtol,1)),
domain = 0;
end
if count >= maxit,
domain = 0;
warning('Maximum number of iterations exceeded, increase maxit')
end
if disp>1,
figure(200);
subplot(2,1,1)
imagesc(auxinit);
axis xy tight
%colorbar
title(['Initial Integral in x, maxorder = ' num2str(maxorder)])
ylabel('y [pixels]')
xlabel('Projection')
subplot(2,1,2),
imagesc(auxtempreg);
axis xy tight
%colorbar
title('Current Integral in x')
ylabel('y [pixels]')
xlabel('Projection')
figure(201)
subplot(2,1,1),
plot(auxinit)
hold on,
plot(meaninit,'r','Linewidth',2)
plot(meaninit,'--w')
hold off,
title(['Initial Integral in x, maxorder = ' num2str(maxorder)])
subplot(2,1,2),
%plot(auxtempreg(:,[1 360])) %28
plot(auxtempreg)
hold on,
plot(meanaux,'r','Linewidth',2)
plot(meanaux,'w')
mean2 = mean(auxtempreg,2);
% plot(mean2,'r','Linewidth',2)
% plot(mean2,'--w')
hold off,
title('Current Integral in x')
figure(2),
plot(deltastack')
drawnow,
title('Object position')
% figure(205);
% plot(erroryinit)
% hold on,
% plot(erroryreg,'r')
% hold off
% legend('Initial error per projection','Current error per projection')
end
end
if pixtol >= 1,
% Compute the shifted images
if nargout == 2,
display('Computing aligned images')
for ii = 1:size(regstack,3),
switch interpmeth
case 'sinc'
regstack(:,:,ii) = real(shiftpp2(regstack(:,:,ii),deltastack(1,ii),deltastack(2,ii)));
case 'linear'
regstack(:,:,ii) = shiftwrapbilinear(regstack(:,:,ii),deltastack(1,ii),deltastack(2,ii));
end
if mod(ii,20) == 0,
display(['Image ' num2str(ii) ' of ' num2str(size(regstack,3))]),
end
end
end
end
if pixtol<1,
%warning(['Subpixel alignment is to be implemented'])
display([' Subpixel alignment'])
% Compute full massx (integral in y) using rounded deltastack for y window
% Compute full massy (integral in x) using rounded deltastack for x window
for ii = 1:size(regstack,3),
massxorig(:,ii) = squeeze(sum(regstack([limsy(1):limsy(2)]+round(deltastack(1,ii)),:,ii),1));
massyorig(:,ii) = squeeze(sum(regstack(:,[limsx(1):limsx(2)]+round(deltastack(2,ii)),ii),2));
end
dosubpix = 1;
count = 0;
deltay = 1;
while dosubpix == 1,
count = count+1;
deltaprev = deltastack;
% Shift massx subpixel for current deltastack
% Recompute center of mass
if alignx
for ii = 1:size(regstack,3),
switch interpmeth
case 'sinc'
massx(:,ii) = real(shiftpp2(massxorig(:,ii),deltastack(2,ii),0));
case 'linear'
massx(:,ii) = shiftwrapbilinear(massxorig(:,ii),deltastack(2,ii),0);
end
mass(ii) = sum(massx([limsx(1):limsx(2)],ii),1);
center(ii) = sum(Xp(1,:).'.*massx([limsx(1):limsx(2)],ii),1);
end
center = center./mass;
else
center = 0;
end
display(['Max correction, center of mass in x = ' num2str(max(abs(center)))])
% Center for iradonfast = ceil(size(R,1)/2)
% Correction with mass center
deltastack(2,:) = deltastack(2,:) + center;
% Compute current shift error
% Shift massy subpixel for current deltastack
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%% Mass distribution registration in y %%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
clear aux
%%% Search for shifts with respect to mean
for ii = 1:size(regstack,3);
shift = 0;
%%% Looking bothways
% compute current shift error
% auxshift = squeeze(sum(regstack([limsy(1):limsy(2)]-shift+deltastack(1,ii),...
% [limsx(1):limsx(2)]+deltastack(2,ii),ii),2));
switch interpmeth
case 'sinc'
auxshift = real(shiftpp2(massyorig(:,ii),deltastack(1,ii),0));
case 'linear'
auxshift = shiftwrapbilinear(massyorig(:,ii),deltastack(1,ii),0);
end
auxshift = auxshift([limsy(1):limsy(2)],:); % Clip to ROI
if rembias,
[coeffs auxshift] = projectleg1D_2(auxshift,maxorder,Yp(:,1),1);
massy(:,ii) = auxshift;
end
currenterror = sum(abs(auxshift-meanaux).^2);
% compute shift forward error
% auxshift = squeeze(sum(regstack([limsy(1):limsy(2)]-(shift+1)+deltastack(1,ii),...
% [limsx(1):limsx(2)]+deltastack(2,ii),ii),2));
switch interpmeth
case 'sinc'
auxshift = real(shiftpp2(massyorig(:,ii),deltastack(1,ii)-pixtol,0));
case 'linear'
auxshift = shiftwrapbilinear(massyorig(:,ii),deltastack(1,ii)-pixtol,0);
end
auxshift = auxshift([limsy(1):limsy(2)],:); % Clip to ROI
if rembias,
[coeffs auxshift] = projectleg1D_2(auxshift,maxorder,Yp(:,1),1);
end
forwarderror = sum(abs(auxshift-meanaux).^2);
% compute shift backward error
% auxshift = squeeze(sum(regstack([limsy(1):limsy(2)]-(shift-1)+deltastack(1,ii),...
% [limsx(1):limsx(2)]+deltastack(2,ii),ii),2));
switch interpmeth
case 'sinc'
auxshift = real(shiftpp2(massyorig(:,ii),deltastack(1,ii)+pixtol,0));
case 'linear'
auxshift = shiftwrapbilinear(massyorig(:,ii),deltastack(1,ii)+pixtol,0);
end
auxshift = auxshift([limsy(1):limsy(2)],:); % Clip to ROI
if rembias,
[coeffs auxshift] = projectleg1D_2(auxshift,maxorder,Yp(:,1),1);
end
backwarderror = sum(abs(auxshift-meanaux).^2);
mini = min([currenterror backwarderror forwarderror]);
switch mini
case currenterror
deltastack(1,ii) = deltastack(1,ii);
% auxtempreg(:,ii) = auxshift; %temporary for debugging
erroryreg(ii) = currenterror;
dir = 0;
continue;
case backwarderror % Looking backward
dir = -1;
case forwarderror % Looking forward
dir = 1;
end
if dir~=0,
shift = shift+dir*pixtol;
currenterror = mini;
do = 1;
while do==1,
% auxshift = squeeze(sum(regstack([limsy(1):limsy(2)]-(shift+dir)+deltastack(1,ii),...
% [limsx(1):limsx(2)]+deltastack(2,ii),ii),2));
switch interpmeth
case 'sinc'
auxshift = real(shiftpp2(massyorig(:,ii),deltastack(1,ii)-(shift+dir*pixtol),0));
case 'linear'
auxshift = shiftwrapbilinear(massyorig(:,ii),deltastack(1,ii)-(shift+dir*pixtol),0);
end
auxshift = auxshift([limsy(1):limsy(2)],:); % Clip to ROI
if rembias,
[coeffs auxshift] = projectleg1D_2(auxshift,maxorder,Yp(:,1),1);
massy(:,ii) = auxshift;
end
nexterror = sum(abs(auxshift-meanaux).^2);
%shift,
if nexterror<=currenterror
shift = shift+dir*pixtol;
currenterror = nexterror;
else
do = 0;
deltastack(1,ii) = deltastack(1,ii) - shift;
auxtempreg(:,ii) = auxshift;
erroryreg(ii) = currenterror;
end
end
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%% Up to here it obtained the next estimate %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Evaluate if pixel tolerance is already met
changey = abs(deltaprev(1,:) - deltastack(1,:));
display(['Max correction in y = ' num2str(max(abs(changey)))])
if (max(abs(center))<pixtol)&&(max(abs(changey))<pixtol)
%if (max(abs(changey))<1)&&(max(abs(center))<1),
dosubpix = 0;
end
if count >= maxit,
dosubpix = 0;
warning('Maximum number of iterations exceeded, increase maxit')
end
if disp>1,
figure(200);
subplot(2,1,2),
imagesc(massy);
axis xy
%colorbar
title('Current Integral in x')
ylabel('y [pixels]')
xlabel('Projection')
figure(201)
subplot(2,1,2),
%plot(auxtempreg(:,[1 360])) %28
plot(massy)
hold on,
plot(meanaux,'r','Linewidth',2)
plot(meanaux,'w')
hold off,
title('Current Integral in x')
figure(2),
plot(deltastack')
drawnow,
title('Object position')
% figure(205);
% plot(erroryinit)
% hold on,
% plot(erroryreg,'r')
% hold off
% legend('Initial error per projection','Current error per projection')
end
end
% Compute the shifted images
if nargout == 2,
if expshift == 0,
display('Computing aligned images')
for ii = 1:size(regstack,3),
switch interpmeth
case 'sinc'
regstack(:,:,ii) = real(shiftpp2(regstack(:,:,ii),deltastack(1,ii),deltastack(2,ii)));
case 'linear'
regstack(:,:,ii) = shiftwrapbilinear(regstack(:,:,ii),deltastack(1,ii),deltastack(2,ii));
end
if mod(ii,20) == 0,
display(['Image ' num2str(ii) ' of ' num2str(size(regstack,3))]),
end
end
elseif expshift == 1,
display('Computing aligned images in phasor space')
for ii = 1:size(regstack,3),
switch interpmeth
case 'sinc'
regstack(:,:,ii) = angle(shiftpp2(exp(1i*regstack(:,:,ii)),deltastack(1,ii),deltastack(2,ii)));
case 'linear'
regstack(:,:,ii) = angle(shiftwrapbilinear(exp(1i*regstack(:,:,ii)),deltastack(1,ii),deltastack(2,ii)));
end
if mod(ii,20) == 0,
display(['Image ' num2str(ii) ' of ' num2str(size(regstack,3))]),
end
end
end
end
if disp>=1,
figure(200)
subplot(2,1,2),
imagesc(massy);
axis xy fill
%colorbar
title('Current Integral in x')
ylabel('y [pixels]')
xlabel('Projection')
figure(201)
subplot(2,1,2),
%plot(auxtempreg(:,[1 360])) %28
plot(massy)
hold on,
plot(meanaux,'r','Linewidth',2)
plot(meanaux,'w')
hold off,
title('Current Integral in x')
figure(2),
plot(deltastack')
drawnow,
title('Object position')
legend('dy','dx')
end
end
+620
View File
@@ -0,0 +1,620 @@
% [deltastack outputsinogram] = alignslice_filt(sinogramder,theta,deltastack,pixtol,disp,paramsalign)
% Function to align projections. It relies on having already aligned the
% vertical direction. The code aligns using the consistency before and
% after tomographic combination of projections. Currently it can only deal
% with one slice. It is recommended to use small values of tomographic
% filter for best results.
% WARNING. The code aligns the center of rotation in the ROI defined by
% limsx so that it is consistent (within the window) with the center
% expected by iradon: ceil(size(R,1)/2). Also note that it expects the
% derivative of the sinogram as input
%
%
% Inputs %
% sinogramder Sinogram derivative, the second index should be the angle
% deltastack Row array with initial estimates of positions (1,n)
% disp Display = 0 no images
% = 1 Final diagnostic images
% = 2 Diagnostic images per iteration (significant overhead)
% pixtol Tolerance for alignment, it is also used as a search step
%
% Extra parameters (in structure paramsalign)
% paramsalign.interpmeth = 'sinc' - Shift images with sinc interpolation (default)
% = 'linear' - Shift images with linear interpolation (default)
% paramsalign.usecircle Use a circular mask to eliminate corners of
% tomogram
% paramsalign.filtertomo Frequency cutoff for tomography filter
% paramsalign.cliplow Minimum value in tomogram
% paramsalign.cliphigh Maximum value in tomogram
% paramsalign.masklims Mask in sinograms to evaluate error metric
% paramsalign.binning Binning of sinograms to increase speed. I am
% not fully aware
%
% Outputs %
% deltastack Object positions
% outputsinogram Aligned sinogram derivatives (optional)
%
% Manuel Guizar 13 July 2011
% This code is provided as is and without guarantees on its performance
% The algorithm is not yet published. Do not distribute and contact
% (mguizar@gmail.com) prior to publication of results where this code plays
% a significant role, or to report a bug.
% Please acknowledge if used.
% v2 Manuel Guizar 2011 09 29
% Added clipping values to tomogram
% Manuel Guizar 2016 03 01 - Adding binning possiblity. Currenlty I suspect
% there may be an offset introduced between different binnings. Beware of
% this while using it and report any behavior that points in this direction.
function [deltastack outputsinogram] = alignslice_filt_v2(sinogramder,theta,deltastack,pixtol,disp,paramsalign)
import utils.*
import math.projectleg1D_2
%deltastack = round(deltastack);
maxit = 40;
display([' Registration of sinogram - Single pixel'])
if nargout>1,
outputsinogram = sinogramder;
end
if exist('paramsalign') == 0,
paramsalign = 0;
end
%%%%%%%
if isfield(paramsalign,'binning') == 0,
binning = 0;
else
binning = paramsalign.binning;
display(['Using binning = ' num2str(binning)])
end
if (binning ~=0)&&(binning ~=1)
display(['Using binning on sinogram = ' num2str(binning)]);
sinogramder_orig = sinogramder;
sinoaux = sinogramder(1:binning:end-binning+1,:);
for ii = 2:binning
sinoaux = sinoaux + sinogramder(ii:binning:end-binning+ii,:);
end
sinogramder = sinoaux/binning;
deltastack = deltastack/binning;
end
%%%%%%
if isfield(paramsalign,'masklims') == 0,
masklims = [1:size(sinogramder,1)];
else
display('Error computed using masked values of sinogram')
masklims = paramsalign.masklims;
end
if isfield(paramsalign,'interpmeth') == 0,
paramsalign.interpmeth = 'sinc';
interpmeth = paramsalign.interpmeth;
else
interpmeth = paramsalign.interpmeth;
if (strcmp(interpmeth,'sinc'))||(strcmp(interpmeth,'linear'))
else
error('Undefined interpolation method')
end
end
if isfield(paramsalign,'usecircle') == 0,
usecircle = true;
else
usecircle = paramsalign.usecircle;
end
% if isfield(paramsalign,'expshift') == 0,
% expshift = false;
% else
% expshift = paramsalign.expshift;
% end
if isfield(paramsalign,'filtertomo') == 0,
filtertomo = 1;
display('Using default filter cutoff = 1')
else
filtertomo = paramsalign.filtertomo;
display(['Using filtertomo = ' num2str(filtertomo)])
end
if isfield(paramsalign,'cliplow') == 0,
cliplow = [];
else
cliplow = paramsalign.cliplow;
display(['Low limit for tomo values = ' num2str(cliplow)])
end
if isfield(paramsalign,'cliphigh') == 0,
cliphigh = [];
else
cliphigh = paramsalign.cliphigh;
display(['High limit for tomo values = ' num2str(cliphigh)])
warning('Code will use a background "cliphigh", assumes tomogram will go negative from there')
end
if exist('pixtol') == 0,
pixtol = 1;
end
if exist('deltastack') == 0,
deltastack = zeros(1,size(sinogramder,2));
end
% Pad sinogram derivatives
padval = 2*round(1/filtertomo);
sinogramder = padarray(sinogramder,[padval 0]);
%%%%%%%%%%%%% Single pixel precision %%%%%%%%%%%%%%
% loop here
clear auxinit; % Before main loop
domain = 1;
count = 0;
N = size(sinogramder,1);
center = floor((N+1)/2);
xt = [-N/2:N/2-1];
[Xt Yt] = meshgrid(xt,xt);
circulo = 1-radtap(Xt,Yt,10,N/2-10);
filteraux = 1-fract_hanning_pad(N,N,round(N*(1-filtertomo)));
filteraux = repmat(fftshift(filteraux(:,1)),[1 length(theta)]);
sinogramderorig_nofilt = sinogramder;
sinogramderorig = real(ifft(fft(sinogramder).*filteraux));
erroryreg = inf;
while domain == 1,
count = count+1;
deltaprev = deltastack;
erroryregprev = erroryreg;
for ii = 1:size(sinogramder,2)
if strcmp(interpmeth,'sinc')
sinogramder(:,ii) = real(shiftpp2(sinogramderorig(:,ii),deltastack(1,ii),0));
elseif strcmp(interpmeth,'linear')
sinogramder(:,ii) = shiftwrapbilinear(sinogramderorig(:,ii),deltastack(1,ii),0);
end
if disp>1,
if strcmp(interpmeth,'sinc')
sinogramder_nofilt(:,ii) = real(shiftpp2(sinogramderorig_nofilt(:,ii),deltastack(1,ii),0));
elseif strcmp(interpmeth,'linear')
sinogramder_nofilt(:,ii) = shiftwrapbilinear(sinogramderorig_nofilt(:,ii),deltastack(1,ii),0);
end
end
end
%%%%%% Sinogram alignment single pixel precision
% Compute tomogram with current sinogramder
% recons = iradonfast_v3(double(sinogramder),...
% theta,'linear','derivative','Han',size(sinogramder,1),filtertomo);
recons = tomo.iradonfast_v3(double(sinogramder),...
theta,'linear','derivative','Ram-Lak',size(sinogramder,1),1);
if ~isempty(cliplow)
recons = recons.*(recons>=cliplow) + cliplow*(recons<cliplow);
end
if ~isempty(cliphigh)
recons = recons.*(recons<=cliphigh) + cliphigh*(recons>cliphigh);
recons = recons-cliphigh;
end
if usecircle
recons = recons.*circulo;
end
figure(203)
imagesc(recons);
axis xy equal tight
colorbar
% Computed sinogram
sinogramcomp = radon(recons,theta);
Nbig = size(sinogramcomp,1);
centerbig = floor((Nbig+1)/2);
if strcmp(interpmeth,'sinc')
sinogramcompder = real(shiftpp2(sinogramcomp,0.5,0)-shiftpp2(sinogramcomp,-0.5,0));
elseif strcmp(interpmeth,'linear')
sinogramcompder = real(shiftwrapbilinear(sinogramcomp,0.5,0)-shiftwrapbilinear(sinogramcomp,-0.5,0));
end
sinogramcompder = sinogramcompder([1:N]+centerbig-center,:);
% Compare sinogramder with sinogramcompder
for ii = 1:size(sinogramder,2)
%ii,
errorinit(ii) = sum(abs(sinogramder(padval+masklims,ii)-sinogramcompder(padval+masklims,ii)).^2);
end
display(['Initial error metric, E = ' num2str(sum(errorinit))])
%%% Search for shifts with respect to sinthetic
for ii = 1:size(sinogramder,2);
shift = 0;
%%% Looking bothways
% compute current shift error
if strcmp(interpmeth,'sinc')
auxshift = real(shiftpp2(sinogramder(:,ii),-shift,0));
elseif strcmp(interpmeth,'linear')
auxshift = real(shiftwrapbilinear(sinogramder(:,ii),-shift,0));
end
% if rembias,
% [coeffs auxshift] = projectleg1D_2(auxshift,maxorder,Yp(:,1),1);
% end
currenterror = sum(abs(auxshift(padval+masklims)-sinogramcompder(padval+masklims,ii)).^2);
% compute shift forward error
if strcmp(interpmeth,'sinc')
auxshift = real(shiftpp2(sinogramder(:,ii),-(shift+1),0));
elseif strcmp(interpmeth,'linear')
auxshift = real(shiftwrapbilinear(sinogramder(:,ii),-(shift+1),0));
end
% if rembias,
% [coeffs auxshift] = projectleg1D_2(auxshift,maxorder,Yp(:,1),1);
% end
forwarderror = sum(abs(auxshift(padval+masklims)-sinogramcompder(padval+masklims,ii)).^2);
% compute shift backward error
if strcmp(interpmeth,'sinc')
auxshift = real(shiftpp2(sinogramder(:,ii),-(shift-1),0));
elseif strcmp(interpmeth,'linear')
auxshift = real(shiftwrapbilinear(sinogramder(:,ii),-(shift-1),0));
end
% if rembias,
% [coeffs auxshift] = projectleg1D_2(auxshift,maxorder,Yp(:,1),1);
% end
backwarderror = sum(abs(auxshift(padval+masklims)-sinogramcompder(padval+masklims,ii)).^2);
mini = min([currenterror backwarderror forwarderror]);
switch mini
case currenterror
deltastack(1,ii) = deltastack(1,ii) - shift;
auxtempreg(:,ii) = auxshift; %temporary for debugging
erroryreg(ii) = currenterror;
dir = 0;
continue;
case backwarderror % Looking backward
dir = -1;
case forwarderror % Looking forward
dir = 1;
end
if dir~=0,
shift = shift+dir;
currenterror = mini;
do = 1;
end
while do==1,
if strcmp(interpmeth,'sinc')
auxshift = real(shiftpp2(sinogramder(:,ii),-(shift+dir),0));
elseif strcmp(interpmeth,'linear')
auxshift = real(shiftwrapbilinear(sinogramder(:,ii),-(shift+dir),0));
end
% if rembias,
% [coeffs auxshift] = projectleg1D_2(auxshift,maxorder,Yp(:,1),1);
% end
nexterror = sum(abs(auxshift(padval+masklims)-sinogramcompder(padval+masklims,ii)).^2);
%shift,
if nexterror<=currenterror
shift = shift+dir;
currenterror = nexterror;
else
do = 0;
deltastack(1,ii) = deltastack(1,ii) - shift;
auxtempreg(:,ii) = auxshift;
erroryreg(ii) = currenterror;
end
end
end
%end
%deltastack(1,:) = deltastack(1,:) - round(mean(deltastack(1,:)));
display(['Final error mectric, E = ' num2str(sum(erroryreg))])
changey = abs(deltaprev(1,:) - deltastack(1,:));
display(['Max correction = ' num2str(max(abs(changey)))])
if (max(abs(changey))<max(pixtol,1)),
domain = 0;
end
if count >= maxit,
domain = 0;
warning('Maximum number of iterations exceeded, increase maxit')
end
if sum(erroryregprev)<sum(erroryreg)
warning('Last iteration made error worse, keeping previous to last positions')
deltastack = deltaprev;
domain = 0;
end
if disp>1,
figure(200);
subplot(2,1,1)
imagesc(sinogramderorig_nofilt);
axis xy tight
%colorbar
title('Initial Sinogram')
ylabel('x [pixels]')
xlabel('Projection')
subplot(2,1,2),
imagesc(sinogramder_nofilt);
axis xy tight
%colorbar
title('Current Sinogram')
ylabel('x [pixels]')
xlabel('Projection')
figure(201);
imagesc(sinogramcompder);
axis xy tight
%colorbar
title('Synthetic Sinogram')
ylabel('x [pixels]')
xlabel('Projection')
figure(2),
plot(deltastack')
drawnow,
title('Object position')
% figure(205);
% plot(erroryinit)
% hold on,
% plot(erroryreg,'r')
% hold off
% legend('Initial error per projection','Current error per projection')
end
end
% if pixtol >= 1,
% % Compute the shifted images
% if nargout == 2,
% display('Computing aligned images')
% for ii = 1:size(regstack,3),
% switch interpmeth
% case 'sinc'
% regstack(:,:,ii) = real(shiftpp2(regstack(:,:,ii),deltastack(1,ii),deltastack(2,ii)));
% case 'linear'
% regstack(:,:,ii) = shiftwrapbilinear(regstack(:,:,ii),deltastack(1,ii),deltastack(2,ii));
% end
% if mod(ii,20) == 0,
% display(['Image ' num2str(ii) ' of ' num2str(size(regstack,3))]),
% end
% end
% end
% end
%%
if pixtol<1,
display(' Subpixel refinement')
clear auxinit; % Before main loop
domain = 1;
count = 0;
while domain == 1,
count = count+1;
deltaprev = deltastack;
erroryregprev = erroryreg;
for ii = 1:size(sinogramder,2)
if strcmp(interpmeth,'sinc')
sinogramder(:,ii) = real(shiftpp2(sinogramderorig(:,ii),deltastack(1,ii),0));
elseif strcmp(interpmeth,'linear')
sinogramder(:,ii) = shiftwrapbilinear(sinogramderorig(:,ii),deltastack(1,ii),0);
end
if disp>1,
if strcmp(interpmeth,'sinc')
sinogramder_nofilt(:,ii) = real(shiftpp2(sinogramderorig_nofilt(:,ii),deltastack(1,ii),0));
elseif strcmp(interpmeth,'linear')
sinogramder_nofilt(:,ii) = shiftwrapbilinear(sinogramderorig_nofilt(:,ii),deltastack(1,ii),0);
end
end
end
%%%%%% Sinogram alignment subpixel precision
% Compute tomogram with current sinogramder
% recons = iradonfast_v3(double(sinogramder),...
% theta,'linear','derivative','Han',size(sinogramder,1),filtertomo);
recons = tomo.iradonfast_v3(double(sinogramder),...
theta,'linear','derivative','Ram-Lak',size(sinogramder,1),1);
if ~isempty(cliplow)
recons = recons.*(recons>=cliplow) + cliplow*(recons<cliplow);
end
if ~isempty(cliphigh)
recons = recons.*(recons<=cliphigh) + cliphigh*(recons>cliphigh);
recons = recons-cliphigh;
end
if usecircle
recons = recons.*circulo;
end
figure(203)
imagesc(recons);
axis xy equal tight
colorbar
% Computed sinogram
sinogramcomp = radon(recons,theta);
Nbig = size(sinogramcomp,1);
centerbig = floor((Nbig+1)/2);
if strcmp(interpmeth,'sinc')
sinogramcompder = real(shiftpp2(sinogramcomp,0.5,0)-shiftpp2(sinogramcomp,-0.5,0));
elseif strcmp(interpmeth,'linear')
sinogramcompder = real(shiftwrapbilinear(sinogramcomp,0.5,0)-shiftwrapbilinear(sinogramcomp,-0.5,0));
end
sinogramcompder = sinogramcompder([1:N]+centerbig-center,:);
% Compare sinogramder with sinogramcompder
for ii = 1:size(sinogramder,2)
%ii,
errorinit(ii) = sum(abs(sinogramder(padval+masklims,ii)-sinogramcompder(padval+masklims,ii)).^2);
end
display(['Initial error metric, E = ' num2str(sum(errorinit))])
%%% Search for shifts with respect to sinthetic
for ii = 1:size(sinogramder,2);
shift = 0;
%%% Looking bothways
% compute current shift error
if strcmp(interpmeth,'sinc')
auxshift = real(shiftpp2(sinogramder(:,ii),-shift,0));
elseif strcmp(interpmeth,'linear')
auxshift = real(shiftwrapbilinear(sinogramder(:,ii),-shift,0));
end
% if rembias,
% [coeffs auxshift] = projectleg1D_2(auxshift,maxorder,Yp(:,1),1);
% end
currenterror = sum(abs(auxshift(padval+masklims)-sinogramcompder(padval+masklims,ii)).^2);
% compute shift forward error
if strcmp(interpmeth,'sinc')
auxshift = real(shiftpp2(sinogramder(:,ii),-(shift+pixtol),0));
elseif strcmp(interpmeth,'linear')
auxshift = real(shiftwrapbilinear(sinogramder(:,ii),-(shift+pixtol),0));
end
% if rembias,
% [coeffs auxshift] = projectleg1D_2(auxshift,maxorder,Yp(:,1),1);
% end
forwarderror = sum(abs(auxshift(padval+masklims)-sinogramcompder(padval+masklims,ii)).^2);
% compute shift backward error
if strcmp(interpmeth,'sinc')
auxshift = real(shiftpp2(sinogramder(:,ii),-(shift-pixtol),0));
elseif strcmp(interpmeth,'linear')
auxshift = real(shiftwrapbilinear(sinogramder(:,ii),-(shift-pixtol),0));
end
% if rembias,
% [coeffs auxshift] = projectleg1D_2(auxshift,maxorder,Yp(:,1),1);
% end
backwarderror = sum(abs(auxshift(padval+masklims)-sinogramcompder(padval+masklims,ii)).^2);
mini = min([currenterror backwarderror forwarderror]);
switch mini
case currenterror
deltastack(1,ii) = deltastack(1,ii) - shift;
auxtempreg(:,ii) = auxshift; %temporary for debugging
erroryreg(ii) = currenterror;
dir = 0;
continue;
case backwarderror % Looking backward
dir = -pixtol;
case forwarderror % Looking forward
dir = pixtol;
end
if dir~=0,
shift = shift+dir;
currenterror = mini;
do = 1;
end
while do==1,
if strcmp(interpmeth,'sinc')
auxshift = real(shiftpp2(sinogramder(:,ii),-(shift+dir),0));
elseif strcmp(interpmeth,'linear')
auxshift = real(shiftwrapbilinear(sinogramder(:,ii),-(shift+dir),0));
end
% if rembias,
% [coeffs auxshift] = projectleg1D_2(auxshift,maxorder,Yp(:,1),1);
% end
nexterror = sum(abs(auxshift(padval+masklims)-sinogramcompder(padval+masklims,ii)).^2);
%shift,
if nexterror<=currenterror
shift = shift+dir;
currenterror = nexterror;
else
do = 0;
deltastack(1,ii) = deltastack(1,ii) - shift;
auxtempreg(:,ii) = auxshift;
erroryreg(ii) = currenterror;
end
end
end
%deltastack(1,:) = deltastack(1,:) - round(mean(deltastack(1,:)));
display(['Final error mectric, E = ' num2str(sum(erroryreg))])
changey = abs(deltaprev(1,:) - deltastack(1,:));
display(['Max correction = ' num2str(max(abs(changey)))])
if (max(abs(changey))<pixtol),
domain = 0;
end
if count >= maxit,
domain = 0;
warning('Maximum number of iterations exceeded, increase maxit')
end
if sum(erroryregprev)<sum(erroryreg)
warning('Last iteration made error worse, keeping previous to last positions')
deltastack = deltaprev;
domain = 0;
end
if disp>1,
figure(200);
subplot(2,1,1)
imagesc(sinogramderorig_nofilt);
axis xy tight
%colorbar
title('Initial Sinogram')
ylabel('x [pixels]')
xlabel('Projection')
subplot(2,1,2),
imagesc(sinogramder_nofilt);
axis xy tight
%colorbar
title('Current Sinogram')
ylabel('x [pixels]')
xlabel('Projection')
figure(201);
imagesc(sinogramcompder);
axis xy tight
%colorbar
title('Synthetic Sinogram')
ylabel('x [pixels]')
xlabel('Projection')
figure(2),
plot(deltastack')
drawnow,
title('Object position')
end
end
end
%%%%%
if (binning ~=0)&&(binning ~=1)
deltastack = deltastack*binning;
sinogramder = sinogramder_orig;
end
%%% Create ouput aligned sinogram sinogramdernopad outputsinogram
if nargout>1,
for ii = 1:size(sinogramder,2),
if strcmp(interpmeth,'sinc')
outputsinogram(:,ii) = real(shiftpp2(outputsinogram(:,ii),deltastack(1,ii),0));
elseif strcmp(interpmeth,'linear')
outputsinogram(:,ii) = shiftwrapbilinear(outputsinogram(:,ii),deltastack(1,ii),0);
end
end
end
+16
View File
@@ -0,0 +1,16 @@
% output = apply_reliability_region(input, weight)
% Apply weighting functions to a stack of projections
% Inputs:
% **input - real or complex image stack
% **weight - weights for each projection
%
% *returns*
% ++output - projections multiplied by their weights
% Written by YJ
function output = apply_reliability_region(input, weight)
output = single(zeros(size(input)));
for i=1:size(input,3)
output(:,:,i) = gather(input(:,:,i)) .* gather(weight(:,:,i));
end
end
+90
View File
@@ -0,0 +1,90 @@
% [volume_new, update] = apply_tomo_constraints(volume, mask, lamino_angle , low_freq_protection, constrain_fun, Niter)
% apply laminography constraints in the real space and try to refill missing cone in laminography by provided prior
% knowledge
% Inputs:
% **volume - (3D array) represeting the refined volume in realspace
% **mask - (vector, array), mask pushing pixels where mask < 1 towards zero. Can be either 3D or only for example along 3r axis ie size(mask) = [1,1,Nlayers]
% **lamino_angle - (scalar), laminography angle from 0 to 90degrees, 90 == classical tomo, it is used to calculate the missing cone
% **low_freq_protection - (bool), used to protect in the fourier space the central region, ie low spatial frequncies. Important when multiscale approach is used
% **constrain_fun - anonymous function providing constrains such as positivity or material range limits
% **Niter - number of optimization iterations
% *returns*
% ++volume_new refined object
% ++update (norm(volume) - norm(update_new)) / norm(volume)
%
% Example:
% see template_tomo_recons_lamino.m for working example
function [volume_new, update] = apply_tomo_constraints(volume, mask, angles, low_freq_protection, value_max, value_min, TV_lambda, Niter)
import utils.Garray
Npix = size(volume);
fft_mask = tomo.get_tomo_fourier_mask_3d( Npix, angles);
fft_mask = Garray(fft_mask);
if low_freq_protection
% avoid modification of the low spatial frequencies that were
% already refined
fft_mask = fftshift(fft_mask);
for i = 1:3
grid{i} = ceil(Npix(i)/2)+[-ceil(Npix(i)/8):floor(Npix(i)/8)];
end
fft_mask(grid{:}) = 0;
fft_mask = fftshift(fft_mask);
end
volume = Garray(volume);
fft_split = 1;
for iter = 1:Niter
utils.progressbar(iter,Niter)
volume_new = volume;
volume_new = regularization.local_TV3D_chambolle(volume_new, TV_lambda, 10);
% positivity constraint
volume_new = arrayfun(@clip_range,volume_new, value_max, value_min, mask);
%volume_new = clip_range(volume_new, value_max, value_min, mask);
%% go to the Fourier space
fvolume = (math.fftn_partial(Garray(volume), fft_split));
fvolume_new = (math.fftn_partial(Garray(volume_new), fft_split));
%% merge updated and original dataset in the fourier space
%% use overrelaxation of the constraint to get faster convergence
relax = 1.5;
regularize = 0;
fvolume = arrayfun(@relax_contraint,fvolume, fvolume_new, fft_mask, relax, regularize);
clear fvolume_new
%% back to the real space
volume_new = real(math.ifftn_partial(Garray(fvolume), fft_split));
clear fvolume
% get difference in update
update = gather(norm(volume(:)-volume_new(:)) ./ norm(volume(:)));
volume = volume_new;
end
volume = gather(volume);
end
% auxiliary function for fast execution on GPU
function fvolume = relax_contraint(fvolume, fvolume_new, fft_mask, relax, regularize)
fvolume = fvolume .* ( 1- relax.*fft_mask) + fvolume_new .* relax.*fft_mask;
%relax_data = 0.2;
%fft_mask_data = 1 - fft_mask;
%fvolume = fvolume .* ( 1- relax_data*fft_mask_data) + fvolume_new .* relax_data.*fft_mask_data;
fvolume = fvolume .* (1 - regularize.*fft_mask);
end
function array = clip_range(array, max_val, min_val, mask)
array = max(min_val, min(max_val, array)) .* mask;
%array = max(min_val, min(max_val, array));
end
+646
View File
@@ -0,0 +1,646 @@
% BLOCK_FUN autosplitting function for fast GPU data processing.
% in the simplest case this function is equivalent to
% [varargout{:}] = fun(fp16.get(stack_object(ROI{:},:)), varargin{2:end});
% But for large datasets, it will split the stack_object along 3rd axis and try to also
% split other inputs if possible. Then it will submit each of the
% blocks on 1 or more GPUs and process. Processed data are returned back to RAM.
% it is useful even to process large blocks of data in RAM, to prevent too large RAM allocation.
% NOTE: Avoiding GPU processing ('use_gpu', false) can be useful for simple operations when the memory transfer to GPU is much slower than task execution !!
% NOTE: Multi GPU processing addes extra overhead for moving data to shared arrays and back, useful only for very expensive operations
% NOTE: If input is fp16 precision, it is automatically converted to singles for processing and result are converted back to fp16 when returned (unless ('use_fp16', false) is used)
% NOTE: Automatic splitting assumes memory required for two FFT operation and real to complex conversion with single precision numbers, ie MEMORY = 4*6*8*numel(stack_object)
%
% varargout = block_fun(fun, stack_object, varargin)
%
% Inputs:
% **fun - function to be executed
% **stack_object - 3D array that will be split along 3rd dimension and processed on GPU (as default)
% **varargin - other arguments, if their size along 1st dimension is the same as 3rd axis of stack_object, they will be split
% **parameter structure - if the last argument is structure,
% it will not be given to the "fun" but it will be used as a parameter input for block_fun.
% SEE DESCRIPTION IN CODE FOR ALL POSSIBLE INPUTS
% *returns*
% ++varargout - 3D array of size stack_object or 1D/2D with 1st dimension assumed to be equal to 3rd axis of stack_object
%
% Examples:
% % simple 1:1 array processing
% X = randn(Nx, Ny, Nz)
% X_fft = block_fun(@fft2,X); % equivalent to fft2(x) for small datasets
% % simple 1:1 array processing with manually defined blocks size , can be useful if autosplitting does not provide good results (ie too large / too small blocks)
% X_fft = block_fun(@fft2,X, struct('Nblocks', 10)); % equivalent to fft2(x) for small datasets
% % information reduction to (Nz,1,1),
% [x,y] = block_fun(@utils.center, X); % equivalent to utils.center(x) for small datasets
% % information reduction to (Nx, Nx,1)
% X_accum = block_fun(@(x)(abs(x).^2), X, struct('reduce_fun', 'plus')); % equivalent to sum(abs(x).^2,3) for small datasets
% % information reduction to (Nx, Nx,1) using only CPU
% X_accum = block_fun(@(x)(abs(x).^2), X, struct('use_gpu', false, 'reduce_fun', 'plus')); % equivalent to sum(abs(x).^2,3) for small datasets but avoids using GPU
% % information reduction to (Nx, Nx,1) and return results as single even if inputs is fp16
% X_accum = block_fun(@(x)(abs(x).^2), X, struct('use_fp16', false, 'reduce_fun', 'plus')); % equivalent to sum(abs(x).^2,3) for small datasets but avoids using GPU
% % use multiple inputs and perform inplace image shifting in order to prevent large memory allocation
% shift_x = randn(Nz,1); shift_y = randn(Nz,1);
% X_accum = block_fun(@utils.imshift_fft, X, shift_x, shift_y, struct('inplace', true)); % equivalent to imshift_fft(X, shift_x, shift_y)
% % process only a small subregion of input array, avoid slow matlab memory copy / allocation
% ROI = {1:10, 4:50};
% X_phase_cropped = block_fun( @angle, X, struct('ROI', ROI)); % angle(X(ROI{:}))
% % make the processing quiet, ie no progressbars are shown
% X_phase = block_fun( @angle, X, struct('verbose_level', 0)); % angle(X)
% clean all memory blocks:
% ! ipcs -m | cut -d' ' -f2 | grep '^[0-9]' | while read x; do ipcrm -m $x; done
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 varargout = block_fun(fun, stack_object, varargin)
global pprev
assert(~isempty(stack_object), 'Processed array is empty')
Nlayers = size(stack_object,3);
varargout = cell(nargout,1);
varargin = [{stack_object}, varargin];
% check if there is given parameter structure
if isstruct(varargin{end})
param = varargin{end};
varargin = varargin(1:end-1); % !!! remove the parameter structure from the vargin list
else
param = struct();
end
ninputs = length(varargin);
assert(~isempty(stack_object), 'Input array is empty')
%% ============= LOAD PARAMETERS FROM PARAM STRUCTURE OR USE PREDEFINED DEFAULTS ================
if ~isfield(param, 'verbose_level')
param.verbose_level = 1;
end
if ~isfield(param, 'use_GPU')
param.use_GPU = true; % some operations are just faster without moving to GPU
end
if ~isfield(param, 'move_to_GPU')
param.move_to_GPU = true; % move blocks directly yo GPU or let the called function to decide
end
if ~isfield(param, 'GPU_list') || isempty(param.GPU_list)
if param.use_GPU && gpuDeviceCount > 0
gpu = gpuDevice;
param.GPU_list = gpu.Index;
else
param.GPU_list = -1; % list of the used GPUs
end
end
if ~isfield(param, 'inplace')
param.inplace = false; % run the function inplace , !! dont stop the operation in middle of process !!
% assume that output is the same as first
% input array and it will write results
% into this memory without reallocating
end
if ~isfield(param, 'ROI') || isempty(param.ROI) % process only limited ROI of all inputs of size stack_object
param.ROI = {':', ':'};
end
Nelements = 1;
for i = 1:2
if strcmpi(param.ROI{i},':')
param.ROI{i} = 1:size(stack_object,i);
end
Nelements = Nelements * length(param.ROI{i});
end
assert(Nelements>0, 'Selected ROI is empty')
if ~isfield(param, 'Nblocks') % number of blocks to split the input volume, [] = auto
param.Nblocks = [];
end
if ~isfield(param, 'full_block_size') % largest volume size in the calculation, default = size(stack_object)
param.full_block_size = size(stack_object);
end
if ~isfield(param, 'use_fp16')
param.use_fp16 = isa(stack_object, 'uint16');
end
% function applied on the computed data to reduce along 3rd axis
if ~isfield(param, 'reduce_fun')
param.reduce_fun = [];
else
assert(ismember(func2str(param.reduce_fun), {'min','max','plus'}))
end
if ~isfield(param, 'use_shared_memory')
param.use_shared_memory = false; % use shared memory for data exchange, it is used only if N_GPU > 1 or for debugging
end
if param.inplace
varargout{1} = stack_object;
end
if param.inplace
warning on
warning off backtrace
warning('Inplace data processing, DO NOT INTERRUPT')
warning on backtrace
end
if (gpuDeviceCount == 0 || isa(stack_object, 'gpuArray') || ismatrix(stack_object)) && ...
(isempty(param.Nblocks) || param.Nblocks == 1)
%% ================ EXECUTE FUN =============================
[varargout{:}] = fun(stack_object(param.ROI{:},:), varargin{2:end});
else
%% GPU or CPU splitting is required
N_GPU = max(1,length(param.GPU_list));
if param.use_GPU
gpu = gpuDevice;
if ~ismember(gpu.Index,param.GPU_list)
gpu = gpuDevice(param.GPU_list(1));
end
end
if isempty(param.Nblocks)
%% autoestimate block size
if param.use_GPU
%% empirical condition assuming FFT involved, may be too pesimistic
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
Nblocks = ceil( (4*6*8* prod(param.full_block_size)) / gpu.AvailableMemory) ;
Nblocks = max(Nblocks, prod(param.full_block_size)/ double(intmax('int32')));
Nblocks = max(Nblocks, N_GPU);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
else
%% if CPU is used, split it to <20GB blocks
max_block_size = min(utils.check_available_memory*1e6, 20e9); %% work with 10GB blocks
Nblocks = ceil( (6*8* prod(param.full_block_size)) / max_block_size) ;
end
else
Nblocks = param.Nblocks;
end
% optimize size of the block to equally use all provided GPUs
N = ceil(Nlayers / Nblocks);
Nblocks = ceil(Nlayers / N / N_GPU) * N_GPU; % make it splitable to N_GPU
N = ceil(Nlayers / Nblocks);
% parse inputs and store infomations needed to split the inputs
splitable_along_3rd_axis = false(ninputs,1);
apply_ROI = false(ninputs,1);
for jj = 1:ninputs
splitable_along_3rd_axis(jj) = ndims(varargin{jj}) == 3 && (size(varargin{jj},3) == size(stack_object,3));
apply_ROI(jj) = all([size(varargin{jj},1),size(varargin{jj},2)] == [size(stack_object,1), size(stack_object,2)]) && ...
((size(varargin{jj},1) ~= length(param.ROI{1})) || (size(varargin{jj},2) ~= length(param.ROI{2})));
end
% expected inputs are singles or uint16, if they are doubles, convert to single so save memory and speed up execution on GPU
for jj = find(splitable_along_3rd_axis)'
if ~ismember(class(varargin{jj}), {'single', 'uint16', 'uint8'})
varargin{jj} = single(varargin{jj});
end
end
if Nblocks == 1
%% in case no splitting is needed
varargout = process_in_single_block(fun, varargin, apply_ROI,splitable_along_3rd_axis, nargout, param);
return
else
% when the function is finished, make sure to execute following code
global status
status = true;
% avoid using shared memory if not needed, it is a bit slower
use_shared_memory = length(param.GPU_list) > 1 || param.use_shared_memory;
if use_shared_memory
out = onCleanup(@myCleanupFun);
end
for ii = 1:length(varargin)
if splitable_along_3rd_axis(ii) || N_GPU > 1
varargin{ii} = gather(varargin{ii});
end
end
ind = {};
for ii = 1:Nblocks
ind{end+1} = 1+N*(ii-1) : min(Nlayers, N*ii);
% avoid single layer arrays
if length(ind{end}) < 2
ind{end-1} = [ind{end-1}, ind{end}];
ind(end) = [];
end
end
Nblocks = length(ind);
%% open parpool to allow multi GPU processing
if N_GPU > 1
poolobj = gcp('nocreate');
if isempty(poolobj) || poolobj.NumWorkers < N_GPU
delete(poolobj);
poolobj = parpool(N_GPU);
end
poolobj.IdleTimeout = 600; % set idle timeout to 10 hours
end
if param.verbose_level>0
pprev = -1;
end
%% START OF OUTER GPU LOOP
outputs_blocks = [];
%% unitialize one solver per GPU
for block_id = 1:N_GPU
% parse inputs and try to split them if possible
[outputs_blocks, inputs_block{block_id}] = submit_block(block_id,block_id, ind, varargin,outputs_blocks ,param, fun, apply_ROI, use_shared_memory, Nlayers,nargout, splitable_along_3rd_axis );
end
unprocessed_blocks = N_GPU+1:Nblocks;
%% merge blocks back from GPUs
for ii = 1:Nblocks
if param.verbose_level>0; utils.progressbar(ii, Nblocks); end
% set values from the small blocks to the final output arrays
[thread_id, varargout] = gather_block(varargout,param,Nlayers, nargout,ind, N, outputs_blocks);
if ~isempty(unprocessed_blocks)
block_id = unprocessed_blocks(1);
unprocessed_blocks(1) = [];
% submit a new job once the previous is finished
[outputs_blocks, inputs_block{thread_id}] = submit_block(block_id,thread_id,ind, varargin,outputs_blocks ,param, fun, apply_ROI, use_shared_memory, Nlayers,nargout, splitable_along_3rd_axis );
if isa(outputs_blocks, 'parallel.FevalFuture') && any(cat(1,[outputs_blocks.Read]))
outputs_blocks
warning on
warning('Unknown error in parallel processing')
keyboard
end
end
end
% END OF OUTER GPU LOOP
end
end
% everything was fine -> no cleaning needed
status = false;
if param.inplace
warning on
warning off backtrace
warning('Inplace data processing finished')
warning on backtrace
end
end
%% auxiliary function
function [outputs_blocks,inputs_block] = submit_block(block_id, thread_id, ind, inputs, outputs_blocks, param, fun, apply_ROI, use_shared_memory, Nlayers , Noutputs, splitable_along_3rd_axis )
inputs_block = inputs;
ind = ind{block_id};
% get blocks of data
for jj = 1:length(inputs)
if splitable_along_3rd_axis(jj)
if apply_ROI(jj)
ROI = param.ROI;
else
ROI = {};
end
% use fast splitting based on MEX files for large splitable blocks
inputs_block{jj} = get_block(inputs_block{jj}, ind, ROI,use_shared_memory);
elseif isnumeric(inputs_block{jj}) && size(inputs_block{jj},1) == Nlayers
% !! assume split along first dimension of the object !!!
inputs_block{jj} = inputs_block{jj}(ind,:,:,:); % split it and provide subfunction only the valid chunk
end
end
if isempty(outputs_blocks); clear outputs_blocks; end
%% ----------- call the function "fun" , execute on GPUs -----------------
if length(param.GPU_list) <= 1 || ~param.use_GPU
%% single GPU
[outputs_blocks.data,outputs_blocks.id]...
= worker(fun,Noutputs, use_shared_memory,param,block_id,1,inputs_block);
else
%% run asynchronous on multiple GPUs
outputs_blocks(thread_id) = parfeval(@worker,2,fun,Noutputs, use_shared_memory,param,block_id,thread_id,inputs_block);
end
end
function out = get_block(full_array, ind, ROI, use_shared_memory)
Nlayers = length(ind);
positions = zeros(Nlayers,2,'int32');
if ~isempty(ROI)
Npix_small = [length(ROI{1}),length(ROI{2}),length(ind)];
assert(all(Npix_small <= size(full_array)), 'Provided ROI is larger than input array')
positions = positions+int32([ROI{1}(1),ROI{2}(1)]-1);
else
Npix_small = [size(full_array,1),size(full_array,2),length(ind)];
end
% allocate in RAM array to load the block from "stack_object"
stack_object_block_in = zeros(Npix_small, 'like', full_array);
if use_shared_memory
s = shm(); % create sharemem object
if isa(full_array, 'single')
s.allocate(stack_object_block_in); % allocate SHM memory
% attach the shared memory
[s, stack_object_block_shm] = s.attach();
% === write data =====
% use self-made MEX OMP function to move the data
utils.get_from_3D_projection(stack_object_block_shm, full_array, positions, ind);
elseif isa(full_array, 'uint16')
stack_object_block_in = zeros(Npix_small, 'like', full_array);
% === write data =====
% use self-made MEX OMP function to move the data
utils.get_from_3D_projection(stack_object_block_in, full_array, positions, ind);
% convert from fp16 to singles
stack_object_block_in = fp16.get(stack_object_block_in);
% upload to the shared memory
s.upload(stack_object_block_in); % seems to be really slow ????
%s.allocate(stack_object_block_in); % allocate SHM memory
%[s, stack_object_block_shm] = s.attach();
% % move to shared memory , a bit faster than the memcpy in the
% % sharedmem function
%utils.get_from_3D_projection(stack_object_block_shm, stack_object_block_in, zeros(Nlayers,2,'int32'), int32(1:Nlayers));
%toc
end
% detach the shared memory
s.detach;
% return structure with shared mem object
out = s;
else
% use custom made MEX OMP function to move the data
stack_object_block_in = utils.get_from_3D_projection(stack_object_block_in, full_array, positions, ind);
% if needed, convert from fp16 to singles
out = fp16.get(stack_object_block_in);
end
end
function myCleanupFun()
global status
if status
warning('!! cleaning all shared memory !!')
% destroy all shared memory that could have been left behind
!ipcs -m | cut -d' ' -f2 | grep '^[0-9]' | while read x; do ipcrm -m $x; done
end
end
function [thread_id, outputs] = gather_block(outputs,param,Nlayers,Noutputs, ind_all, Nl_per_block, output_package)
if isa(output_package, 'parallel.FevalFuture')
% gather results from cluster , WAIT FOR CALCULATIONS TO BE FINISHED
% [~, outputs_block, id] = fetchNext(output_package);
%% my version of the fetchNext function, it seems faster
id = [];
while true
for thread_id =1:length(output_package)
if strcmpi(output_package(thread_id).State, 'finished') && output_package(thread_id).Read == 0
try
[outputs_block, id] = output_package(thread_id).fetchOutputs;
catch err
if strcmpi(err.identifier, 'parallel:fevalqueue:InvalidExecutionResult')
warning('Unknown error, trying to restart parpool')
delete(gcp('nocreate'));
end
if ~isempty(output_package(thread_id).Diary)
fprintf('============ THREAD %i FAILED, OUTPUT: ============= \n', thread_id)
disp(output_package(thread_id).Diary)
end
fprintf('============ THREAD %i FAILED, ERROR: ============= \n', thread_id)
disp(getReport(output_package(thread_id).Error))
keyboard
output_package.cancel
rethrow(err)
end
break
end
end
if ~isempty(id); break; end
pause(0.01) % wait for the data to be prepared
end
else
thread_id = 1;
id = output_package.id;
outputs_block = output_package.data;
end
ind = ind_all{id};
for jj = 1:Noutputs
% get the results from GPU to RAM
% allocate RAM for the final output arrays , allocate when
% with the same complexity as the outputs_block{ii}{jj} has
s = shm();
if isa(outputs_block{jj}, 'shm')
% first download from the shared mem back to workspace
[s, outputs_block{jj}] = outputs_block{jj}.attach;
s.protected = false;
end
% processing returns the same number of layers as input
is_block = ndims(outputs_block{jj}) == 3 && size(outputs_block{jj},3) == length(ind) ;
% processing returns one layer per input
is_reduced = ismatrix(outputs_block{jj}) && size(outputs_block{jj},3) == 1 && size(outputs_block{jj},1) ~= length(ind) && ~isempty(param.reduce_fun) && jj ==1;
if isa(outputs_block{jj}, 'double')
% small arrays / scalars just convert
outputs_block{jj} = single(outputs_block{jj});
% for large complain
if is_block
warning('Output of the function should be blocks in single precision')
end
end
if is_block && param.use_fp16 && isa(outputs_block{jj},'single')
% move to fp16 precision to avoid large memory allocation
outputs_block{jj} = fp16.set(outputs_block{jj});
end
% if the first iteration when it is empty create the large output arrays
if isempty(outputs{jj})
try
if is_block
% return large array of size stack_object
Npix_small = [size(outputs_block{jj},1),size(outputs_block{jj},2),Nlayers];
outputs{jj} = zeros(Npix_small, 'like', outputs_block{jj});
elseif is_reduced
outputs{jj} = [];
elseif size(outputs_block{jj},1) == length(ind) && ndims(outputs_block{jj}) <= 3
% return smaller array split along first dimension
outputs{jj} = zeros([Nlayers, size(outputs_block{jj},2), size(outputs_block{jj},3)], 'like', outputs_block{jj});
else
error('Unimplemented option')
end
catch err
utils.check_available_memory
rethrow(err)
end
end
if is_block
% if the output is 3D array, add the results into the large output stored in RAM
outputs{jj} = tomo.set_to_array(outputs{jj}, outputs_block{jj}, Nl_per_block*(id-1), false);
elseif is_reduced
if isempty(outputs{jj})
outputs{jj} = outputs_block{jj};
else
outputs{jj} = param.reduce_fun(outputs{jj}, outputs_block{jj});
end
else
outputs{jj}(ind,:,:) = outputs_block{jj};
end
end
end
function [outputs,id] = worker(fun, Noutputs, use_sharemem, param, id, thread_id, inputs)
% let parfor to choose which GPU use
gpu_id = param.GPU_list(thread_id);
gpu = gpuDevice();
if ~isempty(param.GPU_list) && gpu.Index ~= gpu_id && gpu_id > 0
gpuDevice(gpu_id); % avoid unneeded initalization
end
if ~isempty(getCurrentTask())
% report only if inside parfor
utils.check_available_memory
if gpu_id > 0
fprintf('GPU %i ===== Memory %3.2GB/%3.2gGB \n',gpu.Index, gpu.AvailableMemory/1e9, gpu.TotalMemory/1e9 )
end
end
%% upload data on GPU
for jj = 1:length(inputs)
if isa(inputs{jj}, 'shm')
% data are downloaded from shared memory
[s,inputs{jj}]=inputs{jj}.attach;
end
if isnumeric(inputs{jj}) && numel(inputs{jj}) > 1e4 && param.use_GPU && param.move_to_GPU
inputs{jj} = gpuArray(inputs{jj});
end
end
outputs = cell(Noutputs,1);
Nlayers = size(inputs{1},3);
try
%%%%%% CALL THE WORKING FUNCTION WITH ARRAYS ALREADY MOVED TO GPU %%%%
[outputs{:}] = fun(inputs{:});
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if ~isempty(param.reduce_fun)
% apply additional function to reduce the data
switch func2str(param.reduce_fun)
case 'plus',outputs{1} = sum(outputs{1},3);
case 'min', outputs{1} = min(outputs{1},[],3);
case 'max', outputs{1} = max(outputs{1},[],3);
end
end
catch err
if any(strcmpi(err.identifier, {'parallel:gpu:array:OOMForOperation', 'parallel:gpu:array:OOM'}))
[~,out] = system(sprintf('nvidia-smi --id=%i | head -n10 | tail -n7', gpu.Index-1));
% disp(getReport(err))
disp(out)
warning('Low memory on GPU %i, RESETTING GPU ... \n', gpu.Index)
for jj = 1:length(inputs)
inputs{jj} = gather(inputs{jj});
end
% try to clean some leftover data from previous calculations
reset(gpuDevice)
fprintf('TRYING TO RECURSIVELLY CALL BLOCKFUN %s WITH 2 SUBBLOCKS \n', func2str(fun))
[outputs{:}] = tomo.block_fun(fun,inputs{:},struct('Nblocks',2));
else
rethrow(err)
end
end
for jj = 1:length(outputs)
%% return data from GPU
if isa(outputs{jj}, 'gpuArray') && param.use_GPU
outputs{jj} = gather(outputs{jj});
end
%% move data to shared memory if needed
is_block = ndims(outputs{jj}) == 3 && (size(outputs{jj},3) == Nlayers);
if is_block && use_sharemem
% data are distributed to shared memory
s = shm(true);
s.upload(outputs{jj})
outputs{jj} = s;
end
end
end
function outputs = process_in_single_block(fun, inputs,apply_ROI, splitable_along_3rd_axis, Noutputs, param)
for jj = 1:length(inputs)
if splitable_along_3rd_axis(jj)
% if stored as fp16 precision, convert to singles
inputs{jj} = fp16.get( inputs{jj});
% if provided and possible, use param.ROI to crop the working / inputs array
if apply_ROI(jj)
inputs{jj} = inputs{jj}(param.ROI{:},:);
end
end
if isa(inputs{jj}, 'double')
inputs{jj} = single(inputs{jj});
end
if param.use_GPU && isnumeric(inputs{jj}) && numel(inputs{jj}) > 1e2 && param.move_to_GPU
% upload on GPU
inputs{jj} = gpuArray(inputs{jj});
end
end
outputs = cell(Noutputs,1);
% execute the worker
[outputs{:}] = fun(inputs{:});
if ~isempty(param.reduce_fun)
% apply additional function to reduce the data
switch func2str(param.reduce_fun)
case 'plus',outputs{1} = sum(outputs{1},3);
case 'min', outputs{1} = min(outputs{1},[],3);
case 'max', outputs{1} = max(outputs{1},[],3);
end
end
for jj = 1:Noutputs
if param.use_GPU && isa(outputs{jj}, 'gpuArray')
% get back from GPU
outputs{jj} = gather(outputs{jj});
end
% processing returns the same number of layers as input
is_block = ndims(outputs{jj}) == 3 && size(outputs{jj},3) == size(inputs{1},3);
if param.use_fp16 && is_block
outputs{jj} = fp16.set(outputs{jj});
end
end
end
@@ -0,0 +1,29 @@
% weight_sino = estimate_reliability_region_grad(complex_projection, probe_size, subsample)
% Estimates the good region in the (real) sinogram for alignment and reconstruction.
% The method is base on gradient magnitude of the image
% Written by YJ
function weight_sino = estimate_reliability_region_grad(sinogram, fill, erode_mat)
%{
disp(size(sinogram))
[Gmag,~] = imgradient(gather(sinogram));
sinogram_temp = imfill(Gmag,fill);
level = graythresh(sinogram_temp);
weight_sino_temp = imbinarize(sinogram_temp,level);
weight_sino = imerode(weight_sino_temp,erode_mat);
%}
weight_sino = single(zeros(size(sinogram)));
for i=1:size(sinogram,3)
if ~isreal(sinogram)
[Gmag,~] = imgradient(angle(sinogram(:,:,i)));
else
[Gmag,~] = imgradient(sinogram(:,:,i));
end
sinogram_temp = imfill(Gmag,fill);
level = graythresh(gather(sinogram_temp));
weight_sino_temp = imbinarize(gather(sinogram_temp),level);
weight_sino(:,:,i) = imerode(weight_sino_temp,erode_mat);
end
end
+146
View File
@@ -0,0 +1,146 @@
% GET_FSC_FROM_SUBTOMOS calculate Fourier shell correlation between two subtomograms
%
% [resolution FSC T freq n FSC_stats] = get_FSC_from_subtomos(tomograms, FSC_vertical_range, rad_apod, radial_smooth, axial_apod, SNRt,thickring, par)
%
% Inputs:
% **tomograms - 2x1 cell containing 2 tomograms to be compared
% **FSC_vertical_range - vector of the selected layers for FSC
% **rad_apod - radial apodization of the tomogram volumes
% **radial_smooth - smoothness range of the radial apodization
% **axial_apod - apodizaton along vertical axis
% **SNRt - signal threshold for FRC resolution
% **thickring - thickness of FSC shells
% **par - tomo parameters structure
%
% returns:
% ++resolution [min, max] resolution estimated from FSC curve
% ++FSC FSC curve values
% ++T Threshold values
% ++freq spatial frequencies
% ++stat stat - structure containing other statistics such as
% SSNR, area under FSC curve. average SNR, ....
% ++fsc_path path to store the FSC curves
%
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 [resolution FSC T freq n FSC_stats, fsc_path] = get_FSC_from_subtomos(tomograms, FSC_vertical_range, rad_apod,radial_smooth,axial_apod,SNRt,thickring,par)
Npix = size(tomograms{1});
utils.verbose(struct('prefix', 'FSC'))
for ii = 1:2
auxtomo{ii} = utils.apply_3D_apodization(tomograms{ii}(:,:,FSC_vertical_range(end:-1:1)), rad_apod, axial_apod);
if ii == 1
% ignore empty regions
tomo_ROI = get_ROI(any(auxtomo{1} ~= 0,3));
end
auxtomo{ii} = auxtomo{ii}(tomo_ROI{:},:);
end
if ishandle(4); close(4); end % force closing and reopening on the front
plotting.smart_figure(4)
subplot(1,2,1)
img_tmp = rot90(squeeze(tomograms{1}(:,ceil(Npix(1)/2),end:-1:1)),1);
imagesc(img_tmp);
caxis(math.sp_quantile(img_tmp, [1e-2, 1-1e-2], 10))
hold on
plotting.hline(FSC_vertical_range(1)+axial_apod/2, 'r')
plotting.hline(FSC_vertical_range(end)-axial_apod/2, 'r')
plotting.vline(rad_apod+radial_smooth/2, 'b')
plotting.vline(Npix(1)-rad_apod-radial_smooth/2, 'b')
hold off
% caxis([4,5.3]*1e-3)
axis xy equal tight
colormap bone
title('Selected FSC range')
subplot(1,2,2)
Npix_aux = size(auxtomo{1});
img_tmp = rot90(squeeze(auxtomo{1}(:,ceil(Npix_aux(1)/2),:)),1);
imagesc(img_tmp);
caxis(math.sp_quantile(img_tmp, [1e-2, 1-1e-2], 10))
colormap bone
hold on
plotting.vline(radial_smooth, 'b')
plotting.vline(Npix_aux(1)-radial_smooth, 'b')
hold off
axis xy equal tight
title('Input to FSC')
if par.windowautopos
win_size = [1000 600];
screensize = get( groot, 'Screensize' );
set(gcf,'Outerposition',[150 min(270,screensize(4)-win_size(2)) win_size]);
end
drawnow
if ~par.online_tomo && ~debug() && strcmpi(input('Accept FSC region (Y/n)? ','s'),'n')
utils.verbose(-1,'Manually adjust FSC_vertical_range / axial_apod / rad_apod')
return
elseif ~debug()
try
fsc_path = fullfile(par.output_folder, sprintf('FSC_region_S%05d_S%05d_%s_freqscl_%0.2f-%s',...
par.scanstomo(1),par.scanstomo(end),par.filter_type,par.freq_scale,datetime('today')));
print('-f4','-deps', [fsc_path, '.eps'])
print('-f4','-dpng', [fsc_path, '.png'])
utils.verbose(-1,['Saved preview to ', fsc_path])
catch
warning('FSC region plot was not saved because figure 4 is missing')
end
end
utils.verbose(-1,'Fourier shell correlation')
[resolution FSC T freq n FSC_stats] = utils.fourier_shell_corr_3D_2(auxtomo{:},par,'dispfsc',true,'SNRt',SNRt,'auto_binning',true, 'thickring', thickring,'figure_id',45);
drawnow
fsc_path = fullfile(par.output_folder,sprintf('FSC_curve_S%05d_S%05d_%s_freqscl_%0.2f-%s',par.scanstomo(1),par.scanstomo(end),par.filter_type,par.freq_scale,datetime('today')));
if ~debug() % ignore in automatic tests
print('-f45','-deps', [fsc_path, '.eps'])
print('-f45','-dpng', [fsc_path, '.png'])
utils.verbose(-1,['Saved FSC curve to ', fsc_path])
if par.online_tomo
fsc_path_online = sprintf('%s_FSC_tomo.png',par.online_tomo_path);
print('-f45','-dpng',fsc_path_online)
system(sprintf('convert -trim %s %s', fsc_path_online, fsc_path_online));
end
end
utils.verbose(struct('prefix', 'template'))
end
+78
View File
@@ -0,0 +1,78 @@
% GET_FROM_ARRAY simplified wrapper around CPU-based multithread mex function "get_from_3D_projection"
%
% object_block = get_from_array(full_object, object_block, indices, offset)
%
%
% Inputs:
% **full_object - (3D array) large volume block where data will be loded from
% **object_block - (3D array) smaller volume block into which the selected data from full_object will be added
% **indices - list of slices along 3rd axis that are written to object_block, starting from 0
% **offset - (numel(indices)x2 nonnegative integers) offset from (1,1) corner
% *returns*
% ++object_block - loaded small block of the full_object
% Example:
% function is equivalent (but much faster) to
% for ii = 1:size(object_block,3)
% object_block(:,:,ii) = full_object(offset(ii,1)+1:size(object_block,1) , offset(ii,2)+1:size(object_block,2),ii);
% end
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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_block = get_from_array(full_object, object_block, indices, offset)
if nargin < 4
offset = [0,0];
end
Nind = length(indices);
if isempty(object_block)
object_block = zeros(size(full_object,1), size(full_object,2), Nind, 'like',full_object);
end
if isa(object_block,'shm')
object_block.allocate(zeros(size(full_object,1), size(full_object,2), Nind, 'like',full_object));
[s,object_block] = object_block.attach();
end
positions = zeros(size(object_block,3),2,'int32') + int32(offset);
% mex custom made fast get function
utils.get_from_3D_projection(object_block,full_object,positions, int32(indices));
if exist('s','var')
s.detach;
object_block = s;
end
end
+146
View File
@@ -0,0 +1,146 @@
function [mask] = get_tomo_fourier_mask_2d( Nproj , N_recon, angles )
%Get fourier mask of the MISSING WEDGE
%Output Image Size
Ny = N_recon(1);
Nx = N_recon(2);
cen_y = floor(Ny/2)+1;
cen_x = floor(Nx/2)+1;
dk_y = 1/Ny;
dk_x = 1/Nx;
Nang = length(angles);
x = (linspace(1,Nproj,Nproj)-cen_x)*dk_x;
x = repmat(x,[Nang,1]);
y = 0;
angles_temp = reshape(angles,[Nang,1]);
angles_temp = repmat(angles_temp,[1,Nproj]);
angles_temp = -angles_temp*pi/180;
y_new = cos(angles_temp)*y + sin(angles_temp).*x;
x_new = -sin(angles_temp)*y + cos(angles_temp).*x;
mask = zeros(Ny, Nx, 4);
%1
p_y = floor(y_new/dk_y)+cen_y;
p_x = floor(x_new/dk_x)+cen_x;
mask_temp = zeros([Ny*Nx,1]);
mask_temp(p_y+(p_x-1)*Ny)=1;
mask(:,:,1) = reshape(mask_temp,[Ny,Nx]);
%2
p_y = ceil(y_new/dk_y)+cen_y;
p_x = floor(x_new/dk_x)+cen_x;
mask_temp = zeros([Ny*Nx,1]);
mask_temp(p_y+(p_x-1)*Ny)=1;
mask(:,:,2) = reshape(mask_temp,[Ny,Nx]);
%3
p_y = floor(y_new/dk_y)+cen_y;
p_x = ceil(x_new/dk_x)+cen_x;
mask_temp = zeros([Ny*Nx,1]);
mask_temp(p_y+(p_x-1)*Ny)=1;
mask(:,:,3) = reshape(mask_temp,[Ny,Nx]);
%4
p_y = ceil(y_new/dk_y)+cen_y;
p_x = ceil(x_new/dk_x)+cen_x;
mask_temp = zeros([Ny*Nx,1]);
mask_temp(p_y+(p_x-1)*Ny)=1;
mask(:,:,4) = reshape(mask_temp,[Ny,Nx]);
mask = sum(mask,3);
mask = mask==0;
% always set center to 1
%mask(cen_y,cen_x) = 1;
%{
%Number of projections
N_ang = max(size(angles));
%Output Image Size
Ny = N_recon(1);
Nx = N_recon(2);
cen_y = floor(Ny/2)+1;
cen_x = floor(Nx/2)+1;
mask = zeros(Ny, Nx);
dk_y = 1/Ny;
dk_x = 1/Nx;
for a = 1:N_ang
%if angles(a)~=0 || angles(a)~=90
ang = -angles(a)*pi/180;
%CurrentcN_recon projection
%p = input(:,a);
%P = fftshift(fft(ifftshift(p)));
%{
if -angles(a) == 90
v(:,cen) = v(:,cen) + P(end:-1:1);
w(:,cen) = w(:,cen) + 1;
output_F(w~=0) = v(w~=0)./w(w~=0);
elseif -angles(a) == -90
v(:,cen) = v(:,cen) + P;
w(:,cen) = w(:,cen) + 1;
output_F(w~=0) = v(w~=0)./w(w~=0);
else
%}
for i=1:1:N_proj
x = (i-cen_x)*dk_x;
y = 0;
%Calculate new coordinate
y_new = cos(ang)*y + sin(ang)*x;
x_new = -sin(ang)*y + cos(ang)*x;
%Calculate weights
sy = abs(floor(y_new) - y_new); if sy<1e-6; sy=0; end
sx = abs(floor(x_new) - x_new); if sx<1e-6; sx=0; end
%Bilinear Extrapolation
%P1
p_y = floor(y_new/dk_y)+cen_y;
p_x = floor(x_new/dk_x)+cen_x;
if p_x >0 && p_x<=Nx && p_y >0 && p_y<=Ny
mask(p_y,p_x) = 1;
end
%P2
p_y = ceil(y_new/dk_y)+cen_y;
p_x = floor(x_new/dk_x)+cen_x;
if p_x>0 && p_x<=Nx && p_y >0 && p_y<=Ny
mask(p_y,p_x) = 1;
end
%P3
p_y = floor(y_new/dk_y)+cen_y;
p_x = ceil(x_new/dk_x)+cen_x;
if p_x>0 && p_x<=Nx && p_y >0 && p_y<=Ny
mask(p_y,p_x) = 1;
end
%P4
p_y = ceil(y_new/dk_y)+cen_y;
p_x = ceil(x_new/dk_x)+cen_x;
if p_x>0 && p_x<=Nx && p_y >0 && p_y<=Ny
mask(p_y,p_x) = 1;
end
end
end
mask = mask==1;
%}
end
+7
View File
@@ -0,0 +1,7 @@
function [mask] = get_tomo_fourier_mask_3d( N_recon, angles )
mask = tomo.get_tomo_fourier_mask_2d(N_recon(2),[N_recon(1),N_recon(2)],angles);
mask = repmat(mask,[1,1,N_recon(3)]);
%mask = mask==0;
mask = ifftshift(mask);
end
+264
View File
@@ -0,0 +1,264 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Function:
%
% [theta_corr_sorted,delta_stack_corr_x_filt,delta_stack_corr_y_filt] = give_calibration(obj_interf_pos_x, obj_interf_pos_y, deltastack, deltaslice, param)
%
% Description:
%
% The function (1) saves the alignment arrays for later use and
% (2) saves the vertical correction into a .txt (and .mat) file for
% correcting the vertical fluctuarion throguh SPEC
%
% Input:
%
% obj_interf_pos_x and obj_interf_pos_y: obatined from running get_auto_tomo.m
% deltastack: alignment for x and y
% deltaslice: additional alignment for x
% param. savedata: 0 (default) or 1
% param. surface_calib_file
% param. get_auto_calibration: 0 or 1 (default)
% param. pixsize (mandatory)
% param. theta (mandatory)
% param. scans (mandatory)
% param. output_folder (default: './')
%
% Output:
%
% theta_corr_sorted
% delta_stack_corr_x_filt
% delta_stack_corr_y_filt
%
% 2017-03-30
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 [thetasort,delta_stack_corr_x_filt,delta_stack_corr_y_filt] = ...
give_calibration(obj_interf_pos_x, obj_interf_pos_y, deltastack, deltaslice, theta, scans, param)
if isfield(param,'get_auto_calibration')
get_auto_calibration = param.get_auto_calibration;
else
fprintf('Using get_auto_calibration = 1\n');
get_auto_calibration = 1;
end
if ~isfield(param,'surface_calib_file')
error('Set param.surface_calib_file');
end
if isfield(param,'output_folder')
output_folder = param.output_folder;
else
fprintf('Set output_folder to the pwd \n');
output_folder = './';
end
if isfield(param,'pixel_size')
pixsize = param.pixel_size;
else
error('Need to specify param.pixel_size\n');
end
[thetasort, indsort] = sort(theta);
if get_auto_calibration
Nangles = length(theta);
%%% Get correction from interferometer and alignment values in meters
delta_stack_corr_y = deltastack(1,:)*pixsize;
delta_stack_corr_x = (deltastack(2,:)+deltaslice)*pixsize;
%% %%% remove outliers by median filter %%%%%%%%%%%%%%%%%%%%
%%% Subtract the a*sin(x+b)+c term from x correction
[rigid_shift] = fit_sinus(theta, delta_stack_corr_x');
delta_stack_corr_x = delta_stack_corr_x - rigid_shift';
%%% Remove constant term from y correction
delta_stack_corr_y = delta_stack_corr_y - mean(delta_stack_corr_y);
%%% filtering to avoid outliers in the alignment, seems to work better when it is done
%%% after sinus removal
delta_stack_corr_x_filt = medfilt1(delta_stack_corr_x, 5);
delta_stack_corr_y_filt = medfilt1(delta_stack_corr_y, 5);
delta_stack_corr_x_filt = delta_stack_corr_x_filt + rigid_shift';
%% %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% apply sorting
delta_stack_corr_x_filt = delta_stack_corr_x_filt(indsort)';
delta_stack_corr_y_filt = delta_stack_corr_y_filt(indsort)';
figure(1);
clf;
subplot(2,1,1)
plot(theta,obj_interf_pos_y*1e6,'.')
title('Interferometer y position')
axis tight ; grid on
xlabel('Angles [deg]')
ylabel('Shift [\mum]')
subplot(2,1,2)
plot(theta,obj_interf_pos_x*1e6,'.')
title('Interferometer x position (plus "clicking correction" term) [microns]')
axis tight ; grid on
xlabel('Angles [deg]')
ylabel('Shift [\mum]')
figure(4);
clf;
subplot(2,1,1)
plot(theta,deltastack(1,:)*pixsize*1e6,'.')
title('Alignment in y (without removal of constant term)')
ylabel('Shift [\mum]')
xlabel('Angles [deg]')
axis tight ; grid on
subplot(2,1,2)
plot(theta,( deltastack(2,:)+deltaslice)*pixsize*1e6,'.')
title('Alignment in x (without removal of sinus term)')
ylabel('Shift [\mum]')
xlabel('Angles [deg]')
axis tight ; grid on
figure(3);
clf;
subplot(2,1,1)
plot(thetasort,delta_stack_corr_y_filt*1e6,'.')
title('Correction in y (after removal of constant term)')
ylabel('Shift [\mum]')
axis tight ; grid on
xlabel('Angles [deg]')
subplot(2,1,2)
plot(thetasort,delta_stack_corr_x_filt*1e6,'.')
title('Correction in x (after removal of constant term)')
ylabel('Shift [\mum]')
axis tight ; grid on
xlabel('Angles [deg]')
output_png1 = [ output_folder 'delta_stack_corr.png'];
fprintf('Writting image files \n %s\n',output_png1);
print('-f3','-dpng','-r300',output_png1);
%% Calculate the correction for interferometers
corr(:,1) = delta_stack_corr_x_filt*1e6;
corr(:,2) = delta_stack_corr_y_filt*1e6;
%%% Filter the correction to remove effects of long term drifts !!
corr = medfilt1(corr, 11);
filter = ceil(Nangles / 10);
corr(:,1) = smooth(thetasort, corr(:,1), filter, 'sgolay');
corr(:,2) = smooth(thetasort, corr(:,2), filter, 'sgolay');
calibration_file = sprintf('correction_interferometers_um_S%05d.txt',scans(1));
% -----
figure(50);
clf()
plot(thetasort,corr, '.-'); grid on; ylabel('Correction \mum')
legend({'Horizontal', 'Vertical'})
title(sprintf('%s',output_folder),'interpreter','none');
xlabel('Angles [deg]')
axis tight; grid on
plotting.suptitle(sprintf('Final interferometer correction in file \n%s', calibration_file),'interpreter', 'none')
output_png1 = fullfile( output_folder, 'y_alignment.png');
fprintf('Writting image files \n %s\n',output_png1);
print('-f50','-dpng','-r300',output_png1);
figure(51);
plot(1:length(theta),theta); grid on;
title(sprintf('%d projections',length(theta)));
output_png1 = fullfile( output_folder , 'theta.png');
fprintf('Writting image files \n %s\n',output_png1);
print('-f51','-dpng','-r300',output_png1);
%%% Save file
utils.verbose(0, 'Saving interferometer correction to %s', param.surface_calib_file)
obj_interf_pos_x_sort = obj_interf_pos_x(indsort);
obj_interf_pos_y_sort = obj_interf_pos_y(indsort);
utils.savefast_safe(param.surface_calib_file, 'thetasort','delta_stack_corr_x_filt','delta_stack_corr_y_filt', 'obj_interf_pos_x_sort', 'obj_interf_pos_y_sort')
%%% save correction for SPEC
if ~exist(calibration_file, 'file') || strcmpi(input(sprintf('Do you want to overwrite %s (y/N)? ', calibration_file),'s'),'y')
utils.verbose(0, 'Saving interferometer correction to %s', calibration_file)
h = fopen(calibration_file,'w');
fprintf(h,'corr_elements = %d \n', Nangles);
fprintf(h,'corr_elements_x = %d \n', Nangles);
for jj = 1:Nangles
fprintf(h,'%s[%d] = %.6f \n',...
'corr_angle',jj-1,thetasort(jj));
fprintf(h,'%s[%d] = %.6f \n',...
'corr_angle_x',jj-1,thetasort(jj));
fprintf(h,'%s[%d] = %.6f \n',...
'corr_pos',jj-1, corr(jj,2));
fprintf(h,'%s[%d] = %.6f \n',...
'corr_pos_x',jj-1, corr(jj,1));
end
fclose(h);
end
end
end
function [rigid_shift] = fit_sinus(theta, signal)
% subtract the a*sin(x+b) from the data
orthbase = [sind(theta(:)), cosd(theta(:)),ones(length(theta),1)]; %
coefs = (orthbase'*orthbase) \ (orthbase'*signal);
% avoid object drifts within the reconstructed FOV
coefs(3) = 0; % preserve the offset
rigid_shift = orthbase*coefs;
end
+322
View File
@@ -0,0 +1,322 @@
function [img,H] = iradon(varargin)
%IRADON Inverse Radon transform.
% I = iradon(R,THETA) reconstructs the image I from projection data in the
% 2-D array R. The columns of R are parallel beam projection data.
% IRADON assumes that the center of rotation is the center point of the
% projections, which is defined as ceil(size(R,1)/2).
%
% THETA describes the angles (in degrees) at which the projections were
% taken. It can be either a vector containing the angles or a scalar
% specifying D_theta, the incremental angle between projections. If THETA
% is a vector, it must contain angles with equal spacing between them. If
% THETA is a scalar specifying D_theta, the projections are taken at
% angles THETA = m * D_theta; m = 0,1,2,...,size(R,2)-1. If the input is
% the empty matrix ([]), D_theta defaults to 180/size(R,2).
%
% IRADON uses the filtered backprojection algorithm to perform the inverse
% Radon transform. The filter is designed directly in the frequency
% domain and then multiplied by the FFT of the projections. The
% projections are zero-padded to a power of 2 before filtering to prevent
% spatial domain aliasing and to speed up the FFT.
%
% I = IRADON(R,THETA,INTERPOLATION,FILTER,FREQUENCY_SCALING,OUTPUT_SIZE)
% specifies parameters to use in the inverse Radon transform. You can
% specify any combination of the last four arguments. IRADON uses default
% values for any of these arguments that you omit.
%
% INTERPOLATION specifies the type of interpolation to use in the
% backprojection. The default is linear interpolation. Available methods
% are:
%
% 'nearest' - nearest neighbor interpolation
% 'linear' - linear interpolation (default)
% 'spline' - spline interpolation
% 'pchip' - shape-preserving piecewise cubic interpolation
% 'cubic' - same as 'pchip'
% 'v5cubic' - the cubic interpolation from MATLAB 5, which does not
% extrapolate and uses 'spline' if X is not equally spaced.
%
% FILTER specifies the filter to use for frequency domain filtering.
% FILTER is a string that specifies any of the following standard filters:
%
% 'Ram-Lak' The cropped Ram-Lak or ramp filter (default). The
% frequency response of this filter is |f|. Because this
% filter is sensitive to noise in the projections, one of
% the filters listed below may be preferable.
% 'Shepp-Logan' The Shepp-Logan filter multiplies the Ram-Lak filter by
% a sinc function.
% 'Cosine' The cosine filter multiplies the Ram-Lak filter by a
% cosine function.
% 'Hamming' The Hamming filter multiplies the Ram-Lak filter by a
% Hamming window.
% 'Hann' The Hann filter multiplies the Ram-Lak filter by a
% Hann window.
%
% FREQUENCY_SCALING is a scalar in the range (0,1] that modifies the
% filter by rescaling its frequency axis. The default is 1. If
% FREQUENCY_SCALING is less than 1, the filter is compressed to fit into
% the frequency range [0,FREQUENCY_SCALING], in normalized frequencies;
% all frequencies above FREQUENCY_SCALING are set to 0.
%
% OUTPUT_SIZE is a scalar that specifies the number of rows and columns in
% the reconstructed image. If OUTPUT_SIZE is not specified, the size is
% determined from the length of the projections:
%
% OUTPUT_SIZE = 2*floor(size(R,1)/(2*sqrt(2)))
%
% If you specify OUTPUT_SIZE, IRADON reconstructs a smaller or larger
% portion of the image, but does not change the scaling of the data.
%
% If the projections were calculated with the RADON function, the
% reconstructed image may not be the same size as the original image.
%
% [I,H] = iradon(...) returns the frequency response of the filter in the
% vector H.
%
% Class Support
% -------------
% R can be double or single. All other numeric input arguments must be double.
% I has the same class as R. H is double.
%
% Example
% -------
% P = phantom(128);
% R = radon(P,0:179);
% I = iradon(R,0:179,'nearest','Hann');
% figure, imshow(P), figure, imshow(I);
%
% See also FAN2PARA, FANBEAM, IFANBEAM, PARA2FAN, PHANTOM, RADON.
% Copyright 1993-2004 The MathWorks, Inc.
% $Revision$ $Date$
% References:
% A. C. Kak, Malcolm Slaney, "Principles of Computerized Tomographic
% Imaging", IEEE Press 1988.
[p,theta,filter,d,interp,N] = parse_inputs(varargin{:});
% use Matlab or C-code for the linear interpolation
use_original_matlab_code = 0;
% Design the filter
len=size(p,1);
H = designFilter(filter, len, d);
p(length(H),1)=0; % Zero pad projections
% In the code below, I continuously reuse the array p so as to
% save memory. This makes it harder to read, but the comments
% explain what is going on.
p = fft(p); % p holds fft of projections
for i = 1:size(p,2)
p(:,i) = p(:,i).*H; % frequency domain filtering
end
p = real(ifft(p)); % p is the filtered projections
p(len+1:end,:) = []; % Truncate the filtered projections
% Define the x & y axes for the reconstructed image so that the origin
% (center) is in the spot which RADON would choose.
center = floor((N + 1)/2);
xleft = -center + 1;
x = (1:N) - 1 + xleft;
x = repmat(x, N, 1);
ytop = center - 1;
y = (N:-1:1).' - N + ytop;
y = repmat(y, 1, N);
if ((~strcmp(interp, 'linear')) || (use_original_matlab_code))
costheta = cos(theta);
sintheta = sin(theta);
img = zeros(N,class(p)); % Allocate memory for the image.
end
ctrIdx = ceil(len/2); % index of the center of the projections
% Zero pad the projections to size 1+2*ceil(N/sqrt(2)) if this
% quantity is greater than the length of the projections
imgDiag = 2*ceil(N/sqrt(2))+1; % largest distance through image.
if size(p,1) < imgDiag
rz = imgDiag - size(p,1); % how many rows of zeros
p = [zeros(ceil(rz/2),size(p,2)); p; zeros(floor(rz/2),size(p,2))];
ctrIdx = ctrIdx+ceil(rz/2);
end
% Backprojection - vectorized in (x,y), looping over theta
switch interp
case 'nearest neighbor'
for i=1:length(theta)
proj = p(:,i);
t = round(x*costheta(i) + y*sintheta(i));
img = img + proj(t+ctrIdx);
end
case 'linear'
if (use_original_matlab_code)
for i=1:length(theta)
proj = p(:,i);
t = x.*costheta(i) + y.*sintheta(i);
a = floor(t);
img = img + (t-a).*proj(a+1+ctrIdx) + (a+1-t).*proj(a+ctrIdx);
% imagesc(img);drawnow;
end
else
img = iradon_c( p, theta, x, y );
end
case {'spline','pchip','cubic','v5cubic'}
interp_method = sprintf('*%s',interp); % Add asterisk to assert
% even-spacing of taxis
for i=1:length(theta)
proj = p(:,i);
taxis = (1:size(p,1)) - ctrIdx;
t = x.*costheta(i) + y.*sintheta(i);
projContrib = interp1(taxis,proj,t(:),interp_method);
img = img + reshape(projContrib,N,N);
end
end
img = img*pi/(2*length(theta));
%%%
%%% Sub-Function: designFilter
%%%
function filt = designFilter(filter, len, d)
% Returns the Fourier Transform of the filter which will be
% used to filter the projections
%
% INPUT ARGS: filter - either the string specifying the filter
% len - the length of the projections
% d - the fraction of frequencies below the nyquist
% which we want to pass
%
% OUTPUT ARGS: filt - the filter to use on the projections
order = max(64,2^nextpow2(2*len));
% First create a ramp filter - go up to the next highest
% power of 2.
filt = 2*( 0:(order/2) )./order;
w = 2*pi*(0:size(filt,2)-1)/order; % frequency axis up to Nyquist
switch filter
case 'ram-lak'
% Do nothing
case 'shepp-logan'
% be careful not to divide by 0:
filt(2:end) = filt(2:end) .* (sin(w(2:end)/(2*d))./(w(2:end)/(2*d)));
case 'cosine'
filt(2:end) = filt(2:end) .* cos(w(2:end)/(2*d));
case 'hamming'
filt(2:end) = filt(2:end) .* (.54 + .46 * cos(w(2:end)/d));
case 'hann'
filt(2:end) = filt(2:end) .*(1+cos(w(2:end)./d)) / 2;
otherwise
eid = sprintf('Images:%s:invalidFilter',mfilename);
msg = 'Invalid filter selected.';
error(eid,'%s',msg);
end
filt(w>pi*d) = 0; % Crop the frequency response
filt = [filt' ; filt(end-1:-1:2)']; % Symmetry of the filter
%%%
%%% Sub-Function: parse_inputs
%%%
function [p,theta,filter,d,interp,N] = parse_inputs(varargin)
% Parse the input arguments and retun things
%
% Inputs: varargin - Cell array containing all of the actual inputs
%
% Outputs: p - Projection data
% theta - the angles at which the projections were taken
% filter - string specifying filter or the actual filter
% d - a scalar specifying normalized freq. at which to crop
% the frequency response of the filter
% interp - the type of interpolation to use
% N - The size of the reconstructed image
if nargin<2
eid = sprintf('Images:%s:tooFewInputs',mfilename);
msg = 'Invalid input arguments.';
error(eid,'%s',msg);
end
p = varargin{1};
theta = pi*varargin{2}/180;
% Default values
N = 0; % Size of the reconstructed image
d = 1; % Defaults to no cropping of filters frequency response
filter = 'ram-lak'; % The ramp filter is the default
interp = 'linear'; % default interpolation is linear
string_args = {'nearest neighbor', 'linear', 'spline', 'pchip', 'cubic', 'v5cubic', ...
'ram-lak','shepp-logan','cosine','hamming', 'hann'};
for i=3:nargin
arg = varargin{i};
if ischar(arg)
idx = strmatch(lower(arg),string_args);
if isempty(idx)
eid = sprintf('Images:%s:unknownInputString',mfilename);
msg = sprintf('Unknown input string: %s.', arg);
error(eid,'%s',msg);
elseif numel(idx) > 1
eid = sprintf('Images:%s:ambiguousInputString',mfilename);
msg = sprintf('Ambiguous input string: %s.', arg);
error(eid,'%s',msg);
elseif numel(idx) == 1
if idx <= 6 % It is the interpolation
interp = string_args{idx};
elseif (idx > 6) && (idx <= 11)
filter = string_args{idx};
end
end
elseif numel(arg)==1
if arg <=1
d = arg;
else
N = arg;
end
else
eid = sprintf('Images:%s:invalidInputParameters',mfilename);
msg = 'Invalid input parameters';
error(eid,'%s',msg);
end
end
% If the user didn't specify the size of the reconstruction, so
% deduce it from the length of projections
if N==0
N = 2*floor( size(p,1)/(2*sqrt(2)) ); % This doesn't always jive with RADON
end
% for empty theta, choose an intelligent default delta-theta
if isempty(theta)
theta = pi / size(p,2);
end
% If the user passed in delta-theta, build the vector of theta values
if numel(theta)==1
theta = (0:(size(p,2)-1))* theta;
end
if length(theta) ~= size(p,2)
eid = sprintf('Images:%s:thetaNotMatchingProjectionNumber',mfilename);
msg = 'THETA does not match the number of projections.';
error(eid,'%s',msg);
end
%this is the end
+381
View File
@@ -0,0 +1,381 @@
function [img,H] = iradonfast_v2(varargin)
%IRADON Inverse Radon transform.
% I = iradon(R,THETA) reconstructs the image I from projection data in the
% 2-D array R. The columns of R are parallel beam projection data.
% IRADON assumes that the center of rotation is the center point of the
% projections, which is defined as ceil(size(R,1)/2).
%
% THETA describes the angles (in degrees) at which the projections were
% taken. It can be either a vector containing the angles or a scalar
% specifying D_theta, the incremental angle between projections. If THETA
% is a vector, it must contain angles with equal spacing between them. If
% THETA is a scalar specifying D_theta, the projections are taken at
% angles THETA = m * D_theta; m = 0,1,2,...,size(R,2)-1. If the input is
% the empty matrix ([]), D_theta defaults to 180/size(R,2).
%
% IRADON uses the filtered backprojection algorithm to perform the inverse
% Radon transform. The filter is designed directly in the frequency
% domain and then multiplied by the FFT of the projections. The
% projections are zero-padded to a power of 2 before filtering to prevent
% spatial domain aliasing and to speed up the FFT.
%
% I = IRADON(R,THETA,INTERPOLATION,FILTER,FREQUENCY_SCALING,OUTPUT_SIZE)
% specifies parameters to use in the inverse Radon transform. You can
% specify any combination of the last four arguments. IRADON uses default
% values for any of these arguments that you omit.
%
% INTERPOLATION specifies the type of interpolation to use in the
% backprojection. The default is linear interpolation. Available methods
% are:
%
% 'nearest' - nearest neighbor interpolation
% 'linear' - linear interpolation (default)
% 'spline' - spline interpolation
% 'pchip' - shape-preserving piecewise cubic interpolation
% 'cubic' - same as 'pchip'
% 'v5cubic' - the cubic interpolation from MATLAB 5, which does not
% extrapolate and uses 'spline' if X is not equally spaced.
%
% FILTER specifies the filter to use for frequency domain filtering.
% FILTER is a string that specifies any of the following standard filters:
%
% 'Ram-Lak' The cropped Ram-Lak or ramp filter (default). The
% frequency response of this filter is |f|. Because this
% filter is sensitive to noise in the projections, one of
% the filters listed below may be preferable.
% 'Shepp-Logan' The Shepp-Logan filter multiplies the Ram-Lak filter by
% a sinc function.
% 'Cosine' The cosine filter multiplies the Ram-Lak filter by a
% cosine function.
% 'Hamming' The Hamming filter multiplies the Ram-Lak filter by a
% Hamming window.
% 'Hann' The Hann filter multiplies the Ram-Lak filter by a
% Hann window.
% 'parzen' The parzen filter multiplies the Ram-Lak filter by a
% Parzen window. Guizar - Nov 30 2010
%
% FREQUENCY_SCALING is a scalar in the range (0,1] that modifies the
% filter by rescaling its frequency axis. The default is 1. If
% FREQUENCY_SCALING is less than 1, the filter is compressed to fit into
% the frequency range [0,FREQUENCY_SCALING], in normalized frequencies;
% all frequencies above FREQUENCY_SCALING are set to 0.
%
% OUTPUT_SIZE is a scalar that specifies the number of rows and columns in
% the reconstructed image. If OUTPUT_SIZE is not specified, the size is
% determined from the length of the projections:
%
% OUTPUT_SIZE = 2*floor(size(R,1)/(2*sqrt(2)))
%
% If you specify OUTPUT_SIZE, IRADON reconstructs a smaller or larger
% portion of the image, but does not change the scaling of the data.
%
% If the projections were calculated with the RADON function, the
% reconstructed image may not be the same size as the original image.
%
% [I,H] = iradon(...) returns the frequency response of the filter in the
% vector H.
%
% Class Support
% -------------
% R can be double or single. All other numeric input arguments must be double.
% I has the same class as R. H is double.
%
% Example
% -------
% P = phantom(128);
% R = radon(P,0:179);
% I = iradon(R,0:179,'nearest','Hann');
% figure, imshow(P), figure, imshow(I);
%
% See also FAN2PARA, FANBEAM, IFANBEAM, PARA2FAN, PHANTOM, RADON.
%
% Copyright 1993-2004 The MathWorks, Inc.
% $Revision: 1.1 $ $Date: 2013/10/31 13:29:18 $
%
% References:
% A. C. Kak, Malcolm Slaney, "Principles of Computerized Tomographic
% Imaging", IEEE Press 1988.
%
% 'derivative' - Optional input argument to use the filter for input
% derivative of projections - Manuel Guizar - Nov 30 2010
%
% Weights for uneven angles
% If the angles are between 0 and 180 degrees it computes custom weights to
% the projections in order to allow for not equal angular sampling. Other
% angles are not considered because it would need significantly more
% checks.
% If you don't want to use this functionality, add 360 to your angles
% (theta+360)
% Manuel Guizar - Oct 10 2015
[p,theta,filter,d,interp,N,derivative] = parse_inputs(varargin{:});
% use Matlab or C-code for the linear interpolation
use_original_matlab_code = 0;
determine_weights = 1;
%%% Determine weights for uneven angular sampling %%%
if any(theta<0)
warning('There are some theta < 0 angles. Using constant angular sampling code.')
determine_weights = 0;
end
if any(theta>=pi)
warning('There are some theta >= 180 angles. Using constant angular sampling code.')
determine_weights = 0;
end
if any(diff(theta)==0)
warning('There are some repeated angles. Using constant angular sampling code.')
determine_weights = 0;
end
if determine_weights
[theta ind_sort] = sort(theta);
weights = theta*0;
weights(2:end-1) = - theta(1:end-2)/2 + theta(3:end)/2;
weights(1) = - (-pi + theta(end))/2 + theta(2)/2;
weights(end) = - theta(end-1)/2 + (pi+theta(1))/2;
p = p(:,ind_sort);
p = p.*repmat(weights,[size(p,1),1])/2;
else
p = p*pi/(2*length(theta));
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% Design the filter
len=size(p,1);
H = designFilter(filter, len, d, derivative);
p(length(H),1)=0; % Zero pad projections
% In the code below, I continuously reuse the array p so as to
% save memory. This makes it harder to read, but the comments
% explain what is going on.
p = fft(p); % p holds fft of projections
for i = 1:size(p,2)
p(:,i) = p(:,i).*H; % frequency domain filtering
end
p = real(ifft(p)); % p is the filtered projections
p(len+1:end,:) = []; % Truncate the filtered projections
% Define the x & y axes for the reconstructed image so that the origin
% (center) is in the spot which RADON would choose.
center = floor((N + 1)/2);
xleft = -center + 1;
x = (1:N) - 1 + xleft;
x = repmat(x, N, 1);
ytop = center - 1;
y = (N:-1:1).' - N + ytop;
y = repmat(y, 1, N);
if ((~strcmp(interp, 'linear')) || (use_original_matlab_code))
costheta = cos(theta);
sintheta = sin(theta);
img = zeros(N,class(p)); % Allocate memory for the image.
end
ctrIdx = ceil(len/2); % index of the center of the projections
% Zero pad the projections to size 1+2*ceil(N/sqrt(2)) if this
% quantity is greater than the length of the projections
imgDiag = 2*ceil(N/sqrt(2))+1; % largest distance through image.
if size(p,1) < imgDiag
rz = imgDiag - size(p,1); % how many rows of zeros
p = [zeros(ceil(rz/2),size(p,2)); p; zeros(floor(rz/2),size(p,2))];
ctrIdx = ctrIdx+ceil(rz/2);
end
% Backprojection - vectorized in (x,y), looping over theta
switch interp
case 'nearest neighbor'
for i=1:length(theta)
proj = p(:,i);
t = round(x*costheta(i) + y*sintheta(i));
img = img + proj(t+ctrIdx);
end
case 'linear'
if (use_original_matlab_code)
for i=1:length(theta)
proj = p(:,i);
t = x.*costheta(i) + y.*sintheta(i);
a = floor(t);
img = img + (t-a).*proj(a+1+ctrIdx) + (a+1-t).*proj(a+ctrIdx);
% imagesc(img);drawnow;
end
else
img = iradon_c( double(p), theta, x, y );
end
case {'spline','pchip','cubic','v5cubic'}
interp_method = sprintf('*%s',interp); % Add asterisk to assert
% even-spacing of taxis
for i=1:length(theta)
proj = p(:,i);
taxis = (1:size(p,1)) - ctrIdx;
t = x.*costheta(i) + y.*sintheta(i);
projContrib = interp1(taxis,proj,t(:),interp_method);
img = img + reshape(projContrib,N,N);
end
end
% img = img*pi/(2*length(theta));
%%%
%%% Sub-Function: designFilter
%%%
function filt = designFilter(filter, len, d, derivative)
% Returns the Fourier Transform of the filter which will be
% used to filter the projections
%
% INPUT ARGS: filter - either the string specifying the filter
% len - the length of the projections
% d - the fraction of frequencies below the nyquist
% which we want to pass
%
% OUTPUT ARGS: filt - the filter to use on the projections
order = max(64,2^nextpow2(2*len));
% First create a ramp filter - go up to the next highest
% power of 2.
if derivative
filt = 0*( 0:(order/2) )+1;
else
filt = 2*( 0:(order/2) )./order;
end
w = 2*pi*(0:size(filt,2)-1)/order; % frequency axis up to Nyquist
switch filter
case 'ram-lak'
% Do nothing
case 'shepp-logan'
% be careful not to divide by 0:
filt(2:end) = filt(2:end) .* (sin(w(2:end)/(2*d))./(w(2:end)/(2*d)));
case 'cosine'
filt(2:end) = filt(2:end) .* cos(w(2:end)/(2*d));
case 'hamming'
filt(2:end) = filt(2:end) .* (.54 + .46 * cos(w(2:end)/d));
case 'hann'
filt(2:end) = filt(2:end) .*(1+cos(w(2:end)./d)) / 2;
case 'parzen'
aux = parzenwin(round(2*size(filt,2)*d)-1)';
aux = aux(round(size(aux,2)/2):round(size(aux,2)));
filt(1:size(aux,2)) = filt(1:size(aux,2)).*aux;
filt(size(aux,2)+1:end) = 0;
otherwise
eid = sprintf('Images:%s:invalidFilter',mfilename);
msg = 'Invalid filter selected.';
error(eid,'%s',msg);
end
filt(w>pi*d) = 0; % Crop the frequency response
if derivative
filt = [filt' ; -filt(end-1:-1:2)']/(1i*pi); % Symmetry of the filter
else
filt = [filt' ; filt(end-1:-1:2)']; % Symmetry of the filter
end
%%%
%%% Sub-Function: parse_inputs
%%%
function [p,theta,filter,d,interp,N,derivative] = parse_inputs(varargin)
% Parse the input arguments and retun things
%
% Inputs: varargin - Cell array containing all of the actual inputs
%
% Outputs: p - Projection data
% theta - the angles at which the projections were taken
% filter - string specifying filter or the actual filter
% d - a scalar specifying normalized freq. at which to crop
% the frequency response of the filter
% interp - the type of interpolation to use
% N - The size of the reconstructed image
if nargin<2
eid = sprintf('Images:%s:tooFewInputs',mfilename);
msg = 'Invalid input arguments.';
error(eid,'%s',msg);
end
p = varargin{1};
theta = pi*varargin{2}/180;
% Default values
N = 0; % Size of the reconstructed image
d = 1; % Defaults to no cropping of filters frequency response
filter = 'ram-lak'; % The ramp filter is the default
interp = 'linear'; % default interpolation is linear
string_args = {'nearest neighbor', 'linear', 'spline', 'pchip', 'cubic', 'v5cubic', ...
'ram-lak','shepp-logan','cosine','hamming', 'hann','parzen','derivative'};
for i=3:nargin
arg = varargin{i};
if ischar(arg)
idx = strmatch(lower(arg),string_args);
if isempty(idx)
eid = sprintf('Images:%s:unknownInputString',mfilename);
msg = sprintf('Unknown input string: %s.', arg);
error(eid,'%s',msg);
elseif numel(idx) > 1
eid = sprintf('Images:%s:ambiguousInputString',mfilename);
msg = sprintf('Ambiguous input string: %s.', arg);
error(eid,'%s',msg);
elseif numel(idx) == 1
if idx <= 6 % It is the interpolation
interp = string_args{idx};
elseif (idx > 6) && (idx <= 12)
filter = string_args{idx};
elseif idx == 13
derivative = true; % Input is a derivative of sinogram
end
end
elseif numel(arg)==1
if arg <=1
d = arg;
else
N = arg;
end
else
eid = sprintf('Images:%s:invalidInputParameters',mfilename);
msg = 'Invalid input parameters';
error(eid,'%s',msg);
end
end
% If the user didn't specify the size of the reconstruction, so
% deduce it from the length of projections
if N==0
N = 2*floor( size(p,1)/(2*sqrt(2)) ); % This doesn't always jive with RADON
end
% for empty theta, choose an intelligent default delta-theta
if isempty(theta)
theta = pi / size(p,2);
end
% If the user passed in delta-theta, build the vector of theta values
if numel(theta)==1
theta = (0:(size(p,2)-1))* theta;
end
if length(theta) ~= size(p,2)
eid = sprintf('Images:%s:thetaNotMatchingProjectionNumber',mfilename);
msg = 'THETA does not match the number of projections.';
error(eid,'%s',msg);
end
if ~exist('derivative')
derivative = false;
end
+538
View File
@@ -0,0 +1,538 @@
function [img,H] = iradonfast_v3(varargin)
%IRADON Inverse Radon transform.
% I = iradon(R,THETA) reconstructs the image I from projection data in the
% 2-D array R. The columns of R are parallel beam projection data.
% IRADON assumes that the center of rotation is the center point of the
% projections, which is defined as ceil(size(R,1)/2).
%
% THETA describes the angles (in degrees) at which the projections were
% taken. It can be either a vector containing the angles or a scalar
% specifying D_theta, the incremental angle between projections. If THETA
% is a vector, it must contain angles with equal spacing between them. If
% THETA is a scalar specifying D_theta, the projections are taken at
% angles THETA = m * D_theta; m = 0,1,2,...,size(R,2)-1. If the input is
% the empty matrix ([]), D_theta defaults to 180/size(R,2).
%
% IRADON uses the filtered backprojection algorithm to perform the inverse
% Radon transform. The filter is designed directly in the frequency
% domain and then multiplied by the FFT of the projections. The
% projections are zero-padded to a power of 2 before filtering to prevent
% spatial domain aliasing and to speed up the FFT.
%
% I = IRADON(R,THETA,INTERPOLATION,FILTER,FREQUENCY_SCALING,OUTPUT_SIZE)
% specifies parameters to use in the inverse Radon transform. You can
% specify any combination of the last four arguments. IRADON uses default
% values for any of these arguments that you omit.
%
% INTERPOLATION specifies the type of interpolation to use in the
% backprojection. The default is linear interpolation. Available methods
% are:
%
% 'nearest' - nearest neighbor interpolation
% 'linear' - linear interpolation (default)
% 'spline' - spline interpolation
% 'pchip' - shape-preserving piecewise cubic interpolation
% 'cubic' - same as 'pchip'
% 'v5cubic' - the cubic interpolation from MATLAB 5, which does not
% extrapolate and uses 'spline' if X is not equally spaced.
%
% FILTER specifies the filter to use for frequency domain filtering.
% FILTER is a string that specifies any of the following standard filters:
%
% 'Ram-Lak' The cropped Ram-Lak or ramp filter (default). The
% frequency response of this filter is |f|. Because this
% filter is sensitive to noise in the projections, one of
% the filters listed below may be preferable.
% 'Shepp-Logan' The Shepp-Logan filter multiplies the Ram-Lak filter by
% a sinc function.
% 'Cosine' The cosine filter multiplies the Ram-Lak filter by a
% cosine function.
% 'Hamming' The Hamming filter multiplies the Ram-Lak filter by a
% Hamming window.
% 'Hann' The Hann filter multiplies the Ram-Lak filter by a
% Hann window.
% 'parzen' The parzen filter multiplies the Ram-Lak filter by a
% Parzen window. Guizar - Nov 30 2010
%
% FREQUENCY_SCALING is a scalar in the range (0,1] that modifies the
% filter by rescaling its frequency axis. The default is 1. If
% FREQUENCY_SCALING is less than 1, the filter is compressed to fit into
% the frequency range [0,FREQUENCY_SCALING], in normalized frequencies;
% all frequencies above FREQUENCY_SCALING are set to 0.
%
% OUTPUT_SIZE is a scalar that specifies the number of rows and columns in
% the reconstructed image. If OUTPUT_SIZE is not specified, the size is
% determined from the length of the projections:
%
% OUTPUT_SIZE = 2*floor(size(R,1)/(2*sqrt(2)))
%
% If you specify OUTPUT_SIZE, IRADON reconstructs a smaller or larger
% portion of the image, but does not change the scaling of the data.
%
% If the projections were calculated with the RADON function, the
% reconstructed image may not be the same size as the original image.
%
% [I,H] = iradon(...) returns the frequency response of the filter in the
% vector H.
%
% Class Support
% -------------
% R can be double or single. All other numeric input arguments must be double.
% I has the same class as R. H is double.
%
% Example
% -------
% P = phantom(128);
% R = radon(P,0:179);
% I = iradon(R,0:179,'nearest','Hann');
% figure, imshow(P), figure, imshow(I);
%
% See also FAN2PARA, FANBEAM, IFANBEAM, PARA2FAN, PHANTOM, RADON.
%
% Copyright 1993-2004 The MathWorks, Inc.
% $Revision: 1.1 $ $Date: 2013/10/31 13:29:18 $
%
% References:
% A. C. Kak, Malcolm Slaney, "Principles of Computerized Tomographic
% Imaging", IEEE Press 1988.
%
% 'derivative' - Optional input argument to use the filter for input
% derivative of projections - Manuel Guizar - Nov 30 2010
%
% Weights for uneven angles
% If the angles are between 0 and 180 degrees it computes custom weights to
% the projections in order to allow for not equal angular sampling. Other
% angles are not considered because it would need significantly more
% checks.
% If you don't want to use this functionality, add 360 to your angles
% (theta+360)
% Manuel Guizar - Oct 10 2015
import utils.*
[p,theta,filter,d,interp,N,derivative] = parse_inputs(varargin{:});
% use Matlab or C-code for the linear interpolation
use_original_matlab_code = 0;
determine_weights = 1;
%%% Determine weights for uneven angular sampling %%%
if any(theta<0)
warning('There are some theta < 0 angles. Using constant angular sampling code.')
determine_weights = 0;
end
if any(theta>=pi)
warning('There are some theta >= 180 angles. Using constant angular sampling code.')
determine_weights = 0;
end
if any(diff(theta)==0)
warning('There are some repeated angles. Using constant angular sampling code.')
determine_weights = 0;
end
if abs(max(theta)-min(theta)-pi) > 5*mean(diff(sort(theta)))
warning('Missing wedge is to large for weighting')
determine_weights = 0;
end
if verLessThan('matlab', '9.1')
error('Only matlab versions >= 2016b are supported')
end
if determine_weights
[theta,ind_sort] = sort(theta);
weights = theta*0;
weights(2:end-1) = - theta(1:end-2)/2 + theta(3:end)/2;
weights(1) = - (-pi + theta(end))/2 + theta(2)/2;
weights(end) = - theta(end-1)/2 + (pi+theta(1))/2;
p = p(:,ind_sort,:);
p = bsxfun(@times, p, weights/2);
else
p = p*pi/(2*length(theta));
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
p = single(p); % single will make it ~2x faster
% Design the filter
len=size(p,1);
H = designFilter(filter, len, d, derivative);
p(length(H),1)=0; % Zero pad projections
% In the code below, I continuously reuse the array p so as to
% save memory. This makes it harder to read, but the comments
% explain what is going on.
p = fft(p); % p holds fft of projections
p = p.*H; % frequency domain filtering
p = real(ifft(p)); % p is the filtered projections
p(len+1:end,:,:) = []; % Truncate the filtered projections
% Define the x & y axes for the reconstructed image so that the origin
% (center) is in the spot which RADON would choose.
center = floor((N + 1)/2);
xleft = -center + 1;
x = (1:N) - 1 + xleft;
x = repmat(x, N, 1);
ytop = center - 1;
y = (N:-1:1).' - N + ytop;
y = repmat(y, 1, N);
if ((~strcmp(interp, 'linear')) || (use_original_matlab_code))
costheta = cos(theta);
sintheta = sin(theta);
img = zeros(N,class(p)); % Allocate memory for the image.
end
ctrIdx = ceil(len/2); % index of the center of the projections
% Zero pad the projections to size 1+2*ceil(N/sqrt(2)) if this
% quantity is greater than the length of the projections
imgDiag = 2*ceil(N/sqrt(2))+1; % largest distance through image.
if size(p,1) < imgDiag
rz = imgDiag - size(p,1); % how many rows of zeros
p = [zeros(ceil(rz/2),size(p,2),size(p,3),class(p)); p; zeros(floor(rz/2),size(p,2),size(p,3), class(p))];
ctrIdx = ctrIdx+ceil(rz/2);
end
Nlayers = size(p,3);
% Backprojection - vectorized in (x,y), looping over theta
switch interp
case 'nearest neighbor'
for i=1:length(theta)
proj = p(:,i);
t = round(x*costheta(i) + y*sintheta(i));
img = img + proj(t+ctrIdx);
end
case 'linear'
if (use_original_matlab_code)
for i=1:length(theta)
proj = p(:,i);
t = x.*costheta(i) + y.*sintheta(i);
a = floor(t);
img = img + (t-a).*proj(a+1+ctrIdx) + (a+1-t).*proj(a+ctrIdx);
end
else
if gpuDeviceCount
try
img = iradon_cuda( p, theta, x, y );
catch err
warning('GPU failed, running slower CPU version\n Error message: %s', err.message)
end
end
if ~exist('img', 'var')
img = zeros(N,N,Nlayers, 'single');
p = double(p);
for i = 1:size(p,3)
progressbar(i,Nlayers)
img(:,:,i) = iradon_c( p(:,:,i), theta, x, y );
end
end
end
case {'spline','pchip','cubic','v5cubic'}
interp_method = sprintf('*%s',interp); % Add asterisk to assert
% even-spacing of taxis
for i=1:length(theta)
proj = p(:,i);
taxis = (1:size(p,1)) - ctrIdx;
t = x.*costheta(i) + y.*sintheta(i);
projContrib = interp1(taxis,proj,t(:),interp_method);
img = img + reshape(projContrib,N,N);
end
end
% img = img*pi/(2*length(theta));
end
%%%
%%% Sub-Function: designFilter
%%%
function filt = designFilter(filter, len, d, derivative)
% Returns the Fourier Transform of the filter which will be
% used to filter the projections
%
% INPUT ARGS: filter - either the string specifying the filter
% len - the length of the projections
% d - the fraction of frequencies below the nyquist
% which we want to pass
%
% OUTPUT ARGS: filt - the filter to use on the projections
order = max(64,2^nextpow2(2*len));
% First create a ramp filter - go up to the next highest
% power of 2.
if derivative
filt = 0*( 0:(order/2) )+1;
else
filt = 2*( 0:(order/2) )./order;
end
w = 2*pi*(0:size(filt,2)-1)/order; % frequency axis up to Nyquist
switch filter
case 'ram-lak'
% Do nothing
case 'shepp-logan'
% be careful not to divide by 0:
filt(2:end) = filt(2:end) .* (sin(w(2:end)/(2*d))./(w(2:end)/(2*d)));
case 'cosine'
filt(2:end) = filt(2:end) .* cos(w(2:end)/(2*d));
case 'hamming'
filt(2:end) = filt(2:end) .* (.54 + .46 * cos(w(2:end)/d));
case 'hann'
filt(2:end) = filt(2:end) .*(1+cos(w(2:end)./d)) / 2;
case 'parzen'
aux = parzenwin(round(2*size(filt,2)*d)-1)';
aux = aux(round(size(aux,2)/2):round(size(aux,2)));
filt(1:size(aux,2)) = filt(1:size(aux,2)).*aux;
filt(size(aux,2)+1:end) = 0;
otherwise
eid = sprintf('Images:%s:invalidFilter',mfilename);
msg = 'Invalid filter selected.';
error(eid,'%s',msg);
end
filt(w>pi*d) = 0; % Crop the frequency response
if derivative
filt = [filt' ; -filt(end-1:-1:2)']/(1i*pi); % Symmetry of the filter
else
filt = [filt' ; filt(end-1:-1:2)']; % Symmetry of the filter
end
end
%%%
%%% Sub-Function: parse_inputs
%%%
function [p,theta,filter,d,interp,N,derivative] = parse_inputs(varargin)
% Parse the input arguments and retun things
%
% Inputs: varargin - Cell array containing all of the actual inputs
%
% Outputs: p - Projection data
% theta - the angles at which the projections were taken
% filter - string specifying filter or the actual filter
% d - a scalar specifying normalized freq. at which to crop
% the frequency response of the filter
% interp - the type of interpolation to use
% N - The size of the reconstructed image
if nargin<2
eid = sprintf('Images:%s:tooFewInputs',mfilename);
msg = 'Invalid input arguments.';
error(eid,'%s',msg);
end
p = varargin{1};
theta = pi*varargin{2}/180;
% Default values
N = 0; % Size of the reconstructed image
d = 1; % Defaults to no cropping of filters frequency response
filter = 'ram-lak'; % The ramp filter is the default
interp = 'linear'; % default interpolation is linear
string_args = {'nearest neighbor', 'linear', 'spline', 'pchip', 'cubic', 'v5cubic', ...
'ram-lak','shepp-logan','cosine','hamming', 'hann','parzen','derivative'};
for i=3:nargin
arg = varargin{i};
if ischar(arg)
idx = strmatch(lower(arg),string_args);
if isempty(idx)
eid = sprintf('Images:%s:unknownInputString',mfilename);
msg = sprintf('Unknown input string: %s.', arg);
error(eid,'%s',msg);
elseif numel(idx) > 1
eid = sprintf('Images:%s:ambiguousInputString',mfilename);
msg = sprintf('Ambiguous input string: %s.', arg);
error(eid,'%s',msg);
elseif numel(idx) == 1
if idx <= 6 % It is the interpolation
interp = string_args{idx};
elseif (idx > 6) && (idx <= 12)
filter = string_args{idx};
elseif idx == 13
derivative = true; % Input is a derivative of sinogram
end
end
elseif numel(arg)==1
if arg <=1
d = arg;
else
N = arg;
end
else
eid = sprintf('Images:%s:invalidInputParameters',mfilename);
msg = 'Invalid input parameters';
error(eid,'%s',msg);
end
end
% If the user didn't specify the size of the reconstruction, so
% deduce it from the length of projections
if N==0
N = 2*floor( size(p,1)/(2*sqrt(2)) ); % This doesn't always jive with RADON
end
% for empty theta, choose an intelligent default delta-theta
if isempty(theta)
theta = pi / size(p,2);
end
% If the user passed in delta-theta, build the vector of theta values
if numel(theta)==1
theta = (0:(size(p,2)-1))* theta;
end
if length(theta) ~= size(p,2)
eid = sprintf('Images:%s:thetaNotMatchingProjectionNumber',mfilename);
msg = 'THETA does not match the number of projections.';
error(eid,'%s',msg);
end
if ~exist('derivative')
derivative = false;
end
end
function img = iradon_cuda( sinogram_full, theta, x, y )
%% preprocess data for the ASTRA toolbox wrapper
import utils.*
[Wsin, Nproj, Nlayers] = size(sinogram_full);
[Nx, Ny] = size(x);
if any(size(sinogram_full) > 4096)
error('Maximal size of CUDA 3D texture is 4096x4096x4096, size of sinogram is %ix%ix%i', size(sinogram_full))
end
%% create data and geometry
Nangles = length(theta);
assert(Nproj == Nangles, 'Wrong input size')
vectors_all = zeros(Nangles, 12);
for i = 1:Nangles
% ray direction
vectors_all(i,1) = sin(theta(i));
vectors_all(i,2) = -cos(theta(i));
vectors_all(i,3) = 0;
vectors_all(i,1:3) = vectors_all(i,1:3);
% center of detector
vectors_all(i,4:6) = 0;
% vector from detector pixel (0,0) to (0,1)
vectors_all(i,7) = cos(theta(i));
vectors_all(i,8) = sin(theta(i));
vectors_all(i,9) = 0;
% vector from detector pixel (0,0) to (1,0)
vectors_all(i,10) = 0;
vectors_all(i,11) = 0;
vectors_all(i,12) = 1;
end
%% astra settings
cfg.iVolX = Nx;
cfg.iVolY = Ny;
cfg.iVolZ = Nlayers;
cfg.iProjAngles = Nangles;
cfg.iProjU = Wsin;
cfg.iProjV = Nlayers;
cfg.iRaysPerDet = 1;
cfg.iRaysPerDetDim = 1;
cfg.iRaysPerVoxelDim = 1;
%% call ASTRA wrapper
gpu = gpuDevice;
AvailableMemory = gpu.AvailableMemory;
% required memory is 2x dataset size, max data size allowed by GPU is 1024MB
% max number of GPU projections is 1024 (limit in ASTRA code, can be fixed ... )
Nangular_slices = ceil(Nangles/1024); % limitation in the ASTRA code
Nslices = ceil(numel(sinogram_full)*8 / min(1024e6, AvailableMemory));
if Nangular_slices > 1 || Nslices > 1
if Nslices > 1
fprintf('Dataset does not fit to GPU memory => autosplitting\nFree memory: %iMB\tDataset size: %iMB\n\n', ceil(AvailableMemory/1e6), ceil(numel(sinogram_full)*4/1e6))
else
fprintf('Number of angles axceeded limit 1024 => autosplitting\nFree memory: %iMB\tDataset size: %iMB\n\n', ceil(AvailableMemory/1e6), ceil(numel(sinogram_full)*4/1e6))
end
img = zeros([Nx, Ny, Nlayers], 'single');
end
for j = 1:Nangular_slices % split in the angular space
ind_angle = 1+(j-1)*ceil(Nangles/Nangular_slices):min(Nangles, j*ceil(Nangles/Nangular_slices));
vectors = vectors_all(ind_angle,:);
%% subpixel shift correction to make it consistent with the matlab iradon code
cfg.iProjAngles = length(ind_angle);
% split the sinogram (avoid copying if possible)
if Nangular_slices > 1
sinogram = sinogram_full(:,ind_angle,:);
else
sinogram = sinogram_full;
end
if Nslices == 1
%%%% apply geometry correction to shift reconstruction into center %%%%%%%%%%%%%%%%%%%%%%%%%%
vectors(:,4:6) = vectors(:,4:6) -(vectors(:,10:12)*cfg.iProjV/2+vectors(:,7:9)*cfg.iProjU/2);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% reshape the inputs for ASTRA
sinogram = reshape(sinogram,[cfg.iProjU,cfg.iProjV,cfg.iProjAngles]);
sinogram = gpuArray(single(sinogram));
%% ASTRA WRAPPER
vol = astra.iradon_gpu_wrapper(sinogram, cfg, vectors);
vol = gather(vol); % backprojection
if Nangular_slices > 1
img = img + vol;
else
img = vol;
end
else % low memory case (a bit slower) => split to several chunks rotation axis
for i = 1:Nslices
progressbar(i,Nslices+1)
ind = 1+(i-1)*ceil(Nlayers/Nslices):min(Nlayers, i*ceil(Nlayers/Nslices));
p_tmp = sinogram(:,:,ind);
cfg.iVolZ = length(ind);
cfg.iProjV = length(ind);
p_tmp = reshape(p_tmp,[cfg.iProjU,cfg.iProjV,cfg.iProjAngles]);
p_tmp = gpuArray(single(p_tmp));
vectors_all_tmp = vectors;
%%%% apply geometry correction to shift reconstruction into center
vectors_all_tmp(:,4:6) = vectors_all_tmp(:,4:6) -(vectors_all_tmp(:,10:12)*cfg.iProjV/2+vectors_all_tmp(:,7:9)*cfg.iProjU/2);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% ASTRA WRAPPER
vol = astra.iradon_gpu_wrapper(p_tmp, cfg, vectors_all_tmp);
vol = gather(vol); % gather from GPU takes 30% of time !!!
if Nangular_slices > 1
img(:,:,ind) = img(:,:,ind) + vol; % adding up to the array is also pretty slow
else
img(:,:,ind) = vol;
end
end
progressbar(i,Nslices)
end
end
% rotate as the output for Matlab
img = rot90(img, 1);
end
+57
View File
@@ -0,0 +1,57 @@
% tomogram = iradonfast_v3_split(p, theta, varargin)
% FUNCTION wrapper around iradonfast_v3 that automatically splits the data into
% smaller blocks to avoid too large memory allocation
% Inputs / Outputs: same as for iradonfast_v3
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 tomogram = iradonfast_v3_split(p, theta, varargin)
[Nlayers,tomo_size,Nangles] = size(p) ;
block_size = ceil(2e9/(tomo_size*Nangles*4)); % split the task into ~2GB blocks
Nblocks = ceil(Nlayers / block_size );
% preallocate array to store results
tomogram = zeros(tomo_size, tomo_size, Nlayers, 'single');
for ii = 1:Nblocks
ind = 1+(ii-1)*block_size:min(ii*block_size, Nlayers);
tomogram(:,:,ind) =tomo.iradonfast_v3(permute(p(ind,:,:),[2,3,1]), theta, varargin{:}); % Calculate slice
end
end
+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
+59
View File
@@ -0,0 +1,59 @@
% FUNCTION full_array = add_to_3D(full_array, small_array, position)
% add one small 3D block into large 3D array
% Inputs:
% full_array
% small_array
% position - offset from (1,1,1) coordinate in pixels
% *-----------------------------------------------------------------------*
% |                                                                       |
% |  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 full_array = add_to_3D(full_array, small_array, position)
position = round(position);
N_f = size(full_array);
N_s = size(small_array);
for i = 1:ndims(full_array)
ind_f{i} = unique(min(N_f(i),max(1,position(i)+(1:N_s(i)))));
ind_s{i} = unique(min(N_s(i),max(1,ind_f{i}-position(i))));
end
full_array(ind_f{:}) = full_array(ind_f{:}) + small_array(ind_s{:});
end
+80
View File
@@ -0,0 +1,80 @@
% IMSHIFT_GENERIC auxiliar function to be performed bu block_fun on GPU
% it applies imshift_fft on the provided image that was first upsampled
% to Npix (if needed) and cropped to region ROI
% after shifting, the image is downsampled by the chosen interpolation
% method "intep_method" that is more accurate than simple binning
%
% img = imshift_generic(img, shift, Npix, affine_matrix, smooth, ROI, downsample, intep_method, interp_sign)
%
% Inputs:
% **img 2D stacked image
% **shift Nx2 vector of shifts applied on the image
% **Npix 2x1 int, size of the img to be upsampled before shift, Npix = [] -> no upsampling
% **affine_matrix affine metrix ! not implemented yet!
% **smooth how many pixels around edges will be smoothed before shifting the array
% **ROI cell array, used to crop the array to smaller size
% **downsample downsample factor , 1 == no downsampling
% **intep_method interpolation method: linear, fft
% **interp_sign sign used for subpixel shifts of the dataset, +1 for unwrapped phase, -1 for phase differene
% *returns*
% ++img 2D stacked image
function img = imshift_generic(img, shift, Npix, affine_matrix, smooth, ROI, downsample, intep_method, interp_sign)
if nargin < 9
interp_sign = 0;
end
import math.*
import utils.*
if isa(img, 'uint8') || (isa(img, 'gpuArray') && strcmpi(classUnderlying(img),'uint8'))
img = single(img) / 255; % assume that the provided image is only compressed into uint8
end
% if needed upsample to the size of the projection
if ~isempty(Npix) && any(Npix(1:2) ~= [size(img,1),size(img,2)])
switch intep_method
case 'linear', img = utils.interpolate_linear(img, Npix);
case 'fft', img = utils.interpolateFT(img, Npix);
end
end
isReal = isreal(img);
if any(shift(:) ~=0 )
smooth_axis = 3-find(any(shift ~= 0));
img = smooth_edges(img, smooth, smooth_axis);
if ~ismatrix(img)
switch intep_method
case 'linear'
img = utils.imshift_linear(img,shift); % interpolation of the weights does not need such precision
case 'fft'
%%% APPLY SHIFT USING FFT -> periodic boundary
img = imshift_fft(img, shift);
end
end
end
% crop the FOV after shift and before "downsample"
if ~isempty(ROI)
img = img(ROI{:},:); % crop to smaller ROI if provided
% apply crop after imshift_fft
end
Np = size(img);
% perform interpolation instead of downsample , it provides more accurate results
if downsample > 1
img = utils.imgaussfilt3_conv(img,[downsample,downsample,0]);
% correct for boundary effects of the convolution based smoothing
img = img ./ utils.imgaussfilt3_conv(ones(Np(1:2), 'like', img),[downsample,downsample,0]);
switch intep_method
case 'linear', img = utils.interpolate_linear(img,ceil(Np(1:2)/downsample/2)*2); % interpolation of the weights does not need such precision
case 'fft', img = utils.interpolateFT_centered(utils.smooth_edges(img, 2*downsample),ceil(Np(1:2)/downsample/2)*2, interp_sign); % accurate interpolation using FFT
end
end
if isReal; img = real(img); end
end
+102
View File
@@ -0,0 +1,102 @@
/* iradon_c.c:
sub-routine of a modified iradon.m, i.e., the time consuming loop
of this routine in C.
Compilation from Matlab:
mex iradon_c.c
maybe a tiny bit faster code is generated by
mex -O COPTIMFLAGS='-O2' LDOPTIMFLAGS='-O2' iradon_c.c
Usage from Matlab:
iradon_c( p, theta, x, y );
*/
#include "mex.h"
#include <math.h>
void mexFunction(int nlhs, mxArray *plhs[],
int nrhs, const mxArray *prhs[])
{
int dim1, dim2, dim1_data;
int i, no_of_angles, ctrIdx;
double *data, *theta, *xorg, *yorg, *imgorg;
/* Check for proper number of arguments. */
if (nrhs != 4)
mexErrMsgTxt("Four input arguments required: Data, theta, x and y.");
else if (nlhs != 1)
mexErrMsgTxt("One output argument has to be specified.");
/* Input must be double. */
if (mxIsDouble(prhs[0]) != 1)
mexErrMsgTxt("Input 1 (data) must be of double precision floating point type.");
if (mxIsDouble(prhs[1]) != 1)
mexErrMsgTxt("Input 2 (theta) must be of double precision floating point type.");
if (mxIsDouble(prhs[2]) != 1)
mexErrMsgTxt("Input 3 (x) must be of double precision floating point type.");
if (mxIsDouble(prhs[3]) != 1)
mexErrMsgTxt("Input 4 (y) must be of double precision floating point type.");
/* get number of different angles */
if (mxGetM(prhs[1]) == 1) {
no_of_angles = mxGetN(prhs[1]);
} else {
if (mxGetN(prhs[1]) == 1) {
no_of_angles = mxGetM(prhs[1]);
} else {
mexErrMsgTxt("Theta has to be a vector, not an array.");
}
}
/* get dimensions and check that they are consistent */
dim1 = mxGetM(prhs[2]);
dim2 = mxGetN(prhs[2]);
if ((dim1 != mxGetM(prhs[3])) || (dim1 != mxGetN(prhs[3])))
mexErrMsgTxt("x and y must have the same dimensions.");
if (no_of_angles > mxGetN(prhs[0]))
mexErrMsgTxt("The second dimension of data must be at least as large as the number of theta angles.");
dim1_data = mxGetM(prhs[0]);
/* allocate memory for image data, to be returned */
plhs[0] =
mxCreateNumericMatrix(dim1, dim2, mxDOUBLE_CLASS, mxREAL);
if (plhs[0] == NULL)
mexErrMsgTxt("Could not allocate memory for return data.");
/* get pointers to input and output data */
data = mxGetPr(prhs[0]);
theta = mxGetPr(prhs[1]);
xorg = mxGetPr(prhs[2]);
yorg = mxGetPr(prhs[3]);
imgorg = mxGetPr(plhs[0]);
/* index to image center */
ctrIdx = ceil(mxGetM(prhs[0]) / 2);
for (i=0; i < no_of_angles; i++) {
double *x = xorg;
double *y = yorg;
double *img = imgorg;
/* temporary variables */
double costheta = cos(*theta);
double sintheta = sin(*theta);
double *proj = &data[i*dim1_data +1];
int j;
for (j=0; j < dim2; j++) {
int k;
for (k=0; k < dim1; k++) {
double t = *x * costheta + *y * sintheta;
int a = floor(t);
*img += (t-a) * proj[a+ctrIdx] + (a+1-t) * proj[a+ctrIdx-1];
x++;
y++;
img++;
}
}
theta++;
}
return;
}
+57
View File
@@ -0,0 +1,57 @@
% FUNCTION full_array = set_to_3D(full_array, small_array, position)
% add one small 3D block into large 3D array
% Inputs:
% full_array
% small_array
% position - [3 x 1] offset from (1,1,1) coordinate in pixels
% *-----------------------------------------------------------------------*
% |                                                                       |
% |  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 full_array = set_to_3D(full_array, small_array, position)
position = round(position);
N_f = size(full_array);
N_s = size(small_array);
for i = 1:3
ind_f{i} = unique(min(N_f(i),max(1,position(i)+(1:N_s(i)))));
end
full_array(ind_f{:}) = small_array;
end
+120
View File
@@ -0,0 +1,120 @@
% RADIATION_DAMAGE_ESTIMATION Plot SVD filtered curved of the vertical fluctuations
% tomo invariant to shown if there was radiation damage
%
% radiation_damage_estimation(object, par, varargin)
%
% Inputs:
% **object - complex valued projections
% **par - tomography paramter structure
% **varargin - see the code
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 radiation_damage_estimation(object, par, varargin)
import plotting.*
import math.*
parser = inputParser;
parser.addParameter('N_SVD_modes', 1 , @isnumeric )
parser.addParameter('smoothing', 15 , @isnumeric )
parser.addParameter('logscale', true , @islogical ) % threshold for mask estimation
parser.addParameter('invariant', 'derivative' , @isstr ) % threshold for mask estimation
parser.addParameter('vert_range', [] , @isnumeric ) % threshold for mask estimation
parser.parse(varargin{:})
r = parser.Results;
if ~isempty(r.vert_range)
object = object((max(1,r.vert_range(1)):min(end,r.vert_range(end))),:,:);
end
switch r.invariant
case 'phase'
%% standard vertical mass fluctuation
phase_diff = tomo.get_phase_gradient(object, 2,0.5);
phase = -tomo.unwrap2D_fft(phase_diff, 2, par.air_gap);
invariant = max(0,squeeze(sum(phase,2)));
case 'derivative'
%% vertical derivative fluctuation
phase_diff_vert = math.get_phase_gradient_1D(object, 1,1);
invariant = squeeze(sum(phase_diff_vert,2));
end
% smoothing in the time domain to remove outliers
mass_filt = medfilt2(invariant,[1,floor(r.smoothing/2)*2+1], 'symmetric');
figure(5);
subplot(3,1,1)
imagesc(invariant)
axis tight xy
colormap(franzmap)
caxis(sp_quantile(invariant, [0.01, 0.99], 10))
title(sprintf('Vertical mass derivative S%05d - S%05d',par.scanstomo(1),par.scanstomo(end)))
xlabel('Projection number')
grid on
subplot(3,1,2)
Navg_slices= 10; % average over last 10 slices
mass_filt_resid = mass_filt - median(mass_filt(:,end-Navg_slices:end),2);
imagesc(mass_filt_resid)
axis tight xy
if r.logscale
set(gca, 'xscale', 'log')
end
colormap bone
caxis(sp_quantile(mass_filt_resid, [0.01, 0.99], 10))
title('Filtered change with respect to median')
xlabel('Projection number')
grid on
subplot(3,1,3)
[U,S,V] = svd(mass_filt);
S = S / sqrt(sum(diag(S.^2)));
plot( V(:,[2:r.N_SVD_modes+1]) * S(2:r.N_SVD_modes+1,2:r.N_SVD_modes+1) )
if r.logscale
set(gca, 'xscale', 'log')
end
title(sprintf('PCA decomposition - shows changes in the sample, Power=%2.3g%%', 100*sum(diag(S(2:end,2:end)).^2)))
axis tight
xlabel('Aprox projection number')
grid on
file_png = fullfile(par.output_folder,'Radiation_damage_curve.png');
disp(['Saving ' file_png])
end
+99
View File
@@ -0,0 +1,99 @@
% REMOVE_PROJECTIONS remove projections and adjust the other relevant paramaters
%
% [stack_object,theta,total_shift,par] = remove_projections(stack_object,theta,total_shift,par, which_remove, plot_fnct = @(x)x)
%
% Inputs:
% **stack_object measured projections
% **theta measured angles
% **total_shift Nx2 vector or projection shifts
% **par parameter structure
% **which_remove indices or logical array denoting the projection to be removed
% **plot_fnct = @(x)x function to be used for plotting, default == @(x)x
% *returns*
% ++stack_object,theta,total_shift,par - inputs after projection 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 [stack_object,theta,total_shift,par] = remove_projections(stack_object,theta,total_shift,par, which_remove, plot_fnct, object_ROI)
if nargin < 6
plot_fnct = @(x)x;
end
if islogical(which_remove)
which_remove = find(which_remove);
end
if isempty(which_remove)
return
end
utils.verbose(1, 'Removing %i/%i projections', length(which_remove), par.num_proj)
[Nx,Ny,~] = size(stack_object);
title_extra = {};
for ii = 1:length(which_remove)
title_extra{end+1} = sprintf(' N residua: %i',par.nresidua_per_frame(which_remove(ii)));
end
%%% Getting rid of unwanted projections %%%
if ~isempty(which_remove)
tomo.show_projections(stack_object(:,:,which_remove), theta(which_remove), par, ...
'title', 'Projection to be removed','plot_residua', true, 'title_extra', title_extra, 'fnct', plot_fnct, ...
'rectangle_pos', [object_ROI{2}(1), object_ROI{2}(end), object_ROI{1}(1), object_ROI{1}(end)])
if strcmpi(input(sprintf('Do you want remove %i missing/wrong projections and keep going (y/N)?',length(which_remove)),'s'),'y')
disp('Removing missing/wrong projections. stack_object, scanstomo, theta and num_proj are modified')
stack_object(:,:,which_remove) = [];
theta(which_remove)=[];
total_shift(which_remove,:) = [];
par.scanstomo(which_remove)=[];
try par.energy(which_remove,:) = []; end
try par.nresidua_per_frame(which_remove) = []; end
try par.subtomos(which_remove) = []; end
par.num_proj = length(par.scanstomo);
disp('Done')
end
end
end
+105
View File
@@ -0,0 +1,105 @@
% SAVE_AS_TIFF simple function to save data as tiff images
%
% save_as_tiff(rec, param)
%
% Inputs:
% **rec reconstructed volume
% **p parameter structure
% **extra_string extra string added to the saved name for example name of the reconstruction method
% Parameters
% params.scans_string = 'name'
% params.save_as_stack = false;
% params.tiff_compression = 'none';
% params.tiff_folder_name = 'folder';
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 save_as_tiff(rec, p, extra_string)
import utils.*
tiff_folder_name = fullfile(p.output_folder,p.tiff_subfolder_name);
if ~exist(tiff_folder_name, 'dir')
mkdir(tiff_folder_name);
elseif ~isfield(p, 'force_overwrite') || p.force_overwrite == false
display(['Folder exists: ' tiff_folder_name])
userans = input(['Do you want to overwrite TIFFs in this folder (Y/n)? '],'s');
if ~strcmpi(userans,'n')
disp('Overwritting');
else
error('Writting of TIFFs aborted')
end
end
cutoff = [min(rec(:)),max(rec(:))];
rec = (rec-cutoff(1))/(cutoff(2)-cutoff(1));
rec_uint16 = uint16( (2^16-1) *rec);
verbose(0,['Saving to:', tiff_folder_name '/' p.name_prefix '_' p.scans_string '_' ...
extra_string '.tif'])
Nlayers = size(rec,3);
for j=1:Nlayers
progressbar(j, Nlayers)
if p.save_as_stack
image_filename_with_path = [tiff_folder_name '/' p.name_prefix '_' p.scans_string '_' ...
p.filter_type '_freqscl_' sprintf('%0.2f',p.freq_scale) '.tif'];
if j == 1
imwrite(rec_uint16,image_filename_with_path,'tiff',...
'Compression',p.tiff_compression);
else
imwrite(rec_uint16,image_filename_with_path,'tiff',...
'Compression',p.tiff_compression,'WriteMode','append');
end
else
imwrite(rec_uint16(:,:,j),[tiff_folder_name '/' p.name_prefix '_' p.scans_string '_' extra_string '_' sprintf('%04d',j) '.tif'], ...
'tiff','Compression',p.tiff_compression);
end
end
fid=fopen([tiff_folder_name '_cutoffs.txt'],'w');
fprintf(fid, '# low_cutoff = %e\n', cutoff(1));
fprintf(fid, '# high_cutoff = %e\n', cutoff(2));
fprintf(fid, '# factor = %e\n', p.factor);
fprintf(fid, '# pixel size = %e\n', p.pixel_size);
fprintf(fid, '# factor_edensity = %e\n', p.factor_edensity);
fprintf(fid, '# Conversion formula\n');
fprintf(fid, '# im_delta_from_tiff = im_tiff*(high_cutoff-low_cutoff)/(2^16-1) + low_cutoff;\n');
fprintf(fid, '# im_edensity_from_tiff = im_delta_from_tiff*factor_edensity;\n');
fclose(fid);
end
+75
View File
@@ -0,0 +1,75 @@
% SAVE_TOMOGRAM simple function to save data as tiff images
% save current tomogram
%
% save_tomogram(tomogram, par, type, circulo,theta, extra_string = '')
%
% Inputs:
% **tomogram - (3D array) saved volume
% **par - tomo parameter structure
% **type - 'delta', 'beta'
% **circulo, theta - other inputs to be saved
% **extra_string - extra string in the name, default == ''
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 save_tomogram(tomogram, par, type, circulo,theta, extra_string)
% save current tomogram
% type: 'delta', 'beta', ''
if nargin < 6
extra_string = '';
end
if ~exist(par.output_folder,'dir')
mkdir(par.output_folder);
end
saveprojfile = fullfile(sprintf('%s/tomogram_%s_%s_%s.mat',par.output_folder,type,par.scans_string, extra_string));
switch type
case 'delta'
tomogram_delta = tomogram;
case 'beta'
tomogram_beta = tomogram;
otherwise
error('Allowed types: "delta", "beta"')
end
utils.verbose(0, 'Saving tomogram ....')
utils.savefast_safe(saveprojfile,['tomogram_',type],'par','circulo', 'theta', par.force_overwrite);
utils.verbose(0, 'Saving done')
end
+71
View File
@@ -0,0 +1,71 @@
% SET_TO_ARRAY simplified wrapper around CPU-based multithread mex function "add_to_3D_volume_mex"
%
% set_to_array(full_object, object_block, offset, add_values = false)
%
% Equivalent but faster to matlab command
% full_object(:,:, offset + 1:size(object_block,3)) = full_object(:,:,offset + 1:size(object_block,3)) + object_block
%
% Inputs
% **full_object - (3D array) array to be added to, note that directly the provided array will be modified, without copying by matlab
% **object_block - (2D/3D array) values to be added to the main structure, can be array or a sharememory class @shm
% **offset -(uint scalar) starting position along the 3rd-axis, assuming that full block is loaded
% **add_values - (bool) if true, the values will be added otherwise overwritten
% *returns*:
% ++full_object or none - (values are written directly to full_object if MEX access is used)
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 full_object = set_to_array(full_object, object_block, offset, add_values)
if nargin < 4
add_values= false; % add values instead of rewritting
end
if isa(object_block,'shm')
[s,object_block] = object_block.attach();
end
% write back to the full array stored in RAM
positions = zeros(size(object_block,3),2);
indices = int32(1:size(object_block,3)) + int32(offset(1));
% use a MEX code to speed it up
full_object = utils.add_to_3D_projection(object_block, full_object,positions,indices, add_values);
if exist('s','var')
s.free;
end
end
+173
View File
@@ -0,0 +1,173 @@
% SHOW_PROJECTIONS show playable animation of projections
% show_projections(img_stack, theta, par, varargin)
%
% Inputs:
% **img_stack - 3D array of projections
% **theta - angles corresponding to the frames
% **par - parameter structure
% *optional*
% **windowautopos % auomtatic window positioning
% **baraxis % plotted range
% **rectangle_pos % draw rectangle at given coordinates [xmin, xmax, ymin, ymax]
% **title % constant title prefix for all projections
% **title_extra % variable title suffix, one cell containing a string per projections !!!
% **fps % maximal frame rate
% **figure_id % id number of the created figure
% **showsorted % show projection sorted by angle
% **fnct % data processing function
% **init_frame % starting frame number
% **show_grid % plot grid ovelaying the plotted image
% **plot_residua % plot residua in the image
% **plot_only_180_range % plot projection with angle > 180 mirrored and merged with projections from 0-180deg
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 show_projections(img_stack, theta, param, varargin)
import math.*
import plotting.*
if nargin < 3
param = struct();
end
parser = inputParser;
parser.addParameter('windowautopos', true , @islogical ) % auomtatic window positioning
parser.addParameter('baraxis', 'auto' ) % plotted range
parser.addParameter('rectangle_pos', [], @isnumeric ) % draw rectangle with at coordinates
parser.addParameter('title', '', @isstr ) % constant title prefix for all projections
parser.addParameter('title_extra', {}, @iscell ) % variable title suffix, one cell containing a string per projections
parser.addParameter('fps', 25, @isnumeric) % maximal frame rate
parser.addParameter('figure_id', 1, @isnumeric) % maximal frame rate
parser.addParameter('showsorted', true, @islogical) % maximal frame rate
parser.addParameter('fnct', @(x)x) % data processing function
parser.addParameter('init_frame', 1, @isnumeric) % starting frame number
parser.addParameter('show_grid', true, @islogical) % starting frame number
parser.addParameter('plot_residua', false, @islogical) % plot residua in the image
parser.addParameter('plot_only_180_range', false, @islogical) % plot residua in the image
parser.parse(varargin{:})
r = parser.Results;
% load all to the param structure
for name = fieldnames(r)'
if ~isfield(param, name) || ~ismember(name, parser.UsingDefaults)
% prefer values in varargin structure
param.(name{1}) = r.(name{1});
end
end
screensize = get( groot, 'Screensize' );
nframes = size(img_stack,3);
frames = 1:nframes;
fig = plotting.smart_figure(param.figure_id);
clf()
if param.windowautopos
win_size = [1060 767];
set(fig,'Outerposition',[150 min(270,screensize(4)-win_size(2)) 1060 767]);
end
if strcmpi(param.baraxis,'auto') && nframes > 1 && isreal(img_stack) && strcmp( func2str( r.fnct ), '(x)x')
% set the same range for all the frames using quantile range
param.baraxis = gather(sp_quantile(img_stack, [1e-3, 1-1e-3], ceil(sqrt(numel(img_stack)))));
end
if r.plot_only_180_range
img_stack = fliplr(img_stack(:,:,theta >= 180 | theta < 0));
theta = mod(theta, 180);
end
if param.showsorted && nframes > 1
[~, ind] = sort(theta);
frames = ind(frames);
end
if isa(img_stack,'uint16') % half precision data
param.fnct = @(x)(param.fnct(fp16.get(x))); % covnvert to singles first
end
for ii = 1:nframes
num=ii; % frames(ii);
try
title_list{ii} = sprintf('%s Scan: %05d Projection: %03d Angle=%5.2f deg Energy=%.3f kev', param.title, param.scanstomo(num), num, theta(num), param.energy(num));
catch
title_list{ii} = sprintf('%s Projection: %03d', param.title, num);
end
if ~isempty(param.title_extra) && length(param.title_extra) == nframes
%title_list{ii} = [title_list{ii}, ' ', param.title_extra{num}];
title_list{ii} = {title_list{ii}; param.title_extra{num}};
end
end
slider_default = [.15 0.01 0.7 0.05];
play_default = [slider_default(1)-0.1 slider_default(2) 0.08 0.05];
edit_default = [slider_default(1)+slider_default(3)+0.01 slider_default(2) 0.08 0.05];
imagesc3D(img_stack, 'title_list', title_list, 'fps', param.fps , 'slider_position',slider_default , ...
'play_position', play_default , 'edit_position', edit_default,'fnct', param.fnct, 'order', frames,...
'init_frame', param.init_frame, 'loop', true, 'plot_residua', param.plot_residua )
colormap bone(256);
axis xy equal tight;
colorbar
if ~isempty(param.rectangle_pos)
hold all
rectangle('Position',[param.rectangle_pos(1), param.rectangle_pos(3), param.rectangle_pos(2)-param.rectangle_pos(1), param.rectangle_pos(4)-param.rectangle_pos(3)],'edgecolor','r')
hold off
end
if ~strcmpi(param.baraxis,'auto')
caxis(param.baraxis)
end
if param.show_grid
grid on
end
drawnow
end
+253
View File
@@ -0,0 +1,253 @@
% SHOW_TOMOGRAM_CUTS show cuts through the reconstructed volume
%
% show_tomogram_cuts(tomogram, scanstomo, par, extra_string = '' )
%
% Inputs:
% **tomogram - reconstructed volume
% **scanstomo - scan numbers, only for naming
% **par - parameter structure
% **extra_string - string added to the saved name , default = ''
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 show_tomogram_cuts(tomogram, scanstomo, par, extra_string)
import math.*
if nargin < 4
extra_string = '';
end
if isa(tomogram, 'gpuArray')
tomogram = gather(tomogram);
end
if par.makemovie % Open movie file
movie_filename = fullfile(par.output_folder,['tomo_movie_', par.scale '_' par.scans_string '_' extra_string ...
'_movie_axis_' sprintf('%01d',par.displayaxis) '.avi']);
if exist(movie_filename,'file')
disp(['File ' movie_filename ' exists,' ])
userans = input('Do you want to overwrite (y/N)? ','s');
if strcmpi(userans,'y')
utils.verbose(0,['Saving movie to ' movie_filename]);
else
utils.verbose(0,['Did not save ' movie_filename])
return
end
else
utils.verbose(0,['Saving movie to ' movie_filename]);
end
writeobj = VideoWriter(movie_filename);
writeobj.Quality=90;
writeobj.FrameRate=5;
open(writeobj);
end
% If displayslices is empty show central slice
if isempty(par.displayslice)&&(~par.animatedslices)
utils.verbose(1,'Displaying central slice along axis %i', par.displayaxis)
par.displayslice = round(size(tomogram,par.displayaxis)/2);
end
par.displayslice = unique(max(1,min(size(tomogram,par.displayaxis),round(par.displayslice))));
% Determine range of tomogram
switch num2str(par.tomobaraxis)
case 'auto_per_frame'
autobar = true;
slices_ind = {':', ':', ':'};
slices_ind{par.displayaxis} = par.displayslice;
par.tomobaraxis = sp_quantile(tomogram(slices_ind{:}), [1e-4, 1-1e-4],5);
case 'auto'
autobar = true;
% ignore outliers
par.tomobaraxis = sp_quantile(tomogram, [1e-4, 1-1e-4],ceil(max(10, sqrt(numel(tomogram))/100)));
% full range
%par.tomobaraxis = [min(tomogram(:), max(tomogram(:))];
otherwise
autobar = false;
end
switch lower(par.scale)
case 'phase'
if autobar
par.tomobaraxis = par.tomobaraxis/par.factor;
tomogram = tomogram / par.factor;
end
strscale = 'phase';
case 'delta'
strscale = 'delta';
case 'edensity'
if autobar
par.tomobaraxis = sort(par.tomobaraxis*par.factor_edensity);
end
strscale = 'electron density [e/A^3]';
case 'amp'
strscale = 'amplitude';
case 'beta'
strscale = 'beta';
case ''
strscale = '';
otherwise
error('scale should be phase, delta, amp, beta or edensity')
end
%%% Here the option for showing animation
if par.average_slices == 1
par.animatedslices = 0;
end
if par.animatedslices
par.displayslice = [1:size(tomogram,par.displayaxis)];
end
if par.average_slices == 0
loopdisplayslice = par.displayslice;
elseif par.average_slices == 1
loopdisplayslice = 1;
end
fig = plotting.smart_figure(1);
clf()
if par.windowautopos
screensize = get( 0, 'Screensize' );
set(gcf,'Outerposition',[1 screensize(4)-650 640 665]);
par.windowautopos = false;
end
rect = get(fig,'Position');
rect(1:2) = [0 0];
for showslice = loopdisplayslice
% Determine sagital, coronal or axial slices
slice = {':',':',':'};
if loopdisplayslice==1
slice{par.displayaxis} = par.displayslice;
else
slice{par.displayaxis} = showslice;
end
if showslice > size(tomogram,par.displayaxis)
continue
end
sliceview = squeeze(mean(tomogram(slice{:}),par.displayaxis))';
if par.displayaxis == 3
sliceview = sliceview';
end
sectionstring = {'Coronal','Sagital', 'Axial'};
sectionstring = sectionstring{par.displayaxis};
switch lower(par.scale)
case 'phase'
sliceview = sliceview/par.factor;
case 'delta'
case 'edensity'
sliceview = sliceview*par.factor_edensity;
case 'amp'
case 'beta'
case ''
otherwise
error('scale should be phase, delta, amp, beta or edensity')
end
if ~par.realaxis
imagesc(sliceview)
else
xaux = ([1 size(sliceview,2)]-size(sliceview,2)/2)*par.pixel_size*1e6;
yaux = ([1 size(sliceview,1)]-size(sliceview,1)/2)*par.pixel_size*1e6;
imagesc(xaux,yaux,sliceview)
xlabel('microns')
ylabel('microns')
end
axis xy image
c = colormap(par.colormapchoice);
if par.reverse_contrast
c = flipud(c);
colormap(c);
end
caxis(sort(par.tomobaraxis))
h = colorbar;
ylabel(h, strscale)
if (~isempty(par.bar_length))&&par.realaxis %% Show scale bar
hold on
axisaux = axis;
rectangle('Position', [axisaux(1)+par.bar_start_point(1)*1e6 axisaux(3)+par.bar_start_point(2)*1e6 par.bar_length*1e6 par.bar_height*1e6], ...
'facecolor',par.bar_color,'edgecolor','none')
text(axisaux(1)+par.bar_start_point(1)*1e6,...
axisaux(3)+par.bar_start_point(2)*1e6+par.bar_height*2e6,...
[num2str(par.bar_length*1e6) ' microns'],'Color',par.bar_color,'FontSize',12);
hold off
end
if par.average_slices == 1
title(strrep(sprintf(['Tomogram ' strscale ': ' par.scans_string, ...
' ' sectionstring ' section: \n Average slices ' num2str(par.displayslice(1)) ' to ' num2str(par.displayslice(end))]),'_', '\_'))
else
title(['Tomogram ' strscale ': ' strrep(par.scans_string, '_', '\_') ...
' ' sectionstring ' section: Slice ' num2str(showslice)])
end
drawnow
if par.makemovie
currFrame = getframe(fig,rect);
writeVideo(writeobj,currFrame);
end
pause(par.pausetime)
end
if par.makemovie == 1
close(writeobj);
end
if par.writesnapshots && ~debug()
output_path = fullfile(par.output_folder,['tomo_cut_', par.scans_string '_' par.scale '_' extra_string '_' num2str(size(sliceview,1)) 'x' num2str(size(sliceview,2)) '_axis_' num2str(par.displayaxis)]);
if par.average_slices == 1
output_path = [output_path, '_average_slices_' num2str(par.displayslice(1)) '_to_' num2str(par.displayslice(end))];
else
output_path = [output_path, '_slice_' num2str(showslice)];
end
fprintf('Writting image files \n %s.png \n %s.eps\n',output_path,output_path);
print('-f1','-dpng','-r300',[output_path,'.png']);
print('-f1','-depsc2',[output_path,'.eps']);
end
end
+245
View File
@@ -0,0 +1,245 @@
% UNWRAP2D_FFT2_SPLIT simple and very fast 2D phase unwrapping with autosplitting for GPU
% It applies iterativelly utils.unwrap2D_fft2 and enforces constrains by
% remove_sinogram_ramp, if abs(angle(img .* exp(-1i*phase))) < 2 , use
% phase = phase + angle(img .* exp(-1i*phase)) for exact unwrapping
%
% Method: estimate phase gradients dX, dY, and perform 2D complex
% integration as for DIC method to get phase (as in p = phase_from_dpc(dpcx,dpcy,varargin) function)
%
% method is similar (but not identical) to
% Sam Jeught, Jan Sijbers, and Joris Dirckx. "Fast Fourier-based phase unwrapping on the graphics processing unit in real-time imaging applications." Journal of Imaging 1.1 (2015): 31-44.
%
% [varargout] = unwrap2D_fft2_split(img, empty_region, polyfit_order, weights, GPU_list, ROI, Niter)
%
% Inputs:
% **img - either complex valued image or real valued phase gradient
% **empty_region - 2x1 or 1x1 vector, size of empty region assumed around edges for phase offset removal , default = []
% **polyfit_order - -1 = dont assume anything about the removed phase,
% subtract linear fit a*x+b for each horizontal line in order to satisfy
% that values in the empty_region are zero
% 0 = (default) assume that it is constant offset and minimize values in the empty_region
% 1 = assume phase ramp it is 2D plane. monimize values in empty_region
% **weights - reliability weights from 0 to 1 ( default = 1), can be just a function
% handle taking as input "img" array or a downsampled array that will
% be fourier interpolated before unwrapping,
% **GPU_list - list of used GPUs, default = current GPU
% **ROI - unwrapped region, default ROI = {':',':'};
% **preprocess_fun - apply custom function on "img" before processing
% **Niter - maximal number of unwrapping refinement interations, default = 5
% *returns*
% ++phase - unwrapped phase
%
% Examples:
% x = linspace(0, 10, 100);
% xc = exp(10*sin(x).*cos(x')); % make some 2D complex valued array
% xc = repmat(xc, 1,1, 100) ; % just show that it works for stacked inputs
% x_unwrapped = unwrap2D_fft2_split(img); % simplest case, no boundary conditions are applied
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 [varargout] = unwrap2D_fft2_split(img, empty_region, polyfit_order, weights_0, GPU_list, ROI, preprocess_fun , Niter)
import utils.*
import math.*
if isreal(img) && ~isa(img, 'uint32')
error('Complex-valued input array was expected')
end
if nargin < 3
polyfit_order = 1;
end
if nargin < 2
empty_region = [];
end
if nargin < 4
weights_0 = 1;
end
if nargin < 5
GPU_list = []; % use default GPU
end
if nargin < 6 || isempty(ROI)
ROI = {':',':'}; % unwrap only a small ROI from the full complex array
end
if nargin < 7
preprocess_fun = [];
end
if nargin < 8
Niter = [] ;
end
gpu = gpuDevice;
if ~isempty(GPU_list) && ~ismember(gpu.Index, GPU_list)
gpu = gpuDevice(GPU_list(1));
end
[Nx,Ny] = size(img(ROI{:},1));
Nz = size(img,3);
if gpuDeviceCount
gpu = gpuDevice;
if ~ismember(gpu.Index, GPU_list) && ~isempty(GPU_list)
if isa(img, 'gpuArray')
error('Non gpuArray input expected, change of GPU id will reset GPU memory content')
end
gpu = gpuDevice(GPU_list(1));
end
AvailableMemory = gpu.AvailableMemory;
else
% run in RAM
AvailableMemory = utils.check_available_memory*1e6;
end
Nblocks = ceil( (2e9+ 10 *8* (Nx+128)*(Ny+128)*size(img,3)) / AvailableMemory) ;
Nblocks = max(Nblocks, (Nx+64)*(Ny+64)*size(img,3) / double(intmax('int32')));
% avoid issues with rouding of Nz
Nblocks = ceil(Nz / floor(Nz/Nblocks));
if ~isempty(weights_0) && isnumeric(weights_0) && (ismatrix(weights_0) || any(size(img) ~= size(weights_0)))
if any([ size(img,1),size(img,2)] ~= [size(weights_0,1),size(weights_0,2)])
for i = 1:2
wROI{i} = unique(ceil(ROI{i}*size(weights_0,i) / size(img,i)));
end
else
wROI = ROI;
end
weights_0 = weights_0(wROI{:},:);
end
params = struct('Nblocks', Nblocks, 'GPU_list', GPU_list, 'ROI', {ROI}, 'use_GPU', gpuDeviceCount > 0, 'use_fp16', false, 'move_to_GPU', false);
varargout = cell(nargout,1);
[varargout{:}] = tomo.block_fun(@unwrap2D_fft2_worker, img, empty_region,weights_0,polyfit_order,preprocess_fun, Niter, params);
end
function [phase_block, residues_block] = unwrap2D_fft2_worker(img_block, empty_region,weights_0,polyfit_order,preprocess_fun, Niter)
import utils.*
import math.*
Npix = size(img_block);
if isempty(weights_0) || isscalar(weights_0)
weights = ones(size(img_block,1), size(img_block,2), 'single');
elseif isa(weights_0, 'function_handle')
weights = weights_0(img_block);
elseif isnumeric(weights_0) && any(Npix(1:2) ~= [size(weights_0,1),size(weights_0,2)])
weights_0 = gpuArray(weights_0);
weights_0 = single(weights_0) / 255;
weights = utils.interpolate_linear(weights_0, Npix(1:2));
else
weights = weights_0;
end
weights = Garray(weights);
img_block = Garray(img_block);
if any(~isfinite(img_block(:)))
error('Unwrapped complex array contains nan/inf values')
end
% apply custom function if provided
if ~isempty(preprocess_fun)
img_block = preprocess_fun(img_block);
end
weights = max(0,weights) / max(weights(:));
%phase_block = unwrap2D_fft2(img_block, empty_region,0,weights,polyfit_order);
% find residua, it is computationally cheap
residues_block = abs(findresidues(img_block)) .* weights(2:end,2:end,:) > 0.1;
residues_block = uint8(residues_block); % add_to_projection MEX function does not support logicals -> use uint8 which has the same size in matlab
% decide how many refinement iterations
if isempty(Niter)
if any(residues_block(:))
%% internal variable to set number of iterative refinements
Niter = 10;
else
Niter = 5;
end
end
% initialize resulting phase
phase_block = 0;
% perform several iterations to refine the quality
W = weights;
for iter = 1:Niter
if iter == 1
% initial unwrapping
img_block_resid =img_block;
else
img_block_resid =img_block.*exp(-1i*phase_block);
end
%% FOR DEBUGGING
% plotting.imagesc3D( W.*angle(img_block.*exp(-1i*phase_block)), 'init_frame', 1)
% axis xy
% colormap hsv(1024)
% colorbar
% caxis([-pi,pi])
% title(sprintf('Iter %i', iter))
% pause(1)
% drawnow
% check that unwrapping is really needed
[a_resid,~,~,~,c_factor] = utils.stabilize_phase(img_block_resid, 'fourier_guess', false, 'weight', W);
a_resid = angle(a_resid);
if all(all(all(abs(W.* (a_resid) )< 2 )))
% if the data are nice, make !! exact !! unwrapping and finish
phase_block = phase_block + W.*(a_resid-c_factor);
if ~isempty(empty_region)
% but still be sure to properly remove phase ramp / offset
phase_block = remove_sinogram_ramp(phase_block,empty_region, polyfit_order);
end
return
end
clear a_resid
phase_block = phase_block + unwrap2D_fft2(img_block_resid,[],0, W, polyfit_order);
if ~isempty(empty_region)
% but still be sure to properly remove phase ramp / offset
phase_block = remove_sinogram_ramp(phase_block,empty_region, polyfit_order);
end
end
end
+185
View File
@@ -0,0 +1,185 @@
% UNWRAP_2D_BOOTSTRAP Refine sinogram using tomography self-consitency ->
% try to improve reconstruction if the phase-gradients are too large or
% dataset contain residua and other unwrapping methods do not work well.
% It is computationally significantly slower than utils.unwrap_2D methods
%
% METHOD:
% This methods reconstructs tomogram in 2x lower resolution to gain
% "redundancy" between the projections. Then synthetic projection of this tomogram
% are subtracted from the measured complex projections -> P_difference = P_orig * conj(-i*phase_synthetic_unwrapped)
% and updated phase is estimated as phase_n = phase_(n-1) + unwrap_2D(P_difference)
% This bootstrap procedure is repeated in several iteratios. If |P_difference| < pi in some projections
% exact unwrapping using phase_n = phase_(n-1) + angle(P_difference) is used.
%
% [sinogram] = unwrap_2D_bootstrap(object, theta ,par, Niter)
%
% Inputs:
% **object - complex valued projections
% **theta - initial sinogram guess
% **par - ASTRA config file
% **Niter - ASTRA config vectors
% Outputs:
% ++sinogram - improved unwrapping of the phase sinogram
%*-----------------------------------------------------------------------*
%|                                                                       |
%|  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 [sinogram] = unwrap_2D_bootstrap(object, theta ,par, Niter, ROI)
% try to refine the sinogram using FBP reconstruction as intial guess
import utils.*
import math.*
binning = 2;
method = 'FBP';
utils.verbose(struct('prefix', 'unwrap'))
% important for laminography case
% weights = tomo.Ax_sup_partial(ones([Npix,Npix,Nlayers], 'single'), cfg, vectors,[1,1,Ngpu],tomo_params{:});
% weights = gather(weights / max(weights(:)));
%
verbose(0,'Bootstrap unwrapping')
% get initial 2D-FFT phase unwrapping
sinogram = -tomo.unwrap2D_fft2_split(object,par.air_gap,0,[],par.GPU_list,ROI);
sinogram_0 = sinogram;
verbose(0,'2D downsampling')
Np = size(sinogram);
sinogram_small = tomo.block_fun(@utils.interpolateFT_centered,sinogram,ceil(Np(1:2)/2/binning)*2, -1);
[Nlayers,width_sinogram,~] = size(sinogram_small);
Npix = ceil(width_sinogram/sqrt(2)/32)*32; % for pillar it can be the same as width_sinogram;
[cfg, vectors] = astra.ASTRA_initialize([Npix,Npix, Nlayers],[Nlayers,width_sinogram],theta,par.lamino_angle);
% find optimal split of the dataset for given GPU
Ngpu = max(1,length(par.GPU_list));
split = astra.ASTRA_find_optimal_split(cfg, Ngpu);
tomo_params = { 'split', [1,1,Ngpu*split(3)], 'split_sub',[split(1:2),1], 'GPU', par.GPU_list, 'verbose', 1};
residua = tomo.block_fun(@aux_get_residua,object);
if all(residua == 0)
verbose(0,'No residua detected, returning FFT_2D unwrapping result')
[sinogram] = tomo.block_fun(@update_sinogram,object, sinogram_small, par,binning, struct('ROI', {ROI}));
return
end
for ii = 1:Niter
switch method
case 'CGLS'
verbose(0,'CGLS')
rec = tomo.CGLS(rec, sinogram_small, cfg, vectors, Niter_tomo, tomo_params{:});
case 'FBP'
verbose(0,'FBP')
rec = tomo.FBP_zsplit(sinogram_small, cfg, vectors,tomo_params{:});
end
% "positivity" constraint
rec = max(0, rec);
verbose(0,'Projection ')
sinogram_small_updated = tomo.Ax_sup_partial(rec, cfg, vectors, [1,1,Ngpu*split(3)], tomo_params{:});
[sinogram, sinogram_small, upd_norm(ii,:)] = tomo.block_fun(@update_sinogram,object, sinogram_small_updated, par,binning, struct('ROI', {ROI}));
%% plot evolution
plotting.smart_figure(244)
subplot(1,2,1)
plot(mean(upd_norm,2))
title('Sinogram update norm')
xlabel('Iteration')
ylabel('Difference between complex-object and sinogram')
grid on
axis tight
subplot(1,2,2)
[~,ind] = sort(theta);
% show only projections with some residuas
ind = ind(ismember(ind, find(residua)));
plotting.imagesc3D(cat(2, sinogram_0(:,:,ind), sinogram(:,:,ind)));
title('Original sinogram (left) Improved sinogram (right)')
axis off xy image
colormap bone
plotting.suptitle('Bootstrap unwrapping')
win_size = [1400 500];
screensize = get( groot, 'Screensize' );
set(gcf,'Outerposition',[150 min(270,screensize(4)-win_size(2)) win_size]);
drawnow
end
utils.verbose(struct('prefix', 'template'))
end
function [sinogram, sinogram_small, upd_norm] = update_sinogram(object, sinogram_small, par, binning)
Np = size(object);
% upsample small sinogram back to the full size
sinogram = utils.interpolateFT_centered(sinogram_small,Np(1:2), -1);
% use the knowledge that around phase jumps is usually zero or very low intensity
W = min(1, abs(object));
%% sinogram refinement
% find sinogram ramp and offset to match the tomo guess
object_resid = object.*exp(1i*sinogram);
% estimate the update using 2D phase unwrap
sinogram = sinogram - W.*math.unwrap2D_fft2(object_resid,par.air_gap,0);
% make sinogram exactly equal to the data ,
% !! dangerous, it can make it even worse
% -> allow it only for the well behaved projections
phase_update = angle(object.*exp(1i*sinogram));
minor_update_ind = all(all(abs(phase_update)<0.5));
sinogram = sinogram - minor_update_ind.*W.*phase_update;
upd_norm = squeeze(math.norm2(angle(object_resid)));
% get a downsampled version of the sinogram
sinogram_small = utils.interpolateFT_centered(sinogram,ceil(Np(1:2)/2/binning)*2, -1);
end
function residua = aux_get_residua(object_block)
% GPU auxiliarly function
residua = squeeze(math.sum2(abs(utils.findresidues(object_block))>0.1));
end