├── data └── README ├── README.md ├── .gitignore ├── LICENSE.md ├── utils ├── encode_double_phase.m ├── propagation_kernel.m ├── fresnel_hologram_rgbd.m ├── holographic_stereogram_apas.m ├── holographic_stereogram_hs.m ├── propagation_slm.m ├── holographic_stereogram_pas.m ├── holographic_stereogram_olas.m └── load_unity_light_field.m └── make_SLM_pattern.m /data/README: -------------------------------------------------------------------------------- 1 | Light fields should be placed in this directory for the make_SLM_pattern 2 | script. 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # OLAS 2 | Source code for the Overlap-Add Stereogram method. See http://www.computationalimaging.org/publications/holographic-near-eye-displays-based-on-overlap-add-stereograms-siggraph-asia-2019/ for details. 3 | 4 | To run, download the light fields from https://drive.google.com/file/d/1qmWAVQQRNAbus2koYrFIGzaphnvhye_t/view and place the contents in the `data/` folder. Other CGH algorithms (found in `utils/`) are included for easy comparison. Uncomment the appropriate lines in `make_SLM_pattern.m` to switch between algorithms. 5 | 6 | If you use the code provided in this repository or find the paper linked above relevant to your work, please cite the paper linked above. The BibTeX citation for this code/paper is below: 7 | 8 | @article{Padmanaban:2019:OLAS, 9 | author = {N. Padmanaban and Y. Peng and G. Wetzstein}, 10 | title = {{Holographic Near-Eye Displays Based on Overlap-Add Stereograms}}, 11 | journal = {ACM Trans. Graph. (SIGGRAPH Asia)}, 12 | issue = {38}, 13 | number = {6}, 14 | year = {2019}, 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | .hypothesis/ 50 | .pytest_cache/ 51 | 52 | # Translations 53 | *.mo 54 | *.pot 55 | 56 | # Django stuff: 57 | *.log 58 | local_settings.py 59 | db.sqlite3 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # Environments 87 | .env 88 | .venv 89 | env/ 90 | venv/ 91 | ENV/ 92 | env.bak/ 93 | venv.bak/ 94 | 95 | # Spyder project settings 96 | .spyderproject 97 | .spyproject 98 | 99 | # Rope project settings 100 | .ropeproject 101 | 102 | # mkdocs documentation 103 | /site 104 | 105 | # mypy 106 | .mypy_cache/ 107 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The OLAS source is licensed under the following license: 2 | 3 | > Copyright (c) 2019, Stanford University 4 | > 5 | > All rights reserved. 6 | > 7 | > Redistribution and use in source and binary forms for academic and other non-commercial purposes with or without modification, are permitted provided that the following conditions are met: 8 | > 9 | > * Redistributions of source code, including modified source code, must retain the above copyright notice, this list of conditions and the following disclaimer. 10 | > 11 | > * Redistributions in binary form or a modified form of the source code 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. 12 | > 13 | > * Neither the name of The Leland Stanford Junior University, any of its trademarks, the names of its employees, nor contributors to the source code may be used to endorse or promote products derived from this software without specific prior written permission. 14 | > 15 | > * Where a modified version of the source code is redistributed publicly in source or binary forms, the modified source code must be published in a freely accessible manner, or otherwise redistributed at no charge to anyone requesting a copy of the modified source code, subject to the same terms as this agreement. 16 | > 17 | > THIS SOFTWARE IS PROVIDED BY THE TRUSTEES OF THE LELAND STANFORD JUNIOR UNIVERSITY "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 LELAND STANFORD JUNIOR UNIVERSITY OR ITS TRUSTEES 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. -------------------------------------------------------------------------------- /utils/encode_double_phase.m: -------------------------------------------------------------------------------- 1 | function [dpac_3pi, dpac_2pi] = encode_double_phase(field, shift_mean_phase_DPAC,... 2 | max_phase, center_phase) 3 | 4 | if nargin < 2 5 | shift_mean_phase_DPAC = false; 6 | end 7 | 8 | if nargin < 3 9 | max_phase = 3 * pi; 10 | end 11 | 12 | max_field = max(abs(field(:))); 13 | if ( max_field > 1 ) 14 | % if this happens, the exponents will be complex-valued, resulting in 15 | % phase+amplitude SLM pattern 16 | disp(['WARNING: image needs to be normalized for this to work! Max ' ... 17 | 'value of x is ' num2str(max_field)]); 18 | field = field ./ max_field; 19 | end 20 | 21 | phase_target = angle(field); 22 | 23 | if shift_mean_phase_DPAC 24 | phase_target = phase_target - mean(phase_target(:)) + center_phase; 25 | phase_target(phase_target < -pi) = phase_target(phase_target < -pi) + 2*pi; 26 | phase_target(phase_target >= pi) = phase_target(phase_target >= pi) - 2*pi; 27 | end 28 | 29 | pa = phase_target - acos(abs(field)); 30 | pb = phase_target + acos(abs(field)); 31 | phi = pa; 32 | phi(1:2:end,2:2:end,:) = pb(1:2:end,2:2:end,:); 33 | phi(2:2:end,1:2:end,:) = pb(2:2:end,1:2:end,:); 34 | 35 | % 3 pi range (or whatever max_phase is set to) 36 | 37 | % nominally center the phases 38 | dpac_3pi = phi + max_phase/2; 39 | 40 | % wrap anything outside the 0 to max_phase range by 2pi 41 | while any(dpac_3pi(:) < 0) 42 | dpac_3pi(dpac_3pi < 0) = dpac_3pi(dpac_3pi < 0) + 2 * pi; 43 | end 44 | while any(dpac_3pi(:) >= max_phase) 45 | dpac_3pi(dpac_3pi >= max_phase) = ... 46 | dpac_3pi(dpac_3pi >= max_phase) - 2 * pi; 47 | end 48 | 49 | % normalize to [0 1] 50 | dpac_3pi = dpac_3pi / max_phase; 51 | 52 | % 2 pi range 53 | if nargout > 1 54 | % wrap the phase values to range -pi to pi 55 | phi_2pi = angle(exp(1i .* phi)); 56 | 57 | % normalize phase to range [0 1] encoding phase [0 2*pi] 58 | dpac_2pi = (phi_2pi + pi) ./ (2*pi); 59 | end 60 | 61 | end -------------------------------------------------------------------------------- /make_SLM_pattern.m: -------------------------------------------------------------------------------- 1 | % Takes a complex field in a mat file and back propagates it 10cm to the 2 | % SLM, followed by dpac. 3 | 4 | clear all; 5 | addpath('utils'); 6 | 7 | color = {'_r', '_g', '_b'}; 8 | scenes = {'landscape_0000', 'landscape_0001',... 9 | 'landscape_day_0000', 'landscape_day_0001',... 10 | 'landscape_night_0000', 'landscape_night_0001',... 11 | 'lfGenLowPolyCastles_0000', 'lfGenLowPolyForest_0000'}; 12 | 13 | for scene = 1:length(scenes) 14 | 15 | slm_dist = 10e-2; % 10 cm 16 | pixel_pitch = 6.4e-6; % 6.4 um 17 | slm_resolution = [1080, 1920]; 18 | wavelengths = [635 520 450] * 1e-9; % rgb, 635, 520, 450 nm 19 | max_phases = [3 3.1 3.1] * pi; 20 | center_phase = pi/4; % for better use of SLM quantization 21 | eyepiece_focal_length = 2.5e-2; % 2.5 cm 22 | wavenums = 2*pi ./ wavelengths; 23 | 24 | % Loading from a light field folder 25 | lf_folder = ['data/' scenes{scene}]; 26 | [lf, d] = load_unity_light_field(lf_folder, eyepiece_focal_length, [], true); 27 | 28 | % compute the complex field for the lf 29 | field = holographic_stereogram_olas(lf, d, wavenums, pixel_pitch); 30 | % replace the above with one of these for a different algorithm 31 | % % field = holographic_stereogram_hs(lf, d, wavenums); 32 | % % field = holographic_stereogram_pas(lf, d, wavenums); 33 | % % field = holographic_stereogram_apas(lf, d, wavenums); 34 | 35 | % for fresnel, also replace the loader so that it's rgbd input 36 | % % [rgb, depth] = load_unity_light_field(lf_folder, eyepiece_focal_length, [], true, true); 37 | % % use_fast_single_plane_fresnel_approx = false; 38 | % % if use_fast_single_plane_fresnel_approx 39 | % % field = sqrt(rgb); 40 | % % else 41 | % % phase_compensation_for_prop = true; 42 | % % field = fresnel_hologram_rgbd(rgb, depth, wavenums, phase_compensation_for_prop, pixel_pitch); 43 | % % end 44 | 45 | % save the field too 46 | field_file = [lf_folder '_olas']; 47 | save(field_file, 'field'); 48 | 49 | % pad up to slm resolution if it's not 50 | pad_amount = slm_resolution - [size(field, 1) size(field, 2)]; 51 | if any(pad_amount > 0) 52 | field = padarray(field, floor(pad_amount / 2), 'pre'); 53 | field = padarray(field, ceil(pad_amount / 2), 'post'); 54 | end 55 | 56 | % propagate to slm 57 | field_slm = zeros(size(field)); 58 | for c = 1:length(wavelengths) 59 | lambda = wavelengths(c); 60 | field_slm(:,:,c) = propagation_slm(field(:,:,c), [pixel_pitch pixel_pitch], ... 61 | lambda, -slm_dist, slm_resolution, slm_resolution); 62 | end 63 | 64 | % flip for convention 65 | field_slm = conj(field_slm); 66 | 67 | % global scaling 68 | field_slm = field_slm / max(field_slm(:)); 69 | 70 | % dpac 71 | dpac_3pi = zeros(size(field_slm)); 72 | for c = 1:length(wavelengths) 73 | dpac_3pi(:,:,c) = encode_double_phase(field_slm(:,:,c), true,... 74 | max_phases(c), center_phase); 75 | imwrite(dpac_3pi(:,:,c), [field_file color{c} '.png'], 'BitDepth', 8); 76 | end 77 | % save to file 78 | imwrite(dpac_3pi, [field_file '_rgb.png'], 'BitDepth', 8); 79 | 80 | end 81 | -------------------------------------------------------------------------------- /utils/propagation_kernel.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % Calculate the propagation kernel for free-space propagation 3 | % 4 | % input: distance - distance from point to hologram plane 5 | % lambda - wavelength 6 | % slmPixelSize - feature size of hologram 7 | % bPhaseCompensation - subtract distance normal to hologram plane 8 | % (i.e. "distance") from distance to each pixel 9 | 10 | function h = propagation_kernel(distance, lambda, slmPixelSize, bPhaseCompensation) 11 | 12 | if nargin<4 13 | bPhaseCompensation = true; 14 | end 15 | 16 | % wave number 17 | k = 2*pi/lambda; 18 | 19 | % check if we're propagating back to SLM plane 20 | bBackpropagation = true; 21 | if distance>=0 22 | bBackpropagation = false; 23 | end 24 | distance = abs(distance); 25 | 26 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 27 | % compute size of subhologram via grating equation 28 | % d * (sin(theta_in) + sin(theta_out)) = lambda 29 | % and then use similar triangles with propagation distance 30 | 31 | % max diffraction angle 32 | maxDiffractionAngleRad = asin(lambda ./ (2*slmPixelSize)); 33 | 34 | % size of subhologram in m 35 | subhologramSize = 2 * distance * tan(maxDiffractionAngleRad); 36 | 37 | % resolution of subhologram in number of slm pixels 38 | subhologramResolution = round((subhologramSize ./ slmPixelSize) ); 39 | 40 | % this should be even 41 | %subhologramResolution = floor(subhologramResolution./2) .* 2 ; 42 | subhologramResolution = floor(subhologramResolution./2) .* 2 + 1; 43 | 44 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 45 | % compute convolution kernel 46 | 47 | % get coordinates of pixel centers in kernel 48 | startY = -slmPixelSize(1)*(subhologramResolution(1)-1)/2; 49 | startX = -slmPixelSize(2)*(subhologramResolution(2)-1)/2; 50 | [XKERNEL, YKERNEL] = meshgrid( startX + ((0:subhologramResolution(2)-1)*slmPixelSize(2)), ... 51 | startY + ((0:subhologramResolution(1)-1)*slmPixelSize(1)) ); 52 | 53 | % compute distance of each pixel 54 | r = sqrt(XKERNEL.^2 + YKERNEL.^2 + distance.^2); % Kirchhoff 55 | 56 | if bPhaseCompensation 57 | h = exp( 1i .* k .* (r-distance) ) ./ r; % this is the convolution kernel 58 | else 59 | h = exp( 1i .* k .* r ) ./ r; 60 | end 61 | 62 | % we want this to be round 63 | maxValue = min(r(1,:)); 64 | mask = double(r<=maxValue); 65 | 66 | % apodize kernel 67 | blurSize = 20; 68 | 69 | %mask = imfilter(mask, fspecial('gaussian', 3.*[blurSize blurSize], blurSize), 'same'); 70 | mask = conv2(mask, fspecial('gaussian', 3.*[blurSize blurSize], blurSize)); 71 | mask = imresize(mask, size(h)); 72 | 73 | h = h.*mask; 74 | 75 | % for backpropagation, use conjugate transpose of kernel 76 | if bBackpropagation 77 | h = h'; 78 | end 79 | end -------------------------------------------------------------------------------- /utils/fresnel_hologram_rgbd.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % load a light field + depth, keep only the central view, compte a 3 | % fresnel hologram from it by propagating every point separately 4 | % ATTENTION: very slow! 5 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 6 | function [hologram] = fresnel_hologram_rgbd(rgb, depth, wavenums, ... 7 | phaseCompensationForPropagation, pixelPitch) 8 | 9 | if nargin < 3 10 | lambdas = 532e-9 * [1 1 1]; % 532 nm 11 | else 12 | lambdas = 2 * pi ./ wavenums; 13 | end 14 | 15 | % phase compensation on or off 16 | if nargin < 4 17 | phaseCompensationForPropagation = true; 18 | end 19 | 20 | if nargin < 5 21 | pixelPitch = 6.4e-6; 22 | end 23 | % SLM pixel size in m 24 | dx = pixelPitch; dy = pixelPitch; 25 | 26 | % list all color channels you want to process here 27 | channels = 1:size(rgb, 3); 28 | 29 | % downsample to reduce time 30 | imageResolution = [1080 1920]./2; 31 | 32 | % resize and convert to amplitude 33 | rgb = sqrt(imresize(rgb, imageResolution)); 34 | depth = imresize(depth, imageResolution); 35 | 36 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 37 | % compute Fresnel hologram 38 | 39 | % initialize hologram 40 | hologram = zeros([size(rgb,1) size(rgb,2) numel(channels)]); 41 | 42 | for kc=1:numel(channels) 43 | 44 | % current color channel 45 | c = channels(kc); 46 | 47 | for ky=1:size(rgb,1) 48 | 49 | disp(['Processing hologram, channel: ' num2str(kc) ' | ' num2str(numel(channels)) ', line ' num2str(ky) ' | ' num2str(size(rgb,1))]); 50 | 51 | for kx=1:size(rgb,2) 52 | 53 | % get current depth value 54 | currentDepth = depth(ky,kx); 55 | 56 | % compute propagation kernel for this depth 57 | kernel = propagation_kernel(currentDepth, lambdas(kc), [dy dx], phaseCompensationForPropagation); 58 | 59 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 60 | % get indices to splat the kernel to 61 | 62 | cutTop = max(1 - (ky-floor(size(kernel,1)/2)),0); 63 | cutBottom = max((ky+floor(size(kernel,1)/2)) - size(rgb,1), 0); 64 | 65 | cutLeft = max(1 - (kx-floor(size(kernel,2)/2)),0); 66 | cutRight = max((kx+floor(size(kernel,2)/2)) - size(rgb,2), 0); 67 | 68 | % these are the coordinates for the kernel 69 | kernelIdxY = 1:size(kernel,1); kernelIdxY = kernelIdxY(1+cutTop:end-cutBottom); 70 | kernelIdxX = 1:size(kernel,2); kernelIdxX = kernelIdxX(1+cutLeft:end-cutRight); 71 | % get x and y indices 72 | [idxXkernel, idxYkernel] = meshgrid(kernelIdxX,kernelIdxY); 73 | 74 | % these are the coordinates for the image 75 | startY = max(1,ky-floor(size(kernel,1)/2)); 76 | endY = min(size(rgb,1),ky+floor(size(kernel,1)/2)); 77 | startX = max(1,kx-floor(size(kernel,2)/2)); 78 | endX = min(size(rgb,2),kx+floor(size(kernel,2)/2)); 79 | % get x and y indices 80 | [idxX, idxY] = meshgrid(startX:endX,startY:endY); 81 | 82 | % convert to matrix indices 83 | linearIndImg = sub2ind(size(depth), idxY(:), idxX(:)); 84 | linearIndKernel = sub2ind(size(kernel), idxYkernel(:), idxXkernel(:)); 85 | 86 | 87 | % temp array 88 | hologramTmp = zeros([size(rgb,1) size(rgb,2)]); 89 | hologramTmp(linearIndImg) = rgb(ky,kx,c) .* kernel(linearIndKernel); 90 | 91 | % splat into hologram 92 | hologram(:,:,kc) = hologram(:,:,kc) + hologramTmp; 93 | 94 | end 95 | 96 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 97 | % plot intermediate results 98 | 99 | subplot(1,2,1); 100 | imagesc(abs(hologram)); 101 | 102 | subplot(1,2,2); 103 | imagesc(angle(hologram)); 104 | drawnow; 105 | end 106 | end 107 | 108 | end -------------------------------------------------------------------------------- /utils/holographic_stereogram_apas.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % Light field to hologram conversion using tiled-window-type holographic 3 | % stereogram method: 4 | % APAS - sliding window phase-added stereogram - accurate methodm 5 | % i.e. only keep window center (with depth) 6 | % 7 | % Input: lightField - light field with size [My Mx Ny Nx C] 8 | % My, Mx are number of angular samples 9 | % Ny, Nx are number of spatial samples 10 | % C are number of color channels 11 | % 12 | % lightFieldDepth - optional light field with metric depth 13 | % values per ray with size [My Mx Ny Nx] 14 | % My, Mx are number of angular samples 15 | % Ny, Nx are number of spatial samples 16 | % 17 | % waveNum - wavenumbers, i.e. 2*pi/lambda for each 18 | % color channel (only need to specify when 19 | % using with depth) 20 | % 21 | % Output: apas - accurate phase-added stereogram (with depth) 22 | % with sliding window, but only keep center 23 | % value = complex-valued wave field with phase 24 | % and amplitude of size [Ny Nx C] 25 | % 26 | % Example: apas = holographic_stereogram(lightField,lightFieldDepth); 27 | % 28 | % Gordon Wetzstein 29 | % Stanford Computational Imaging Lab 30 | % gordon.wetzstein@stanford.edu 31 | % 12/2018 32 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 33 | 34 | function [apas] = holographic_stereogram_apas(lightField, lightFieldDepth, waveNum) 35 | 36 | if nargin < 3 37 | lambda = 532e-9; % 532 nm 38 | waveNum = [1 1 1] .* 2*pi/lambda; 39 | end 40 | 41 | if length(waveNum) == 1 42 | waveNum = [1 1 1] * waveNum; 43 | end 44 | 45 | % hogel size is set to 1/3 of the angular resolution 46 | outerHogelResolution = [size(lightField,1) size(lightField,2)]; 47 | hogelResolution = ceil(outerHogelResolution / 3); 48 | hogelCropPx = (outerHogelResolution - hogelResolution) / 2; 49 | 50 | % resolution of hologram is same spatial resolution of light field 51 | hologramResolution = [size(lightField,3) size(lightField,4)]; 52 | 53 | % number of hogels and indexing offsets 54 | hogelRadius = floor(hogelResolution / 2); 55 | numHogels = floor(hologramResolution ./ hogelResolution); 56 | hogelOffset = floor((hologramResolution - numHogels .* hogelResolution) / 2); 57 | 58 | % number of color channels 59 | numColorChannels = 1; 60 | if ndims(lightField) > 4 61 | numColorChannels = size(lightField,5); 62 | end 63 | 64 | % initialize hologram with zeros 65 | apas = zeros([hologramResolution numColorChannels]); 66 | 67 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 68 | 69 | for i = 0:numHogels(1)-1 70 | % indices are corners of the hogels 71 | ky = (i * hogelResolution(1)) + hogelOffset(1) + 1; 72 | for j = 0:numHogels(2)-1 73 | kx = (j * hogelResolution(2)) + hogelOffset(2) + 1; 74 | 75 | for c=1:numColorChannels 76 | 77 | % get angular samples of light field and depth at idx, with 78 | % offset by hogelRadius to get the center pixel spatially 79 | % for correct angular neighborhood 80 | lfPatch = squeeze(lightField(:,:,ky+hogelRadius(1),kx+hogelRadius(2),c)); 81 | depPatch = squeeze(lightFieldDepth(:,:,ky+hogelRadius(1),kx+hogelRadius(2))); 82 | 83 | % compute hogel via fft 84 | hogel = fftshift(fft2(ifftshift( sqrt(lfPatch) ... 85 | .* exp( 1i.*waveNum(c).*depPatch) ))) ./ prod(hogelResolution); % complex-valued 86 | 87 | % crop and store center of hogel 88 | apas(ky:ky+hogelResolution(1)-1, kx:kx+hogelResolution(2)-1,c) = ... 89 | hogel(1+hogelCropPx(1):end-hogelCropPx(1),... 90 | 1+hogelCropPx(2):end-hogelCropPx(2)); 91 | end 92 | 93 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 94 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 95 | 96 | end 97 | end 98 | 99 | end -------------------------------------------------------------------------------- /utils/holographic_stereogram_hs.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % Light field to hologram conversion using the holographic stereogram 3 | % method or, if depth for each light ray is available, the phase-added 4 | % stereogram method. 5 | % 6 | % Note: if no depth information is specified, the algorithm will compute 7 | % the holographic stereogram (HS) and with depth data it will 8 | % compute the phase-added stereogram (PAS). 9 | % 10 | % Input: lightField - light field with size [My Mx Ny Nx C] 11 | % My, Mx are number of angular samples 12 | % Ny, Nx are number of spatial samples 13 | % C are number of color channels 14 | % 15 | % lightFieldDepth - optional light field with metric depth values per ray with size [My Mx Ny Nx] 16 | % My, Mx are number of angular samples 17 | % Ny, Nx are number of spatial samples 18 | % 19 | % k - wavenumbers, i.e. 2*pi/lambda for each 20 | % color channel (only need to specify when 21 | % using with depth) 22 | % 23 | % Output: hs - holographic stereogram = complex-valued wave field with phase and amplitude of size [Ny Nx C] 24 | % pas - phase-added stereogram = complex-valued wave field with phase and amplitude of size [Ny Nx C] 25 | % 26 | % Example: hs = holographic_stereogram(lightField); 27 | % [hs,pas] = holographic_stereogram(lightField,lightFieldDepth,k); 28 | % 29 | % Gordon Wetzstein 30 | % Stanford Computational Imaging Lab 31 | % gordon.wetzstein@stanford.edu 32 | % 12/2018 33 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 34 | 35 | function [hs] = holographic_stereogram_hs(lightField, ~, k) 36 | 37 | if nargin < 3 38 | %disp('WARNING: need to specify wavenumber! Using default values.'); 39 | lambda = 532e-9; % 532 nm 40 | k = [1 1 1] .* 2*pi/lambda; 41 | end 42 | 43 | if length(k) == 1 44 | k = [1 1 1] * k; 45 | end 46 | 47 | % hogel size is same as angular resolution 48 | hogelResolution = [size(lightField,1) size(lightField,2)]; 49 | 50 | % target resolution of hologram is same spatial resolution of light field 51 | hologramCroppedResolution = [size(lightField,3) size(lightField,4)]; 52 | 53 | % pad so that we fit full hogels of the next size up 54 | numHogels = ceil(hologramCroppedResolution ./ hogelResolution); 55 | hologramCropAmt = numHogels .* hogelResolution - hologramCroppedResolution; 56 | % make the crop amount a multiple of 2 for centered light field padding 57 | cropRadius = [ceil(hologramCropAmt / 2); floor(hologramCropAmt / 2)]; 58 | hologramResolution = numHogels .* hogelResolution; 59 | 60 | % pad light field to match hologram 61 | lightField = padarray(lightField, [0 0 cropRadius(1,:)], 'pre'); 62 | lightField = padarray(lightField, [0 0 cropRadius(2,:)], 'post'); 63 | 64 | % number of color channels 65 | numColorChannels = 1; 66 | if ndims(lightField) > 4 67 | numColorChannels = size(lightField,5); 68 | end 69 | 70 | % center pixel within each hogel 71 | hogelCenter = floor(hogelResolution/2)+1; 72 | 73 | % number of hogels over SLM 74 | numTiledHogels = floor(hologramResolution./hogelResolution); 75 | 76 | % initialize hologram with zeros 77 | hs = zeros([hologramResolution numColorChannels]); 78 | 79 | hc = hogelCenter; hr = hogelResolution; 80 | complexLF = sqrt(lightField(:,:,hc(1):hr(1):end,hc(2):hr(2):end,:)); 81 | lfShape = size(complexLF); 82 | 83 | % fft the first dimension 84 | complexLF = reshape(complexLF, lfShape(1), []); 85 | complexLF = fftshift(fft(ifftshift(complexLF, 1), [], 1), 1) / lfShape(1); 86 | complexLF = reshape(complexLF, lfShape); 87 | % fft the second dimension 88 | complexLF = permute(complexLF, [2 1 3 4 5]); 89 | complexLF = reshape(complexLF, lfShape(2), []); 90 | complexLF = fftshift(fft(ifftshift(complexLF, 1), [], 1), 1) / lfShape(2); 91 | complexLF = reshape(complexLF, [lfShape(2) lfShape(1) lfShape(3:end)]); 92 | complexLF = permute(complexLF, [2 1 3 4 5]); 93 | 94 | % tile the hogels 95 | for ky = 1:hogelResolution(1) 96 | for kx = 1:hogelResolution(2) 97 | hs(ky:hogelResolution(1):end, kx:hogelResolution(2):end, : ) =... 98 | squeeze(complexLF(ky, kx, :, :, :)); 99 | end 100 | end 101 | 102 | % crop back to original light field size 103 | hs = hs(1+cropRadius(1,1):end-cropRadius(2,1),... 104 | 1+cropRadius(1,2):end-cropRadius(2,2),:); 105 | 106 | 107 | end -------------------------------------------------------------------------------- /utils/propagation_slm.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % Implementation of free space propagation of coherent light with a 3 | % phase-only spatial light modulator. 4 | % 5 | % Input parameters (all distances in meters!): 6 | % 7 | % fieldIn - complex-valued field on the input plane 8 | % slmPixelSize - [height width] of an SLM pixel 9 | % slmResolution - [height width] in number of pixels of the SLM 10 | % imageResolution - width and height of target image (should be square) 11 | % lambda - wavelength of light 12 | % distance - propagation distance (use positive value for 13 | % propagation from SLM to target plane and negative 14 | % value for backpropagation) 15 | % 16 | % Output parameters: 17 | % fieldOut - complex-valued field on the output plane 18 | % 19 | % Other parameters: 20 | % bIsFresnel - use Fresnel propagation instead of Kirchhoff-Fresnel 21 | % 22 | % Gordon Wetzstein 23 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 24 | 25 | function fieldOut = propagation_slm(fieldIn, slmPixelSize, lambda, distance, slmResolution, imageResolution) 26 | 27 | % check if we're propagating back to SLM plane 28 | bBackpropagation = true; 29 | if distance>=0 30 | bBackpropagation = false; 31 | end 32 | distance = abs(distance); 33 | 34 | % wave number 35 | k = 2*pi/lambda; 36 | 37 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 38 | % compute size of subhologram via grating equation 39 | 40 | % max diffraction angle 41 | maxDiffractionAngleRad = asin(lambda ./ (2*slmPixelSize)); 42 | 43 | % size of subhologram in m 44 | subhologramSize = 2 * distance * tan(maxDiffractionAngleRad); 45 | 46 | %subhologramSize = 2 * distance * lambda ./ (2*pixelSize); % this is the small angle approximation 47 | 48 | % subhologram feature size in m 49 | subhologramFeatureSize = subhologramSize ./ imageResolution; 50 | 51 | % resolution of subhologram in number of slm pixels 52 | subhologramResolution = round((subhologramSize ./ slmPixelSize)); 53 | 54 | % this should be even 55 | subhologramResolution = floor(subhologramResolution./2) .* 2; 56 | 57 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 58 | % compute convolution kernel 59 | 60 | % get coordinates of pixel centers in kernel 61 | startY = -slmPixelSize(1)*(subhologramResolution(1)-1)/2; 62 | startX = -slmPixelSize(2)*(subhologramResolution(2)-1)/2; 63 | [XKERNEL, YKERNEL] = meshgrid( startX + ((0:subhologramResolution(2)-1)*slmPixelSize(2)), ... 64 | startY + ((0:subhologramResolution(1)-1)*slmPixelSize(1)) ); 65 | 66 | % compute distance of each pixel 67 | r = sqrt(XKERNEL.^2 + YKERNEL.^2 + distance.^2); % Kirchhoff 68 | 69 | 70 | h = exp( 1i .* k .* (r-distance) ) ./ r; % this is the convolution kernel 71 | 72 | % we want this to be round 73 | maxValue = min(r(1,:)); 74 | mask = double(r<=maxValue); 75 | 76 | % apodize kernel 77 | blurSize = 20; 78 | 79 | %mask = imfilter(mask, fspecial('gaussian', 3.*[blurSize blurSize], blurSize), 'same'); 80 | mask = conv2(mask, fspecial('gaussian', 3.*[blurSize blurSize], blurSize)); 81 | mask = imresize(mask, size(h)); 82 | 83 | h = h.*mask; 84 | 85 | % for backpropagation, use conjugate transpose of kernel 86 | if bBackpropagation 87 | h = h'; 88 | end 89 | 90 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 91 | 92 | fieldOut = propagation_conv(fieldIn, h); 93 | 94 | end 95 | 96 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 97 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 98 | 99 | function fieldOut = propagation_conv(fieldIn, h) 100 | 101 | opFFT2 = @(x) fft2(x); 102 | opIFFT2 = @(x) ifft2(x); 103 | 104 | % pad input image by a bit more than half the kernel size 105 | padSize = floor(size(h)./2)+1; 106 | fieldIn = padarray(fieldIn,padSize, median(abs(fieldIn(:)))); 107 | 108 | % get kernel in frequency domain 109 | H = psf2otf(h, size(fieldIn)); 110 | 111 | % get OTF and perform convolution in frequency domain 112 | fieldOut = opIFFT2( opFFT2(fieldIn).*H ); 113 | 114 | % crop back 115 | if prod(padSize)>0 116 | fieldOut = fieldOut( padSize(1)+1:end-padSize(1), padSize(2)+1:end-padSize(2) ); 117 | end 118 | 119 | end -------------------------------------------------------------------------------- /utils/holographic_stereogram_pas.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % Light field to hologram conversion using the holographic stereogram 3 | % method or, if depth for each light ray is available, the phase-added 4 | % stereogram method. 5 | % 6 | % Note: if no depth information is specified, the algorithm will compute 7 | % the holographic stereogram (HS) and with depth data it will 8 | % compute the phase-added stereogram (PAS). 9 | % 10 | % Input: lightField - light field with size [My Mx Ny Nx C] 11 | % My, Mx are number of angular samples 12 | % Ny, Nx are number of spatial samples 13 | % C are number of color channels 14 | % 15 | % lightFieldDepth - optional light field with metric depth values per ray with size [My Mx Ny Nx] 16 | % My, Mx are number of angular samples 17 | % Ny, Nx are number of spatial samples 18 | % 19 | % k - wavenumbers, i.e. 2*pi/lambda for each 20 | % color channel (only need to specify when 21 | % using with depth) 22 | % 23 | % Output: hs - holographic stereogram = complex-valued wave field with phase and amplitude of size [Ny Nx C] 24 | % pas - phase-added stereogram = complex-valued wave field with phase and amplitude of size [Ny Nx C] 25 | % 26 | % Example: hs = holographic_stereogram(lightField); 27 | % [hs,pas] = holographic_stereogram(lightField,lightFieldDepth,k); 28 | % 29 | % Gordon Wetzstein 30 | % Stanford Computational Imaging Lab 31 | % gordon.wetzstein@stanford.edu 32 | % 12/2018 33 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 34 | 35 | function [pas] = holographic_stereogram_pas(lightField, lightFieldDepth, k) 36 | 37 | if nargin < 3 38 | %disp('WARNING: need to specify wavenumber! Using default values.'); 39 | lambda = 532e-9; % 532 nm 40 | k = [1 1 1] .* 2*pi/lambda; 41 | end 42 | 43 | if length(k) == 1 44 | k = [1 1 1] * k; 45 | end 46 | 47 | % hogel size is same as angular resolution 48 | hogelResolution = [size(lightField,1) size(lightField,2)]; 49 | 50 | % target resolution of hologram is same spatial resolution of light field 51 | hologramCroppedResolution = [size(lightField,3) size(lightField,4)]; 52 | 53 | % pad so that we fit full hogels of the next size up 54 | numHogels = ceil(hologramCroppedResolution ./ hogelResolution); 55 | hologramCropAmt = numHogels .* hogelResolution - hologramCroppedResolution; 56 | % make the crop amount a multiple of 2 for centered light field padding 57 | cropRadius = [ceil(hologramCropAmt / 2); floor(hologramCropAmt / 2)]; 58 | hologramResolution = numHogels .* hogelResolution; 59 | 60 | % pad light field to match hologram 61 | lightField = padarray(lightField, [0 0 cropRadius(1,:)], 'pre'); 62 | lightField = padarray(lightField, [0 0 cropRadius(2,:)], 'post'); 63 | lightFieldDepth = padarray(lightFieldDepth, [0 0 cropRadius(1,:)], 'pre'); 64 | lightFieldDepth = padarray(lightFieldDepth, [0 0 cropRadius(2,:)], 'post'); 65 | 66 | % number of color channels 67 | numColorChannels = 1; 68 | if ndims(lightField) > 4 69 | numColorChannels = size(lightField,5); 70 | end 71 | 72 | % center pixel within each hogel 73 | hogelCenter = floor(hogelResolution/2)+1; 74 | 75 | % number of hogels over SLM 76 | numTiledHogels = floor(hologramResolution./hogelResolution); 77 | 78 | % initialize hologram with zeros 79 | pas = zeros([hologramResolution numColorChannels]); 80 | 81 | hc = hogelCenter; hr = hogelResolution; 82 | complexLF = sqrt(lightField(:,:,hc(1):hr(1):end,hc(2):hr(2):end,:)); 83 | depthWaves = zeros(size(complexLF)); 84 | for c = 1:numColorChannels 85 | depthWaves(:,:,:,:,c) = k(c) .* lightFieldDepth(:,:,hc(1):hr(1):end,hc(2):hr(2):end); 86 | end 87 | complexLF = complexLF .* exp(1i .* depthWaves); 88 | lfShape = size(complexLF); 89 | 90 | % fft the first dimension 91 | complexLF = reshape(complexLF, lfShape(1), []); 92 | complexLF = fftshift(fft(ifftshift(complexLF, 1), [], 1), 1) / lfShape(1); 93 | complexLF = reshape(complexLF, lfShape); 94 | % fft the second dimension 95 | complexLF = permute(complexLF, [2 1 3 4 5]); 96 | complexLF = reshape(complexLF, lfShape(2), []); 97 | complexLF = fftshift(fft(ifftshift(complexLF, 1), [], 1), 1) / lfShape(2); 98 | complexLF = reshape(complexLF, [lfShape(2) lfShape(1) lfShape(3:end)]); 99 | complexLF = permute(complexLF, [2 1 3 4 5]); 100 | 101 | % tile the hogels 102 | for ky = 1:hogelResolution(1) 103 | for kx = 1:hogelResolution(2) 104 | pas(ky:hogelResolution(1):end, kx:hogelResolution(2):end, : ) =... 105 | squeeze(complexLF(ky, kx, :, :, :)); 106 | end 107 | end 108 | 109 | % crop back to original light field size 110 | pas = pas(1+cropRadius(1,1):end-cropRadius(2,1),... 111 | 1+cropRadius(1,2):end-cropRadius(2,2),:); 112 | 113 | 114 | end -------------------------------------------------------------------------------- /utils/holographic_stereogram_olas.m: -------------------------------------------------------------------------------- 1 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 2 | % Light field to hologram conversion using sliding-window-type holographic 3 | % stereogram methods: 4 | % OLA-APAS - sliding window phase-added stereogram - overlap and add method (with depth) 5 | % 6 | % Input: lightField - light field with size [My Mx Ny Nx C] 7 | % My, Mx are number of angular samples 8 | % Ny, Nx are number of spatial samples 9 | % C are number of color channels 10 | % 11 | % lightFieldDepth - optional light field with metric depth 12 | % values per ray with size [My Mx Ny Nx] 13 | % My, Mx are number of angular samples 14 | % Ny, Nx are number of spatial samples 15 | % 16 | % waveNum - wavenumbers, i.e. 2*pi/lambda for each 17 | % color channel (only need to specify when 18 | % using with depth) 19 | % 20 | % Output: apas_ola - accurate phase-added stereogram (with depth) 21 | % with sliding window (overlap and add) = 22 | % complex-valued wave field with phase and 23 | % amplitude of size [Ny Nx C] 24 | % 25 | % Example: apas_ola = holographic_stereogram(lightField,lightFieldDepth); 26 | % 27 | % Gordon Wetzstein 28 | % Stanford Computational Imaging Lab 29 | % gordon.wetzstein@stanford.edu 30 | % 12/2018 31 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 32 | 33 | function [apas_ola] = holographic_stereogram_olas(lightField, lightFieldDepth, waveNum,... 34 | pixelPitch) 35 | 36 | if nargin < 3 37 | lambda = 532e-9; % 532 nm 38 | waveNum = [1 1 1] .* 2*pi/lambda; 39 | end 40 | 41 | if nargin < 4 42 | pixelPitch = 6.4e-6; 43 | end 44 | 45 | if length(waveNum) == 1 46 | waveNum = [1 1 1] * waveNum; 47 | end 48 | 49 | lambda = 2 * pi ./ waveNum; 50 | 51 | % hogel size is same as angular resolution 52 | hogelResolution = [size(lightField,1) size(lightField,2)]; 53 | 54 | % resolution of hologram is same spatial resolution of light field 55 | hologramResolution = [size(lightField,3) size(lightField,4)]; 56 | 57 | % number of color channels 58 | numColorChannels = 1; 59 | if ndims(lightField) > 4 60 | numColorChannels = size(lightField,5); 61 | end 62 | 63 | % initialize hologram with zeros, padded to avoid edges/for centering 64 | hogelRadius = floor(hogelResolution / 2); 65 | apas_ola = zeros([(hologramResolution + hogelRadius*2) numColorChannels]); 66 | 67 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 68 | % compute synthesis window 69 | 70 | % custom version of hann without zeros at ends 71 | function wndw = w_func(len) 72 | wndw = hann(len + 2); 73 | wndw = wndw(2:end-1); 74 | end 75 | 76 | win = w_func(hogelResolution(1)) * w_func(hogelResolution(1))'; 77 | win = win / sum(win(:)); 78 | 79 | %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% 80 | 81 | lfShape = size(lightField); 82 | 83 | % compute complex field 84 | complexLF = zeros(lfShape); 85 | for c = 1:numColorChannels 86 | 87 | % apply depth compensation 88 | compensatedDepth = zeros(size(lightFieldDepth)); 89 | freqX = linspace(-1+1/hogelResolution(2), 1-1/hogelResolution(2), ... 90 | hogelResolution(2)) / (2 * pixelPitch); 91 | freqY = linspace(-1+1/hogelResolution(1), 1-1/hogelResolution(1), ... 92 | hogelResolution(1)) / (2 * pixelPitch); 93 | for ky = 1:hogelResolution(1) 94 | for kx = 1:hogelResolution(2) 95 | theta = asin(sqrt(freqX(kx)^2 + freqY(ky)^2) * lambda(c)); 96 | compensatedDepth(ky, kx, :, :) = lightFieldDepth(ky, kx, :, :)... 97 | * (1 - cos(theta)); 98 | end 99 | end 100 | 101 | complexLF(:,:,:,:,c) = sqrt(lightField(:,:,:,:,c)) ... 102 | .* exp(1i * waveNum(c) .* compensatedDepth); 103 | end 104 | clear lightField lightFieldDepth compensatedDepth; 105 | 106 | % fft the first dimension 107 | complexLF = reshape(complexLF, lfShape(1), []); 108 | complexLF = fftshift(fft(ifftshift(complexLF, 1), [], 1), 1) / lfShape(1); 109 | complexLF = reshape(complexLF, lfShape); 110 | % fft the second dimension 111 | complexLF = permute(complexLF, [2 1 3 4 5]); 112 | complexLF = reshape(complexLF, lfShape(2), []); 113 | complexLF = fftshift(fft(ifftshift(complexLF, 1), [], 1), 1) / lfShape(2); 114 | complexLF = reshape(complexLF, [lfShape(2) lfShape(1) lfShape(3:end)]); 115 | complexLF = permute(complexLF, [2 1 3 4 5]); 116 | 117 | % apply window 118 | complexLF = complexLF .* repmat(win, [1 1 lfShape(3:end)]); 119 | 120 | % overlap and add the hogels 121 | for ky = 1:hogelResolution(1) 122 | for kx = 1:hogelResolution(2) 123 | apas_ola(ky:ky+hologramResolution(1)-1, kx:kx+hologramResolution(2)-1, :) = ... 124 | apas_ola(ky:ky+hologramResolution(1)-1, kx:kx+hologramResolution(2)-1, :) ... 125 | + squeeze(complexLF(ky, kx, :, :, :)); 126 | end 127 | end 128 | 129 | % crop back to light field size 130 | apas_ola = apas_ola(1+hogelRadius(1):end-hogelRadius(1),... 131 | 1+hogelRadius(2):end-hogelRadius(2), :); 132 | end -------------------------------------------------------------------------------- /utils/load_unity_light_field.m: -------------------------------------------------------------------------------- 1 | function [light_field, depth] = load_unity_light_field(datapath,... 2 | eyepieceFocalLength, frameNum, flipLFOutput, loadOnlyCentralView) 3 | 4 | if nargin < 5 5 | loadOnlyCentralView = false; 6 | end 7 | 8 | % json calibration file name 9 | json_fname = fullfile(datapath, 'cameras.json'); 10 | 11 | % read json file 12 | json = jsondecode(fileread(json_fname)); 13 | 14 | % near clipping plane 15 | zNear = json.NearClip; 16 | 17 | % far clipping plane 18 | zFar = json.FarClip; 19 | 20 | % height and width of viewport plane 21 | h = json.ViewportHeight; 22 | w = json.ViewportWidth; 23 | 24 | % get resolution and scaling factor for SLM units, forces all light fields 25 | % to be same size if unitScale is set to imageWidth/ViewportWidth 26 | slmPitch = 6.4e-6; 27 | imageResolution = [json.PixelHeight, json.PixelWidth]; 28 | imageWidth = slmPitch * imageResolution(2); 29 | if nargin >= 2 && ~isempty(eyepieceFocalLength) 30 | % scale imageWidth by magnification 31 | eyepieceVirtualImageDist = json.CameraDistance - eyepieceFocalLength; 32 | eyepieceHologramDist = 1/(1/eyepieceFocalLength + 1/eyepieceVirtualImageDist); 33 | magnification = eyepieceFocalLength / (eyepieceFocalLength - eyepieceHologramDist); 34 | imageWidth = imageWidth * magnification; 35 | end 36 | unitScale = imageWidth / json.ViewportWidth; 37 | 38 | h = unitScale * h; 39 | w = unitScale * w; 40 | zNear = unitScale * zNear; 41 | zFar = unitScale * zFar; 42 | 43 | % get a grid for x and y coords in window coordinates 44 | [xx_win, yy_win] = meshgrid(... 45 | linspace(0, imageResolution(2), imageResolution(2)),... 46 | linspace(imageResolution(1), 0, imageResolution(1))); 47 | 48 | % calculate pixel positions given depth 49 | xx_ndc = xx_win ./ imageResolution(2) - 1/2; 50 | yy_ndc = yy_win ./ imageResolution(1) - 1/2; 51 | 52 | if loadOnlyCentralView 53 | % specify the coordinates of the center view 54 | centerYView = floor(json.CameraRows/2) + 1; 55 | centerXView = floor(json.CameraColumns/2) + 1; 56 | else 57 | % allocate memory for light field and depth 58 | light_field = zeros(json.CameraRows, json.CameraColumns,... 59 | imageResolution(1), imageResolution(2), 3); 60 | depth = zeros(json.CameraRows, json.CameraColumns,... 61 | imageResolution(1), imageResolution(2)); 62 | end 63 | 64 | for camy=1:json.CameraRows 65 | % fprintf('%d', camy) 66 | % skip views if loading only central view 67 | if loadOnlyCentralView && camy ~= centerYView 68 | continue 69 | end 70 | 71 | for camx=1:json.CameraColumns 72 | % fprintf('.'); 73 | % skip views if loading only central view 74 | if loadOnlyCentralView && camx ~= centerXView 75 | continue 76 | end 77 | 78 | % camera index, flip y coordinate 79 | camidx = (json.CameraRows-camy)*json.CameraColumns + camx; 80 | 81 | % camera position relative to central view 82 | campos_x = json.Cameras(camidx).parameters.localPosition.x; 83 | campos_y = json.Cameras(camidx).parameters.localPosition.y; 84 | 85 | campos_x = unitScale * campos_x; 86 | campos_y = unitScale * campos_y; 87 | 88 | % load depth map and light field view 89 | if nargin < 3 || isempty(frameNum) 90 | imageFilePath = fullfile(datapath,... 91 | [json.Cameras(camidx).key '_rgbd.png']); 92 | else 93 | imageFilePath = fullfile(datapath, json.Cameras(camidx).key,... 94 | sprintf('rgbd_%04d.png', frameNum)); 95 | end 96 | [I, ~, D] = imread(imageFilePath); 97 | if ndims(I) == 2 98 | I = reshape([I I I], [size(I) 3]); 99 | end 100 | 101 | % convert to normalized double precision floating point values 102 | I = im2double(I); 103 | D = 1 ./ (im2double(D) .* (1 ./ zNear - 1 ./ zFar) + 1 ./ zFar); 104 | 105 | % get/reset zero disparity plane 106 | zero_disp_plane = json.CameraDistance; 107 | zero_disp_plane = unitScale * zero_disp_plane; 108 | 109 | % target position on SLM/viewport/zero_disparity_plane for each pixel 110 | xx_slm = xx_ndc * w; 111 | yy_slm = yy_ndc * h; 112 | 113 | % account for camera position's depth-dependent shift 114 | x_offset = (zero_disp_plane - D) / zero_disp_plane * campos_x; 115 | y_offset = (zero_disp_plane - D) / zero_disp_plane * campos_y; 116 | 117 | % point cloud relative to central camera position 118 | xx_metric = xx_ndc * w .* D / zero_disp_plane + x_offset; 119 | yy_metric = yy_ndc * h .* D / zero_disp_plane + y_offset; 120 | 121 | % use focal length to convert depth to be relative to hologram 122 | % plane (which is assumed to be the zero disparity plane) 123 | if nargin >= 2 && ~isempty(eyepieceFocalLength) 124 | virtualImageDist = D - eyepieceFocalLength; 125 | imageDist = 1 ./ (1/eyepieceFocalLength + 1./virtualImageDist); 126 | imageMag = eyepieceFocalLength ./ (eyepieceFocalLength - imageDist); 127 | virtualZeroDisp = zero_disp_plane - eyepieceFocalLength; 128 | 129 | zero_disp_plane = 1 ./ (1/eyepieceFocalLength + 1./virtualZeroDisp); 130 | zeroDispMag = eyepieceFocalLength ./ (eyepieceFocalLength - zero_disp_plane); 131 | 132 | xx_metric = xx_metric ./ imageMag; 133 | yy_metric = yy_metric ./ imageMag; 134 | xx_slm = xx_slm ./ zeroDispMag; 135 | yy_slm = yy_slm ./ zeroDispMag; 136 | D = imageDist; 137 | end 138 | 139 | % positions relative to corresponding SLM pixel 140 | xx_dist = xx_slm - xx_metric; 141 | yy_dist = yy_slm - yy_metric; 142 | zz_dist = zero_disp_plane - D; 143 | 144 | % distance from pixel to corresponding SLM pixel 145 | abs_dist = sqrt(xx_dist.^2 + yy_dist.^2 + zz_dist.^2); 146 | % sign for which side of slm 147 | metric_dist = abs_dist .* sign(zz_dist); 148 | 149 | if loadOnlyCentralView 150 | light_field = I; 151 | depth = metric_dist; 152 | else 153 | light_field(camy, camx, :, :, :) = I; 154 | depth(camy, camx, :, :) = metric_dist; 155 | end 156 | 157 | end 158 | end 159 | 160 | if nargin >= 4 && flipLFOutput && ~loadOnlyCentralView 161 | light_field = flip(light_field, 1); 162 | light_field = flip(light_field, 2); 163 | depth = flip(depth, 1); 164 | depth = flip(depth, 2); 165 | depth = -depth; 166 | end 167 | 168 | 169 | end --------------------------------------------------------------------------------