mirror of
https://github.com/c-sooyoung/fold_slice.git
synced 2026-09-17 19:39:08 +09:00
initial commit
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user