Files
fold_slice/ptycho/+engines/+GPU_MS/DM.m
T
2026-08-07 15:56:42 +09:00

299 lines
12 KiB
Matlab
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
%DM Difference-Map algorithm
%
% [self, cache, psi_dash, fourier_error ] = DM(self,par,cache,psi_dash,fourier_error, iter)
%
%
% ** self structure containing inputs: e.g. current reconstruction results, data, mask, positions, pixel size, ..
% ** par structure containing parameters for the engines
% ** psi_dash complex projection from previous iteration, or emppty in first iteration
% ** cache structure with precalculated values to avoid unnecessary overhead
% ** fourier_error array [Npos,1] containing evolution of reconstruction error
% ** iter current iteration number
%
% returns:
% ++ self self-like structure with final reconstruction
% ++ cache structure with precalculated values to avoid unnecessary overhead
% ++ psi_dash complex projection from previous iteration, or emppty in first iteration
% ++ fourier_error array [Npos,1] containing evolution of reconstruction error
%
% Publications most relevant to the Difference-Map implementation
% + P. Thibault, M. Dierolf, A. Menzel, O. Bunk, C. David, F. Pfeiffer,
% "High-Resolution Scanning X-ray Diffraction Microscopy," Science 321, 379-382 (2008)
% + P. Thibault, M. Dierolf, O. Bunk, A. Menzel, F. Pfeiffer,
% "Probe retrieval in ptychographic coherent diffractive imaging,"
% Ultramicroscopy 109, 338343 (2009)
function [self, cache, psi_dash, fourier_error ] = DM(self,par,cache,psi_dash,fourier_error, iter)
import utils.*
import math.*
import engines.GPU_MS.shared.*
import engines.GPU_MS.GPU_wrapper.*
psi = cell(par.Nmodes, 1);
Psi = cell(par.Nmodes, 1);
%%%%%%%%%%%%%%%%%%%%%%%%% Difference maps algorithm %%%%%%%%%%%%%%%%%%%
beta = 1;
gamma = 1;
relax_mask = 1; % smoothly change relaxation of the mask
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
probe_norm = norm2(self.probe{1});
object_modes = length(self.object);
probe_amp_corr = [0,0];
for ll = 1:par.object_modes
obj_proj{ll} = Gzeros([self.Np_p, 0], true);
end
for ll = 1:object_modes
obj_illum{ll,1} = Gzeros(self.Np_o);
obj_update{ll,1} = Gzeros(self.Np_o, true);
end
for ll = 1:par.probe_modes
probe_illum{ll} = Gzeros(size(self.probe{ll}));
probe_update{ll} = Gzeros(size(self.probe{ll}), true);
end
% use precalculated blocks to be solved in parallel
indices = cache.preloaded_indices_simple{1}.indices;
scan_ids = cache.preloaded_indices_simple{1}.scan_ids;
Nind = length(indices);
p_ind = cell(max(par.probe_modes, par.object_modes),1);
for jj = 1:Nind
g_ind = indices{jj};
for ll = 1:max(par.probe_modes, par.object_modes)
if par.share_probe || ll > 1 % share incoherent modes
p_ind{ll} = 1;
else
if all(scan_ids{jj} == scan_ids{jj}(1))
p_ind{ll} = scan_ids{jj}(1);
else
p_ind{ll} = scan_ids{jj};
end
end
end
% load on GPU if needed
if ~par.keep_on_gpu
for ll = 1:max(par.probe_modes, par.object_modes)
try; psi_dash{ll,jj} = Garray(psi_dash{ll,jj}); end
end
end
%% fourier propagation
for ll = 1:par.object_modes
obj_proj{ll} = get_views(self.object, obj_proj{ll},1,ll, g_ind, cache, scan_ids{jj},[]);
end
for ll = 1:max(par.probe_modes, par.object_modes)
probe{ll} = self.probe{min(ll,end)}(:,:,p_ind{ll});
psi{ll} = bsxfun(@times, obj_proj{min(ll, end)}, probe{ll});
if isempty(psi_dash{ll,jj}); psi_dash{ll,jj} = psi{ll}; end % initial guess
% P_M (P_0(psi))
Psi{ll} = Gfun(@DM_update_psi, gamma,psi{ll}, psi_dash{ll,jj} );
Psi{ll} = fwd_fourier_proj(Psi{ll}, self.modes{min(ll,end)} );
end
%% load data to GPU (if not loaded yet)
modF = get_modulus(self, cache, g_ind,true,jj);
mask = get_mask(self, cache.mask_indices, g_ind);
% get intensity (modulus) on detector including different corrections
aPsi = get_reciprocal_model(self, Psi, modF, mask,iter, g_ind, par,cache);
if iter > 0 && (par.get_error && (mod(iter,min(20, 2^(floor(2+iter/50)))) == 0 || iter < 20) || iter == par.number_iterations )
[fourier_error(iter,g_ind)] = get_fourier_error(modF, aPsi, [],mask);
end
if ~isempty(mask)
mask = single(1)-mask;
mask = relax_mask + (min(relax_mask, par.pfft_relaxation) - relax_mask ) * mask;
else
mask = par.pfft_relaxation;
end
if iter == 0
% in the first iteration only find optimal scale for the probe
probe_amp_corr(1) = probe_amp_corr(1) + Ggather(sum(modF(:).^2));
probe_amp_corr(2) = probe_amp_corr(2) + Ggather(sum(aPsi(:).^2));
for ll = 1:size(psi_dash,2); psi_dash{ll,jj} = []; end
continue
end
%% fourier (modulus) contraint
Psi = modulus_constraint(modF,aPsi,Psi, mask, [], par, 0 );
aPsi = []; modF = []; mask = [];
for ll = 1:max(par.probe_modes, par.object_modes)
Psi{ll} = back_fourier_proj(Psi{ll}, self.modes{min(end,ll)});
psi_dash{ll,jj} = Gfun(@DM_update, psi_dash{ll,jj} ,beta, Psi{ll},psi{ll});
end
Psi = [];
% get back from GPU if required
if ~par.keep_on_gpu
for ll = 1:max(par.probe_modes, par.object_modes)
psi_dash{ll,jj} = Ggather(psi_dash{ll,jj});
end
end
end
if iter == 0
% apply initial correction for the probe intensity and return
probe_amp_corr = sqrt(probe_amp_corr(1) / probe_amp_corr(2)); %% calculate ratio between modF^2 and aPsi^2
for ii = 1:par.probe_modes
self.probe{ii} = self.probe{ii}*probe_amp_corr;
end
psi_dash = cell(size(psi_dash));
verbose(2,'Probe amplitude corrected by %.3g',probe_amp_corr)
return
end
%% iterative solver of the overlap constraint, important for initial convergence
for kk = 1:10
for ll = 1:object_modes
obj_illum{ll}(:) = 0;
obj_update{ll}(:) = 0i;
end
for ll = 1:par.probe_modes
probe_illum{ll}(:) = 0;
probe_update{ll}(:) = 0i;
end
probe_0 = self.probe;
%% obj_update , obj_illum,probe_update,probe_illum is not reset to make it more stable
for jj = 1:length(indices)
for ll = 1:max(par.probe_modes, par.object_modes)
if par.share_probe || ll > 1 % share incoherent modes
p_ind{ll} = 1;
else
if all(scan_ids{jj} == scan_ids{jj}(1))
p_ind{ll} = scan_ids{jj}(1);
else
p_ind{ll} = scan_ids{jj};
end
end
end
for ll = 1:par.probe_modes
probe{ll} = self.probe{ll}(:,:,p_ind{ll});
cprobe{ll} = conj(probe{ll});
aprobe{ll} = real(probe{ll}.*cprobe{ll});
end
% move to GPU (if not there yet)
for ll = 1:max(par.probe_modes, par.object_modes)
psi_dash{ll,jj} = Garray(psi_dash{ll,jj});
end
g_ind = indices{jj};
for ll = 1:par.object_modes
obj_proj{ll} = get_views(self.object, obj_proj{ll},1,ll, g_ind, cache, scan_ids{jj},[]);
end
if iter >= par.probe_change_start
for ll = 1:par.probe_modes
%% update probe
[probe_update{ll},probe_illum{ll}] = QQ_probe(psi_dash{ll,jj}, obj_proj{min(end,ll)}, probe_update{ll},probe_illum{ll}, p_ind{ll});
end
end
if iter >= par.object_change_start
%% update object
[obj_update,obj_illum] = QQ_object(psi_dash{ll,jj}, obj_update,obj_illum, aprobe{min(ll,end)}, cprobe{min(ll,end)}, g_ind,min(ll, object_modes),scan_ids{jj},cache);
end
% get back from GPU if required
if ~par.keep_on_gpu
for ll = 1:max(par.probe_modes, par.object_modes)
psi_dash{ll,jj} = Ggather(psi_dash{ll,jj});
end
end
end
for ll = 1:max([par.probe_modes, object_modes])
if iter >= par.probe_change_start && ll <= par.probe_modes
% add some inertia to prevent oscilations
self.probe{ll} = update_probe(self, self.probe{ll} , probe_update{ll} , probe_illum{ll} , par, ll);
end
if iter >= par.object_change_start && ll <= object_modes
self.object{ll} = Gfun(@update_object, self.object{ll}, obj_update{ll}, obj_illum{ll}, cache.MAX_ILLUM(ll)*1e-4, par.probe_inertia);
end
end
if iter > par.probe_change_start
% if both object and probe are recontructed, solve the
% realspace constraint iterativelly
min_iter = 1 + par.keep_on_gpu; % if par.keep_on_gpu==true, then the code is so slow that it is not worthy skip the norm calculation
min_change = 0.01;
if kk >= min_iter || verbose >= 2
dprobe = max(norm2(self.probe{1} - probe_0{1}) ./ probe_norm);
verbose(2,'Update probe difference: %3.2f%%', dprobe*100)
end
if kk >= min_iter && dprobe < min_change % change is below 1% and at least 2 iterations were done
break
end
end
end
if verbose > 2
for kk = 1:object_modes
Nresid = sum2(utils.findresidues(self.object{kk}(cache.object_ROI{:})) > 0.1);
if Nresid > 0
verbose(1,'Number of residua in object %i: %i', kk, Nresid)
end
end
end
end
function [probe_update,probe_illum] = QQ_probe(psi, obj_proj, probe_update,probe_illum, p_ind)
import engines.GPU_MS.GPU_wrapper.*
%% update probe
[upd, illum] = Gfun(@QQ_probe_Gfun, psi,obj_proj);
if size(probe_update,3)==1 % shared probe
probe_update = probe_update+sum(upd,3);
probe_illum = probe_illum+sum(illum,3);
else
probe_update(:,:,p_ind) = probe_update(:,:,p_ind)+sum(upd,3);
probe_illum(:,:,p_ind) = probe_illum(:,:,p_ind)+sum(illum,3);
end
end
function norm = get_probe_norm_aux(P0, P1)
norm = abs(P0-P1).^2;
end
function [upd, illum] = QQ_probe_Gfun(psi,proj)
upd = psi .* conj(proj) ;
illum = abs(proj).^2;
end
function [obj_update,obj_illum] = QQ_object(psi,obj_update,obj_illum, aprobe, cprobe, g_ind,ll,scan_ids,cache)
import engines.GPU_MS.shared.*
%% update object
psi = psi .* cprobe;
[obj_update,obj_illum] = set_views_rc(obj_update,obj_illum,psi,aprobe,1,ll, g_ind, cache, scan_ids,[]);
end
function psi = DM_update_psi(gamma,psi, psi_dash )
% real space update function for difference maps
psi = (1+gamma)*psi - gamma*psi_dash;
end
function psi_dash = DM_update(psi_dash,beta, psi_tmp,psi )
% update funciton for difference maps
psi_dash = psi_dash + beta * ( psi_tmp - psi ) ;
end
function object = update_object(object, object_upd, object_illum, delta, inertia)
% apply also some inertia in the object update
object = object*inertia + (1-inertia)*object_upd./ (object_illum+delta);
end
function probe = update_probe(self, probe, probe_update, probe_illum, par, probe_id)
import engines.GPU_MS.shared.*
probe_new = probe_update ./ (probe_illum+1e-6);
% apply probe support on the first probe mode
if probe_id == 1
probe_new = apply_probe_contraints(probe_new, self.modes{probe_id});
end
% add some inertia to prevent oscilations
probe = par.probe_inertia*probe + (1-par.probe_inertia)*probe_new;
end