initial commit

This commit is contained in:
2026-08-07 15:56:42 +09:00
commit 91ad25aca9
1012 changed files with 159314 additions and 0 deletions
@@ -0,0 +1,172 @@
% INITIAL_CHECKS check if the inputs are valid or try to correct them
%
% [self,par] = initial_checks(self, par)
%
% ** self structure containing inputs: e.g. current reconstruction results, data, mask, positions, pixel size, ..
% ** par structure containing parameters for the engines
%
% returns:
% ++ self structure containing inputs: e.g. current reconstruction results, data, mask, positions, pixel size, ..
% ++ par structure containing parameters for the engines
function [self,par] = check_inputs(self, par)
import engines.GPU.shared.*
import engines.GPU.GPU_wrapper.*
import math.*
import utils.*
[self.Np_o(1),self.Np_o(2),~] = size(self.object{1});
[self.Np_p(1),self.Np_p(2),~] = size(self.probe{1});
par.Nrec = 1;
par.Nscans = length(self.reconstruct_ind);
if ischar(par.extension)
par.extension = {par.extension};
end
for ii = 1:numel(self.object)
assert(all(isfinite(self.object{ii}(:))), 'Provided object contains nan / inf')
end
for ii = 1:numel(self.probe)
assert(all(isfinite(self.probe{ii}(:))), 'Provided probes contain nan / inf')
end
Np_d = size(self.diffraction);
if any(self.Np_p ~= Np_d(1:2)) % && isempty(self.modF_ROI)
error('Size of probe and data is different')
end
tmp = self.diffraction(1:self.Np_p(1)*7:end); % get some small sample
tmp = tmp * 2^(par.upsampling_data_factor*2); % remove upsampling effects
if par.compress_data && max(abs((tmp - round(tmp)))) > 0.2
verbose(1,'Data are not integers, cannot use compression')
par.compress_data = false;
end
%%%%%%%%%%%%% accelerated solver %%%%%%%%%%%%%%%%%%%
if par.accelerated_gradients_start < par.number_iterations && ~is_method(par, 'MLs')
verbose(3, 'accelerated_gradients_start < number_iterations is supported only for MLc engine')
par. accelerated_gradients_start = inf;
end
% if par.accelerated_gradients_start < par.number_iterations && par.momentum > 0 && is_method(par, 'ML')
% error('accelerated_gradients_start < inf cannot be used if momemtum > 0 ')
% end
%%%%%%%%%%%%% variable probe %%%%%%%%%%%%%%%%%%%%%%%%
if ~par.variable_probe
par.variable_probe_modes = 0;
end
if par.variable_probe && par.variable_probe_modes > 0 && ~is_method(par, {'PIE', 'ML'})
warning('Variable probe implemented only for PIE and ML')
par.variable_probe = false;
end
if par.variable_probe && par.variable_probe_modes == 0
error('Choose more than 0 variable_probe_modes for OPRP')
par.variable_probe_modes = 1;
end
if par.variable_probe && ~par.share_probe && is_method(par, 'PIE')
par.share_probe = true;
% variable probe means automatically shared variable probe
end
if ~is_method(par, {'PIE', 'ML'}) && strcmpi(par.likelihood, 'poisson')
warning('Poisson likelihood supported only for PIE methods')
par.likelihood = 'L1';
end
if ~ismember(lower( par.likelihood), {'l1','poisson'})
error('Unsupported error estimation')
end
%%%%%%%%%%%%%% check if position correction is allowed
if ~ is_method(par, {'PIE', 'ML'}) && par.probe_position_search < par.number_iterations
verbose(2, 'Position correction supported only for PIE/ML methods ')
par.probe_position_search = inf;
end
if any(~ismember(par.probe_geometry_model, {'scale', 'asymmetry', 'rotation', 'shear'}))
missing_option = setdiff(par.probe_geometry_model, {'scale', 'asymmetry', 'rotation', 'shear'} );
error('Unsupported geometry model option: "%s"', missing_option{1})
end
if par.probe_position_search < par.number_iterations && par.detector_scale_search < par.number_iterations && any(ismember(par.probe_geometry_model,'scale'))
error('Do not use probe_position_search with probe_geometry_model==''scale'' and detector_scale_search together')
end
%%%%%% checks for the multilayer method %%%%%%%%%%%%%%%%
par.Nlayers = length(self.z_distance);
assert(sum(~isfinite(self.z_distance)) <= 1, 'Provided distanced of layers are not possible to be used')
if par.Nlayers > 1 && ~is_method(par, {'PIE', 'ML'})
error('Multilayer extension is supported only for PIE/ML methods')
end
if par.Nlayers > 1 && par.probe_modes > 1
error('Multilayer extension is not supported with incoherent modes')
end
% if par.Nlayers > 1 && par.Nscans > 1
% error('Multilayer extension is not supported with multiple scans')
% end
%%%%%%%%%% fast scanning %%%%%%%%%%%%%%%%%%%%%%%%%
if is_used(par, 'fly_scan') && ~is_method(par, {'PIE', 'ML'})
error('Fly scan is supported only for PIE/ML methods')
end
if is_used(par, 'fly_scan')
if par.Nmodes == 1
warning('Flyscan has no effect with a single mode')
par.extension = setdiff(par.extension, 'fly_scan');
par.apply_subpix_shift= true;
end
par.Nrec = par.Nmodes;
% par.apply_multimodal_update = true;
end
%%%%%%%% nearfield %%%%%%%%%%%%%%%%%%%%%%%%%%%
if par.estimate_NF_distance < par.number_iterations && isinf(self.z_distance(end))
error('estimate_NF_distance valid only for nearfield mode')
end
%%%%%%%%%%%%%%% OTHER %%%%%%%%%%%%%%%%%%%%
if strcmpi(par.likelihood, 'poisson') && par.background_detection && ~isinf(par.background_detection)
error('Background detection does not work well with Poisson likelihood')
end
if prod(self.Np_p) *self.Npos > intmax('int32') && par.keep_on_gpu && is_method(par, {'MLs', 'ePIE'})
warning('Dataset as more than 2147483647 elements (max of int32). Set par.keep_on_gpu to false')
par.keep_on_gpu = false;
end
if any(self.noise(:) == 0) && par.relax_noise
warning('Some values of expected noise are 0')
self.noise = max(0.5, self.noise);
end
if par.Nrec > max([par.Nmodes, par.probe_modes , par.object_modes])
warning('Number of modes is too high')
end
if length(self.probe_positions) ~= self.Npos
self.probe_positions = [];
end
if par.mirror_objects && par.Nscans ~= 2
error('Object mirroring is supported only for two scans')
end
%%%%%% position correction %%%%%
if ~is_method(par, {'PIE', 'ML'}) && par.probe_position_search < par.number_iterations
warning('Position corrections works only for PIE/ML methods')
end
if is_method(par, {'PIE', 'ML'}) && par.probe_position_search < par.number_iterations && ~(par.apply_subpix_shift || is_used(par,'fly_scan'))
verbose(2,'Subpixel shifting is strongly recommended for position refinement => enforcing par.apply_subpix_shift = true')
par.apply_subpix_shift = true;
end
end
@@ -0,0 +1,123 @@
% INITIALIZE generate list of default parameters
% [param] = get_defaults
%
%
% returns:
% ++ param structure containing parameters for the engines
function [param] = get_defaults
%%%%%%%%%%%%%% GPU SETTINGS %%%%%%%%%%%%%%%%%%%%%%%%%%
param.use_gpu = true; % use GPU if possible
param.keep_on_gpu = true; % keep the data all the time on GPU
param.compress_data = true; % apply online compress on the GPU data
param.gpu_id = []; % default GPU id, [] means choosen by matlab
param.check_gpu_load = true;
%% basic recontruction parameters
%% PIE
param.beta_object = 1;
param.beta_probe = 1; % step size, faster convergence , more instable ??
%% DM
param.pfft_relaxation = 0.1;
param.probe_inertia = 0.3; % add inertia to the probe reconstruction to avoid oscilations
%% general
param.share_probe = true;
param.share_object = false;
param.delta = 0; % press values to zero out of the probe area !! illim < max*delta is removed
param.relax_noise = 0.0; % relaxation for noise, lower => slower convergence, more robust
param.positivity_constraint_object = 0; % enforce weak positivity in object
param.Nmodes = 1; % number of multi apertures , always better to start wih one !!
param.probe_modes = 1; % number of probes
param.object_modes = 1; % number of multi apertures , always better to start wih one !!
param.probe_change_start = 1; % iteration when the probe reconstruction is started
param.object_change_start = 1; % iteration when the object reconstruction is started
param.number_iterations = 300;
param.time_limit = inf; % added by YJ: set a time limit (in seconds) for reconstruction
param.grouping = inf;
param.method = 'MLs';
param.likelihood = 'L1' ; % l1 or poisson, - choose which likelihood should be used for solver, poisson is suported only for PIE
param.verbose_level = 1;
param.plot_results_every = 50;
param.remove_residues = false; % autodetect and remove phase residua
param.extension = '';
%% data handling
param.upsampling_data_factor = 0; % assume that the data were created by upsampling using function utils.unbinning
param.damped_mask = 5e-3; % if damped_mask = 0 -> do nothing, if 1>x>0 -> push masked regions weakly towards measured magnitude value in each iteration
param.background_detection = false;
param.background_width = inf;
%% ADVANCED OPTIONS
param.object_regular = [0, 0]; % enforce smoothness !!!, use between [0-0.1 ]
param.remove_object_ambiguity = true; % remove intensity ambiguity between the object and the probes
param.variable_probe = false; % Use SVD to account for variable illumination during a single (coupled) scan
param.apply_subpix_shift = false; % apply FFT-based subpixel shift, important for good position refinement but it is slow
param.probe_geometry_model = {'scale', 'asymmetry', 'rotation', 'shear'}; % list of free parameters in the geometry model
param.probe_position_search = inf;
param.apply_relaxed_position_constraint = true; %added by YJ: allow position update without geom model constraint
param.update_pos_weight_every = inf; %added by YJ: allow position weight to be updated multiple times. Default = inf: only calculate once
param.max_pos_update_shift = 0.1; %added by YJ: allow user to specify the maximum position update allowed in each iteration. Default = 0.1 (pixel).
param.probe_position_search_momentum = 0; % added by YJ: enable momentum acceleration for position correction. Default = 0: no acceleration.
param.probe_fourier_shift_search = inf;
param.estimate_NF_distance = inf;
param.detector_rotation_search = inf; % rotation of the detector axis with respect to the sample axis, similar as rotation option in the position refinement geometry model but works also for 0/180deg rotation shared scans
param.detector_scale_search = inf; % pixel scale of the detector, can be used to refine propagation distance in ptycho
param.apply_multimodal_update = false; % use thibault modes to get higher signal, it can cause isses, not real gain if blur method is used
param.probe_backpropagate = 0;
param.beta_LSQ = 0.9; % use predictive step length
param.delta_p = 0.1; % LSQ damping constant
param.variable_probe_modes = 1; % OPRP settings
param.variable_probe_smooth = 0;% OPRP settings
param.variable_intensity = false; % account fort variable intensity
param.relaxed_object_constrain = 0; % enforce known object (inputs.object_orig)
param.probe_position_error_max = 10e-9; % max expected error of the stages
param.probe_fourier_shift_search = inf;
param.momentum = 0; % use mementume accelerated gradient decsent method
param.regularize_layers = 0; % 0<R<1 -> apply regularization on the reconstructed layers
param.preshift_ML_probe = true; % multilayer ptycho extension: if true, assume that the provided probe is reconstructed in center of the sample.
param. initial_probe_rescaling = true; % find the optimal scaling correction for the provided probe guess in the initial iteration
param. accelerated_gradients_start = inf; % use accelerated gradients to speed up the convergence
param. align_shared_objects = false; % align multiple objects from various scans
% extra analysis
param. get_fsc_score = false; % measure evolution of the Fourier ring correlation during convergence
param. mirror_objects = false; % mirror objects, useful for 0/180deg scan sharing
param. align_shared_objects = false; % align the objects before sharing them onto single one
% fly scans
param.flyscan_offset = 0;
param.flyscan_dutycycle = 1;
param.flyscan_intensity = 'varying'; % Added by YJ. Specify how to combine flyscan modes: 'varying' (default) or 'constant'.
param.flyscan_trajectory = 'line'; % Added by YJ. Specify trajectory type for arbitrary-path fly-scan:
%'line' (default): line scan with big jumps.
%'continuous': contiuous path.
%'external': load positions from external files
% regularizations added by YJ
% Remove grid artifacts in the phase image via a windowed Fourier filter (assume raster scan)
% Based on the idea in https://doi.org/10.1063/1.4993744
param.rm_grid_artifact_step_size = [0,0]; % scan step size in the [horizontal, vertical] directions. No filter if any of them is 0 (default).
param.rm_grid_artifact_window_size = [5,5]; % window size in the [horizontal, vertical] directions
param.rm_grid_artifact_direction = 'xy'; % filter directions: 'x' (horizontal), 'y' (vertical), or 'xy' (default)
%rng('default');
%rng('shuffle');
% convergence check - stop reconstruction if fourier error is larger than the previous one by given (relative) threshold.
param.fourier_error_threshold = inf; % default: no convergence check.
% I/O
param.save_init_probe = false; % Added by YJ. If true, save initial probe function in the .mat output file. Default is false.
param.save_images = {'obj_ph','probe'}; % Added by YJ. Save intermediate results as tiff images.
% Options: {'obj_ph','obj_ph_sum','obj_ph_stack','obj_mag','obj_ph_sum','obj_mag_stack','probe_mag','probe'}
end
@@ -0,0 +1,161 @@
% GET_PARALLEL_BLOCKS Find the optimal groups to be solved in parallel on GPU/CPU
%
%[cache, par] = get_parallel_blocks(self, par, cache)
%
% ** self structure containing inputs: e.g. current reconstruction results, data, mask, positions, pixel size, ..
% ** par structure containing parameters for the engines
% ** cache structure with precalculated values to avoid unnecessary overhead
%
% returns:
% ++ self structure containing inputs: e.g. current reconstruction results, data, mask, positions, pixel size, ..
% ++ par structure containing parameters for the engines
% Academic License Agreement
%
% Source Code
%
% Introduction
% This license agreement sets forth the terms and conditions under which the PAUL SCHERRER INSTITUT (PSI), CH-5232 Villigen-PSI, Switzerland (hereafter "LICENSOR")
% will grant you (hereafter "LICENSEE") a royalty-free, non-exclusive license for academic, non-commercial purposes only (hereafter "LICENSE") to use the cSAXS
% ptychography MATLAB package computer software program and associated documentation furnished hereunder (hereafter "PROGRAM").
%
% Terms and Conditions of the LICENSE
% 1. LICENSOR grants to LICENSEE a royalty-free, non-exclusive license to use the PROGRAM for academic, non-commercial purposes, upon the terms and conditions
% hereinafter set out and until termination of this license as set forth below.
% 2. LICENSEE acknowledges that the PROGRAM is a research tool still in the development stage. The PROGRAM is provided without any related services, improvements
% or warranties from LICENSOR and that the LICENSE is entered into in order to enable others to utilize the PROGRAM in their academic activities. It is the
% LICENSEEs responsibility to ensure its proper use and the correctness of the results.
% 3. THE PROGRAM IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
% A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. IN NO EVENT SHALL THE LICENSOR, THE AUTHORS OR THE COPYRIGHT
% HOLDERS BE LIABLE FOR ANY CLAIM, DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY ARISING FROM, OUT OF OR IN CONNECTION WITH THE PROGRAM OR THE USE
% OF THE PROGRAM OR OTHER DEALINGS IN THE PROGRAM.
% 4. LICENSEE agrees that it will use the PROGRAM and any modifications, improvements, or derivatives of PROGRAM that LICENSEE may create (collectively,
% "IMPROVEMENTS") solely for academic, non-commercial purposes and that any copy of PROGRAM or derivatives thereof shall be distributed only under the same
% license as PROGRAM. The terms "academic, non-commercial", as used in this Agreement, mean academic or other scholarly research which (a) is not undertaken for
% profit, or (b) is not intended to produce works, services, or data for commercial use, or (c) is neither conducted, nor funded, by a person or an entity engaged
% in the commercial use, application or exploitation of works similar to the PROGRAM.
% 5. LICENSEE agrees that it shall make the following acknowledgement in any publication resulting from the use of the PROGRAM or any translation of the code into
% another computing language:
% "Data processing was carried out using the cSAXS ptychography MATLAB package developed by the Science IT and the coherent X-ray scattering (CXS) groups, Paul
% Scherrer Institut, Switzerland."
%
% Additionally, any publication using the package, or any translation of the code into another computing language should cite for difference map:
% P. Thibault, M. Dierolf, A. Menzel, O. Bunk, C. David, F. Pfeiffer, High-resolution scanning X-ray diffraction microscopy, Science 321, 379382 (2008).
% (doi: 10.1126/science.1158573),
% for mixed coherent modes:
% P. Thibault and A. Menzel, Reconstructing state mixtures from diffraction measurements, Nature 494, 6871 (2013). (doi: 10.1038/nature11806),
% for LSQ-ML method
% M. Odstrcil, A. Menzel, M.G. Sicairos, Iterative least-squares solver for generalized maximum-likelihood ptychography, Optics Express, 2018
% for OPRP method
% M. Odstrcil, P. Baksh, S. A. Boden, R. Card, J. E. Chad, J. G. Frey, W. S. Brocklesby, "Ptychographic coherent diffractive imaging with orthogonal probe relaxation." Optics express 24.8 (2016): 8360-8369
% and/or for multislice:
% E. H. R. Tsai, I. Usov, A. Diaz, A. Menzel, and M. Guizar-Sicairos, X-ray ptychography with extended depth of field, Opt. Express 24, 2908929108 (2016).
% 6. Except for the above-mentioned acknowledgment, LICENSEE shall not use the PROGRAM title or the names or logos of LICENSOR, nor any adaptation thereof, nor the
% names of any of its employees or laboratories, in any advertising, promotional or sales material without prior written consent obtained from LICENSOR in each case.
% 7. Ownership of all rights, including copyright in the PROGRAM and in any material associated therewith, shall at all times remain with LICENSOR, and LICENSEE
% agrees to preserve same. LICENSEE agrees not to use any portion of the PROGRAM or of any IMPROVEMENTS in any machine-readable form outside the PROGRAM, nor to
% make any copies except for its internal use, without prior written consent of LICENSOR. LICENSEE agrees to place the following copyright notice on any such copies:
% © All rights reserved. PAUL SCHERRER INSTITUT, Switzerland, Laboratory for Macromolecules and Bioimaging, 2017.
% 8. The LICENSE shall not be construed to confer any rights upon LICENSEE by implication or otherwise except as specifically set forth herein.
% 9. DISCLAIMER: LICENSEE shall be aware that Phase Focus Limited of Sheffield, UK has an international portfolio of patents and pending applications which relate
% to ptychography and that the PROGRAM may be capable of being used in circumstances which may fall within the claims of one or more of the Phase Focus patents,
% in particular of patent with international application number PCT/GB2005/001464. The LICENSOR explicitly declares not to indemnify the users of the software
% in case Phase Focus or any other third party will open a legal action against the LICENSEE due to the use of the program.
% 10. This Agreement shall be governed by the material laws of Switzerland and any dispute arising out of this Agreement or use of the PROGRAM shall be brought before
% the courts of Zürich, Switzerland.
%
%
function [cache, par] = get_parallel_blocks(self, par, cache)
import engines.GPU.GPU_wrapper.*
import utils.*
import engines.GPU.shared.*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%% FIND MAXIMAL GROUP SIZE IF GPU IS USED %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
grouping_0 = par.grouping;
par.grouping = round(min(self.Npos, par.grouping));
global gpu
if par.use_gpu
%% !! very empirical estimation of the GPU memory requirements !!!
% use the estimated memory requirements to prevent low memory issues
[required_mem_constant] = estimate_req_memory(self, par, 0);
[required_mem] = estimate_req_memory(self, par, 1);
% precheck size of the block and try to optimize size of the groups
% allowed for given GPU
max_group_size = floor( (gpu.AvailableMemory - required_mem_constant) ./ (required_mem - required_mem_constant));
max_group_size = min(self.Npos, max_group_size);
verbose(1,'Maximal possible grouping %i', max_group_size);
% if group size was set to infinity, is the maximal group size possible
if isinf(grouping_0)
par.grouping= max_group_size;
else
% otherwise use max_group_size as a top limit
par.grouping = min(par.grouping, max_group_size);
end
% adjust grouping to minimize overhead -> make the group sizes more
% equal
if is_method(par, {'ML', 'PIE'})
% allows to calculate several scans together
par.grouping = ceil(self.Npos/ceil(self.Npos/par.grouping));
else
% consider each scan separatelly
Npos_scan = cellfun(@length, self.reconstruct_ind);
par.grouping = max(ceil(Npos_scan./ceil(Npos_scan./par.grouping)));
end
if par.grouping ~= grouping_0
verbose(1,'Optimal grouping was changed from %i to %i ', grouping_0, par.grouping);
end
if par.grouping < 1
error('Too low memory, use smaller dataset or try ePIE')
end
verbose(1,'Selected grouping %i', par.grouping);
else
if is_method(par, {'DM', 'ML'})
par.grouping = self.Npos;
end
end
% precalculate distance matrix for pseudo ePIE / hPIE / MLs to get
% least overlapping indices
if is_method(par, {'ML', 'PIE'})
if self.Npos/par.Nscans < 1e3 %added by YJ to save memory
for ll = 1:par.Nscans
dist_mat = single(distmat(self.probe_positions_0(self.reconstruct_ind{ll},:)));
dist_mat(dist_mat==0 | dist_mat > max(self.Np_p)/2) = inf;
cache.distances_matrix{ll} = dist_mat;
end
end
end
if is_method(par, 'MLc')
% get higly overlapping subsets of indices for PIE / ML
[cache.preloaded_indices_compact{1}.indices,cache.preloaded_indices_compact{1}.scan_ids] = ...
get_close_indices(self, cache, par );
elseif is_method(par, {'MLs', 'PIE'})
% preload order of indices , generate several of them to add randomness
for i = 1:min(par.number_iterations,10)
[cache.preloaded_indices_sparse{i}.indices,cache.preloaded_indices_sparse{i}.scan_ids] = ...
get_nonoverlapping_indices(self, cache, par );
end
end
% get just some predefined sets of indices - RAAR, DM , !! order
% does not matter
[cache.preloaded_indices_simple{1}.indices,cache.preloaded_indices_simple{1}.scan_ids] = ...
get_scanning_indices(self, cache, par );
end
@@ -0,0 +1,598 @@
% INITIALIZE_SOLVER initialize GPU ptycho reconstruction, generate cache values, fftshift data, etc
%
% [self, cache] = initialize_solver(self,par)
%
% ** self structure containing inputs: e.g. current reconstruction results, data, mask, positions, pixel size, ..
% ** par structure containing parameters for the engines
%
% returns:
% ++ self structure containing inputs: e.g. current reconstruction results, data, mask, positions, pixel size, ..
% ++ cache structure with precalculated values to avoid unnecessary overhead
function [self, cache] = init_solver(self,par)
import engines.GPU.shared.*
import math.*
import utils.*
import plotting.*
import engines.GPU.GPU_wrapper.*
par.Nscans = length(self.reconstruct_ind); %number of scans
cache.skip_ind = setdiff(1:self.Npos,[self.reconstruct_ind{:}]); % wrong datasets to skip
if ~any(self.probe_support(:))
self.probe_support = [];
end
%% avoid probe to be larger than a certain oversampling !!!!
if isempty(self.probe_support)
par.probe_backpropagate = 0;
end
if ~isempty(self.background) && any(self.background(:) > 0)
Background = self.background;
elseif par.background_detection
Background = 0;
else
Background = []; % array of background light
end
Noise = [];
%% prepare data / noise / mask
if par.relax_noise && ~isempty(self.noise) && strcmp(par.likelihood, 'L1')
Noise = self.noise;
Noise = (sqrt(posit(self.diffraction + Noise)) - sqrt(posit(self.diffraction - Noise)))/2;
Noise(self.diffraction == 0) = 1;
disp('Using measured noise')
Noise = max(0.5, Noise);
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%% PREPARE MASK AND DATA %%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% prepare mask , note that bool in matlab has size of uint8 !!
cache.mask_indices = [];
if any(self.mask(:))
Mask = [];
% single mask
if all(all(mean(self.mask,3) == self.mask(:,:,1)))
Mask = self.mask(:,:,1);
else
% mask for each scan
for ll = 1:par.Nscans
ind = self.reconstruct_ind{ll};
%if there is only one repeated mask over whole scan
if all(all(all(bsxfun(@eq, self.mask(:,:,ind), self.mask(:,:,ind(1))))))
Mask(:,:,ll) = self.mask(:,:,ind(1));
end
cache.mask_indices(ind) = ll;
end
end
if isempty(Mask)
% mask for each position
Mask = self.mask; % otherwise just store original
cache.mask_indices(ind) = 1:self.Npos;
end
% important to save memory
if all(Mask(:) == 1 | Mask(:) == 0)
Mask = logical(Mask );
else
Mask = uint8(Mask*255); % if there are nonlogical values in mask, store them as uint8 to save memory
end
else
Mask = [];
end
%% prepare diffraction data
Diffraction = self.diffraction;
if par.upsampling_data_factor
% downsample the data down to original size to save memory
Diffraction = utils.binning_2D(Diffraction, 2^par.upsampling_data_factor) * (2^(2*par.upsampling_data_factor));
if ~isempty(Mask)
Mask = utils.binning_2D(Mask, 2^par.upsampling_data_factor) == 1;
end
end
Diffraction = single(max(0,Diffraction));
if ~isempty(Mask)
if size(Mask,3) == par.Nscans && par.Nscans > 1
for ll = 1:par.Nscans
ind = self.reconstruct_ind{ll};
Diffraction(:,:,ind) = Diffraction(:,:,ind) .* ~Mask(:,:,ll);
end
else
Diffraction = Diffraction .* ~Mask;
end
end
if ~isinf(self.z_distance(end)) % && mod(Ninf,2)~=0
% assume inputs already fftshifted, but in case of nearfield
% fftshift it back for the ASM propagator
Noise = fftshift_2D(Noise);
Diffraction = fftshift_2D(Diffraction);
Mask = fftshift_2D(Mask);
end
%%%% compress data if requested %%%%%%
if par.compress_data
DATA_MAX = quantile(max2(abs(Diffraction)), 1-1e-2);
C_factor_0 = 2; % compression factor >=2 seems to be safe, >=4 is pratically lossless
if par.compress_data == 1 || DATA_MAX < 2^(2*8) / C_factor_0^2
Diffraction = sqrt(single(Diffraction));
if DATA_MAX < 2^(2*8) / C_factor_0^2
% simple sqrt compression to 8 bits
verbose(1, 'Online data compression to 8-bits')
Diffraction = uint8(C_factor_0*Diffraction);
cache.C_factor = C_factor_0;
elseif DATA_MAX < 2^(2*16) / 16^2
% failsafe option: sqrt compression to 16 bits
verbose(1, 'Online data compression to 16-bits')
cache.C_factor = 16; % use compression factor 16, to be super safe just because we have space
Diffraction = uint16(cache.C_factor*Diffraction);
else
error('Online compression will fail')
end
elseif par.compress_data == 2
% SVD subtraction compression to 8 bits (failsafe is compression to 16bits)
% additionally remove some SVD modes
Diffraction = sqrt(single(Diffraction));
Nmodes = par.Nscans;
[U,S,V] = fsvd(reshape(Diffraction,prod(self.Np_p),[]), Nmodes);
ind_relevant = diag(S).^2/sum(diag(S).^2) > 1e-2; % more than 1% of power
cache.US_diffraction = (U(:,ind_relevant)*S(ind_relevant,ind_relevant));
cache.V_diffraction = V(:,ind_relevant);
svd_Diffraction = round(reshape(cache.US_diffraction*cache.V_diffraction',[self.Np_p, self.Npos]));
%% compress
cDiffraction = single(Diffraction) - svd_Diffraction;
% reestimate optimal compression factor to keep values < 128
C_factor = min(C_factor_0, 128/quantile(max2(abs(cDiffraction)), 1-1e-2));
if C_factor > 3
verbose(1, 'Online data compression to 8-bits + SVD')
cache.C_factor = C_factor;
cache.US_diffraction = cache.US_diffraction;
cache.V_diffraction = cache.V_diffraction*C_factor;
Diffraction = int8(cDiffraction*C_factor);
elseif DATA_MAX < 2^(2*16) / 16^2
% sqrt compression to 16 bits
%warning(sprintf('Too high online compression of data, it may cause problems\n Compression factor is %2.2f but should be >= 2\n Switching from 8 to 16bits',C_factor))
verbose(1, 'Online data compression to 16-bits')
C_factor = 16;
cache.C_factor = C_factor;
Diffraction = uint16(C_factor*Diffraction);
else
error('Online compression will fail')
end
clear svd_Diffraction cDiffraction
else
error('Unimplented level of compression')
end
else
% precalculate sqrt from the data, store as singles
Diffraction = sqrt(single(max(0,Diffraction)));
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% write back the data arrays
self.diffraction = Diffraction;
self.mask = Mask;
self.noise = Noise;
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%% PREPARE GEOMETRY, PROPAGATION, MODES%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% precalculate ASM factor for propagation distance recovery
[ASM_difference] = near_field_evolution_gradient(self.Np_p, self.lambda, self.pixel_size .*self.Np_p );
cache.ASM_difference = fftshift(ASM_difference);
% custom propagator to account for tilted plane sample
if any(par.p.sample_rotation_angles(1:2)) && check_option(par.p, 'apply_tilted_plane_correction', 'propagation')
% get propagators to the tilted plane
[tilted_plane_propagate_fwd, tilted_plane_propagate_back] = ...
get_tilted_plane_propagators(Garray(self.probe{1}), ...
[par.p.sample_rotation_angles(1:2),0],...
self.lambda, self.pixel_size);
else
tilted_plane_propagate_fwd = []; tilted_plane_propagate_back = [];
end
if ~iscell(self.affine_matrix)
self.affine_matrix = {self.affine_matrix};
end
% Note by YJ:
% par.Nmodes seems to be the # of probe modes in mixed-state or arbitrary-path fly-scan
modes = cell(max(par.Nmodes, par.Nlayers),1);
for i = 1:max(par.Nmodes, par.Nlayers)
verbose(2,'Creating new modes files ')
modes{i}.lambda = self.lambda;
% decompose affine matrix into scale, asymmetry, rotation, shear
%affine = scale*[1+asym/2,0; 0,1-asym/2]*[cosd(rot), sind(rot); -sind(rot), cosd(rot)] * [1,0;tand(shear),1];
affine_matrix = self.affine_matrix{min(i,end)};
[scale, asymmetry, rotation, shear] = decompose_affine_matrix(affine_matrix);
% store initial geometry parameters
modes{i}.scales = repmat(scale, 1,par.Nscans);
modes{i}.asymmetry = repmat(asymmetry, 1,par.Nscans);
modes{i}.shear = repmat(shear, 1,par.Nscans);
modes{i}.rotation = repmat(rotation, 1,par.Nscans);
modes{i}.affine_matrix = repmat(affine_matrix, 1,1,par.Nscans);
modes{i}.shift_scans = zeros(2, par.Nscans);
modes{i}.probe_scale_upd = 0;
modes{i}.probe_rotation = ones(1,par.Nscans) * par.sample_rotation_angles(3); % one rotation per scan
if par.mirror_objects
modes{i}.probe_rotation = modes{i}.probe_rotation .* [1,-1]; % flip the coordinates for mirrored object (ie 0 vs 180deg rotation)
end
modes{i}.probe_rotation_all = zeros(self.Npos,1);
for jj = 1:par.Nscans
modes{i}.probe_rotation_all(self.reconstruct_ind{jj}) = modes{i}.probe_rotation(jj); % one rotation per scan
end
distance = self.z_distance(min(end,i));
if ~isinf(distance)
verbose(2, 'Layer %i distance %g um ', i, distance*1e6 )
end
modes{i}.distances = distance;
if is_used(par, 'fly_scan') && (~isfield(modes{i}, 'probe_positions') || isempty(modes{i}.probe_positions) )
%% get positions for fly scans
self = prepare_flyscan_positions(self, par);
modes{i}.probe_positions = self.modes{i}.probe_positions; %added by YJ. seems like a bug
modes{i}.probe_positions_0 = self.probe_positions_0; %added by YJ. seems like a bug
else
%% get positions for normal tomo
try % try to reuse the positions of there are saved
modes{i}.probe_positions = self.modes{i}.probe_positions;
verbose(0,'Using saved positions')
catch
if (modes{i}.scales(end) == modes{1}.scales(end)) && ~isempty(self.probe_positions)
modes{i}.probe_positions = self.probe_positions;
verbose(0,'Using saved positions')
else
verbose(2,'Using original positions')
modes{i}.probe_positions = (affine_matrix*self.probe_positions_0')';
end
end
try
modes{i}.probe_positions_0 = self.modes{i}.probe_positions_0;
catch
modes{i}.probe_positions_0 = self.probe_positions_0;
end
end
modes{i}.probe_positions_update = { zeros(size(modes{i}.probe_positions)) };
modes{i}.probe_positions_all = {modes{i}.probe_positions};
modes{i}.probe_positions_weight = zeros(self.Npos, 1);
if isfield(self, 'probe_fourier_shift') && ~isempty(self.probe_fourier_shift) && i == 1
modes{i}.probe_fourier_shift = self.probe_fourier_shift;
else
modes{i}.probe_fourier_shift = zeros(self.Npos,2);
end
if ~isempty(self.probe_support) && i <= par.Nrec
modes{i}.probe_support = self.probe_support;
if i == 1
verbose(2,'Using real-space probe support')
end
else
modes{i}.probe_support = [];
end
if ~isempty(self.probe_support_fft) && i <= par.Nrec
modes{i}.probe_support_fft = fftshift(self.probe_support_fft);
if i == 1
verbose(2,'Using far-field probe support')
end
else
modes{i}.probe_support_fft = [];
end
F = mean( self.pixel_size)^2 .* mean(self.Np_p) / (modes{i}.lambda * modes{i}.distances);
if F ~= 0
verbose(3,'Nearfield propagation: Fresnel number/Npix %3.3g', F)
end
scale = modes{i}.scales(end);
modes{i}.ASM_factor = [] ;
modes{i}.cASM_factor = [] ;
if ~isinf(modes{i}.distances(end))
%% near field factor
ASM = exp( modes{i}.distances(end)* cache.ASM_difference);
modes{i}.ASM_factor = ASM;
modes{i}.cASM_factor = conj(ASM);
end
%% far field factor
modes{i}.FAR_factor = [];
modes{i}.cFAR_factor = conj(modes{i}.FAR_factor);
if isinf( par.probe_backpropagate)
modes{i}.support_fwd_propagation_factor = inf;
modes{i}.support_back_propagation_factor = -inf;
elseif par.probe_backpropagate ~= 0
[~, modes{i}.support_fwd_propagation_factor] = utils.prop_free_nf( self.probe{1}(:,:,1), par.probe_backpropagate,...
modes{i}.lambda, self.pixel_size ./ scale );
modes{i}.support_fwd_propagation_factor = fftshift( modes{i}.support_fwd_propagation_factor );
modes{i}.support_back_propagation_factor = conj(modes{i}.support_fwd_propagation_factor);
else
modes{i}.support_fwd_propagation_factor = [];
modes{i}.support_back_propagation_factor = [];
end
modes{i}.tilted_plane_propagate_fwd = tilted_plane_propagate_fwd;
modes{i}.tilted_plane_propagate_back = tilted_plane_propagate_back;
end
%%check fly scan positions
%{
figure
scatter(modes{1}.probe_positions(:,1),modes{1}.probe_positions(:,2),'.'); axis image
hold on
for i=2:par.Nmodes
disp(i)
scatter(modes{i}.probe_positions(:,1),modes{i}.probe_positions(:,2),'.'); axis image
end
%}
%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%% PREPARE PROBES, INCOHERENT MODES %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
probe_0 = mean(self.probe{1},3);
for i = 1:par.probe_modes
try
probe{i} = self.probe{i};
%% test if the probe size ok for the variable probe settings etc
assert( size(probe{i},4) == 1+par.variable_probe_modes || ...
~(par.variable_probe) || i > 1)
assert((size(probe{i},3) ==1 || par.variable_probe) || ...
(size(probe{i},3) == par.Nscans && ~par.share_probe) ) % no variable prob extension and multiple probes used
assert(size(probe{i},3) == par.Nscans || par.share_probe || par.variable_probe, 'Wrong probe size for not shared probe option')
catch
if i <= par.Nrec || is_used(par, 'fly_scan')
verbose(2, 'Creating probe')
if ~par.share_probe && size(probe{i},3) == 1
% dont share probe between scans
probe{i} = repmat(probe_0,[1,1,par.Nscans]);
end
if (par.variable_probe && par.variable_probe_modes > 0) && i == 1
verbose(2,'Creating variable probe ')
probe{i}(:,:,:,2:1+par.variable_probe_modes) = ...
randn([self.Np_p, size(probe{i},3), par.variable_probe_modes])+randn([self.Np_p,size(probe{i},3), par.variable_probe_modes])*1i;
continue
end
end
if length(probe) < i % none of above
% simply create slightly shifted modes in fourier domain, it is useful for
% inital guess of incoherent modes after orthogonalization
step = median(diff(self.probe_positions_0));
probe{i} = 0.01*fftshift(imshift_fft(fftshift(probe_0), randn, randn, false));
end
% fill the unreconstructed positions if the OPRP method is used
if par.variable_probe && is_method(par, 'PIE') && i ==1
ind_wrong = setdiff(1:self.Npos, [self.reconstruct_ind{:}]);
probe{i}(:,:,ind_wrong) = repmat(mean(probe{i},3),1,1,length(ind_wrong));
end
end
end
%% added by YJ: only use the primary probe for arbitrary-path fly-scan modes
if is_used(par, 'fly_scan') && par.Nrec > 1
for i = 2:par.Nrec %par.Nrec = par.Nmodes
probe{i} = probe{1};
end
end
%%
if par.probe_modes > par.Nrec
% orthogonalization of incoherent probe modes
if is_used(par, 'fly_scan')
probe_tmp = probe;
% orthogonalize the modes with all the other shifted modes
for i = 1:par.Nrec %par.Nrec = par.Nmodes
dx = median(modes{i}.probe_positions - modes{1}.probe_positions);
probe_tmp{i} = imshift_fft(probe_tmp{i}, dx);
end
probe_tmp = ortho_modes(probe_tmp); % perform othogonalization
probe(1+par.Nrec:par.probe_modes) = probe_tmp(1+par.Nrec:par.probe_modes);
else
ind = [1,1+par.Nrec:par.probe_modes]; % skip polyvave/multilayer probe_tmp
probe(ind) = ortho_modes_eig(probe(ind)); %% slightly better
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%% PREPARE OBJECT, MULTILAYER OBJECT %%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% updated illumination
aprobe2 = abs(self.probe{1}(:,:,1)).^2;
for ll = 1:par.Nscans
if par.share_object
ind = [self.reconstruct_ind{:}];
else
ind = self.reconstruct_ind{ll};
end
[cache.oROI_s{1}] = find_reconstruction_ROI( modes{1}.probe_positions,self.Np_o, self.Np_p);
% avoid oscilations by adding momentum term
illum_sum_0{ll} = Ggather(set_views(Gzeros(self.Np_o), Garray(aprobe2), 1,1, ind, cache));
end
%% multilayer extension
% if object has only a single layer, expand it to multiple using
% unwrapping
if size(self.object,2) > par.Nlayers
for ll = 1:par.Nscans
self.object{ll,1} = prod(cat(3,self.object{ll,:}),3);
end
self.object(:,2:end) = [];
end
if size(self.object,2) < par.Nlayers
N_add = par.Nlayers - size(self.object,2);
for ll = 1:size(self.object,1)
obj{ll} = self.object(ll,:);
for ii = 1:N_add
if mod(ii, 2) == 1
obj{ll}{end+1} = ones(self.Np_o, 'single') + 1e-9i*randn(self.Np_o, 'single'); % add empty slice at the end
else
obj{ll}(2:end+1) = obj{ll};
obj{ll}{1} = ones(self.Np_o, 'single') + 1e-9i*randn(self.Np_o, 'single'); % add empty slice at the beginning
end
end
end
self.object = cat(1, obj{:});
end
% if object has more layers but only one is needed
if size(self.object,2) > 1 && par.Nlayers == 1
for ll = 1:par.Nscans
object{ll,1} = prod(cat(3,self.object{ll,:}),3);
end
self.object = object;
end
for j = 1:par.Nlayers % add extra layers
for i = 1:max(1, par.Nscans * ~par.share_object)
try
object{i,j} = self.object{min(end,i),j};
object{i,j}(1);
catch
%% add fully transparent slice at the end
object{i,j} = ones(self.Np_o, 'single');
if size(self.object,2) == 1
% swap order of the new layers to keep the original
% reconstruction in center
object(i,:) = object(i,end:-1:1);
end
end
end
end
for i = 1:numel(object)
object{i} = single(object{i});
object{i} = complex(object{i});
end
for i = 1:numel(probe)
probe{i} = single(probe{i});
probe{i} = complex(probe{i});
end
%% STORE RESULTS TO SELF CLASS
self.object = object;
self.probe = probe;
self.modes = modes;
self.diffraction = Diffraction;
self.noise = Noise;
self.mask = Mask;
self.background = reshape(Background,1,1,[]);
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%%%%%%%%%% PRECALCULATE USEFUL VALUES %%%%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if ~isfield(self, 'probe_evolution' )
% initial coefficients for OPRP approximation
self.probe_evolution(:,1) = ones(self.Npos,1); % first mode is constant
end
new_probe_modes_ind = 1+(size(self.probe_evolution,2):par.variable_probe_modes);
self.probe_evolution(:,new_probe_modes_ind) = 1e-6*randn(self.Npos,length(new_probe_modes_ind));
if par.variable_probe
pnorm = norm2(self.probe{1});
self.probe{1}(:,:,:,2:end) = self.probe{1}(:,:,:,2:end) ./ pnorm(1,1,:,2:end);
self.probe_evolution(:,2:end) = self.probe_evolution(:,2:end) .* squeeze(mean(pnorm(1,1,:,2:end),3))';
end
if par.background_detection || ~isempty(self.background)
%% auto-estimate background correction distribution
if isempty(self.mask)
mask = 0;
else
mask = self.mask;
end
if par.background_detection
background_weight = sum(self.diffraction.^2,3) ./ max(1,sum(~mask,3));
background_weight = imgaussfilt(background_weight,1);
background_weight = 1./sqrt(max(1e-3, background_weight)) .* ~any(mask,3);
background_weight = max(0,background_weight - 0.3*mean(background_weight(:)));
cache.background_weight = ( fftshift_2D(background_weight / sum2(background_weight)));
end
if isinf(par.background_width)
cache.background_profile = 1;
else
mdiffr = Garray(fftshift(mean(get_modulus(self,cache,1:self.Npos,false).^2,3)));
W = par.background_width;
X = (-self.Np_p(1):self.Np_p(1)-1);
Y = (-self.Np_p(2):self.Np_p(2)-1);
[X,Y] = meshgrid(X,Y);
background_profile = exp(-sqrt( (X/W(1)).^2 +(Y/W(1)).^2));
background_profile = conv2(mdiffr,background_profile, 'same');
background_profile = background_profile / max2(background_profile);
background_profile = utils.crop_pad(background_profile,self.Np_p);
cache.background_profile = gather(fftshift(background_profile));
end
if ~isempty(self.diffraction_deform_matrix)
apply_deform = @(x,D)single(reshape(full(D * double(reshape(x,[],size(x,3)))), size(x)));
% apply deformation effects caused by tilted sample , be sure to enforce the mask before
% interpolation, the hotpixels can spread around after the correction
if isscalar(cache.background_profile)
cache.background_profile = ones(self.Np_p, 'single');
end
cache.background_profile = apply_deform(cache.background_profile, self.diffraction_deform_matrix');
end
else
cache.background_profile_weight = 1;
end
for ll = 1:par.Nscans
illum_sum_0{ll} = Ggather(illum_sum_0{ll});
cache.MAX_ILLUM(ll) = max(illum_sum_0{ll}(:));
cache.illum_sum_0{ll} = illum_sum_0{ll};
end
%% precalculate illumination ROIs
cache = precalculate_ROI(self,cache, Ggather(sqrt(aprobe2)));
%% prepare mask needed for subpixel shifts of object views
cache.apodwin = single(0.1+0.9*tukeywin(self.Np_p(1),0.05) .* tukeywin(self.Np_p(2), 0.05)');
if par.initial_probe_rescaling
%% initial rescaling of probe intensity , just a very rough guess
mean_aPsi = mean2(abs(fwd_fourier_proj(self.probe{1}(:,:,1), self.modes{end})).^2);
mean_diffraction_intensity = mean(mean2(self.diffraction(:,:,randi(self.Npos, [10,1])))); % take roughly average intensity
for ii = 1:par.probe_modes
self.probe{ii} = self.probe{ii} * sqrt( mean_diffraction_intensity / mean_aPsi);
end
end
end
@@ -0,0 +1,448 @@
% LOAD_FROM_P load parameters from the p-structure to param and self structures for GPU
% engine
%
% [self, param] = load_from_p(self, param, p)
%
%
% ** self structure containing inputs: e.g. current reconstruction results, data, mask, positions, pixel size, ..
% ** param structure containing parameters for the engines
% ** p ptychoshelves p structure
%
% returns:
% ++ self structure containing inputs: e.g. current reconstruction results, data, mask, positions, pixel size, ..
% ++ param structure containing parameters for the engines
function [self, param, p] = load_from_p(param, p)
import math.*
import utils.*
import engines.GPU.shared.*
[Np_p(1),Np_p(2),Npos] = size( p.fmag);
self.reconstruct_ind = p.scanidxs;
%% load default variables with different name from the main ptycho code
param.Nmodes = p.probe_modes;
param.plot_results_every = p.plot.interval;
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% additional features added by YJ
%for output intermediated results,
param.fout = p.fout;
if isfield(p, 'save_results_every')
param.save_results_every = p.save_results_every;
if param.save_results_every<=p.number_iterations
verbose(0, 'Intermediate results will be saved in the directory below every %i iterations.', param.save_results_every)
%disp(strcat('Intermediate results will be saved in the directory below every ',n)
%disp(param.fout)
verbose(0,param.fout)
end
end
if isfield(p, 'extraPrintInfo')
param.extraPrintInfo = p.extraPrintInfo;
end
if isfield(p, 'affine_matrix')
param.affine_matrix_init = p.affine_matrix;
end
if isfield(p, 'beam_source')
param.beam_source = p.beam_source;
end
if isfield(p, 'TV_lambda')
param.TV_lambda = p.TV_lambda;
end
if isfield(p,'avg_photon_threshold') && p.avg_photon_threshold>=0
avg_photon_threshold = p.avg_photon_threshold;
else %default
if isfield(param,'beam_source') && strcmp(param.beam_source,'electron')
avg_photon_threshold = 0.0001;
else
avg_photon_threshold = 0.01;
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%
% if defined in p, use from p otherwise use defaults in param
try;param.object_regular = p.reg_mu; end
try;param.probe_inertia = p.probe_regularization; end
if check_option(p,'opt_errmetric','poisson')
param.likelihood = 'poisson';
else
param.likelihood = 'L1';
end
if get_option(p,'background_width') && get_option(p,'binning')
param.background_width = param.background_width / 2^p.binning;
end
%% load variables from the main ptycho code and merge it with the defaults
for field = fieldnames(param)'
field = field{1};
if isfield(p, field)
param.(field) = p.(field);
end
end
% set verbosity for GPU engine
param.verbose_level = max(-2,p.verbose_level-2); % adjust verbosity for GPU code , verbose_level 0 is enough for commmon use
verbose(param.verbose_level)
% load additional reconstructed parameters , otherwise use default
for item = {{'background',[]}, {'intensity_corr',[]}, {'probe_fourier_shift',[]}, {'rotation',0},{'shear',0},{'relative_pixel_scale',1}}
item = item{1};
if isfield(p, item{1}) && ~isempty(p.(item{1}))
self.(item{1}) = p.(item{1});
else
self.(item{1}) = item{2};
end
end
if any(ismember(fieldnames(p), {'shear', 'rotation', 'relative_pixel_scale'})) && isfield(p, 'positions_0')
warning('Reseting probe positions to original values')
p.positions = p.positions_0;
p = rmfield(p, 'positions_0');
end
if isempty(p.affine_matrix)
p.affine_matrix = diag([1,1]);
end
self.diffraction_deform_matrix = [];
if ~check_option(p,'asize_presolve')
param.Np_p_presolve = [];
else
param.Np_p_presolve = min(p.asize_presolve, p.asize);
end
% Other parameters
param.fourier_ptycho = check_option(p,'fourier_ptycho');
param.upsampling_data_factor = p.detector.upsampling;
% Offaxis ptychography correction
if check_option(p, 'sample_rotation_angles')
param. sample_rotation_angles = p.sample_rotation_angles; % 3x1 vector rotation around [X,Y,beam] axes in degrees , apply a correction accounting for tilted plane oR the sample and ewald sphere curvature (high NA correction)
else
param. sample_rotation_angles = [0,0,0]; % conventional ptychography
end
self.pixel_size = p.dx_spec;
self.Np_p = Np_p;
Nscans = length(self.reconstruct_ind);
%%%%%%%%%%%%%%%%%%%%%%
%% load probes%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%
if param.share_probe
p.share_probe_ID(:) = 1; % enforce single object if sharing is requested
end
% normalization for consistency with the other CPU engines
probes = single(p.probes ./ (prod(sqrt(Np_p))*2*p.renorm));
for i = 1:min(p.probe_modes, size(probes,4))
% variable probe
if isfield(p, 'probe_PCA') && ~isempty(p.probe_PCA) && i == 1 && size(p.probe_PCA.eigen_vec,1) == p.asize(1) && param.variable_probe && is_method(param, 'PIE')
verbose(1,'Loading PCA probe (%i)', i)
self.probe{i} = reshape(p.probe_PCA.eigen_vec,prod(p.asize),[]) * p.probe_PCA.evolution' /(prod(sqrt(Np_p))*2*p.renorm);% normalization for consistency with the CPU code;
elseif isfield(p, 'probe_variable') && ~isempty(p.probe_variable) && i == 1 && size(p.probe_variable.eigen_vec,1) == p.asize(1) && param.variable_probe && is_method(param, 'ML')
% ML methods, OPRP approx
verbose(0,'Loading variable probe (%i)', i)
% constant part
self.probe{i}(:,:,:,1) = probes(:,:,:,1);
% variable part
self.probe{i}(:,:,:,2) = p.probe_variable.eigen_vec /(prod(sqrt(Np_p))*2*p.renorm);
% evolution of the variable part
self.probe_evolution = p.probe_variable.evolution ;
assert(length(self.probe_evolution)==Npos, 'Wrong size of variable probe coefficients')
else
% constant probe
verbose(1,'Loading constant probe (%i)', i)
if param.share_probe
self.probe{i} = mean(probes(:,:,:,i),3);
else
if size(p.probes,3) == Nscans
% one probe for each scan
self.probe{i} = probes(:,:,:,i);
else
% rather take only first to avoid issues
self.probe{i} = probes(:,:,1,i);
end
end
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% prepare support contraints%%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% provide estimate of the probe support
if check_option(p, 'probe_mask') && check_option(p,'use_probe_support') && any(p.probe_mask)
self.probe_support = ~p.probe_mask;
elseif check_option(p,'probe_support_radius') && p.probe_support_radius < sqrt(2)
% very useful for DM code
[X,Y] = meshgrid((-p.asize(2)/2+1:p.asize(2)/2)/p.asize(2), (-p.asize(1)/2+1:p.asize(1)/2)/p.asize(1));
self.probe_support = sqrt(X.^2+Y.^2) < p.probe_support_radius/2;
else
self.probe_support = [];
end
% estimate of the probe support in detector plane
if check_option(p,'probe_support_fft') && ~check_option(p, 'prop_regime', 'nearfield')
if ~check_option(p.model, 'probe_focal_length') && ~check_option(p.model, 'probe_outer_zone_width')
error('Missing model.probe_focal_length and model.probe_outer_zone_width of Fresnel zone plate' )
end
if ~check_option(p.model, 'probe_outer_zone_width')
p.model.probe_outer_zone_width = p.lambda * p.model.probe_focal_length / p.model.probe_diameter;
end
FZP_cone_diameter = p.lambda* p.z/(p.model.probe_outer_zone_width * p.ds);
% add some extra space
FZP_cone_diameter = FZP_cone_diameter * 1.2;
[X,Y] = meshgrid(-p.asize(2)/2+1:p.asize(2)/2, -p.asize(1)/2+1:p.asize(1)/2);
self.probe_support_fft = utils.imgaussfilt2_fft(sqrt(X.^2+Y.^2) < FZP_cone_diameter/2, FZP_cone_diameter/50);
af_probe = sqrt(abs(fftshift(fft2(self.probe{1}(:,:,1)))));
[cx, cy] = center(max(0,af_probe-0.1*max(af_probe(:))));
self.probe_support_fft = imshift_fast(self.probe_support_fft, -cx, -cy,[], 'nearest');
self.probe_support_fft = max(0, min(1, self.probe_support_fft));
verbose(1, 'Using farfield probe support constraint')
else
self.probe_support_fft = [];
end
%%%%%%%%%%%%%%%%%%%%%%
%% load object%%%%%%%%
%%%%%%%%%%%%%%%%%%%%%%
if param.share_object
p.share_object_ID(:) = 1; % enforce single object if sharing is requested
end
% correct positions for sample tilt
positions = p.positions ; %if start from a previous recon w. position correction, p.positions has the refined positions
if isfield(p, 'positions_0')
positions_0 = p.positions_0 ;
end
verbose(1, 'Load and pad object and probe')
% update current size of the object
for i = 1:length(p.object)
for j = 1:size(p.object{i},4) % load multiple layers of the object
self.object{i,j} = p.object{i}(:,:,1,j);
end
p.object_size(i,:) = size(self.object{i,1});
end
% calculate optimal size !! find minimal object to fit all scans
Np_o = max(p.object_size,[],1);
% expand object size if the probe p
if p.number_iterations > check_option(p, 'probe_position_search') && is_method(param, {'ML', 'PIE'})
extra = 0.2; % add plenty of extra space for geometry refinement
else
extra = 0.05; % do just a little of extra space
end
% shift the positions to account for the expanded object size AND
% center them !!! (GPU code assumes positions to be centered, better for scale / probe positions are unknown)
%%%%%%%%%%%%%%%%%%%%%%
%% load positions %%%%
%%%%%%%%%%%%%%%%%%%%%%
for i = unique(p.share_object_ID)
ind = [self.reconstruct_ind{p.share_object_ID == i}];
position_offset = 1+floor((max(positions(ind,:))-min(positions(ind,:)))/2 + min(positions(ind,:)) );
if isfield(p, 'positions_0') %previous recon has position correction
self.probe_positions_0(ind,:) = positions_0(ind,:) - position_offset; %remember initial positions
self.probe_positions(ind,:) = positions(ind,:) - position_offset;
else
self.probe_positions_0(ind,:) = positions(ind,:) - position_offset;
self.probe_positions = [];
end
end
% get object extent
self.Np_o = max(Np_o, ceil((1+extra) * ( self.Np_p + (max(self.probe_positions_0) - min(self.probe_positions_0)) )));
% store object size without padding, useful for plotting
p.object_size = max(p.object_size, ceil(( self.Np_p + (max(self.probe_positions_0) - min(self.probe_positions_0)) )));
self.probe_positions_0 = self.probe_positions_0(:,[2,1]); %swap x&y axis
if ~isempty(self.probe_positions)
self.probe_positions = self.probe_positions(:,[2,1]);
end
self.Npos = Npos;
% only a relative correction with respect to the affine matrix already
% applied in p-struct
for ii = 1:Nscans
self.affine_matrix{ii} = diag([1,1]);
end
%%%%%%%%%%%%%%%%%%%%%%
%% adjust object %%%%%
%%%%%%%%%%%%%%%%%%%%%%
for i = 1:size(self.object,1)
for layer = 1:size(self.object,2)
% if needed expand the object to allow position refinement
% and shift for consistency with the CPU code
self.object{i,layer} = imshift_fast(self.object{i,layer},1,1,self.Np_o, 'nearest', mean(self.object{i,layer}(:)));
end
end
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%% load data, mask noise %%%%
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
verbose(1, 'Preparing data and masks')
assert(all(isfinite(p.fmag(:))), 'Provide p.fmag contains NaN/Inf')
self.noise = [];
self.diffraction = (single(p.fmag .* p.fmask) / single(p.renorm) ).^2;
self.mask = (~p.fmask);
if check_option(p, 'damped_mask')
% if relaxed mask is used, try to fill the smallest gaps (hot pixels) by neighbors
mask_ind = find(self.mask);
self.diffraction(mask_ind) = self.diffraction(min(mask_ind+1, numel(self.diffraction)));
end
low_photon_count_dp = sum(sum(self.diffraction)) / prod(p.asize) < avg_photon_threshold;
if any(low_photon_count_dp)
error('%0.2f%% diffraction patterns has average photon count < %f', sum(low_photon_count_dp)/size(self.diffraction,3)*100, avg_photon_threshold)
end
%% automatic data centering / flipping / tilted plane correction
if check_option(p, 'auto_center_data') || check_option(p, 'custom_data_flip') || check_option(p, 'sample_rotation_angles')
self.diffraction = fftshift_2D(self.diffraction);
self.mask = fftshift_2D(self.mask);
warning on
warning off backtrace
if check_option(p, 'auto_center_data')
warning('Enforcing automatic data centering')
for ii = 1:Nscans
[x0,y0]=math.center(abs(fftshift(fft2(fftshift(self.probe{1}(:,:,min(end,ii)))))));
W = mean2(self.diffraction(:,:,self.reconstruct_ind{ii}));
W = ((W - min(W)) / (max(W)-min(W))).^4; % give more weight to the more transpared regions (air)
avg_pattern = mean(W.*sqrt(max(0,single(self.diffraction(:,:,self.reconstruct_ind{ii})))),3);
[x,y]=math.center(avg_pattern);
x = round(x-x0); y = round(y-y0);
self.diffraction(:,:,self.reconstruct_ind{ii}) = imshift_fast(self.diffraction(:,:,self.reconstruct_ind{ii}),x,y);
self.mask(:,:,self.reconstruct_ind{ii}) = imshift_fast(self.mask(:,:,self.reconstruct_ind{ii}), x,y);
fprintf('Data in scan %i shifted by %i %i pixels\n', ii, x,y);
end
if ~isempty(self.probe_support_fft )
self.probe_support_fft = imshift_fast(self.probe_support_fft , x,y);
end
end
% apply custom flip of the diffraction data
if check_option(p, 'custom_data_flip') && any(p.custom_data_flip)
warning('Applying custom data flip: %i %i %i ', p.custom_data_flip(1), p.custom_data_flip(2), p.custom_data_flip(3))
if p.custom_data_flip(1)
self.diffraction = flipud(self.diffraction);
self.mask = flipud(self.mask);
end
if p.custom_data_flip(2)
self.diffraction = fliplr(self.diffraction);
self.mask = fliplr(self.mask);
end
if p.custom_data_flip(3)
self.diffraction = permute(self.diffraction, [2,1,3]);
self.mask = permute(self.mask, [2,1,3]);
end
end
%
if isfield(p, 'sample_rotation_angles') && any(p.sample_rotation_angles) && check_option(p, 'apply_tilted_plane_correction', 'diffraction')
%% OFFAXIS PTYCHOGRAPHY CORRECTION
if utils.verbose > -1
warning('Applying tilted plane correction: %3.5g %3.3g %3.3g\n Note that current implementation assumes low NA illumination, if this is not true, the central diffraction cone can be malformed', p.sample_rotation_angles(1), p.sample_rotation_angles(2), p.sample_rotation_angles(3))
end
% create matrix of deformation to apply effects similar to
deform_mat = get_tilted_plane_correction_matrix(max(self.Np_p), ...
p.z ,p.detectors{1}.pixel_size, ...
p.sample_rotation_angles(1),...
p.sample_rotation_angles(2),...
p.sample_rotation_angles(3));
if self.Np_p(1) ~= self.Np_p(2)
% quick fix for asymmetric probe dimensions
blank = true(self.Np_p);
blank_ind = find(utils.crop_pad(blank,[ max(self.Np_p), max(self.Np_p)]));
deform_mat = deform_mat(blank_ind,blank_ind);
end
apply_deform = @(x,D)single(reshape(full(D * double(reshape(x,[],size(x,3)))), size(x)));
plotting.smart_figure(3423)
ax(1)=subplot(1,2,1);
imagesc(log(1+max(self.diffraction, [],3))); axis off image xy ; colormap(plotting.franzmap)
title('Diffraction BEFORE tilted plane correction')
% apply deformation effects caused by tilted sample , be sure to enforce the mask before
% interpolation, the hotpixels can spread around after the correction
self.diffraction = apply_deform(self.diffraction .* ~self.mask, deform_mat');
self.mask = apply_deform(self.mask, deform_mat') > 0;
% plotting.imagesc3D(log(1+max(self.diffraction,[],3))); grid on
ax(2)=subplot(1,2,2);
imagesc(log(1+max(self.diffraction, [],3))); axis off image xy ; colormap(plotting.franzmap)
title('Diffraction AFTER tilted plane correction')
plotting.suptitle('Effect of tilted ptychography correction')
linkaxes(ax, 'xy')
drawnow
self.diffraction_deform_matrix = deform_mat;
end
warning on
self.diffraction = ifftshift_2D(self.diffraction);
self.mask = ifftshift_2D(self.mask);
end
self.filename = [p.detector.data_prefix, p.run_name];
% other basic parameters
self.path = '';
if check_option(p, 'prop_regime', 'nearfield')
self.z_distance = p.z;
else
self.z_distance = inf; % farfield
end
% multilayer extension
if isfield(p, 'delta_z')
assert(all(isfinite(p.delta_z)), 'Some of the provided layer distanced delta_z is not finite' )
self.z_distance = [p.delta_z(:)', self.z_distance];
end
self.lambda = p.lambda;
self.diff_pattern_blur = 0; % incoherent smoothing
self.modes = [];
% keep p structure for plotting purposes
param.p = p;
% if requested, shift the average probe to center and shift the object
% to correspond to the probe shift
if check_option(p, 'auto_center_probe')
[x,y] = center(mean(abs(self.probe{1}(:,:,:,1))));
for ii = 1:numel(self.probe)
self.probe{ii} = imshift_fft(self.probe{ii}, -x,-y);
end
for ii = 1:numel(self.object)
self.object{ii} = imshift_fft(self.object{ii}, -x, -y);
end
end
% check if all is ok (remove in future !!!)
positions = self.probe_positions_0(:,[2,1]);
positions = bsxfun(@plus, positions, ceil(self.Np_o/2-self.Np_p/2));
positions = round(positions);
range = ([min(positions(:,1)), max(positions(:,1))+ Np_p(1), min(positions(:,2)), max(positions(:,2))+ Np_p(2)]);
if range(1) < 0 || range(2) > self.Np_o(1) || range(3) < 0 || range(4) > self.Np_o(2)
warning('Object size is too small, not enough space for probes !! ')
end
end
@@ -0,0 +1,97 @@
% PREPARE_FLYSCAN_POSITIONS from finit number of measured position interpolate possitions for each
% measured frame when fly scan is used
%
% self = prepare_flyscan_positions(self, par)
%
%
% ** self structure containing inputs: e.g. current reconstruction results, data, mask, positions, pixel size, ..
% ** par structure containing parameters for the engines
%
% returns:
% ++ self structure containing inputs: e.g. current reconstruction results, data, mask, positions, pixel size, ..
function self = prepare_flyscan_positions(self, par)
import engines.GPU.GPU_wrapper.*
import math.*
import utils.*
import plotting.*
switch par.flyscan_trajectory
case 'line' % line scan w. big jumps
jumps = diff(self.probe_positions_0);
%step = median(jumps,1);
jumps = sum(abs(jumps),2);
% empirical condition
jumps = find(jumps > 10*median(jumps));
%% ADVANCED FLY SCAN - LINE SCAN. Modified by YJ for more general use
% interpolate the other modes into new positions
pos = self.modes{1}.probe_positions;
%pos(:,1): horizontal positions in pixels
%pos(:,2): vertical positions in pixels
N_lines = length(jumps)+1;
jumps = [0; jumps; length(pos(:,1))];
for ll = 1:par.Nmodes
ratio = par.flyscan_dutycycle*(ll-1)/par.Nmodes;
pos_temp = pos;
p_lb = 1;
for i=1:N_lines
x = 1:(jumps(i+1)-jumps(i));
x_interp = (x(1)+ratio):1:(x(end)+ratio);
p_ub = p_lb + length(x)-1;
pos_temp(p_lb:p_ub,1) = interp1(x,pos(p_lb:p_ub,1),x_interp,'spline');
pos_temp(p_lb:p_ub,2) = interp1(x,pos(p_lb:p_ub,2),x_interp,'spline');
self.modes{ll}.probe_positions = pos_temp;
p_lb = p_ub+1;
end
end
%{
%MO's code - may have some bugs
for ll = 1:par.Nmodes
ratio = par.flyscan_dutycycle*(ll-1)/par.Nmodes;
self.modes{ll}.probe_positions = pos(min((1:self.Npos)+1,self.Npos),:)*ratio + (1-ratio)*pos;
if ~isempty(jumps)
% expected step continuation
self.modes{ll}.probe_positions(jumps,:) = bsxfun(@plus, self.modes{ll}.probe_positions(jumps-1,:),step);
end
end
%}
case 'continuous' % assume that smooth path is used
for ii = 1:par.Nscans
ind = self.reconstruct_ind{ii};
pos = self.probe_positions_0(ind,:);
pos_temp = pos;
for ll = 1:par.Nmodes
ratio = par.flyscan_dutycycle*(ll-1)/par.Nmodes;
x = 1:size(pos,1);
x_interp = (x(1)+ratio):1:(x(end)+ratio);
pos_temp(:,1) = interp1(x,pos(:,1),x_interp,'spline');
pos_temp(:,2) = interp1(x,pos(:,2),x_interp,'spline');
self.modes{ll}.probe_positions(ind,:) = pos_temp;
end
%{
% MO's code - seems only work for spiral trajectory
assert(~any(isfinite(par.probe_position_search)), 'Position refinement and fly scans not suported')
ind = self.reconstruct_ind{ii};
pos = self.probe_positions_0(ind,:);
[ang, rad] = cart2pol(pos(:,1)-pos(1,1), pos(:,2)-pos(1,2));
ang = unwrap(ang);
% get interpolate d positions of the sub probes
ang_all = ang + (par.flyscan_offset -0.5+linspace(0,par.flyscan_dutycycle*(par.Nmodes-1)/par.Nmodes, par.Nmodes) ).*[diff(ang);0];
rad_all = interp1(ang, rad, ang_all, 'pchip');
[X,Y] = pol2cart(ang_all, rad_all);
for ll = 1:par.Nmodes
self.modes{ll}.probe_positions(ind,:) = pos(1,1:2) + [X(:,ll), Y(:,ll)];
%if iter == 1; self.probe{ll} = self.probe{1}; end
end
%}
end
case 'external' % use positions from external measurements
end
end
@@ -0,0 +1,157 @@
% DISTMAT Compute a Distance Matrix for One or Two Sets of Points
%
%
%
% Copyright (c) 2015, Joseph Kirk
% All rights reserved.
%
% Redistribution and use in source and binary forms, with or without
% modification, are permitted provided that the following conditions are
% met:
%
% * Redistributions of source code must retain the above copyright
% notice, this list of conditions and the following disclaimer.
% * Redistributions in binary form must reproduce the above copyright
% notice, this list of conditions and the following disclaimer in
% the documentation and/or other materials provided with the distribution
%
% THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
% AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
% IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
% ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
% LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
% CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
% SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
% INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
% CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
% ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
% POSSIBILITY OF SUCH DAMAGE.
%
% Filename: distmat.m
%
% Description: Computes a matrix of pair-wise distances between points in
% A and B, using one of {euclidean,cityblock,chessboard} methods
%
% Author:
% Joseph Kirk
% jdkirk630@gmail.com
%
% Date: 02/27/15
%
% Release: 2.0
%
% Inputs:
% A - (required) MxD matrix where M is the number of points in D dimensions
% B - (optional) NxD matrix where N is the number of points in D dimensions
% if not provided, B is set to A by default
% METHOD - (optional) string specifying one of the following distance methods:
% 'euclidean' Euclidean distance (default)
% 'taxicab','manhattan','cityblock' Manhattan distance
% 'chebyshev','chessboard','chess' Chebyshev distance
% 'grid','diag' Diagonal grid distance
%
% Outputs:
% DMAT - MxN matrix of pair-wise distances between points in A and B
%
% Usage:
% dmat = distmat(a)
% -or-
% dmat = distmat(a,b)
% -or-
% dmat = distmat(a,method)
% -or-
% dmat = distmat(a,b,method)
%
% Example:
% % Pairwise Euclidean distances within a single set of 2D points
% xy = 10*rand(25,2); % 25 points in 2D
% dmat = distmat(xy);
% figure; plot(xy(:,1),xy(:,2),'.');
% for i=1:25, text(xy(i,1),xy(i,2),[' ' num2str(i)]); end
% figure; imagesc(dmat); colorbar
%
% Example:
% % Pairwise Manhattan distances within a single set of 2D points
% xy = 10*rand(25,2); % 25 points in 2D
% dmat = distmat(xy,'cityblock');
% figure; plot(xy(:,1),xy(:,2),'.');
% for i=1:25, text(xy(i,1),xy(i,2),[' ' num2str(i)]); end
% figure; imagesc(dmat); colorbar
%
% Example:
% % Pairwise Chebyshev distances within a single set of 2D points
% xy = 10*rand(25,2); % 25 points in 2D
% dmat = distmat(xy,'chebyshev');
% figure; plot(xy(:,1),xy(:,2),'.');
% for i=1:25, text(xy(i,1),xy(i,2),[' ' num2str(i)]); end
% figure; imagesc(dmat); colorbar
%
% Example:
% % Inter-point Euclidean distances for 2D points
% xy = 10*rand(15,2); % 15 points in 2D
% uv = 10*rand(25,2); % 25 points in 2D
% dmat = distmat(xy,uv);
% figure; plot(xy(:,1),xy(:,2),'.');
% for i=1:15, text(xy(i,1),xy(i,2),[' ' num2str(i)]); end
% figure; plot(uv(:,1),uv(:,2),'.');
% for i=1:25, text(uv(i,1),uv(i,2),[' ' num2str(i)]); end
% figure; imagesc(dmat); colorbar
%
% See also:
%
function dmat = distmat(a,varargin)
% Set defaults
method = 'euclidean';
b = a;
% Error check primary input
if ~isnumeric(a)
error('Expecting a matrix of floating point values for A input.');
end
% Process optional inputs
for var = varargin
arg = var{1};
if ischar(arg)
method = arg;
elseif ~isempty(arg)
b = arg;
end
end
% Check input dimensionality
[na,aDims] = size(a);
[nb,bDims] = size(b);
if (aDims ~= bDims)
error('Input matrices must have the same dimensionality.');
end
% Create index matrices
[j,i] = meshgrid(1:nb,1:na);
% Compute array of inter-point differences
delta = a(i,:) - b(j,:);
% Compute distance by specified method
dmat = zeros(na,nb);
switch lower(method)
case {'euclidean','euclid'}
% Euclidean distance
dmat(:) = sqrt(sum(delta.^2,2));
case {'cityblock','city','block','manhattan','taxicab','taxi'}
% Cityblock distance
dmat(:) = sum(abs(delta),2);
case {'chebyshev','cheby','chessboard','chess'}
% Chebyshev distance
dmat(:) = max(abs(delta),[],2);
case {'grid','diag'}
dmat(:) = max(abs(delta),[],2) + (sqrt(2) - 1)*min(abs(delta),[],2);
otherwise
error('Unrecognized distance method %s',method);
end
end
@@ -0,0 +1,300 @@
% GET_CLOSE_INDICES simple based method to select indices for DM
% !! GPU needs the sets to be with similar , ideally the same sizes !!!
%
% [indices_out, scan_ids_out] = get_close_indices(self, cache, par )
%
%
% ** self structure containing inputs: e.g. current reconstruction results, data, mask, positions, pixel size, ..
% ** par structure containing parameters for the engines
% ** cache structure with precalculated values to avoid unnecessary overhead
%
% returns:
% ++ indices_out cell of arrays, contain indices of positions processed in parallel
% ++ scan_ids_out cell of arrays, contain scan numbers for each position
% Academic License Agreement
%
% Source Code
%
% Introduction
% This license agreement sets forth the terms and conditions under which the PAUL SCHERRER INSTITUT (PSI), CH-5232 Villigen-PSI, Switzerland (hereafter "LICENSOR")
% will grant you (hereafter "LICENSEE") a royalty-free, non-exclusive license for academic, non-commercial purposes only (hereafter "LICENSE") to use the cSAXS
% ptychography MATLAB package computer software program and associated documentation furnished hereunder (hereafter "PROGRAM").
%
% Terms and Conditions of the LICENSE
% 1. LICENSOR grants to LICENSEE a royalty-free, non-exclusive license to use the PROGRAM for academic, non-commercial purposes, upon the terms and conditions
% hereinafter set out and until termination of this license as set forth below.
% 2. LICENSEE acknowledges that the PROGRAM is a research tool still in the development stage. The PROGRAM is provided without any related services, improvements
% or warranties from LICENSOR and that the LICENSE is entered into in order to enable others to utilize the PROGRAM in their academic activities. It is the
% LICENSEEs responsibility to ensure its proper use and the correctness of the results.
% 3. THE PROGRAM IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
% A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. IN NO EVENT SHALL THE LICENSOR, THE AUTHORS OR THE COPYRIGHT
% HOLDERS BE LIABLE FOR ANY CLAIM, DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY ARISING FROM, OUT OF OR IN CONNECTION WITH THE PROGRAM OR THE USE
% OF THE PROGRAM OR OTHER DEALINGS IN THE PROGRAM.
% 4. LICENSEE agrees that it will use the PROGRAM and any modifications, improvements, or derivatives of PROGRAM that LICENSEE may create (collectively,
% "IMPROVEMENTS") solely for academic, non-commercial purposes and that any copy of PROGRAM or derivatives thereof shall be distributed only under the same
% license as PROGRAM. The terms "academic, non-commercial", as used in this Agreement, mean academic or other scholarly research which (a) is not undertaken for
% profit, or (b) is not intended to produce works, services, or data for commercial use, or (c) is neither conducted, nor funded, by a person or an entity engaged
% in the commercial use, application or exploitation of works similar to the PROGRAM.
% 5. LICENSEE agrees that it shall make the following acknowledgement in any publication resulting from the use of the PROGRAM or any translation of the code into
% another computing language:
% "Data processing was carried out using the cSAXS ptychography MATLAB package developed by the Science IT and the coherent X-ray scattering (CXS) groups, Paul
% Scherrer Institut, Switzerland."
%
% Additionally, any publication using the package, or any translation of the code into another computing language should cite for difference map:
% P. Thibault, M. Dierolf, A. Menzel, O. Bunk, C. David, F. Pfeiffer, High-resolution scanning X-ray diffraction microscopy, Science 321, 379382 (2008).
% (doi: 10.1126/science.1158573),
% for mixed coherent modes:
% P. Thibault and A. Menzel, Reconstructing state mixtures from diffraction measurements, Nature 494, 6871 (2013). (doi: 10.1038/nature11806),
% for LSQ-ML method
% M. Odstrcil, A. Menzel, M.G. Sicairos, Iterative least-squares solver for generalized maximum-likelihood ptychography, Optics Express, 2018
% for OPRP method
% M. Odstrcil, P. Baksh, S. A. Boden, R. Card, J. E. Chad, J. G. Frey, W. S. Brocklesby, "Ptychographic coherent diffractive imaging with orthogonal probe relaxation." Optics express 24.8 (2016): 8360-8369
% and/or for multislice:
% E. H. R. Tsai, I. Usov, A. Diaz, A. Menzel, and M. Guizar-Sicairos, X-ray ptychography with extended depth of field, Opt. Express 24, 2908929108 (2016).
% 6. Except for the above-mentioned acknowledgment, LICENSEE shall not use the PROGRAM title or the names or logos of LICENSOR, nor any adaptation thereof, nor the
% names of any of its employees or laboratories, in any advertising, promotional or sales material without prior written consent obtained from LICENSOR in each case.
% 7. Ownership of all rights, including copyright in the PROGRAM and in any material associated therewith, shall at all times remain with LICENSOR, and LICENSEE
% agrees to preserve same. LICENSEE agrees not to use any portion of the PROGRAM or of any IMPROVEMENTS in any machine-readable form outside the PROGRAM, nor to
% make any copies except for its internal use, without prior written consent of LICENSOR. LICENSEE agrees to place the following copyright notice on any such copies:
% © All rights reserved. PAUL SCHERRER INSTITUT, Switzerland, Laboratory for Macromolecules and Bioimaging, 2017.
% 8. The LICENSE shall not be construed to confer any rights upon LICENSEE by implication or otherwise except as specifically set forth herein.
% 9. DISCLAIMER: LICENSEE shall be aware that Phase Focus Limited of Sheffield, UK has an international portfolio of patents and pending applications which relate
% to ptychography and that the PROGRAM may be capable of being used in circumstances which may fall within the claims of one or more of the Phase Focus patents,
% in particular of patent with international application number PCT/GB2005/001464. The LICENSOR explicitly declares not to indemnify the users of the software
% in case Phase Focus or any other third party will open a legal action against the LICENSEE due to the use of the program.
% 10. This Agreement shall be governed by the material laws of Switzerland and any dispute arising out of this Agreement or use of the PROGRAM shall be brought before
% the courts of Zürich, Switzerland.
function [indices_out, scan_ids_out] = get_close_indices(self, cache, par )
import math.*
import utils.*
grouping = par.grouping;
% in case of a shared scan join together all positions to find the optimal groups
group_across_scans = true;
if par.share_object && group_across_scans
Nsets = 1;
else
Nsets = par.Nscans;
end
cluster_refinement_time = 0;
cluster_time = 0;
% in simplest case process all positions together
if Nsets == 1 && grouping >= self.Npos
indices_out = {[self.reconstruct_ind{:}]};
scan_ids_out{1} = [];
for ii = 1:length(self.reconstruct_ind)
scan_ids_out{1} = [scan_ids_out{1}; ii*ones(length(self.reconstruct_ind{ii}),1)];
end
return
end
%rng default
for kk = 1:Nsets
% take them sequentially but with random offset
if par.share_object && group_across_scans
% join all indices into one large set if the object is shared
indices_0 = [self.reconstruct_ind{:}];
for ii = 1:length(self.reconstruct_ind)
scans_0(self.reconstruct_ind{ii}) = ii;
end
else
indices_0 = self.reconstruct_ind{kk};
scans_0 = kk * ones(size(indices_0)); % scan number
end
N = length(indices_0);
Ngroups=ceil(N/grouping);
positions = self.probe_positions_0(indices_0,:);
Npos = length(positions);
% get initial set distribution
[groups, C, sum_D, D] = get_best_kmeans(positions, Ngroups);
iter = 0;
t0 = tic;
while true
iter= iter +1;
[nbins,bins] = hist(groups, unique(groups));
% if less than 2 types of groups are present, finish
Ngroups_sizes = length(unique(nbins));
% try to find distribution with most similar sets sizes, if not
% easy, end with suboptimal distribution after 50 iterations
if ( Ngroups_sizes <= max(2, ceil(iter/1e3)) && (Ngroups*grouping ~= N || iter > 1e3 )) ...
|| Ngroups_sizes == 1 % choose suboptimal solution if better is not found soon
break
end
% find group with lowest number of members , add new points into
% this group
min_group = bins(argmin(nbins));
large_groups = bins(nbins>grouping);
if isempty(large_groups) || any(ismember(min_group, large_groups)) ; break; end
% choose closest position from the largest group to be moved to the
% smallest group
ind_large = (D(:,min_group) == min(D(ismember(groups, large_groups), min_group)));
groups(ind_large) = min_group;
end
% remove empty groups
ugroups = unique(groups);
Ngroups = length(ugroups);
groups = sum((1:Ngroups) .*(groups == ugroups'),2);
cluster_time = cluster_time + toc(t0);
for ii = 1:Ngroups
C(ii,:) = median(positions(groups == ii,:));
end
for ii = 1:Ngroups
D(:,ii) = (sum((positions - C(ii,:)).^2,2));
end
t0 = tic;
%% find more compact refinement
% find the most distanced points
[~,sind] = sort(D,2);
% positions to be improved -> find the best matching group
optimal_group = sind(:,1);
nonoptimal_ratio_0 = 1;
for iter = 1:10
ind_switch = (groups ~= optimal_group);
nonoptimal_ratio = sum(ind_switch) / numel(ind_switch);
if nonoptimal_ratio > 0
verbose(0, 'Indexes to be switched: %3.2g%% positions', nonoptimal_ratio * 100)
end
if nonoptimal_ratio >= nonoptimal_ratio_0
break
end
nonoptimal_ratio_0 = nonoptimal_ratio;
max_dist_0 = inf;
for i = 1:sum(ind_switch)
% calculate distance for each point from its group center
center_dist = (D(sub2ind(size(D), (1:Npos)', groups)));
max_dist_0 = max(center_dist(ind_switch));
% start from the worst case
ind_worse = find(max(center_dist(ind_switch)) == center_dist, 1, 'first');
% initial group
group_old = groups(ind_worse);
% better fitting group
group_new = optimal_group(ind_worse);
% position to be switched in the new group
ind_new = find(D(:,group_old) == min(D(groups == group_new, group_old)), 1, 'first');
% switch the group members
groups(ind_worse) = group_new;
groups(ind_new) = group_old;
ind_switch([ind_worse, ind_new]) = 0;
if all(ind_switch == 0)
break
end
end
% ind_switch = (groups ~= sind(:,1));
% for ii = Ngroups
% clf
% hold all;
% ind = groups == ii;
% ax = plot(self.probe_positions_0(ind & ind_switch, 1), self.probe_positions_0(ind & ind_switch, 2), 'o');
% ax2 = plot(self.probe_positions_0(ind & ~ind_switch, 1), self.probe_positions_0(ind & ~ind_switch, 2), 'x');
% try; ax2.Color = ax.Color; end
% plot(C(ii,1),C(ii,2),'x','Linewidth', 2)
% % drawnow
% % pause(1)
% end
% title(num2str(iter))
% axis tight equal
% pause(1)
%
end
cluster_refinement_time = cluster_refinement_time + toc(t0);
%% optimally sort the indices to help GPU
[nbins,bins] = hist(groups, unique(groups));
[~,ind] = sort(nbins,2,'descend');
for ii = 1:length(bins)
indices{kk}{ii} = indices_0((groups == bins(ind(ii))));
scan_ids{kk}{ii} = scans_0((groups == bins(ind(ii))));
end
verbose(2,'=== Number of cluster sizes %i', length(unique(nbins)))
end
verbose(0,'=== Position clusters found in %i iterations in %3.2gs', iter, cluster_time)
verbose(0,'=== Position clusters refined in %i iterations in %3.2gs', iter, cluster_refinement_time)
%rng shuffle
if verbose() > 1 && Ngroups_sizes > 1
warning('Unequal group sizes, it may cause slower calculation')
end
indices_out = horzcat(indices{:});
scan_ids_out = horzcat(scan_ids{:});
if Ngroups == 1 && Nsets == 1
%% merge groups from multiple scans into larger chunks if grouping is too large
indices_out = {horzcat(indices_out{:})};
scan_ids_out = {horzcat(scan_ids_out{:})};
end
%% sort them to minimize allocation of new projection matrices
Nitems = cellfun(@length, indices_out);
if all(max(Nitems) - min(Nitems) <= 1) && all(Nitems > 100)
% just neglect one scanning position to keep the bunches with the same
% size -> faster run on GPU
for i = 1:length(indices_out)
indices_out{i} = indices_out{i}(1:min(Nitems));
scan_ids_out{i} = scan_ids_out{i}(1:min(Nitems));
end
else
[~,ind] = sort(Nitems(:),1,'descend' );
indices_out = indices_out(ind);
scan_ids_out = scan_ids_out(ind);
end
end
function [groups, C, sum_D, D] = get_best_kmeans(positions, Ngroups)
% make several guesses to get better Kmean distribution
warning('off','stats:kmeans:FailedToConverge')
for i = 1:10
[groups{i}, C{i}, sum_D{i}, D{i}] = kmeans(positions, Ngroups);
nbins = hist(groups{i}, unique(groups{i}));
score(i) = std(nbins);
end
best = math.argmin(score);
groups = groups{best};
C = C{best};
sum_D = sum_D{best};
D = D{best};
end
@@ -0,0 +1,203 @@
% GET_NONOVERLAPPING_INDICES a heuristic based method to select pseudorandom indices of non overlapping regions
% Note: It can be slow for large number of scanning positions
%
% [indices_out, scan_ids_out] = get_nonoverlapping_indices(self, cache, par )
%
% ** self structure containing inputs: e.g. current reconstruction results, data, mask, positions, pixel size, ..
% ** par structure containing parameters for the engines
% ** cache structure with precalculated values to avoid unnecessary overhead
%
% returns:
% ++ indices_out cell of arrays, contain indices of positions processed in parallel
% ++ scan_ids_out cell of arrays, contain scan numbers for each position
% Academic License Agreement
%
% Source Code
%
% Introduction
% This license agreement sets forth the terms and conditions under which the PAUL SCHERRER INSTITUT (PSI), CH-5232 Villigen-PSI, Switzerland (hereafter "LICENSOR")
% will grant you (hereafter "LICENSEE") a royalty-free, non-exclusive license for academic, non-commercial purposes only (hereafter "LICENSE") to use the cSAXS
% ptychography MATLAB package computer software program and associated documentation furnished hereunder (hereafter "PROGRAM").
%
% Terms and Conditions of the LICENSE
% 1. LICENSOR grants to LICENSEE a royalty-free, non-exclusive license to use the PROGRAM for academic, non-commercial purposes, upon the terms and conditions
% hereinafter set out and until termination of this license as set forth below.
% 2. LICENSEE acknowledges that the PROGRAM is a research tool still in the development stage. The PROGRAM is provided without any related services, improvements
% or warranties from LICENSOR and that the LICENSE is entered into in order to enable others to utilize the PROGRAM in their academic activities. It is the
% LICENSEEs responsibility to ensure its proper use and the correctness of the results.
% 3. THE PROGRAM IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
% A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. IN NO EVENT SHALL THE LICENSOR, THE AUTHORS OR THE COPYRIGHT
% HOLDERS BE LIABLE FOR ANY CLAIM, DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY ARISING FROM, OUT OF OR IN CONNECTION WITH THE PROGRAM OR THE USE
% OF THE PROGRAM OR OTHER DEALINGS IN THE PROGRAM.
% 4. LICENSEE agrees that it will use the PROGRAM and any modifications, improvements, or derivatives of PROGRAM that LICENSEE may create (collectively,
% "IMPROVEMENTS") solely for academic, non-commercial purposes and that any copy of PROGRAM or derivatives thereof shall be distributed only under the same
% license as PROGRAM. The terms "academic, non-commercial", as used in this Agreement, mean academic or other scholarly research which (a) is not undertaken for
% profit, or (b) is not intended to produce works, services, or data for commercial use, or (c) is neither conducted, nor funded, by a person or an entity engaged
% in the commercial use, application or exploitation of works similar to the PROGRAM.
% 5. LICENSEE agrees that it shall make the following acknowledgement in any publication resulting from the use of the PROGRAM or any translation of the code into
% another computing language:
% "Data processing was carried out using the cSAXS ptychography MATLAB package developed by the Science IT and the coherent X-ray scattering (CXS) groups, Paul
% Scherrer Institut, Switzerland."
%
% Additionally, any publication using the package, or any translation of the code into another computing language should cite for difference map:
% P. Thibault, M. Dierolf, A. Menzel, O. Bunk, C. David, F. Pfeiffer, High-resolution scanning X-ray diffraction microscopy, Science 321, 379382 (2008).
% (doi: 10.1126/science.1158573),
% for mixed coherent modes:
% P. Thibault and A. Menzel, Reconstructing state mixtures from diffraction measurements, Nature 494, 6871 (2013). (doi: 10.1038/nature11806),
% for LSQ-ML method
% M. Odstrcil, A. Menzel, M.G. Sicairos, Iterative least-squares solver for generalized maximum-likelihood ptychography, Optics Express, 2018
% for OPRP method
% M. Odstrcil, P. Baksh, S. A. Boden, R. Card, J. E. Chad, J. G. Frey, W. S. Brocklesby, "Ptychographic coherent diffractive imaging with orthogonal probe relaxation." Optics express 24.8 (2016): 8360-8369
% and/or for multislice:
% E. H. R. Tsai, I. Usov, A. Diaz, A. Menzel, and M. Guizar-Sicairos, X-ray ptychography with extended depth of field, Opt. Express 24, 2908929108 (2016).
% 6. Except for the above-mentioned acknowledgment, LICENSEE shall not use the PROGRAM title or the names or logos of LICENSOR, nor any adaptation thereof, nor the
% names of any of its employees or laboratories, in any advertising, promotional or sales material without prior written consent obtained from LICENSOR in each case.
% 7. Ownership of all rights, including copyright in the PROGRAM and in any material associated therewith, shall at all times remain with LICENSOR, and LICENSEE
% agrees to preserve same. LICENSEE agrees not to use any portion of the PROGRAM or of any IMPROVEMENTS in any machine-readable form outside the PROGRAM, nor to
% make any copies except for its internal use, without prior written consent of LICENSOR. LICENSEE agrees to place the following copyright notice on any such copies:
% © All rights reserved. PAUL SCHERRER INSTITUT, Switzerland, Laboratory for Macromolecules and Bioimaging, 2017.
% 8. The LICENSE shall not be construed to confer any rights upon LICENSEE by implication or otherwise except as specifically set forth herein.
% 9. DISCLAIMER: LICENSEE shall be aware that Phase Focus Limited of Sheffield, UK has an international portfolio of patents and pending applications which relate
% to ptychography and that the PROGRAM may be capable of being used in circumstances which may fall within the claims of one or more of the Phase Focus patents,
% in particular of patent with international application number PCT/GB2005/001464. The LICENSOR explicitly declares not to indemnify the users of the software
% in case Phase Focus or any other third party will open a legal action against the LICENSEE due to the use of the program.
% 10. This Agreement shall be governed by the material laws of Switzerland and any dispute arising out of this Agreement or use of the PROGRAM shall be brought before
% the courts of Zürich, Switzerland.
function [indices_out, scan_ids_out] = get_nonoverlapping_indices(self, cache, par )
% find groups accross the scans in order to further minimize overlap
group_across_scans = true; %need to be true for sharing object amongs scans
if group_across_scans
% divide the grouping equally over all the scans
grouping = ceil(par.grouping/par.Nscans);
else
grouping = par.grouping;
end
max_groups = 0;
for kk = 1:par.Nscans
% setdiff sort the indices by size
indices_0 = self.reconstruct_ind{kk}; % remove unwanted from the decision process
ind_start(kk) = min(indices_0)-1;
indices_0 = indices_0 - ind_start(kk); %
Npos_tmp=length(indices_0);
% randomly permutate the indices
indices_0 = indices_0(randperm(Npos_tmp));
max_groups = max(max_groups, ceil(Npos_tmp/grouping));
% fill it with some initial random guess
for ii = 1:ceil(Npos_tmp/grouping)
indices{kk}{ii} = indices_0(1+(ii-1)*grouping : min(Npos_tmp,ii*grouping));
end
% no need for this method ot it calculation would be too long -> use
% just the random initial guess
if (self.Npos/par.Nscans > 1e3 ) || (grouping == 1) || ~isfield(cache, 'distances_matrix')
%%%for ii = 1:length(indices{1}) %why length(indices{1})? Bug?
for ii = 1:length(indices{kk}) %modified by YJ to prevent error when different scans have differernt number of positions
scan_ids{kk}{ii} = ones(1,length(indices{kk}{ii}))*kk; % note their scan origin
end
continue
end % hope that for large number of positions the random statistics will be enough
try
update_score = 0;
for i = 1:ceil(Npos_tmp/grouping)-1
id = indices{kk}{i};
dist_mat_small = cache.distances_matrix{kk}(id,id);
for ii = 0:2*length(indices{kk}{i+1}) % go twice through all positions
j = 1+mod(ii, length(indices{kk}{i+1}));
min_dist = 1./sum(1./dist_mat_small.^2); % find the shortest distance between the probes
if all(isinf(min_dist)) % all(isnan(min_dist))
break
end
[~,min_dist_ind] = min(min_dist);
% make a swap with the j position in i+1 index array
tmp = indices{kk}{i+1}(j);
indices{kk}{i+1}(j) = indices{kk}{i}(min_dist_ind);
indices{kk}{i}(min_dist_ind) = tmp;
% update distance matrix
dist_mat_small_update = cache.distances_matrix{kk}(tmp,indices{kk}{i});
dist_mat_small(min_dist_ind,:) = dist_mat_small_update';
dist_mat_small(:,min_dist_ind) = dist_mat_small_update;
end
update_score = update_score +j;
end
catch
keyboard
end
% fill the last group by the skip indieces but do not expand it
skip_ind = cache.skip_ind(randperm(length(cache.skip_ind)));
indices{kk}{end} = [indices{kk}{end}, skip_ind(1:min(end, grouping-length(indices{kk}{end})))]; % join skip_ind back to the last (smallest) set
for ii = 1:length(indices{kk})
scan_ids{kk}{ii} = ones(1,length(indices{kk}{ii}))*kk; % note their scan origin
end
end
if group_across_scans
indices_out = cell(max_groups,1);
scan_ids_out = cell(max_groups,1);
%% merge groups from difference scans into larger chunks if required
for ii = 1:max_groups
indices_out{ii} = [];
scan_ids_out{ii} = [];
% from each scan add one group
for kk = 1:par.Nscans
if ii <= length(indices{kk})
indices_out{ii} = [indices_out{ii}, indices{kk}{ii}+ind_start(kk)];
scan_ids_out{ii} = [scan_ids_out{ii}, scan_ids{kk}{ii}];
end
end
if length(scan_ids_out) > 1 && length(scan_ids_out{end}) < grouping / 10
% if the a group is too small, merge it with the previous to
% reduce the overhead
indices_out{end-1} = [indices_out{end-1}, indices_out{end}];
scan_ids_out{end-1} = [scan_ids_out{end-1}, scan_ids_out{end}];
scan_ids_out(end) = []; indices_out(end) = [];
end
end
else
indices_out = {};
for ii = 1:par.Nscans
for kk = 1:length(indices{ii})
indices_out = [indices_out, indices{ii}{kk}+ind_start(ii)];
end
end
scan_ids_out = [scan_ids{:}]';
end
%% sort them to minimize allocation of new projection matrices
Nitems = cellfun(@length, indices_out);
if all(max(Nitems) - min(Nitems) <= 1) && all(Nitems > 100)
% just neglect one scanning position to keep the bunches with the same
% size -> faster run on GPU
for i = 1:length(indices_out)
indices_out{i} = indices_out{i}(1:min(Nitems));
scan_ids_out{i} = scan_ids_out{i}(1:min(Nitems));
end
else
[~,ind] = sort(Nitems(:),1,'descend' );
indices_out = indices_out(ind);
scan_ids_out = scan_ids_out(ind);
end
end
@@ -0,0 +1,147 @@
% GET_SCANNING_INDICES simple based method to select indices for DM
%
% [indices_out, scan_ids_out] = get_scanning_indices(self, cache, par )
%
% ** self structure containing inputs: e.g. current reconstruction results, data, mask, positions, pixel size, ..
% ** par structure containing parameters for the engines
% ** cache structure with precalculated values to avoid unnecessary overhead
%
% returns:
% ++ indices_out cell of arrays, contain indices of positions processed in parallel
% ++ scan_ids_out cell of arrays, contain scan numbers for each position
% Academic License Agreement
%
% Source Code
%
% Introduction
% This license agreement sets forth the terms and conditions under which the PAUL SCHERRER INSTITUT (PSI), CH-5232 Villigen-PSI, Switzerland (hereafter "LICENSOR")
% will grant you (hereafter "LICENSEE") a royalty-free, non-exclusive license for academic, non-commercial purposes only (hereafter "LICENSE") to use the cSAXS
% ptychography MATLAB package computer software program and associated documentation furnished hereunder (hereafter "PROGRAM").
%
% Terms and Conditions of the LICENSE
% 1. LICENSOR grants to LICENSEE a royalty-free, non-exclusive license to use the PROGRAM for academic, non-commercial purposes, upon the terms and conditions
% hereinafter set out and until termination of this license as set forth below.
% 2. LICENSEE acknowledges that the PROGRAM is a research tool still in the development stage. The PROGRAM is provided without any related services, improvements
% or warranties from LICENSOR and that the LICENSE is entered into in order to enable others to utilize the PROGRAM in their academic activities. It is the
% LICENSEEs responsibility to ensure its proper use and the correctness of the results.
% 3. THE PROGRAM IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
% A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. IN NO EVENT SHALL THE LICENSOR, THE AUTHORS OR THE COPYRIGHT
% HOLDERS BE LIABLE FOR ANY CLAIM, DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY ARISING FROM, OUT OF OR IN CONNECTION WITH THE PROGRAM OR THE USE
% OF THE PROGRAM OR OTHER DEALINGS IN THE PROGRAM.
% 4. LICENSEE agrees that it will use the PROGRAM and any modifications, improvements, or derivatives of PROGRAM that LICENSEE may create (collectively,
% "IMPROVEMENTS") solely for academic, non-commercial purposes and that any copy of PROGRAM or derivatives thereof shall be distributed only under the same
% license as PROGRAM. The terms "academic, non-commercial", as used in this Agreement, mean academic or other scholarly research which (a) is not undertaken for
% profit, or (b) is not intended to produce works, services, or data for commercial use, or (c) is neither conducted, nor funded, by a person or an entity engaged
% in the commercial use, application or exploitation of works similar to the PROGRAM.
% 5. LICENSEE agrees that it shall make the following acknowledgement in any publication resulting from the use of the PROGRAM or any translation of the code into
% another computing language:
% "Data processing was carried out using the cSAXS ptychography MATLAB package developed by the Science IT and the coherent X-ray scattering (CXS) groups, Paul
% Scherrer Institut, Switzerland."
%
% Additionally, any publication using the package, or any translation of the code into another computing language should cite for difference map:
% P. Thibault, M. Dierolf, A. Menzel, O. Bunk, C. David, F. Pfeiffer, High-resolution scanning X-ray diffraction microscopy, Science 321, 379382 (2008).
% (doi: 10.1126/science.1158573),
% for mixed coherent modes:
% P. Thibault and A. Menzel, Reconstructing state mixtures from diffraction measurements, Nature 494, 6871 (2013). (doi: 10.1038/nature11806),
% for LSQ-ML method
% M. Odstrcil, A. Menzel, M.G. Sicairos, Iterative least-squares solver for generalized maximum-likelihood ptychography, Optics Express, 2018
% for OPRP method
% M. Odstrcil, P. Baksh, S. A. Boden, R. Card, J. E. Chad, J. G. Frey, W. S. Brocklesby, "Ptychographic coherent diffractive imaging with orthogonal probe relaxation." Optics express 24.8 (2016): 8360-8369
% and/or for multislice:
% E. H. R. Tsai, I. Usov, A. Diaz, A. Menzel, and M. Guizar-Sicairos, X-ray ptychography with extended depth of field, Opt. Express 24, 2908929108 (2016).
% 6. Except for the above-mentioned acknowledgment, LICENSEE shall not use the PROGRAM title or the names or logos of LICENSOR, nor any adaptation thereof, nor the
% names of any of its employees or laboratories, in any advertising, promotional or sales material without prior written consent obtained from LICENSOR in each case.
% 7. Ownership of all rights, including copyright in the PROGRAM and in any material associated therewith, shall at all times remain with LICENSOR, and LICENSEE
% agrees to preserve same. LICENSEE agrees not to use any portion of the PROGRAM or of any IMPROVEMENTS in any machine-readable form outside the PROGRAM, nor to
% make any copies except for its internal use, without prior written consent of LICENSOR. LICENSEE agrees to place the following copyright notice on any such copies:
% © All rights reserved. PAUL SCHERRER INSTITUT, Switzerland, Laboratory for Macromolecules and Bioimaging, 2017.
% 8. The LICENSE shall not be construed to confer any rights upon LICENSEE by implication or otherwise except as specifically set forth herein.
% 9. DISCLAIMER: LICENSEE shall be aware that Phase Focus Limited of Sheffield, UK has an international portfolio of patents and pending applications which relate
% to ptychography and that the PROGRAM may be capable of being used in circumstances which may fall within the claims of one or more of the Phase Focus patents,
% in particular of patent with international application number PCT/GB2005/001464. The LICENSOR explicitly declares not to indemnify the users of the software
% in case Phase Focus or any other third party will open a legal action against the LICENSEE due to the use of the program.
% 10. This Agreement shall be governed by the material laws of Switzerland and any dispute arising out of this Agreement or use of the PROGRAM shall be brought before
% the courts of Zürich, Switzerland.
%
%
function [indices_out, scan_ids_out] = get_scanning_indices(self, cache, par )
import engines.GPU.GPU_wrapper.*
import engines.GPU.shared.*
grouping = par.grouping;
max_groups = 0;
if self.Npos == grouping && par.Nscans == 1
indices_out = self.reconstruct_ind;
scan_ids_out = {ones(self.Npos,1)};
return
end
for kk = 1:par.Nscans
N = length(self.reconstruct_ind{kk});
% !! indices ordering has to be always the same for DM !!!
indices_0 = self.reconstruct_ind{kk};
max_groups = max(max_groups, ceil(N/grouping));
for ii = 1:ceil(N/grouping)
indices{kk}{ii} = indices_0(1+(ii-1)*grouping : min(end,ii*grouping));
end
% fill the last group by the skip indices but do not expand it
skip_ind = cache.skip_ind(randperm(length(cache.skip_ind)));
indices{kk}{end} = [indices{kk}{end}, skip_ind(1:min(end, grouping-length(indices{kk}{end})))]; % join skip_ind back to the last (smallest) set
for ii = 1:length(indices{kk})
scan_ids{kk}{ii} = kk * ones(1,length(indices{kk}{ii})); % note their scan origin
end
end
% how many scans should be merged to reach the desired grouping
Njoin = ceil(par.grouping / (self.Npos/par.Nscans));
if Njoin > 1 && par.Nscans > 1 && is_method(par, {'PIE', 'ML'})
% join several scan to improve performance
indices_out = cell(ceil(par.Nscans/Njoin),1);
scan_ids_out = cell(ceil(par.Nscans/Njoin),1);
%% merge groups from difference scans into larger chunks
for kk = 1:ceil(par.Nscans/Njoin)
indices_out{kk} = [];
scan_ids_out{kk} = [];
for ii = 1:Njoin
if kk+(ii-1)*ceil(par.Nscans/Njoin) <= par.Nscans
indices_out{kk} = [indices_out{kk}, indices{kk+(ii-1)*ceil(par.Nscans/Njoin)}{1}];
scan_ids_out{kk} = [scan_ids_out{kk}, scan_ids{kk+(ii-1)*ceil(par.Nscans/Njoin)}{1}];
end
end
end
else
indices_out = horzcat(indices{:});
scan_ids_out = horzcat(scan_ids{:});
end
%% sort them to minimize allocation of new projection matrices
Nitems = cellfun(@length, indices_out);
if all(max(Nitems) - min(Nitems) <= 1) && all(Nitems > 100)
% just neglect one scanning position to keep the bunches with the same
% size -> faster run on GPU
for i = 1:length(indices_out)
indices_out{i} = indices_out{i}(1:min(Nitems));
scan_ids_out{i} = scan_ids_out{i}(1:min(Nitems));
end
else
[~,ind] = sort(Nitems(:),1,'descend' );
indices_out = indices_out(ind);
scan_ids_out = scan_ids_out(ind);
end
end
@@ -0,0 +1,166 @@
% RESCALE_INPUTS multigrid scheme method
%
% self = rescale_inputs(self, Np_p_new, rescale_data)
%
%
% ** self structure containing inputs: e.g. current reconstruction results, data, mask, positions, pixel size, ..
% ** Np_p_new new size of the rescaled dataset
% ** rescale_data true -> rescale also data + mask + noise arrays
%
% returns:
% ++ self structure containing inputs: e.g. current reconstruction results, data, mask, positions, pixel size, ..
% Academic License Agreement
%
% Source Code
%
% Introduction
% This license agreement sets forth the terms and conditions under which the PAUL SCHERRER INSTITUT (PSI), CH-5232 Villigen-PSI, Switzerland (hereafter "LICENSOR")
% will grant you (hereafter "LICENSEE") a royalty-free, non-exclusive license for academic, non-commercial purposes only (hereafter "LICENSE") to use the cSAXS
% ptychography MATLAB package computer software program and associated documentation furnished hereunder (hereafter "PROGRAM").
%
% Terms and Conditions of the LICENSE
% 1. LICENSOR grants to LICENSEE a royalty-free, non-exclusive license to use the PROGRAM for academic, non-commercial purposes, upon the terms and conditions
% hereinafter set out and until termination of this license as set forth below.
% 2. LICENSEE acknowledges that the PROGRAM is a research tool still in the development stage. The PROGRAM is provided without any related services, improvements
% or warranties from LICENSOR and that the LICENSE is entered into in order to enable others to utilize the PROGRAM in their academic activities. It is the
% LICENSEEs responsibility to ensure its proper use and the correctness of the results.
% 3. THE PROGRAM IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
% A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. IN NO EVENT SHALL THE LICENSOR, THE AUTHORS OR THE COPYRIGHT
% HOLDERS BE LIABLE FOR ANY CLAIM, DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY ARISING FROM, OUT OF OR IN CONNECTION WITH THE PROGRAM OR THE USE
% OF THE PROGRAM OR OTHER DEALINGS IN THE PROGRAM.
% 4. LICENSEE agrees that it will use the PROGRAM and any modifications, improvements, or derivatives of PROGRAM that LICENSEE may create (collectively,
% "IMPROVEMENTS") solely for academic, non-commercial purposes and that any copy of PROGRAM or derivatives thereof shall be distributed only under the same
% license as PROGRAM. The terms "academic, non-commercial", as used in this Agreement, mean academic or other scholarly research which (a) is not undertaken for
% profit, or (b) is not intended to produce works, services, or data for commercial use, or (c) is neither conducted, nor funded, by a person or an entity engaged
% in the commercial use, application or exploitation of works similar to the PROGRAM.
% 5. LICENSEE agrees that it shall make the following acknowledgement in any publication resulting from the use of the PROGRAM or any translation of the code into
% another computing language:
% "Data processing was carried out using the cSAXS ptychography MATLAB package developed by the Science IT and the coherent X-ray scattering (CXS) groups, Paul
% Scherrer Institut, Switzerland."
%
% Additionally, any publication using the package, or any translation of the code into another computing language should cite for difference map:
% P. Thibault, M. Dierolf, A. Menzel, O. Bunk, C. David, F. Pfeiffer, High-resolution scanning X-ray diffraction microscopy, Science 321, 379382 (2008).
% (doi: 10.1126/science.1158573),
% for mixed coherent modes:
% P. Thibault and A. Menzel, Reconstructing state mixtures from diffraction measurements, Nature 494, 6871 (2013). (doi: 10.1038/nature11806),
% for LSQ-ML method
% M. Odstrcil, A. Menzel, M.G. Sicairos, Iterative least-squares solver for generalized maximum-likelihood ptychography, Optics Express, 2018
% for OPRP method
% M. Odstrcil, P. Baksh, S. A. Boden, R. Card, J. E. Chad, J. G. Frey, W. S. Brocklesby, "Ptychographic coherent diffractive imaging with orthogonal probe relaxation." Optics express 24.8 (2016): 8360-8369
% and/or for multislice:
% E. H. R. Tsai, I. Usov, A. Diaz, A. Menzel, and M. Guizar-Sicairos, X-ray ptychography with extended depth of field, Opt. Express 24, 2908929108 (2016).
% 6. Except for the above-mentioned acknowledgment, LICENSEE shall not use the PROGRAM title or the names or logos of LICENSOR, nor any adaptation thereof, nor the
% names of any of its employees or laboratories, in any advertising, promotional or sales material without prior written consent obtained from LICENSOR in each case.
% 7. Ownership of all rights, including copyright in the PROGRAM and in any material associated therewith, shall at all times remain with LICENSOR, and LICENSEE
% agrees to preserve same. LICENSEE agrees not to use any portion of the PROGRAM or of any IMPROVEMENTS in any machine-readable form outside the PROGRAM, nor to
% make any copies except for its internal use, without prior written consent of LICENSOR. LICENSEE agrees to place the following copyright notice on any such copies:
% © All rights reserved. PAUL SCHERRER INSTITUT, Switzerland, Laboratory for Macromolecules and Bioimaging, 2017.
% 8. The LICENSE shall not be construed to confer any rights upon LICENSEE by implication or otherwise except as specifically set forth herein.
% 9. DISCLAIMER: LICENSEE shall be aware that Phase Focus Limited of Sheffield, UK has an international portfolio of patents and pending applications which relate
% to ptychography and that the PROGRAM may be capable of being used in circumstances which may fall within the claims of one or more of the Phase Focus patents,
% in particular of patent with international application number PCT/GB2005/001464. The LICENSOR explicitly declares not to indemnify the users of the software
% in case Phase Focus or any other third party will open a legal action against the LICENSEE due to the use of the program.
% 10. This Agreement shall be governed by the material laws of Switzerland and any dispute arising out of this Agreement or use of the PROGRAM shall be brought before
% the courts of Zürich, Switzerland.
%
function self = rescale_inputs(self, Np_p_new, rescale_data)
import utils.*
import math.*
if isempty(Np_p_new)
return
end
scale = Np_p_new ./ self.Np_p;
if all(scale == 1)
return
end
self.modes = [];
if ~isempty(self.diffraction_deform_matrix)
verbose(1,'Rescalling with diffraction_deform_matrix is not implemented')
self.diffraction_deform_matrix = [];
end
self.Np_p = ceil(self.Np_p.*scale);
self.Np_o = ceil(self.Np_o.*scale);
if ~isempty(self.probe_positions) && any(self.probe_positions(:) ~= self.probe_positions_0(:))
self.probe_positions = scale([2,1]) .* self.probe_positions;
else
self.probe_positions = [];
end
self.probe_positions_0 = scale([2,1]) .* self.probe_positions_0;
self.pixel_size = self.pixel_size ./ scale;
for i = 1:length(self.probe)
% scale also intensity
self.probe{i} = interpolateFT_addnoise(self.probe{i}, self.Np_p, 1 )./prod(scale);
%self.probe{i} = imresize(self.probe{i}, self.Np_p)./prod(scale);
end
for i = 1:numel(self.object)
self.object{i} = interpolateFT_addnoise(self.object{i}, self.Np_o , 1);
%self.object{i} = imresize(self.object{i}, self.Np_o);
end
if ~isempty(self.probe_support)
self.probe_support = interpolateFT( self.probe_support, self.Np_p) ;
end
if ~isempty(self.probe_support_fft)
self.probe_support_fft = crop_pad( self.probe_support_fft, self.Np_p) ;
end
if rescale_data
self.diffraction = fftshift_2D(self.diffraction);
self.diffraction = crop_pad(self.diffraction, self.Np_p);
self.diffraction = ifftshift_2D(self.diffraction);
if ~isempty(self.mask)
fill_value = 0.9; % fill value in case of ptychographic "super resolution"
self.mask = fftshift_2D(self.mask);
self.mask = crop_pad(self.mask, self.Np_p, fill_value);
self.mask = ifftshift_2D(self.mask);
end
if ~isempty(self.noise)
self.noise = fftshift_2D(self.noise);
self.noise = crop_pad(self.noise, self.Np_p);
self.noise = ifftshift_2D(self.noise);
end
end
end
function [ imout ] = interpolateFT_addnoise(im,outsize, noise_amplitude)
% fill the empty regions in the FFT interpolated data by some weak
% random noise
import math.fftshift_2D
import math.ifftshift_2D
import utils.crop_pad
Nout = outsize;
Nin = size(im);
imFT = fftshift_2D(fft2(im));
imout = crop_pad(imFT, outsize);
% add noise to avoid correlation between upsampled imaged from
% interpolation artefacts
imout = imout + noise_amplitude * randn(outsize).*mean(min(abs(imFT)));
imout = ifft2(ifftshift_2D(imout))*(Nout(1)*Nout(2)/(Nin(1)*Nin(2)));
end
@@ -0,0 +1,177 @@
% SAVE_TO_P save parameters and recosntrutions from param and self structures to the p-structure
%
% p_out = save_to_p(self, param, p, fourier_error)
%
%
% ** self structure containing inputs: e.g. current reconstruction results, data, mask, positions, pixel size, ..
% ** param structure containing parameters for the engines
% ** p ptychoshelves p structure
% ** fourier_error array [Npos,1] containing evolution of reconstruction error
%
% returns:
% ** p_out updated ptychoshelves p structure
% Academic License Agreement
%
% Source Code
%
% Introduction
% This license agreement sets forth the terms and conditions under which the PAUL SCHERRER INSTITUT (PSI), CH-5232 Villigen-PSI, Switzerland (hereafter "LICENSOR")
% will grant you (hereafter "LICENSEE") a royalty-free, non-exclusive license for academic, non-commercial purposes only (hereafter "LICENSE") to use the cSAXS
% ptychography MATLAB package computer software program and associated documentation furnished hereunder (hereafter "PROGRAM").
%
% Terms and Conditions of the LICENSE
% 1. LICENSOR grants to LICENSEE a royalty-free, non-exclusive license to use the PROGRAM for academic, non-commercial purposes, upon the terms and conditions
% hereinafter set out and until termination of this license as set forth below.
% 2. LICENSEE acknowledges that the PROGRAM is a research tool still in the development stage. The PROGRAM is provided without any related services, improvements
% or warranties from LICENSOR and that the LICENSE is entered into in order to enable others to utilize the PROGRAM in their academic activities. It is the
% LICENSEEs responsibility to ensure its proper use and the correctness of the results.
% 3. THE PROGRAM IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR
% A PARTICULAR PURPOSE AND NONINFRINGEMENT OF ANY PATENTS, COPYRIGHTS, TRADEMARKS OR OTHER RIGHTS. IN NO EVENT SHALL THE LICENSOR, THE AUTHORS OR THE COPYRIGHT
% HOLDERS BE LIABLE FOR ANY CLAIM, DIRECT, INDIRECT OR CONSEQUENTIAL DAMAGES OR OTHER LIABILITY ARISING FROM, OUT OF OR IN CONNECTION WITH THE PROGRAM OR THE USE
% OF THE PROGRAM OR OTHER DEALINGS IN THE PROGRAM.
% 4. LICENSEE agrees that it will use the PROGRAM and any modifications, improvements, or derivatives of PROGRAM that LICENSEE may create (collectively,
% "IMPROVEMENTS") solely for academic, non-commercial purposes and that any copy of PROGRAM or derivatives thereof shall be distributed only under the same
% license as PROGRAM. The terms "academic, non-commercial", as used in this Agreement, mean academic or other scholarly research which (a) is not undertaken for
% profit, or (b) is not intended to produce works, services, or data for commercial use, or (c) is neither conducted, nor funded, by a person or an entity engaged
% in the commercial use, application or exploitation of works similar to the PROGRAM.
% 5. LICENSEE agrees that it shall make the following acknowledgement in any publication resulting from the use of the PROGRAM or any translation of the code into
% another computing language:
% "Data processing was carried out using the cSAXS ptychography MATLAB package developed by the Science IT and the coherent X-ray scattering (CXS) groups, Paul
% Scherrer Institut, Switzerland."
%
% Additionally, any publication using the package, or any translation of the code into another computing language should cite for difference map:
% P. Thibault, M. Dierolf, A. Menzel, O. Bunk, C. David, F. Pfeiffer, High-resolution scanning X-ray diffraction microscopy, Science 321, 379382 (2008).
% (doi: 10.1126/science.1158573),
% for mixed coherent modes:
% P. Thibault and A. Menzel, Reconstructing state mixtures from diffraction measurements, Nature 494, 6871 (2013). (doi: 10.1038/nature11806),
% for LSQ-ML method
% M. Odstrcil, A. Menzel, M.G. Sicairos, Iterative least-squares solver for generalized maximum-likelihood ptychography, Optics Express, 2018
% for OPRP method
% M. Odstrcil, P. Baksh, S. A. Boden, R. Card, J. E. Chad, J. G. Frey, W. S. Brocklesby, "Ptychographic coherent diffractive imaging with orthogonal probe relaxation." Optics express 24.8 (2016): 8360-8369
% and/or for multislice:
% E. H. R. Tsai, I. Usov, A. Diaz, A. Menzel, and M. Guizar-Sicairos, X-ray ptychography with extended depth of field, Opt. Express 24, 2908929108 (2016).
% 6. Except for the above-mentioned acknowledgment, LICENSEE shall not use the PROGRAM title or the names or logos of LICENSOR, nor any adaptation thereof, nor the
% names of any of its employees or laboratories, in any advertising, promotional or sales material without prior written consent obtained from LICENSOR in each case.
% 7. Ownership of all rights, including copyright in the PROGRAM and in any material associated therewith, shall at all times remain with LICENSOR, and LICENSEE
% agrees to preserve same. LICENSEE agrees not to use any portion of the PROGRAM or of any IMPROVEMENTS in any machine-readable form outside the PROGRAM, nor to
% make any copies except for its internal use, without prior written consent of LICENSOR. LICENSEE agrees to place the following copyright notice on any such copies:
% © All rights reserved. PAUL SCHERRER INSTITUT, Switzerland, Laboratory for Macromolecules and Bioimaging, 2017.
% 8. The LICENSE shall not be construed to confer any rights upon LICENSEE by implication or otherwise except as specifically set forth herein.
% 9. DISCLAIMER: LICENSEE shall be aware that Phase Focus Limited of Sheffield, UK has an international portfolio of patents and pending applications which relate
% to ptychography and that the PROGRAM may be capable of being used in circumstances which may fall within the claims of one or more of the Phase Focus patents,
% in particular of patent with international application number PCT/GB2005/001464. The LICENSOR explicitly declares not to indemnify the users of the software
% in case Phase Focus or any other third party will open a legal action against the LICENSEE due to the use of the program.
% 10. This Agreement shall be governed by the material laws of Switzerland and any dispute arising out of this Agreement or use of the PROGRAM shall be brought before
% the courts of Zürich, Switzerland.
function p_out = save_to_p(self, param, p, fourier_error)
import utils.*
Np_p = self.Np_p;
p_out = p;
%% return calculated values back to the main code (p-structure)
p_out.probe_modes = length(self.probe);
p_out.numprobs = size(self.probe{1},3);
for ll = 1:p_out.probe_modes
if size(self.probe{ll},3) == self.Npos
% classical OPRP method
for ii = 1:p.numscans
probes(:,:,ii,ll) = mean(self.probe{ll}(:,:,self.reconstruct_ind{ii}),3);
end
% save the variable modes
[X,V] = extract_PCA(self.probe{ll}, param.variable_probe_modes);
p_out.probe_PCA.eigen_vec = X * (prod(sqrt(Np_p))*2*p.renorm);% normalization for consistency with the CPU code
p_out.probe_PCA.evolution = V;
else
% store constant part
probes(:,:,1:size(self.probe{ll},3),ll) = self.probe{ll}(:,:,:,1);
if ndims(self.probe{ll}) == 4
p_ind = zeros(self.Npos,1);
for kk = 1:length(self.reconstruct_ind)
p_ind = p_ind + kk*ismember(1:self.Npos, self.reconstruct_ind{kk})';
end
p_out.probe_variable.eigen_vec = self.probe{ll}(:,:,:,2) * (prod(sqrt(Np_p))*2*p.renorm);% normalization for consistency with the CPU code
p_out.probe_variable.evolution = self.probe_evolution;
end
end
end
% normalization for consistency with the CPU code
probes = probes * (prod(sqrt(Np_p))*2*p.renorm);
p_out.probes = probes;
position_offset = 1+floor((p.object_size-self.Np_p)/2);
% for consistency with the CPU code revert the object to the original size
p_out.numobjs = size(self.object,1);
p_out.object = cell(1,p_out.numobjs);
for i = 1:p_out.numobjs
obj_size = p.object_size(min(end,p.share_object_ID(i)),:);
p_out.object{i} = single([]);
for layer = 1:param.Nlayers
p_out.object{i}(:,:,1,layer) = imshift_fast(self.object{i,layer}, -1,-1, obj_size, 'nearest', mean(self.object{i,layer}(:)));
end
end
for i = 1:p.numscans
id = p.share_object_ID(i);
obj_size = p.object_size(min(end,id),:);
p_out.illum_sum{id} = imshift_fast(self.illum_sum{id}, -1,-1, obj_size, 'nearest');
end
if param.probe_position_search < param.number_iterations
% store the refined positions
p_out.positions = self.probe_positions(:,[2,1]);
% return to the original coordinates
p_out.positions_0 = self.probe_positions_0(:,[2,1]);
for i = 1:length(self.reconstruct_ind)
ind = self.reconstruct_ind{i};
p_out.positions(ind,:) = p_out.positions(ind,:) + position_offset(p.share_object_ID(i),:);
p_out.positions_0(ind,:) = p_out.positions_0(ind,:) + position_offset(p.share_object_ID(i),:);
end
else
for i = 1:length(self.reconstruct_ind)
ind = self.reconstruct_ind{i};
if ~isempty(self.probe_positions)
p_out.positions(ind,:) = self.probe_positions(ind,[2,1]) + position_offset(p.share_object_ID(i),:);
else
p_out.positions(ind,:) = self.probe_positions_0(ind,[2,1]) + position_offset(p.share_object_ID(i),:);
end
end
end
if param.probe_position_search < param.number_iterations || param.detector_rotation_search < param.number_iterations || param.detector_scale_search < param.number_iterations
p_out = engines.GPU.analysis.report_refined_geometry(self, param, p_out);
end
% save additional reconstructed parameters
for item = {'background', 'intensity_corr', 'probe_fourier_shift' }
try
p_out.(item{1}) = self.(item{1});
end
end
% save error metrics
ind_ok = any(~isnan(fourier_error),2); % plot only the reported values
p_out.error_metric.value = nanmean(fourier_error(ind_ok,:),2);
p_out.error_metric.iteration = find(ind_ok);
if strcmp(param.likelihood,'poisson' )
p_out.error_metric.err_metric = 'poisson';
else
p_out.error_metric.err_metric = 'L1';
end
p_out.error_metric.method = ['GPU-',param.method, ' metric:' , param.likelihood ];
end