function [C, optics, appear] = votf(optics, appear)

% votfoct  Calculate the projected vectorial optical transfer function
% [C, optics, appear] = votfoct(optics, appear)
%
%   Inputs:
%       optics describes the physics of the situation being modeled
%       appear has the details of how to present the results
%
%   Outputs:
%       C = complex vectorial OTF = Cx + Cy + Cz
%       optics = physical paramaters 
%                (so you can see what the defaults were set to)
%       appear = parameters used for display plus extra things added during run
%
%   To run with the defaults, just use votf(0,0);
%
% MRA USyd June 2002
% $Id: votf.m,v 1.4 2003-06-24 11:11:54+10 matthewa Exp matthewa $

% Copyright (C) 2003 Matthew Arnison
% This program is copyleft under the GNU General Public License
% See http://www.gnu.org/copyleft/gpl.html for details

% This program is free software; you can redistribute it and/or
% modify it under the terms of the GNU General Public License
% as published by the Free Software Foundation; either version 2
% of the License, or (at your option) any later version.

% todo: truncate size of output C to max bandwidth, as determined by the aperture

system('date;');
disp('$Id: votf.m,v 1.4 2003-06-24 11:11:54+10 matthewa Exp matthewa $')
% bypass this bit in MATLAB for now
if exist('OCTAVE_VERSION') == 102
	rcsdiff('$RCSfile: votf.m,v $');
	fflush(stdout);
end

% save input parameters
opticsin = optics;
appearin = appear;

% defaults
optics.alpha = pi/3; 			% aperture half angle
optics.Apv = 0;					% strength of cubic phase mask, peak to valley, in waves
optics.apodisation = 'sine';	% can be sine or herschel
optics.kmax = 4;				% maximum frequency in k space @ x0 +/- N/2
optics.lambda0 = 0.53;			% wavelength of light in vacuum
optics.n1 = 1.518;				% refractive index in focal region
optics.model = 'vectorial'; 	% accuracy: vectorial, scalar, or paraxial
optics.N = 512;					% accuracy: number of samples of pupil
optics.pupilshape = 'square';	% shape of the aperture: square or circle
optics.zdefocus = 0;			% microns of defocus
optics.z45 = 'clear';			% CJRS aperture filtering: clear, opaque, flip
								% see MRA logbook 13aug02 p. 117
								% DIC options, set dicshear to zero to
								% disable DIC
optics.dicshear = 0;			% DIC shear distance 2 delta x
optics.dicshearxy = 'x';		% DIC shear along x or y axis
optics.dicbias = 0;				% DIC bias 2 theta

appear.result = 'otf';			% what result in C: pupil, psf, ipsf, otf
appear.figstart = 1;			% where to start figure numbers for new plots
appear.logscale = 1;			% whether to use a log10 scale for plots
appear.realabs = 'abs';			% whether plots should show real() or abs()
appear.savemem = 1;				% whether to save memory by clearing temp matrices
appear.showpupil = 0;			% whether to display images of the pupil
appear.showplots = 1;			% whether to display plots
appear.showphase = 1;			% whether to display phase image of result
appear.saveabns = 0;			% whether to save the aberration functions 
								% (in optics.defocus & optics.cubic)
appear.savephase = 0;			% whether to save the phase of the pupil
								% in order to check Nyquist is satisfied
appear.debug = 0;				% debugging flag: set to 1 for extra output

% set values for this run

keys = fieldnames(opticsin);
sizekeys = size(keys); numkeys = sizekeys(1);

for keyi = 1:numkeys
	eval(['optics.' keys{keyi} ' = opticsin.' keys{keyi} ';']);
end

keys = fieldnames(appearin);
sizekeys = size(keys); numkeys = sizekeys(1);

for keyi = 1:numkeys
	eval(['appear.' keys{keyi} ' = appearin.' keys{keyi} ';']);
end

%%% end of config options

% store version of code used to do calculations inside optics output structure
% this gets overwritten each time the code is executed
optics.versionid = '$Id: votf.m,v 1.4 2003-06-24 11:11:54+10 matthewa Exp matthewa $';

% scaling factor to fit over a circular pupil 
% (or a square fitting inside that pupil)
% from logbook 13/2/2 p. 166
% not quite right, but within 5%, close enough for now
Acpv = sqrt(2)*optics.Apv/(2*sin(optics.alpha)^3);

% for max aperture NA=1, alpha = pi/2, radius is 1
% use aperture angle alpha to scale down from there
Rp = sin(optics.alpha);

% find centre
x0 = optics.N/2 + 1;
y0 = x0;

optics.lambda1 = optics.lambda0/optics.n1;
% so k here is actually k1, i.e. wave number in focal region
k = 2*pi/optics.lambda1;

