├── .gitignore ├── LICENSE ├── README.md ├── main.m └── multipliers.m /.gitignore: -------------------------------------------------------------------------------- 1 | *.asv 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Angular spectrum 2 | A tidy MATLAB implementation of angular spectrum method to calculate beam propagation 3 | -------------------------------------------------------------------------------- /main.m: -------------------------------------------------------------------------------- 1 | % physical parameters 2 | pix_xyz = [0.1, 0.1, 0.1]; % pixel size, um 3 | lambda0 = 0.6; % wavelength in vaccum, um 4 | n0 = 1.0; % background refractive index 5 | res = pix_xyz./lambda0.*n0; % note: only 'res' is used in the following code 6 | 7 | % initialization 8 | shape = [512 512]; % xy FOV, 51.2um x 51.2um 9 | mu = multipliers(shape, res); 10 | 11 | % angular frequency of z 12 | kz = mu.gamma * 2 * pi * res(3); 13 | % Remove evanescent wave 14 | eva = exp(min((mu.gamma - 0.2) * 5, 0)); 15 | 16 | 17 | %% Example 1: a tilted gaussian beam 18 | % You can see the gaussian spot moving along the wave vector, and all the 19 | % energy is absorbed by the boundary finally. 20 | u = mu.tilt([sin(pi/6), 0]) .* mu.gaussian([0.5 0.5], [50 50]); 21 | figure; 22 | imagesc(abs(u).^2); caxis([0, 1]); colorbar; axis image; drawnow; 23 | 24 | steps = 100; 25 | dz = 6; % px 26 | p_mat = exp(1i * dz * kz) .* eva; 27 | 28 | for i = 1:steps 29 | fprintf("z = %.1f um\n", i*dz*pix_xyz(3)); 30 | a = fft2(u); 31 | a = a .* p_mat; 32 | u = ifft2(a); 33 | u = u .* mu.soft_crop(0.2, 1, steps); % change strength smaller and you can see energy leak from the other side 34 | % pause(0.05); 35 | imagesc(abs(u).^2); caxis([0, 1]); colorbar; axis image; drawnow; 36 | end 37 | pause(1) 38 | 39 | 40 | %% Example 2: negative circular diffraction, on-axis illumination 41 | % You can first see the famous Arago spot (Poisson spot) and then a 42 | % Fraunhofer diffraction pattern. Boundary absorption ensures there is no 43 | % fake interference pattern of the energy that goes out of the FOV. 44 | u = mu.tilt([0, 0]); 45 | current_phase = 1; 46 | % lazy way to make a circular shadow, r=30pix=3um 47 | u = u .* (mu.gaussian([0.4, 0.4], [30, 30]) < 1/exp(1)); 48 | figure; 49 | imagesc(abs(u).^2); caxis([0, 2]); colorbar; axis image; drawnow; 50 | 51 | steps = 100; 52 | dz = 6; % px 53 | p_mat = exp(1i * dz * kz) .* eva; 54 | 55 | for i = 1:steps 56 | fprintf("z = %.1f um\n", i*dz*pix_xyz(3)); 57 | a = fft2(u); 58 | a = a .* p_mat; 59 | u = ifft2(a); 60 | current_phase = current_phase * p_mat(1,1); 61 | u = (u-current_phase) .* mu.soft_crop(0.2, 1, steps) + current_phase; 62 | % pause(0.05); 63 | imagesc(abs(u).^2); caxis([0, 2]); colorbar; axis image; drawnow; 64 | end 65 | pause(1) 66 | 67 | 68 | %% Example 3: negative rectangle diffraction, off-axis illumination 69 | % You can see the combined result of diffraction, pattern moving because of 70 | % off-axis illumination, and boundary absorption. 71 | u = mu.tilt([sin(pi/6), sin(pi/6)]); 72 | current_bg = u; 73 | 74 | u(250:290, 150:300) = 0; 75 | figure; 76 | imagesc(abs(u).^2); caxis([0, 2]); colorbar; axis image; drawnow; 77 | 78 | steps = 100; 79 | dz = 6; % px 80 | p_mat = exp(1i * dz * kz) .* eva; 81 | 82 | for i = 1:steps 83 | fprintf("z = %.1f um\n", i*dz*pix_xyz(3)); 84 | a = fft2(u); 85 | a = a .* p_mat; 86 | u = ifft2(a); 87 | current_bg = ifft2(fft2(current_bg) .* p_mat); 88 | u = (u-current_bg) .* mu.soft_crop(0.2, 1, steps) + current_bg; 89 | % pause(0.05); 90 | imagesc(abs(u).^2); caxis([0, 2]); colorbar; axis image; drawnow; 91 | end 92 | -------------------------------------------------------------------------------- /multipliers.m: -------------------------------------------------------------------------------- 1 | classdef multipliers < handle 2 | properties % (Access=private) 3 | cache 4 | shape 5 | res 6 | end 7 | methods(Static) % (Access=private) 8 | function out=linspace_near_0(len, pos_0, edge) 9 | % Give 1d arr of linespace [-0.5, 0.5) 10 | % Example of edge: 11 | % true->0, 0.25, -0.5, 0 12 | % false->0.125, 0.375, -0.375, -0.125 13 | arguments 14 | len, pos_0=0, edge=true 15 | end 16 | out = 0:len-1; 17 | out = mod((out + 0.5*~edge)/len - pos_0 + 0.5, 1) - 0.5; 18 | end 19 | end 20 | 21 | methods 22 | function obj=multipliers(shape, res) 23 | obj.shape = shape; 24 | obj.res = res; 25 | end 26 | function arr=gamma(obj) 27 | if ~isfield(obj.cache, 'gamma') 28 | EPS = 1e-10; 29 | alpha = obj.linspace_near_0(obj.shape(2)) / obj.res(1); 30 | beta = obj.linspace_near_0(obj.shape(1)) / obj.res(2); 31 | obj.cache.gamma = sqrt(max(1-alpha.^2-beta'.^2, EPS)); 32 | end 33 | arr = obj.cache.gamma; 34 | end 35 | function [arr, alpha_beta]=tilt(obj, alpha_beta, trunc) 36 | arguments 37 | obj, alpha_beta, trunc=true 38 | end 39 | nm = obj.shape([2 1]) .* obj.res([1 2]); 40 | if trunc 41 | alpha_beta = fix(alpha_beta .* nm) ./ nm; 42 | end 43 | % if ~isfield(obj.cache, 'tilt') && obj.cache.tilt(alpha_beta) 44 | % end 45 | xr = linspace(0,1,obj.shape(2)+1) * alpha_beta(1) * nm(1); 46 | yr = linspace(0,1,obj.shape(1)+1) * alpha_beta(2) * nm(2); 47 | xr = xr(1:end-1); 48 | yr = yr(1:end-1); 49 | phase = mod(xr - yr', 1); 50 | arr = exp(2j * pi * phase); 51 | % normalize the phase using center point 52 | arr = arr / arr(floor(obj.shape(1)/2), floor(obj.shape(2)/2)); 53 | end 54 | function arr=soft_crop(obj, width, strength, total_slices) 55 | fac1 = log(100 * strength) + 0.8; 56 | fac2 = 100 * strength / total_slices; 57 | x_fac = exp(-exp(-(obj.linspace_near_0(obj.shape(2), 0) * 2 / width) .^ 2 * fac1)*fac2); 58 | y_fac = exp(-exp(-(obj.linspace_near_0(obj.shape(1), 0) * 2 / width) .^ 2 * fac1)*fac2); 59 | arr = x_fac .* y_fac'; 60 | end 61 | function arr=gaussian(obj, mu, sigma) 62 | % mu in 0~1, sigma in px 63 | x_fac = exp(-(obj.linspace_near_0(obj.shape(2), mu(1))) .^ 2 / 2 / (sigma(1) / obj.shape(2)) ^ 2); 64 | y_fac = exp(-(obj.linspace_near_0(obj.shape(1), mu(2))) .^ 2 / 2 / (sigma(2) / obj.shape(1)) ^ 2); 65 | arr = x_fac .* y_fac'; 66 | end 67 | end 68 | end --------------------------------------------------------------------------------