├── matlab_code ├── 3ddpc_functions │ ├── gen1DCoordinate.m │ ├── convertScatteringPotentialToRI.m │ ├── sourceCompute.m │ ├── prox_projection.m │ ├── prox_L1.m │ ├── optimize_SP1.m │ └── genTransferFunction3D.m └── main_3ddpc.m ├── README.md ├── LICENSE.txt └── python_code └── algorithm_3ddpc.py /matlab_code/3ddpc_functions/gen1DCoordinate.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | %gen1DCoordinate generates 1D Cartesian coordinate % 3 | %Inputs: % 4 | % N : number of points on the 1D coordinate % 5 | % unit : separation between points % 6 | % datatype : output datatype % 7 | %Output: % 8 | % coordinate: generated 1D coordinate % 9 | % % 10 | % by Michael Chen % 11 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 12 | function coordinate = gen1DCoordinate(N, unit, datatype) 13 | coordinate = -(N-mod(N,2))/2:1:(N-mod(N,2))/2-(mod(N,2)==0); 14 | coordinate = unit*coordinate; 15 | if strcmp(datatype, 'single') 16 | coordinate = single(coordinate); 17 | elseif strcmp(datatype, 'double') 18 | coordinate = double(coordinate); 19 | end 20 | end -------------------------------------------------------------------------------- /matlab_code/3ddpc_functions/convertScatteringPotentialToRI.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | %convertScatteringPotentialToRI computes the 3D refractive index given a % 3 | %3D scattering potential % 4 | %Inputs: % 5 | % scattering_potential: 4D tensors contains real part and imaginary part% 6 | % wavelength : wavelength of incident light % 7 | % RI : refractive index of the surrounding medium % 8 | %Output: % 9 | % RI_3D : computed 3D refractive index % 10 | % % 11 | % by Michael Chen % 12 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 13 | function RI_3D = convertScatteringPotentialToRI(scattering_potential, wavelength, RI) 14 | wavenumber = 2*pi/wavelength; 15 | B = -(RI^2-scattering_potential(:,:,:,1)/wavenumber^2); 16 | C = -(-scattering_potential(:,:,:,2)/wavenumber^2/2).^2; 17 | RI_3D = sqrt((-B+sqrt(B.^2-4*C))/2); 18 | end -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This repository provides Matlab and Python codes that implement a 3D refractive index reconstruction algorithm using 3D DPC through focus intensity measurements. Images should be captured with an LED array illumination or other switchable light sources, generating three(or more) half circular(or half annular) patterns. The 3D Weak Object Transfer Functions(WOTFs) are calculated according to the source patterns, pupil function, and the defocus step size. Finally, 3D refractive index are solved after a 3D deconvolution process. As in 2D DPC case, a least squares algorithm with Tikhonov regularization is implemented. Alternatively, deconvolution with total variation(TV) regularization and non-negativity constraint further mitigates artifacts based on a-priori knowledge of the object. GPU codes are available if hardware allows, which largely reduces computation time. In Python codes, we use ArrayFire library (https://arrayfire.com/) and its python wrapper (https://github.com/arrayfire/arrayfire-python) for GPU processing implementation. 2 | 3 | **Run the "main_3ddpc.m" under matlab_code folder, or open the "main_3ddpc.ipynb" jupyter notebook under python_code folder. 4 | 5 | **Example dataset can be downloaded [here](https://drive.google.com/drive/folders/1U1JBysZeTS_YpybkYdcIPl6u968ZscLF?usp=sharing) 6 | 7 | Please cite as: 8 | [1] M. Chen, L. Tian and L. Waller, 3D differential phase contrast microscopy, Biomed. Opt. Express 7(10), 3940-3950 (2016). 9 | [2] M. Chen and L. Waller, 3D Phase Microscopy with Coded Illumination, Biomed. Optics in the Life Sciences Congress:Novel Techniques in Microscopy, NM2C.2 (2017). 10 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2019, Waller Lab 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -------------------------------------------------------------------------------- /matlab_code/3ddpc_functions/sourceCompute.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | %sourceCompute calculates the effective DPC source shape % 3 | %Inputs: % 4 | % rot_angle: rotation angle of the asymmetric axis % 5 | % NA_illum : illumination NA % 6 | % lambda : wavelength % 7 | % Fx,Fy : spaital frequency axes % 8 | %Output: % 9 | % source : DPC source shape % 10 | % % 11 | % by Michael Chen % 12 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 13 | function [ source ] = sourceCompute(rot_angle, NA_illum, lambda, Fx, Fy) 14 | % support of the source 15 | source_all = sqrt(Fx.^2+Fy.^2)<=NA_illum/lambda; 16 | asymmetry = zeros(size(Fx)); 17 | 18 | % asymmetric mask based on illumination angle 19 | asymmetry(Fy>=(Fx*tand(rot_angle)))=1; 20 | asymmetry(Fy<=(Fx*tand(rot_angle)))=0; 21 | source = source_all.*asymmetry; 22 | 23 | if rot_angle == 270 || rot_angle == 90 || rot_angle == 180 24 | source = fftshift(source); 25 | source = padarray(source,[1,1], 'post'); 26 | source = flip(source, 1); 27 | source = flip(source, 2); 28 | source = source(1:end-1,1:end-1); 29 | source = ifftshift(source); 30 | end 31 | 32 | end 33 | 34 | -------------------------------------------------------------------------------- /matlab_code/3ddpc_functions/prox_projection.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | %prox_projection performs Euclidean norm projection to impose positivity % 3 | %or negativity constraints on the scattering potential % 4 | %Inputs: % 5 | % SP1_k : scattering potential % 6 | % y2_k : Lagrange multipliers % 7 | % direction : Euclidean projection directions % 8 | %Output: % 9 | % SP2_k : splitting of scattering potential % 10 | % % 11 | % by Michael Chen % 12 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 13 | function SP2_k = prox_projection(SP1_k, y2_k, direction) 14 | 15 | SP_re = SP1_k(:,:,:,1) + y2_k(:,:,:,1); 16 | SP_im = SP1_k(:,:,:,2) + y2_k(:,:,:,2); 17 | 18 | if direction(1) == 1 19 | %positivity constraint on the real part of scattering potential 20 | SP_re(SP_re<0) = 0; 21 | else 22 | %negativity constraint on the real part of scattering potential 23 | SP_re(SP_re>0) = 0; 24 | end 25 | 26 | if direction(2) == 1 27 | %positivity constraint on the imaginary part of scattering potential 28 | SP_im(SP_im<0) = 0; 29 | else 30 | %negativity constraint on the imaginary part of scattering potential 31 | SP_im(SP_im>0) = 0; 32 | end 33 | 34 | SP2_k = cat(4,SP_re,SP_im); 35 | end -------------------------------------------------------------------------------- /matlab_code/3ddpc_functions/prox_L1.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | %prox_L1 performs the proximal operator and solves the LASSO problem for % 3 | %total variation regularization % 4 | %Inputs: % 5 | % SP1_k : scattering potential % 6 | % y1_k : Lagrange multipliers % 7 | % y2_k : Lagrange multipliers % 8 | % rho : penalty parameter % 9 | % tau : total variation regularization parameter % 10 | % use_gpu : flag to specify gpu usage % 11 | %Output: % 12 | % DSP_k1 : gradient vectors of the scattering potential % 13 | % delta : change of the gradient vectors % 14 | % % 15 | % by Michael Chen % 16 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 17 | function [DSP_k1, delta] = prox_L1(SP1_k, y1_k, rho, tau, use_gpu) 18 | global N_x N_y N_z 19 | 20 | DSP_k = zeros(N_y,N_x,N_z,6, 'like', SP1_k); 21 | if use_gpu 22 | DSP_k = gpuArray(DSP_k); 23 | end 24 | 25 | DSP_k(:,:,:,1) = SP1_k(:,:,:,1) - circshift(SP1_k(:,:,:,1),[0,-1,0]); 26 | DSP_k(:,:,:,2) = SP1_k(:,:,:,1) - circshift(SP1_k(:,:,:,1),[-1,0,0]); 27 | DSP_k(:,:,:,3) = SP1_k(:,:,:,1) - circshift(SP1_k(:,:,:,1),[0,0,-1]); 28 | DSP_k(:,:,:,4) = SP1_k(:,:,:,2) - circshift(SP1_k(:,:,:,2),[0,-1,0]); 29 | DSP_k(:,:,:,5) = SP1_k(:,:,:,2) - circshift(SP1_k(:,:,:,2),[-1,0,0]); 30 | DSP_k(:,:,:,6) = SP1_k(:,:,:,2) - circshift(SP1_k(:,:,:,2),[0,0,-1]); 31 | 32 | DSP_k1 = DSP_k - y1_k; 33 | DSP_k1 = max(0, DSP_k1 - tau/rho) - max(0, -DSP_k1 - tau/rho); 34 | delta = DSP_k1 - DSP_k; 35 | 36 | end -------------------------------------------------------------------------------- /matlab_code/3ddpc_functions/optimize_SP1.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | %optimize_SP1 solves the Least-Squares problem in the ADMM iterative % 3 | %algorithm or finds the analytical solution using Tikhonov regularization % 4 | %Inputs: % 5 | % SP2_k : splitting of scattering potential % 6 | % method : ADMM mode or Tikonov mode % 7 | % use_gpu : flag to specify gpu usage % 8 | % DSP_k : gradient vectors of the scattering potential % 9 | % y1_k : Lagrange multipliers % 10 | % y2_k : Lagrange multipliers % 11 | % rho : penalty parameter % 12 | %Output: % 13 | % SP1_k : scattering potential % 14 | % % 15 | % by Michael Chen % 16 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 17 | function SP1_k = optimize_SP1(SP2_k, method, use_gpu, DSP_k, y1_k, y2_k, rho) 18 | 19 | global Hr_Hrc Hi_Hrc Hr_Hic Hi_Hic AHI1 AHI2 Dx Dy Dz 20 | 21 | if nargin<4 22 | if ~strcmp(method, 'Tikhonov') 23 | disp('Error in optimize_SP1.m: wrong method flag or more inputs should be provided!'); 24 | return 25 | end 26 | end 27 | 28 | F = @(x) fftn(x); 29 | IF = @(x) ifftn(x); 30 | SP1_k = zeros(size(SP2_k), 'like', SP2_k); 31 | if use_gpu 32 | SP1_k = gpuArray(SP1_k); 33 | end 34 | 35 | denominator = Hr_Hrc.*Hi_Hic-Hi_Hrc.*Hr_Hic; 36 | 37 | if strcmp(method, 'Tikhonov') 38 | SP1_k(:,:,:,1) = real(IF((AHI1.*Hi_Hic-AHI2.*Hi_Hrc)./denominator)); 39 | SP1_k(:,:,:,2) = real(IF((AHI2.*Hr_Hrc-AHI1.*Hr_Hic)./denominator)); 40 | elseif strcmp(method, 'TV') 41 | AHI1_k1 = AHI1 + rho*(F(SP2_k(:,:,:,1) - y2_k(:,:,:,1))... 42 | + conj(Dx).*F(DSP_k(:,:,:,1) + y1_k(:,:,:,1))... 43 | + conj(Dy).*F(DSP_k(:,:,:,2) + y1_k(:,:,:,2))... 44 | + conj(Dz).*F(DSP_k(:,:,:,3) + y1_k(:,:,:,3))); 45 | AHI2_k1 = AHI2 + rho*(F(SP2_k(:,:,:,2) - y2_k(:,:,:,2))... 46 | + conj(Dx).*F(DSP_k(:,:,:,4) + y1_k(:,:,:,4))... 47 | + conj(Dy).*F(DSP_k(:,:,:,5) + y1_k(:,:,:,5))... 48 | + conj(Dz).*F(DSP_k(:,:,:,6) + y1_k(:,:,:,6))); 49 | SP1_k(:,:,:,1) = real(IF((AHI1_k1.*Hi_Hic-AHI2_k1.*Hi_Hrc)./denominator)); 50 | SP1_k(:,:,:,2) = real(IF((AHI2_k1.*Hr_Hrc-AHI1_k1.*Hr_Hic)./denominator)); 51 | end 52 | 53 | end -------------------------------------------------------------------------------- /matlab_code/3ddpc_functions/genTransferFunction3D.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | %genTransferFunction3D calculates the 3D transfer functions for both real % 3 | %and imaginary parts of the scattering potential (V) % 4 | %Inputs: % 5 | % lambda : wavelength of illumination % 6 | % source : source shape % 7 | % pupil : pupil function % 8 | % z : position of each layer % 9 | % Fx : horizontal coordinate in Fourier space % 10 | % Fy : vertical coordinate in Fourier space % 11 | % RI : background medium refractive index % 12 | %Outputs: % 13 | % imaginaryTransferFunction: transfer function of imaginary part of V % 14 | % realTransferFunction : transfer function of real part of V % 15 | % % 16 | % by Michael Chen % 17 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 18 | function [imaginaryTransferFunction,realTransferFunction] = genTransferFunction3D(lambda,source,pupil,z,Fx,Fy,RI) 19 | % 2D FFT operators 20 | F = @(x) fft2(x); 21 | IF = @(x) ifft2(x); 22 | 23 | [N_x, N_y]= size(Fx); 24 | dfx = Fx(1,2)-Fx(1,1); 25 | dfy = Fy(2,1)-Fy(1,1); 26 | 27 | % flip the source 28 | Sf = padarray(source,[1,1],'post'); 29 | Sf = flip(Sf,1); 30 | Sf = flip(Sf,2); 31 | Sf = Sf(1:end-1,1:end-1); 32 | Sf = ifftshift(Sf); 33 | 34 | % evaluate the oblique factor 35 | G = 1./sqrt((RI/lambda)^2-(Fx.^2+Fy.^2))/4/pi; 36 | 37 | % initialize process bar 38 | w = waitbar(0,'Transfer Function Calculating...'); 39 | perc = 0; 40 | 41 | % preallocate memories 42 | imaginaryTransferFunction = zeros(N_y, N_x, length(z), 'like', source); 43 | realTransferFunction = zeros(N_y, N_x, length(z), 'like', source); 44 | 45 | for j = 1:length(z) 46 | porp_phase = exp(1i*2*pi*z(j)*sqrt((1/lambda)^2-(Fx.^2+Fy.^2))); 47 | porp_phase(abs(pupil)==0) = 0; 48 | FPSfph_cFPphG = F(Sf.*pupil.*porp_phase).*conj(F(pupil.*porp_phase.*G))*dfx*dfy; 49 | imaginaryTransferFunction(:,:,j) = 2*IF(real(FPSfph_cFPphG)); 50 | realTransferFunction(:,:,j) = 2*IF(1i*imag(FPSfph_cFPphG)); 51 | 52 | if mod(j,round((length(z)-1)/5))==0 53 | perc = perc+20; 54 | waitbar(perc/100,w,sprintf('Transfer Function Calculating...%d%%',perc)) 55 | end 56 | end 57 | 58 | window = reshape(ifftshift(hamming(length(z))), [1, 1, length(z)]); 59 | imaginaryTransferFunction = fft(bsxfun(@times,imaginaryTransferFunction,window),[],3)*(z(2)-z(1)); 60 | realTransferFunction = fft(bsxfun(@times,realTransferFunction,window),[],3)*(z(2)-z(1)); 61 | DC = sum(Sf(:).*abs(pupil(:)).^2)*dfx*dfy; 62 | imaginaryTransferFunction = imaginaryTransferFunction/DC; 63 | realTransferFunction = 1i*realTransferFunction/DC; 64 | close(w); 65 | 66 | end 67 | 68 | -------------------------------------------------------------------------------- /matlab_code/main_3ddpc.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % main_3ddpc recovers 3D refractive index of weakly scattering object. By % 3 | % measuring a few through-focus intensity stacks under partially coherent % 4 | % illuminations, like DPC patterns, 3D phase contrast is captured. Hence, % 5 | % it is possible to retrieve quantitative information of the object via a % 6 | % deconvolution process. With the ADMM algorithm provided in this code, % 7 | % total variation and positivity constraints can be applied during 3D % 8 | % refractive index reconstruction. If a GPU device is available, the ADMM % 9 | % iterations will run much faster than using CPU for compuataion. % 10 | % % 11 | % by Michael Chen % 12 | % % 13 | % Please cite: % 14 | % M. Chen, L. Tian, and L. Waller, "3D differential phase contrast % 15 | % microscopy," Biomed. Opt. Express 7, 3940-3950 (2016) % 16 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 17 | clear; clc; close all; 18 | set(0,'DefaultFigureWindowStyle','docked'); 19 | addpath('./3ddpc_functions'); 20 | 21 | % define global variables and FFT/IFFT operations 22 | global N_x N_y N_z Hr_Hrc Hi_Hrc Hr_Hic Hi_Hic AHI1 AHI2 Dx Dy Dz 23 | F = @(x) fftn(x); % 3D FFT operator 24 | IF = @(x) ifftn(x); % 3D IFFT operator 25 | use_gpu = false; % true: use gpu, false: use cpu 26 | datatype = 'single'; % we recommend to save variables with single precision to save RAM 27 | 28 | %% Load Data 29 | 30 | %LOAD YOUR DATA HERE or USE THE EXAMPLE DATASET (single polystyrene bead) 31 | load('../example_3ddpc_dataset.mat', 'I_DPC'); 32 | if strcmp(datatype, 'single')==1 33 | I_DPC = single(I_DPC); 34 | elseif strsmp(datatype, 'double')==1 35 | I_DPC = double(I_DPC); 36 | end 37 | fI_DPC = zeros(size(I_DPC), 'like', I_DPC); 38 | 39 | % mean subtraction and intensity normalization 40 | for stack_idx = 1:size(I_DPC, 4) 41 | I_load = I_DPC(:,:,:,stack_idx); 42 | I_DPC(:,:,:,stack_idx) = (I_DPC(:,:,:,stack_idx)-mean(I_load(:)))/mean(I_load(:)); 43 | fI_DPC(:,:,:,stack_idx) = F(I_DPC(:,:,:,stack_idx)); 44 | end 45 | 46 | clear I_load; 47 | N_x = size(I_DPC,2); % number of columns 48 | N_y = size(I_DPC,1); % number of rows 49 | N_z = size(I_DPC,3); % number of z steps 50 | nangle = size(I_DPC,4); % number of DPC illumination patterns 51 | 52 | % plot the loaded DPC image stacks 53 | for stack_idx = 1:nangle 54 | figure('Name', ['DPC image stack, ', num2str(stack_idx)]); 55 | for plot_idx = 1:3 56 | subplot(1,3,plot_idx) 57 | if plot_idx==1 58 | imagesc(I_DPC(:,:,round(N_z/2)+1, stack_idx)); 59 | title(['DPC_x_y_,_', num2str(stack_idx)], 'fontsize', 24); 60 | xlabel('x, pixel', 'fontsize', 16); 61 | ylabel('y, pixel', 'fontsize', 16); 62 | elseif plot_idx==2 63 | imagesc(squeeze(I_DPC(:,round(N_x/2)+1,:,stack_idx))); 64 | title(['DPC_z_y_,_', num2str(stack_idx)], 'fontsize', 24); 65 | xlabel('z, pixel', 'fontsize', 16); 66 | ylabel('y, pixel', 'fontsize', 16); 67 | else 68 | imagesc(squeeze(I_DPC(round(N_y/2)+1,:,:,stack_idx))); 69 | title(['DPC_z_x_,_', num2str(stack_idx)], 'fontsize', 24); 70 | xlabel('z, pixel', 'fontsize', 16); 71 | ylabel('x, pixel', 'fontsize', 16); 72 | end 73 | colormap gray; 74 | axis square; 75 | caxis([-0.2, 0.2]); 76 | end 77 | drawnow; 78 | end 79 | 80 | %% Setup System Parameters 81 | 82 | %NEED TO MODIFY THE FOLLOWING PARAMETERS BASED ON EXPERIMENT SETTINGS 83 | sigma = 1; % partial coherence factor 84 | NA_obj = 0.65; % numerical aperture of objective lens 85 | NA_illu = sigma*NA_obj; % numerical aperture of illumination 86 | magnification = 41; % magnification of the optical system 87 | rotation = [0 180 270 90]; % rotation angles of illumination 88 | medium_index = 1.59; % refractive index of immersion media 89 | lambda = 0.514; % wavelength in micron 90 | ps_camera = 6.5; % camera's pixel size in micron 91 | ps = ps_camera/magnification;% demagnified pixel size in micron 92 | psz = 1.0; % z step in micron 93 | 94 | %% Setup System Coordinates 95 | 96 | % coordinates in image space 97 | x = gen1DCoordinate(N_x, ps, datatype); 98 | y = gen1DCoordinate(N_y, ps, datatype); 99 | z = gen1DCoordinate(N_z, psz, datatype); 100 | [X,Y,Z] = meshgrid(x, y, z); 101 | z = ifftshift(z); 102 | 103 | % coordinates in frequency space 104 | dfx = 1/N_x/ps; 105 | dfy = 1/N_y/ps; 106 | dfz = 1/N_z/psz; 107 | fx = gen1DCoordinate(N_x, dfx, datatype); 108 | fy = gen1DCoordinate(N_y, dfy, datatype); 109 | fz = gen1DCoordinate(N_z, dfz, datatype); 110 | [Fx,Fy] = meshgrid(fx, fy); 111 | [~,~,Fz]= meshgrid(fx, fy, fz); 112 | Fx = ifftshift(Fx); 113 | Fy = ifftshift(Fy); 114 | Fz = ifftshift(Fz); 115 | 116 | %% Compute Transferfunctions 117 | 118 | % if image has lateral dimensions smaller than 512 pixels, first upsample 119 | % the source and pupil for transfer function calculation and then 120 | % downsample to the original image size. 121 | if N_x < 512 122 | downsamp_rate_col = ceil(512/N_x); 123 | upsamp_N_x = downsamp_rate_col*N_x; 124 | else 125 | downsamp_rate_col = 1; 126 | upsamp_N_x = N_x; 127 | end 128 | 129 | if N_y < 512 130 | downsamp_rate_row = ceil(512/N_y); 131 | upsamp_N_y = downsamp_rate_row*N_y; 132 | else 133 | downsamp_rate_row = 1; 134 | upsamp_N_y = N_y; 135 | end 136 | 137 | % generate upsampled coordinates for transfer functions evaluation 138 | upsamp_dfx = 1/upsamp_N_x/ps; 139 | upsamp_dfy = 1/upsamp_N_y/ps; 140 | upsamp_fx = gen1DCoordinate(upsamp_N_x, upsamp_dfx, datatype); 141 | upsamp_fy = gen1DCoordinate(upsamp_N_y, upsamp_dfy, datatype); 142 | upsamp_fx = ifftshift(upsamp_fx); 143 | upsamp_fy = ifftshift(upsamp_fy); 144 | [upsamp_Fx,upsamp_Fy] = meshgrid(upsamp_fx, upsamp_fy); 145 | 146 | % allocate memories for transfer functions 147 | sources = zeros(upsamp_N_y, upsamp_N_x, nangle, 'like', fI_DPC); 148 | pupil = ((upsamp_Fx.^2+upsamp_Fy.^2)*lambda^21 306 | fprintf(repmat('\b',1,43+(floor(log10(max_iteration))+1)*2)); 307 | end 308 | fprintf('elapsed time: %5.2f seconds, iteration : %02d/%02d\n', toc(), iter, max_iteration); 309 | end 310 | close('Name', 'Recovered 3D RI Over Iterations'); 311 | end 312 | %% Display The Optimized 3D RI 313 | 314 | RI_3D = convertScatteringPotentialToRI(SP1_k, lambda, medium_index); 315 | 316 | figure('Name', 'Final 3D RI Reconstruction'); 317 | subplot(1,3,1) 318 | imagesc(x,y,RI_3D(:,:,N_z/2+1)); axis square; colormap jet; 319 | caxis([medium_index-0.01,medium_index+0.01]); 320 | axis([-15, 15, -15, 15]); 321 | xlabel('x, \mum', 'fontsize', 16) 322 | ylabel('y, \mum', 'fontsize', 16) 323 | title('RI_x_y','fontsize',24); 324 | 325 | subplot(1,3,2) 326 | imagesc(fftshift(z),y,squeeze(RI_3D(:,round(N_x/2),:,1))); axis square; colormap jet; 327 | caxis([medium_index-0.01,medium_index+0.01]); 328 | axis([-15, 15, -15, 15]); 329 | xlabel('z, \mum', 'fontsize', 16) 330 | ylabel('y, \mum', 'fontsize', 16) 331 | title('RI_z_y','fontsize',24); 332 | 333 | subplot(1,3,3) 334 | imagesc(fftshift(z),x,squeeze(RI_3D(round(N_y/2),:,:,1))); axis square; colormap jet; 335 | caxis([medium_index-0.01,medium_index+0.01]); 336 | axis([-15, 15, -15, 15]); 337 | xlabel('z, \mum', 'fontsize', 16) 338 | ylabel('x, \mum', 'fontsize', 16) 339 | title('RI_z_x','fontsize',24); 340 | drawnow; 341 | -------------------------------------------------------------------------------- /python_code/algorithm_3ddpc.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | import time 3 | pi = np.pi 4 | naxis = np.newaxis 5 | F_2D = lambda x: np.fft.fft2(x, axes=(0, 1)) 6 | IF_2D = lambda x: np.fft.ifft2(x, axes=(0, 1)) 7 | F_3D = lambda x: np.fft.fftn(x, axes=(0, 1, 2)) 8 | IF_3D = lambda x: np.fft.ifftn(x, axes=(0, 1, 2)) 9 | 10 | def pupilGen(fxlin, fylin, wavelength, na, na_in=0.0): 11 | ''' 12 | pupilGen create a circular pupil function in Fourier space. 13 | Inputs: 14 | fxlin : 1D spatial frequency coordinate in horizontal direction 15 | fylin : 1D spatial frequency coordinate in vertical direction 16 | wavelength: wavelength of incident light 17 | na : numerical aperture of the imaging system 18 | na_in : put a non-zero number smaller than na to generate an annular function 19 | Output: 20 | pupil : pupil function 21 | ''' 22 | pupil = np.array(fxlin[naxis, :]**2+fylin[:, naxis]**2 <= (na/wavelength)**2, dtype="float32") 23 | if na_in != 0.0: 24 | pupil[fxlin[naxis, :]**2+fylin[:, naxis]**2 < (na_in/wavelength)**2] = 0.0 25 | return pupil 26 | 27 | def _genGrid(size, dx): 28 | ''' 29 | _genGrid create a 1D coordinate vector. 30 | Inputs: 31 | size : length of the coordinate vector 32 | dx : step size of the 1D coordinate 33 | Output: 34 | grid : 1D coordinate vector 35 | ''' 36 | xlin = np.arange(size, dtype='complex64') 37 | return (xlin-size//2)*dx 38 | 39 | class Solver3DDPC: 40 | ''' 41 | Solver3DDPC class provides methods to preprocess 3D DPC measurements and recovers 3D refractive index with Tikhonov or TV regularziation. 42 | ''' 43 | def __init__(self, dpc_imgs, wavelength, na, na_in, pixel_size, pixel_size_z, rotation, RI_medium): 44 | ''' 45 | Initialize system parameters and functions for DPC phase microscopy. 46 | ''' 47 | self.wavelength = wavelength 48 | self.na = na 49 | self.na_in = na_in 50 | self.pixel_size = pixel_size 51 | self.pixel_size_z = pixel_size_z 52 | self.rotation = rotation 53 | self.dpc_num = len(rotation) 54 | self.fxlin = np.fft.ifftshift(_genGrid(dpc_imgs.shape[1], 1.0/dpc_imgs.shape[1]/self.pixel_size)) 55 | self.fylin = np.fft.ifftshift(_genGrid(dpc_imgs.shape[0], 1.0/dpc_imgs.shape[0]/self.pixel_size)) 56 | self.dpc_imgs = dpc_imgs.astype('float32') 57 | self.RI_medium = RI_medium 58 | self.window = np.fft.ifftshift(np.hamming(dpc_imgs.shape[2])) 59 | self.pupil = pupilGen(self.fxlin, self.fylin, self.wavelength, self.na) 60 | self.phase_defocus = self.pupil*2.0*pi*((1.0/wavelength)**2-self.fxlin[naxis, :]**2-self.fylin[:, naxis]**2)**0.5 61 | self.oblique_factor = self.pupil/4.0/pi/((RI_medium/wavelength)**2-self.fxlin[naxis, :]**2-self.fylin[:, naxis]**2)**0.5 62 | self.normalization() 63 | self.sourceGen() 64 | self.WOTFGen() 65 | 66 | def normalization(self): 67 | ''' 68 | Normalize the 3D intensity stacks by their average illumination intensities, and subtract the mean. 69 | ''' 70 | self.dpc_imgs /= np.mean(self.dpc_imgs, axis=(0, 1, 2), keepdims=True) 71 | self.dpc_imgs -= 1.0 72 | 73 | def sourceGen(self): 74 | ''' 75 | Generate DPC source patterns based on the rotation angles and numerical aperture of the illuminations. 76 | ''' 77 | self.source = [] 78 | pupil = pupilGen(self.fxlin, self.fylin, self.wavelength, self.na, na_in=self.na_in) 79 | for rot_index in range(self.dpc_num): 80 | self.source.append(np.zeros((self.dpc_imgs.shape[:2]), dtype='float32')) 81 | rotdegree = self.rotation[rot_index] 82 | if rotdegree < 180: 83 | self.source[-1][self.fylin[:, naxis]*np.cos(np.deg2rad(rotdegree))+1e-15>= 84 | self.fxlin[naxis, :]*np.sin(np.deg2rad(rotdegree))] = 1.0 85 | self.source[-1] *= pupil 86 | else: 87 | self.source[-1][self.fylin[:, naxis]*np.cos(np.deg2rad(rotdegree))+1e-15< 88 | self.fxlin[naxis, :]*np.sin(np.deg2rad(rotdegree))] = -1.0 89 | self.source[-1] *= pupil 90 | self.source[-1] += pupil 91 | self.source = np.asarray(self.source) 92 | 93 | def sourceFlip(self, source): 94 | ''' 95 | Flip the sources in vertical and horizontal directions, since the coordinates of the source plane and the pupil plane are opposite. 96 | ''' 97 | source_flip = np.fft.fftshift(source) 98 | source_flip = source_flip[::-1, ::-1] 99 | if np.mod(source_flip.shape[0], 2)==0: 100 | source_flip = np.roll(source_flip, 1, axis=0) 101 | if np.mod(source_flip.shape[1], 2)==0: 102 | source_flip = np.roll(source_flip, 1, axis=1) 103 | 104 | return np.fft.ifftshift(source_flip) 105 | 106 | def WOTFGen(self): 107 | ''' 108 | Generate the absorption (imaginary part) and phase (real part) weak object transfer functions (WOTFs) using the sources and the pupil. 109 | ''' 110 | dim_x = self.dpc_imgs.shape[1] 111 | dim_y = self.dpc_imgs.shape[0] 112 | dfx = 1.0/dim_x/self.pixel_size 113 | dfy = 1.0/dim_y/self.pixel_size 114 | z_lin = np.fft.ifftshift(_genGrid(self.dpc_imgs.shape[2], self.pixel_size_z)) 115 | prop_kernel = np.exp(1.0j*z_lin[naxis, naxis, :]*self.phase_defocus[:, :, naxis]) 116 | self.H_real = [] 117 | self.H_imag = [] 118 | for rot_index in range(self.dpc_num): 119 | source_flip = self.sourceFlip(self.source[rot_index]) 120 | FSP_cFPG = F_2D(source_flip[:, :, naxis]*self.pupil[:, :, naxis]*prop_kernel)*\ 121 | F_2D(self.pupil[:, :, naxis]*prop_kernel*self.oblique_factor[:, :, naxis]).conj() 122 | self.H_real.append(2.0*IF_2D(1.0j*FSP_cFPG.imag*dfx*dfy)) 123 | self.H_real[-1] *= self.window[naxis, naxis, :] 124 | self.H_real[-1] = np.fft.fft(self.H_real[-1], axis=2)*self.pixel_size_z 125 | self.H_imag.append(2.0*IF_2D(FSP_cFPG.real*dfx*dfy)) 126 | self.H_imag[-1] *= self.window[naxis, naxis, :] 127 | self.H_imag[-1] = np.fft.fft(self.H_imag[-1], axis=2)*self.pixel_size_z 128 | total_source = np.sum(source_flip*self.pupil*self.pupil.conj())*dfx*dfy 129 | self.H_real[-1] *= 1.0j/total_source 130 | self.H_imag[-1] *= 1.0/total_source 131 | print("3D weak object transfer function {:02d}/{:02d} has been evaluated.".format(rot_index+1, self.dpc_num), end="\r") 132 | self.H_real = np.array(self.H_real).astype('complex64') 133 | self.H_imag = np.array(self.H_imag).astype('complex64') 134 | 135 | def _V2RI(self, V_real, V_imag): 136 | ''' 137 | Convert the complex scattering potential (V) into the refractive index. Imaginary part of the refractive index is dumped. 138 | ''' 139 | wavenumber = 2.0*pi/self.wavelength 140 | B = -1.0*(self.RI_medium**2-V_real/wavenumber**2) 141 | C = -1.0*(-1.0*V_imag/wavenumber**2/2.0)**2 142 | RI_obj = ((-1.0*B+(B**2-4.0*C)**0.5)/2.0)**0.5 143 | 144 | return np.array(RI_obj) 145 | 146 | def setRegularizationParameters(self, reg_real=5e-5, reg_imag=5e-5, tau=5e-5, rho = 5e-5): 147 | ''' 148 | Set regularization parameters for Tikhonov deconvolution and total variation regularization. 149 | ''' 150 | # Tikhonov regularization parameters 151 | self.reg_real = reg_real 152 | self.reg_imag = reg_imag 153 | 154 | # TV regularization parameters 155 | self.tau = tau 156 | 157 | # ADMM penalty parameter 158 | self.rho = rho 159 | 160 | def _prox_LASSO(self, V1_k, y_DV_k, use_gpu): 161 | ''' 162 | _prox_LASSO performs the proximal operator and solves the LASSO problem with L1 norm for total variation regularization. 163 | Inputs: 164 | V1_k : complex scattering potential 165 | y_DV_k : Lagrange multipliers for the gradient vectors of the scattering potential 166 | use_gpu : flag to specify gpu usage 167 | Output: 168 | DV_k : soft-thresholded gradient vectors of the scattering potential 169 | DV_k_or_diff : difference between the thresholded gradient vectors and the original ones 170 | ''' 171 | if use_gpu: 172 | shape_3d = self.dpc_imgs.shape[:3] 173 | DV_k_or_diff = af.constant(0.0, shape_3d[0], shape_3d[1], shape_3d[2], 6, dtype=af.Dtype.f32) 174 | DV_k_or_diff[:,:,:,0] = V1_k[:,:,:,0] - af.shift(V1_k[:,:,:,0], 0, -1) 175 | DV_k_or_diff[:,:,:,1] = V1_k[:,:,:,0] - af.shift(V1_k[:,:,:,0], -1) 176 | DV_k_or_diff[:,:,:,2] = V1_k[:,:,:,0] - af.shift(V1_k[:,:,:,0], 0, 0, -1) 177 | DV_k_or_diff[:,:,:,3] = V1_k[:,:,:,1] - af.shift(V1_k[:,:,:,1], 0, -1) 178 | DV_k_or_diff[:,:,:,4] = V1_k[:,:,:,1] - af.shift(V1_k[:,:,:,1], -1) 179 | DV_k_or_diff[:,:,:,5] = V1_k[:,:,:,1] - af.shift(V1_k[:,:,:,1], 0, 0, -1) 180 | else: 181 | DV_k_or_diff = np.zeros(self.dpc_imgs.shape[:3]+ (6, ), dtype='float32') 182 | DV_k_or_diff[:,:,:,0] = V1_k[:,:,:,0] - np.roll(V1_k[:,:,:,0], -1, axis=1) 183 | DV_k_or_diff[:,:,:,1] = V1_k[:,:,:,0] - np.roll(V1_k[:,:,:,0], -1, axis=0) 184 | DV_k_or_diff[:,:,:,2] = V1_k[:,:,:,0] - np.roll(V1_k[:,:,:,0], -1, axis=2) 185 | DV_k_or_diff[:,:,:,3] = V1_k[:,:,:,1] - np.roll(V1_k[:,:,:,1], -1, axis=1) 186 | DV_k_or_diff[:,:,:,4] = V1_k[:,:,:,1] - np.roll(V1_k[:,:,:,1], -1, axis=0) 187 | DV_k_or_diff[:,:,:,5] = V1_k[:,:,:,1] - np.roll(V1_k[:,:,:,1], -1, axis=2) 188 | 189 | DV_k = DV_k_or_diff - y_DV_k 190 | if use_gpu: 191 | DV_k = af.maxof(DV_k-self.tau/self.rho, 0.0) - af.maxof(-DV_k-self.tau/self.rho, 0.0) 192 | else: 193 | DV_k = np.maximum(DV_k-self.tau/self.rho, 0.0) - np.maximum(-DV_k-self.tau/self.rho, 0.0) 194 | 195 | DV_k_or_diff = DV_k - DV_k_or_diff 196 | 197 | return DV_k, DV_k_or_diff 198 | 199 | def _prox_projection(self, V1_k, V2_k, y_V2_k, boundary_constraint): 200 | ''' 201 | _prox_projection performs Euclidean norm projection to impose positivity or negativity constraints on the scattering potential. 202 | Inputs: 203 | V1_k : complex scattering potential 204 | V2_k : splitted complex scattering potential 205 | y_V2_k : Lagrange multipliers for the splitted scattering potential 206 | boundary_constraint : indicate whether to use positive or negative constraint on the scattering potential 207 | Output: 208 | V2_k : updated splitted complex scattering potential 209 | ''' 210 | V2_k = V1_k + y_V2_k 211 | V_real = V2_k[:,:,:,1] 212 | V_imag = V2_k[:,:,:,0] 213 | 214 | if boundary_constraint["real"]=="positive": 215 | V_real[V_real<0.0] = 0.0 216 | elif boundary_constraint["real"]=="negative": 217 | V_real[V_real>0.0] = 0.0 218 | 219 | if boundary_constraint["imag"]=="positive": 220 | V_imag[V_real<0.0] = 0.0 221 | elif boundary_constraint["imag"]=="negative": 222 | V_imag[V_real>0.0] = 0.0 223 | 224 | V2_k[:,:,:,0] = V_imag 225 | V2_k[:,:,:,1] = V_real 226 | 227 | return V2_k 228 | 229 | def _deconvTikhonov(self, AHA, AHy, determinant, use_gpu): 230 | ''' 231 | _deconvTikhonov solves a Least-Squares problem with L2 regularization. 232 | ''' 233 | if use_gpu: 234 | V_real = af.real(af.ifft3((AHA[0]*AHy[1]-AHA[2]*AHy[0])/determinant)) 235 | V_imag = af.real(af.ifft3((AHA[3]*AHy[0]-AHA[1]*AHy[1])/determinant)) 236 | else: 237 | V_real = IF_3D((AHA[0]*AHy[1]-AHA[2]*AHy[0])/determinant).real 238 | V_imag = IF_3D((AHA[3]*AHy[0]-AHA[1]*AHy[1])/determinant).real 239 | 240 | return V_real, V_imag 241 | 242 | def _deconvTV(self, AHA, determinant, fIntensity, fDx, fDy, fDz, tv_max_iter, boundary_constraint, use_gpu): 243 | ''' 244 | _deconvTV solves the 3D DPC deconvolution with total variation regularization and boundary value constraints using the ADMM algorithm. 245 | ''' 246 | AHy =[(self.H_imag.conj()*fIntensity).sum(axis=0), (self.H_real.conj()*fIntensity).sum(axis=0)] 247 | 248 | if use_gpu: 249 | shape_3d = self.dpc_imgs.shape[:3] 250 | V1_k = af.constant(0.0, shape_3d[0], shape_3d[1], shape_3d[2], 2, dtype=af.Dtype.f32) 251 | V2_k = af.constant(0.0, shape_3d[0], shape_3d[1], shape_3d[2], 2, dtype=af.Dtype.f32) 252 | DV_k = af.constant(0.0, shape_3d[0], shape_3d[1], shape_3d[2], 6, dtype=af.Dtype.f32) 253 | y_DV_k = af.constant(0.0, shape_3d[0], shape_3d[1], shape_3d[2], 6, dtype=af.Dtype.f32) 254 | y_V2_k = af.constant(0.0, shape_3d[0], shape_3d[1], shape_3d[2], 2, dtype=af.Dtype.f32) 255 | AHy = [af.to_array(AHy_i) for AHy_i in AHy] 256 | else: 257 | V1_k = np.zeros(self.dpc_imgs.shape[:3]+ (2, ), dtype='float32') 258 | V2_k = np.zeros(self.dpc_imgs.shape[:3]+ (2, ), dtype='float32') 259 | DV_k = np.zeros(self.dpc_imgs.shape[:3]+ (6, ), dtype='float32') 260 | y_DV_k = np.zeros(self.dpc_imgs.shape[:3]+ (6, ), dtype='float32') 261 | y_V2_k = np.zeros(self.dpc_imgs.shape[:3]+ (2, ), dtype='float32') 262 | 263 | t_start = time.time() 264 | for iteration in range(tv_max_iter): 265 | # solve Least-Squares 266 | if use_gpu: 267 | AHy_k = [AHy[0]+self.rho*(af.fft3(V2_k[:,:,:,0]-y_V2_k[:,:,:,0])+ af.conjg(fDx)*af.fft3(DV_k[:,:,:,0]+y_DV_k[:,:,:,0])\ 268 | + af.conjg(fDy)*af.fft3(DV_k[:,:,:,1]+y_DV_k[:,:,:,1])\ 269 | + af.conjg(fDz)*af.fft3(DV_k[:,:,:,2]+y_DV_k[:,:,:,2])),\ 270 | AHy[1]+self.rho*(af.fft3(V2_k[:,:,:,1]-y_V2_k[:,:,:,1])+ af.conjg(fDx)*af.fft3(DV_k[:,:,:,3]+y_DV_k[:,:,:,3])\ 271 | + af.conjg(fDy)*af.fft3(DV_k[:,:,:,4]+y_DV_k[:,:,:,4])\ 272 | + af.conjg(fDz)*af.fft3(DV_k[:,:,:,5]+y_DV_k[:,:,:,5]))] 273 | else: 274 | AHy_k = [AHy[0]+self.rho*(F_3D(V2_k[:,:,:,0]-y_V2_k[:,:,:,0])+ fDx.conj()*F_3D(DV_k[:,:,:,0]+y_DV_k[:,:,:,0])\ 275 | + fDy.conj()*F_3D(DV_k[:,:,:,1]+y_DV_k[:,:,:,1])\ 276 | + fDz.conj()*F_3D(DV_k[:,:,:,2]+y_DV_k[:,:,:,2])),\ 277 | AHy[1]+self.rho*(F_3D(V2_k[:,:,:,1]-y_V2_k[:,:,:,1])+ fDx.conj()*F_3D(DV_k[:,:,:,3]+y_DV_k[:,:,:,3])\ 278 | + fDy.conj()*F_3D(DV_k[:,:,:,4]+y_DV_k[:,:,:,4])\ 279 | + fDz.conj()*F_3D(DV_k[:,:,:,5]+y_DV_k[:,:,:,5]))] 280 | V1_k[:,:,:,1],\ 281 | V1_k[:,:,:,0] = self._deconvTikhonov(AHA, AHy_k, determinant, use_gpu) 282 | 283 | # solve LASSO proximal step 284 | DV_k, DV_k_diff = self._prox_LASSO(V1_k, y_DV_k, use_gpu) 285 | 286 | # solve Euclidean proximal step 287 | V2_k = self._prox_projection(V1_k, V2_k, y_V2_k, boundary_constraint) 288 | 289 | # dual update 290 | y_DV_k += DV_k_diff; 291 | y_V2_k += V1_k - V2_k; 292 | 293 | print("elapsed time: {:5.2f} seconds, iteration : {:02d}/{:02d}".format(time.time()-t_start, iteration+1, tv_max_iter), end="\r") 294 | 295 | return V1_k[:,:,:,1], V1_k[:,:,:,0] 296 | 297 | def solve(self, method="Tikhonov", tv_max_iter=20, boundary_constraint={"real":"negative", "imag":"negative"}, use_gpu=False): 298 | ''' 299 | _prox_LASSO performs the proximal operator and solves the LASSO problem with L1 norm for total variation regularization. 300 | Inputs: 301 | method : select "Tikhonov" or "TV" deconvolution methods. 302 | tv_max_iter : If "TV" method is used, specify the number of iterations of the ADMM algorithm 303 | boundary_constraint : indicate whether to use positive or negative constraint on the scattering potential 304 | use_gpu : flag to specify gpu usage 305 | Output: 306 | RI_obj : reconstructed 3D refractive index 307 | ''' 308 | if use_gpu: 309 | globals()["af"] = __import__("arrayfire") 310 | 311 | AHA = [(self.H_imag.conj()*self.H_imag).sum(axis=0), (self.H_imag.conj()*self.H_real).sum(axis=0),\ 312 | (self.H_real.conj()*self.H_imag).sum(axis=0), (self.H_real.conj()*self.H_real).sum(axis=0)] 313 | fIntensity = F_3D(self.dpc_imgs).transpose(3, 0, 1, 2).astype('complex64') 314 | 315 | if method == "Tikhonov": 316 | print("="*10+" Solving 3D DPC with Tikhonov regularization "+"="*10) 317 | AHA[0] += self.reg_imag 318 | AHA[3] += self.reg_real 319 | AHy = [(self.H_imag.conj()*fIntensity).sum(axis=0), (self.H_real.conj()*fIntensity).sum(axis=0)] 320 | if use_gpu: 321 | AHA = [af.to_array(AHA_i) for AHA_i in AHA] 322 | AHy = [af.to_array(AHy_i) for AHy_i in AHy] 323 | 324 | determinant = AHA[0]*AHA[3]-AHA[1]*AHA[2] 325 | V_real, V_imag = self._deconvTikhonov(AHA, AHy, determinant, use_gpu) 326 | 327 | elif method == "TV": 328 | print("="*10+" Solving 3D DPC with total variation regularization and boundary value constraint "+"="*10) 329 | fDx = np.zeros(self.dpc_imgs.shape[:3], dtype='complex64') 330 | fDy = np.zeros(self.dpc_imgs.shape[:3], dtype='complex64') 331 | fDz = np.zeros(self.dpc_imgs.shape[:3], dtype='complex64') 332 | fDx[0, 0, 0] = 1.0; fDx[0, -1, 0] = -1.0; fDx = F_3D(fDx).astype('complex64') 333 | fDy[0, 0, 0] = 1.0; fDy[-1, 0, 0] = -1.0; fDy = F_3D(fDy).astype('complex64') 334 | fDz[0, 0, 0] = 1.0; fDz[0, 0, -1] = -1.0; fDz = F_3D(fDz).astype('complex64') 335 | AHA[0] += self.rho*(fDx*fDx.conj() + fDy*fDy.conj() + fDz*fDz.conj() + 1.0) 336 | AHA[3] += self.rho*(fDx*fDx.conj() + fDy*fDy.conj() + fDz*fDz.conj() + 1.0) 337 | if use_gpu: 338 | AHA = [af.to_array(AHA_i) for AHA_i in AHA] 339 | fDx = af.to_array(fDx) 340 | fDy = af.to_array(fDy) 341 | fDz = af.to_array(fDz) 342 | 343 | determinant = AHA[0]*AHA[3]-AHA[1]*AHA[2] 344 | V_real, V_imag = self._deconvTV(AHA, determinant, fIntensity, fDx, fDy, fDz, tv_max_iter, boundary_constraint, use_gpu) 345 | RI_obj = self._V2RI(V_real, V_imag) 346 | 347 | return RI_obj 348 | --------------------------------------------------------------------------------