% find width and edges of pupil
appear.pwidth = optics.N/optics.kmax*Rp;
appear.pledge = round(x0 - appear.pwidth/2);
appear.predge = round(x0 + appear.pwidth/2);
appear.prange = appear.pledge:appear.predge;

% find width and edges of OTF
sqwidth = optics.N/2*Rp/sqrt(2) - 2;
cwidth = optics.N/2*Rp - 2;
width = sqwidth;
ledge = round(x0 - width/2);
redge = round(x0 + width/2);
cledge = round(x0 - cwidth/2);
credge = round(x0 + cwidth/2);

if strcmp(appear.result, 'otf')
	kcircrange = linspace(-2*Rp, 2*Rp, credge - cledge + 1);
	kdiagrange = linspace(-2*Rp, 2*Rp, redge - ledge + 1);
	ksqrange = linspace(-2*Rp/sqrt(2), 2*Rp/sqrt(2), redge - ledge + 1);
elseif strcmp(appear.result, 'ipsf') || strcmp(appear.result, 'psf') ...
	|| strcmp(appear.result, 'pupil')
	
	% scaling for focal region
	% see logbook 23/9/02 p 135
	% physical units the same as lambda0

	% xmax is half the width of PSF as x=0 is in the centre
	% this gives the distance from the middle of the PSF to
	% the horizontal edge in the same units as optics.lambda0
	appear.psfxmax = optics.N*optics.lambda1/(4*optics.kmax);
	
	% xstep is the physical distance between x steps
	appear.psfxstep = appear.psfxmax/(optics.N/2);
	
	% just match up ends of k*range with OTF subN range for now, 
	% i.e. ledge to	redge
	ksqrange = linspace(-appear.psfxmax*Rp/2/sqrt(2), ...
						appear.psfxmax*Rp/2/sqrt(2), redge - ledge + 1);
	kdiagrange = linspace(-appear.psfxmax*Rp/2, appear.psfxmax*Rp/2, ...
							redge - ledge + 1);
	kcircrange = linspace(-appear.psfxmax*Rp/2, appear.psfxmax*Rp/2, ...
							credge - cledge + 1);
end

% save useful paramaters for return to caller
appear.kcircrange = kcircrange;
appear.kdiagrange = kdiagrange;
appear.ksqrange = ksqrange;
appear.ledge = ledge;
appear.redge = redge;
appear.cledge = cledge;
appear.credge = credge;
appear.x0 = x0;
appear.y0 = y0;

