function [xSfGridCyclesDeg,ySfGridCyclesDeg,otf] = PsfToOtf(xGridMinutes,yGridMinutes,psf,varargin) % Convert a 2D point spread function to a 2D optical transfer fucntion. % [xSfGridCyclesDeg,ySfGridCyclesDeg,otf] = PsfToOtf([xGridMinutes,yGridMinutes],psf,varargin) % % Converts a point spread function specified over two-dimensional % positions in minutes to a optical transfer function specified over % spatial frequency in cycles per degree. For human vision, these are % each natural units. % % The input positions should be specified in matlab's grid matrix format % and x and y should be specified over the same spatial extent and with % the same number of evenly spaced samples. Position (0,0) should be at % location floor(n/2)+1 in each dimension. The OTF is returned with % spatial frequency (0,0) at location floor(n/2)+1 in each dimension. % % Spatial frequencies are returned using the same conventions. % % If you want the spatial frequency representation to have frequency % (0,0) in the upper left, as seems to be the more standard Matlab % convention, apply ifftshift to the returned value. That is % otfUpperLeft = ifftshift(otf); % And then if you want to put it back in the form for passing to our % OtfToPsf routine, apply fftshift: % otf = fftshift(otfUpperLeft); % The isetbio code (isetbio.org) thinks about OTFs in the upper left % format, at least for its optics structure, which is one place where % you'd want to know this convention. % % No normalization is performed. If the phase of the OTF are very small % (less than 1e-10) the routine assumes that the input psf was spatially % symmetric around the origin and takes the absolute value of the % computed otf so that the returned otf is real. % % We wrote this rather than simply relying on Matlab's potf2psf/psf2otf % because we don't understand quite how that shifts position of the % passed psf and because we want a routine that deals with the % conversion of spatial support to spatial frequency support. % % If you pass the both position args as empty, both sf grids are % returned as empty and just the conversion on the OTF is performed. % % PsychOpticsTest shows that this works very well when we go back and % forth for diffraction limited OTF/PSF. But not exactly exactly % perfectly. A signal processing maven might be able to track down % whether this is just a numerical thing or whether some is some small % error, for example in how position is converted to sf or back again in % the OtfToPsf. % % See also OtfToPsf, PsychOpticsTest. % History: % 01/26/18 dhb We used to zero out small imaginary values. This, % however, can cause numerical problems much worse than % having small imaginary values in the otf. So we don't % do it anymore. %% Handle sf args and converstion if (~isempty(xGridMinutes) & ~isempty(yGridMinutes)) % They can both be passed as non-empty, in which case we do a set of sanity % checks and then do the conversion. [m,n] = size(xGridMinutes); centerPosition = floor(n/2) + 1; if (m ~= n) error('psf must be passed on a square array'); end [m1,n1] = size(yGridMinutes); if (m1 ~= m || n1 ~= n) error('x and y positions are not consistent'); end [m2,n2] = size(psf); if (m2 ~= m || n2 ~= n) error('x and y positions are not consistent'); end if (~all(xGridMinutes(:,centerPosition) == 0)) error('Zero position is not in right place in the passed xGrid'); end if (~all(yGridMinutes(centerPosition,:) == 0)) error('Zero position is not in right place in the passed yGrid'); end if (xGridMinutes(1,centerPosition) ~= yGridMinutes(centerPosition,1)) error('Spatial extent of x and y grids does not match'); end diffX = diff(xGridMinutes(:,centerPosition)); if (any(diffX ~= diffX(1))) error('X positions not evenly spaced'); end diffY = diff(yGridMinutes(centerPosition,:)); if (any(diffY ~= diffY(1))) error('Y positions not evenly spaced'); end if (diffX(1) ~= diffY(1)) error('Spatial sampling in x and y not matched');e end % Generate spatial frequency grids [xSfGridCyclesDeg,ySfGridCyclesDeg] = PositionGridMinutesToSfGridCyclesDeg(xGridMinutes,yGridMinutes); elseif (isempty(xGridMinutes) & isempty(yGridMinutes)) % This case is OK, we set the output grids to empty xSfGridCyclesDeg = []; ySfGridCyclesDeg = []; else % This case is not allowable error('Either both position grids must be empty, or neither'); end %% Compute otf otf = fftshift(fft2(ifftshift(psf))); end