% setup 2D array where real part is x co-ord and imag part is y co-ord
% tricky N+1 and cut fiddle to get a zero at (N/2+1,N/2+1) i.e. (x0,y0)
% k1width is width of k=1 aperture (i.e. full aperture if alpha is max)
% round is needed for odd optics.N
appear.k1width = round(optics.N/optics.kmax);
[m n] = meshgrid(linspace(-1, 1, appear.k1width + 1));
m = m(1:appear.k1width, 1:appear.k1width);
n = n(1:appear.k1width, 1:appear.k1width);
% m increases from left to right
% n increases from top to bottom (reverse of usual)
% so when displaying things need to flipud() first
% (I tried to reverse it but couldn't get the spiral stuff to still work)
mn = m + i*n;

if strcmp(optics.pupilshape, 'circle')
	% create a filled circle of radius Rp: 1 inside, 0 outside
	circle = mn/Rp;
	circle(abs(circle) <= 1) = 1;
	circle(abs(circle) > 1) = 0;
	pupil = circle;
else
	% create a filled square that fits inside the circle
	sq = mn;
	sq(abs(m) <= Rp/sqrt(2)) = 1;
	sq(abs(n) <= Rp/sqrt(2)) = 1;
	sq(abs(m) > Rp/sqrt(2)) = 0;
	sq(abs(n) > Rp/sqrt(2)) = 0;
	pupil = sq;
end

if appear.savemem
	clear circle sq
end

if appear.savephase
	optics.pupilphase = zeros(optics.N/optics.kmax, optics.N/optics.kmax);
end

if strcmp(optics.z45, 'clear')
	z45filter = 1;
else
	R45 = sin(pi/4);
	if strcmp(optics.z45, 'opaque')
		z45filter = ones(optics.N/optics.kmax, optics.N/optics.kmax);
		z45filter(abs(m) > R45) = 0;
	else
		% assume optics.z45 = flip
		phase = zeros(optics.N/optics.kmax, optics.N/optics.kmax);
		phase(m > R45) = -pi/2;
		phase(m < R45) = pi/2;
		z45filter = exp(i*phase);
		if appear.savephase
			optics.pupilphase = optics.pupilphase + phase;
		end
		clear phase;
	end
	z45filter = z45filter.*pupil;
end

% debug: save z45filter to test that I am setting it right
if appear.debug
	optics.z45filter = z45filter;
end

l = abs(mn).*pupil;

splus = sqrt(1 - l.^2);

if optics.zdefocus ~= 0
	if strcmp(optics.model, 'paraxial')
		% 1 out the front of l.^2 has no physical effect (phase piston)
		% but makes it easier to compare the unwrapped pupil phase
		phase = k*optics.zdefocus*(1-l.^2/2);
		defocus = exp(i*phase);
	else
		% high NA defocus pupil multiplier
		phase = k*optics.zdefocus*splus;
		defocus = exp(i*phase);
	end
	optics.defocusmax = max(max(phase));
	optics.defocusmin = min(min(phase));
	optics.defocuspv = optics.defocusmax - optics.defocusmin;
	if appear.savephase
		optics.pupilphase = optics.pupilphase + phase;
	end
	clear phase;
else
	defocus = 1;
end

% DIC optics - see logbook 6nov02 pp 163-165
% start with shear in x direction
if optics.dicshear ~= 0
	if strcmp(optics.dicshearxy, 'x')
		dic = -2*i*sin(optics.dicbias/2 + k*optics.dicshear*0.5.*m);
	else
		dic = -2*i*sin(optics.dicbias/2 + k*optics.dicshear*0.5.*n);
	end
	if appear.savephase
		optics.pupilphase = optics.pupilphase + phase;
	end
else
	dic = 1;
end

if appear.savemem
	clear l mn
end

if strcmp(optics.model, 'vectorial')
	ax = (1 - m.^2./(1 + splus)).*pupil;
	ay = (-m.*n./(1 + splus)).*pupil;
	az = -m.*pupil;
else
	ax = 1; ay = 1; az = 1;
end

% create a cubic phase function
if Acpv ~= 0
	% we need to scale the Acpv range to lie between the pupil cutoffs
	% ah well another day
	phase = 2*pi*Acpv*(m.^3 + n.^3);
	cubic = exp(i*phase);
	if appear.savephase
		optics.pupilphase = optics.pupilphase + phase;
	end
	clear phase;
else
	cubic = 1;
end

if strcmp(optics.apodisation, 'sine') && ~strcmp(optics.model, 'paraxial')
	S = sqrt(splus);
else
	S = 1;
end

T = cubic.*z45filter.*dic.*defocus.*S.*pupil;

optics.cubicmax = 2*pi*Acpv*(max(max(m.*pupil))^3 + max(max(m.*pupil))^3);
optics.cubicmin = 2*pi*Acpv*(min(min(m.*pupil))^3 + min(min(m.*pupil))^3);
optics.cubicpv = optics.cubicmax - optics.cubicmin;

if appear.saveabns
	optics.cubic = cubic;
	optics.z45filter = z45filter;
	optics.dic = dic;
	optics.defocus = defocus;
	optics.S = S;
end

if appear.savephase
	optics.pupilphase = optics.pupilphase.*pupil;
	[ppdx, ppdy] = gradient(optics.pupilphase);
	optics.ppgrad = abs(ppdx + i*ppdy).*pupil;
	clear ppdx ppdy

	% crop phase gradient to just over pi
	% 'cos if it goes over pi we're stuffed anyway
	optics.ppgrad(abs(optics.ppgrad) > pi) = 3.5;
end

if appear.savemem
	clear cubic z45filter dic defocus S m n pupil
end

if strcmp(optics.model, 'vectorial')
	Qx = (1./splus).*ax.*T;
	Qy = (1./splus).*ay.*T;
	Qz = (1./splus).*az.*T;
elseif strcmp(optics.model, 'scalar')
	Qx = (1./splus).*T;
	Qy = Qx; Qz = Qx;
else
	% paraxial approximation
	Qx = T; 
	Qy = Qx; Qz = Qx;
end

if appear.savemem
	clear T ax ay az splus
end

if appear.showpupil
	pscale = [-1 1];

	figure(appear.figstart); 

	subplot(2,3,1);
	imagesc(abs(Qx)); 
	%axis square; axis xy; colorbar; colormap(jet);

	subplot(2,3,2);
	imagesc(abs(Qy)); 
	%axis square; axis xy; colorbar; colormap(jet);

	subplot(2,3,3);
	imagesc(abs(Qz)); 
	%axis square; axis xy; colorbar; colormap(jet);

	subplot(2,3,4);
	imagesc(angle(Qz)); 
	%axis square; axis xy; colorbar; colormap(jet);

	subplot(2,3,5);
	imagesc(angle(Qz)); 
	%axis square; axis xy; colorbar; colormap(jet);

	subplot(2,3,6);
	imagesc(angle(Qz)); 
	%axis square; axis xy; colorbar; colormap(jet);
end % appear.showplots

% pad pupils out to full size (optics.N) before doing each FFT chain
% impad() is from octave-forge

% might be worth trying to use the padding built into fft2 instead
% fft2(matrix, N, M) pads to size NxM

appear.padding = round((optics.N - optics.N/optics.kmax)/2);

Qx = impad(Qx, appear.padding, appear.padding);

if strcmp(appear.result, 'otf')
	C = fftshift(ifft2(abs(fft2(fftshift(Qx))).^2));
elseif strcmp(appear.result, 'ipsf')
	% need to add intensities to get vectorial PSF
	C = abs(fftshift(fft2(fftshift(Qx)))).^2;
elseif strcmp(appear.result, 'psf')
	C.x = fftshift(fft2(fftshift(Qx)));
elseif strcmp(appear.result, 'pupil')
	C.x = Qx;
end

if appear.saveabns
	optics.Qx = Qx;
end

if appear.savemem clear Qx; end

Qy = impad(Qy, appear.padding, appear.padding);

if strcmp(appear.result, 'otf')
	C = C + fftshift(ifft2(abs(fft2(fftshift(Qy))).^2));
elseif strcmp(appear.result, 'ipsf')
	C = C + abs(fftshift(fft2(fftshift(Qy)))).^2;
elseif strcmp(appear.result, 'psf')
	C.y = fftshift(fft2(fftshift(Qy)));
elseif strcmp(appear.result, 'pupil')
	C.y = Qy;
end

if appear.saveabns
	optics.Qy = Qy;
end

if appear.savemem clear Qy; end

Qz = impad(Qz, appear.padding, appear.padding);

if strcmp(appear.result, 'otf')
	C = C + fftshift(ifft2(abs(fft2(fftshift(Qz))).^2));
elseif strcmp(appear.result, 'ipsf')
	C = C + abs(fftshift(fft2(fftshift(Qz)))).^2;
elseif strcmp(appear.result, 'psf')
	C.z = fftshift(fft2(fftshift(Qz)));
elseif strcmp(appear.result, 'pupil')
	C.z = Qz;
end

if appear.saveabns
	optics.Qz = Qz;
end

if appear.savemem clear Qz; end

% normalise
if strcmp(appear.result, 'otf')
	C = C/abs(C(x0,y0));
end
 
if strcmp(appear.result, 'psf') || strcmp(appear.result, 'pupil')
	% plotting code not setup to deal with C.x, C.y, C.z, so
	% just force plots off for PSF case for now
	appear.showplots = 0;
end

if appear.showplots
	scale = [-2*Rp/sqrt(2) 2*Rp/sqrt(2)];

	if strcmp(appear.realabs, 'real')
		ptmp = real(C(ledge:redge,ledge:redge));
	else
		if strcmp(appear.result, 'otf')
			ptmp = abs(C(ledge:redge,ledge:redge));
		else
			% show intensity for the IPSF or image case - 
			% C is already intensity - see above
			ptmp = C;
		end
	end

	if appear.logscale
		ptmp = log10(ptmp);
	end

	figure(appear.figstart + 1); %clf reset;
	imagesc(scale, scale, ptmp); 
	%axis square; axis xy; colorbar; colormap(jet);

	figure(appear.figstart + 2); %clf reset;
	if appear.showphase
		imagesc(scale, scale, angle(C(ledge:redge,ledge:redge))); 
	end
	%axis square; axis xy; colorbar; colormap(jet);

	figure(appear.figstart + 3); %clf reset;

	% can plot 1D diagonals using something like this:
	% Ccpm = C;

	% dodgy normalise x axis to match BW99 Fig. 9.13
	% Rp = 1;

	diagC = ones(1,optics.N)*(C.*eye(optics.N));

	dtmp = diagC(ledge:redge);

	if strcmp(optics.pupilshape, 'circle')
		htmp = C(x0, cledge:credge);
		vtmp = C(cledge:credge, y0);
		axesrange = kcircrange;
	else
		htmp = C(x0, ledge:redge);
		vtmp = C(ledge:redge, y0);
		axesrange = ksqrange;
	end

	if strcmp(appear.realabs, 'real')
		dtmp = real(dtmp);
		htmp = real(htmp);
		vtmp = real(vtmp);
	else
		dtmp = abs(dtmp);
		htmp = abs(htmp);
		vtmp = abs(vtmp);
	end
	
	if appear.logscale
		semilogy(kdiagrange, dtmp,'r', axesrange, htmp,'g', axesrange, vtmp,'b');
		legend('diagonal', 'horizontal', 'vertical');
		axis('auto');
	else
		plot(kdiagrange, dtmp,'r', axesrange, htmp,'g', axesrange, vtmp,'b');
		legend('diagonal', 'horizontal', 'vertical');
		axis('auto');
		grid('on');
	end

	if appear.debug
		appear.axesrange = axesrange;
		appear.dtmp = dtmp;
		appear.htmp = htmp;
		appear.vtmp = vtmp;
	end
	
end % appear.showplots
