File: OtfToPsf.m

package info (click to toggle)
psychtoolbox-3 3.0.19.14.dfsg1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 86,796 kB
  • sloc: ansic: 176,245; cpp: 20,103; objc: 5,393; sh: 2,753; python: 1,397; php: 384; makefile: 193; java: 113
file content (174 lines) | stat: -rw-r--r-- 7,859 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
function [xGridMinutes,yGridMinutes,psf] = OtfToPsf(xSfGridCyclesDeg,ySfGridCyclesDeg,otf,varargin)
%OTFTOPSF  Convert a 2D optical transfer fucntion to a 2D point spread function.
%    [xSfGridCyclesDeg,ySfGridCyclesDeg,otf] = PsfToOtf([xGridMinutes,yGridMinutes],psf)
%
%    Converts a optical transfer function specified over two-dimensional
%    spatial frequency in cycles per degree to a point spread function
%    specified over positions in minutes.  For human vision, these are each
%    natural units.
%
%    The input spatial frequencies should be specified in matlab's grid
%    matrix format and sf for x and y should be specified over the same
%    spatialfrequency extent and with the same number of evenly spaced
%    samples. Spatial frequency (0,0) should be at location floor(n/2)+1 in
%    each dimension.  The PSF is returned with position (0,0) at location
%    floor(n/2)+1 in each dimension.  This is the form we mostly want to
%    look at and use.  
%
%    The OTF is assummed to have the DC term in the center poistion,
%    floor(n/2)+1 of the passed matrix.  If you have your hands on an OTF
%    with the DC term in the upper left position (1,1), apply fftshift to
%    it before passing to this routine.  The DC in upper left is the Matlab
%    native format for applying the ifft, and is also the format stored by
%    isetbio in its optics structure.
%
%    Positions are returned using the same conventions.
%
%    No normalization is performed.  The psf should be real, and we
%    complain (throw an error) if it is not, to reasonable numerial
%    precision. If it seems OK, we make sure it is real.
%
%    We also make sure that the returned psf is all postive and sums to 
%    1.  In some cases, we found that there were small negative values
%    and after setting these to zero renormalization was needed.
%
%    We wrote this rather than simply relying on Matlab otf2psf/psf2otf
%    because we want a routine that deals with the conversion of spatial
%    frequency to spatial support.
%
%    If you pass the both sf args as empty, both position 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 PsfToOtf.
%
% Optional key/value pairs:
%    'warningInsteadOfErrorForNegativeValuedPSF'  - Set to 1 (default
%                                                   0) to get a warning
%                                                   not an error if the psf
%                                                   values are too negative
%                                                   before they are forced
%                                                   to zero. Set to 2 for
%                                                   no warning msg or
%                                                   error.
%    'negativeFractionalTolerance'                - The error/warning is
%                                                   thrown if the magnitude
%                                                   of the most negative
%                                                   value is more than this
%                                                   fraction of the maximum
%                                                   (positve) value.
%                                                   (Default 1e-3).
%
% See also: PsfToOtf, PsychOpticsTest.

% History:
%   01/26/18  dhb 
%   03/31/18  dhb   Document key/value pair added by someone else.
%             dhb   Add key/value pair for negative value tolerance.
%                   This is now 1e-3 rather than 1e-10
%   10/17/18  dhb   Now 10-2.

%% Parse input
p = inputParser;
p.addParameter('warningInsteadOfErrorForNegativeValuedPSF', 0, @isnumeric);
p.addParameter('negativeFractionalTolerance', 1e-2, @isnumeric);
p.parse(varargin{:});

%% Handle sf args and converstion
if (~isempty(xSfGridCyclesDeg) & ~isempty(ySfGridCyclesDeg))
    % 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(xSfGridCyclesDeg);
    centerPosition = floor(n/2) + 1;
    if (m ~= n)
        error('psf must be passed on a square array');
    end
    [m1,n1] = size(ySfGridCyclesDeg);
    if (m1 ~= m || n1 ~= n)
        error('x and y positions are not consistent');
    end
    [m2,n2] = size(otf);
    if (m2 ~= m || n2 ~= n)
        error('x and y positions are not consistent');
    end
    if (~all(xSfGridCyclesDeg(:,centerPosition) == 0))
        error('Zero spatial frequency is not in right place in the passed xGrid');
    end
    if (~all(ySfGridCyclesDeg(centerPosition,:) == 0))
        error('Zero spatial frequency is not in right place in the passed yGrid');
    end
    if (xSfGridCyclesDeg(1,centerPosition) ~= ySfGridCyclesDeg(centerPosition,1))
        error('Spatial frequency extent of x and y grids does not match');
    end
    diffX = diff(xSfGridCyclesDeg(:,centerPosition));
    if (any(diffX ~= diffX(1)))
        error('X positions not evenly spaced');
    end
    diffY = diff(ySfGridCyclesDeg(centerPosition,:));
    if (any(diffY ~= diffY(1)))
        error('Y positions not evenly spaced');
    end
    if (diffX(1) ~= diffY(1))
        error('Spatial frequency sampling in x and y not matched');e
    end
    
    %% Generate position grids
    %
    % Samples are evenly spaced and the same for both x and y (checked above).
    % Handle even versus odd dimension properly for fft conventions.
    [xGridMinutes,yGridMinutes] = SfGridCyclesDegToPositionGridMinutes(xSfGridCyclesDeg,ySfGridCyclesDeg);
    
elseif (isempty(xSfGridCyclesDeg) & isempty(ySfGridCyclesDeg))
    % This case is OK, we set the output grids to empty
    xGridMinutes = [];
    yGridMinutes = [];
    
else
    % This case is not allowable
    error('Either both sf grids must be empty, or neither');
end

%% Compute otf and put spatial center in the middle of the grid
psf = fftshift(ifft2(ifftshift(otf)));

%% See if there is stray imaginary stuff, get rid of it if so.
%
% Throw an error if the returned psf isn't in essence real valued.
% Then set residual imaginary parts to 0.
if (any(abs(imag(psf(:))) > 1e-10))
    error('Computed psf is not sufficiently real');
end
if (any(imag(psf(:))) ~= 0)
    psf = psf - imag(psf)*1i;
end

% Check for large negative psf values, and then set any small
% negative values to zero.  For some cases (e.g. Marimont-Wandell
% OTF, we do get negative values because the way that was constructed
% is an approximation to measurements that does not absolutely guarantee an
% all positive OTF.
if (max(psf(:)) <= 0)
    error('Computed PSF has no positive values.  This is not good.');
end
if (min(psf(:)) < 0 && abs(min(psf(:))) > p.Results.negativeFractionalTolerance*max(psf(:)))
    if (p.Results.warningInsteadOfErrorForNegativeValuedPSF == 1)
        fprintf(2,'Mysteriously large negative psf values, min value is %g, relative to max of %g, fraction %g\n',min(psf(:)),max(psf(:)),abs(min(psf(:)))/max(psf(:)));
    elseif (p.Results.warningInsteadOfErrorForNegativeValuedPSF == 0)
        fprintf(2,'Mysteriously large negative psf values: min value is %g, relative to max of %g, fraction %g\n',min(psf(:)),max(psf(:)),abs(min(psf(:)))/max(psf(:)));
        error('Mysteriously large negative psf values');
    end
end
psf(psf < 0) = 0;

% Make sure return is real
psf = abs(psf);

% Make sure return sums to 1.  It might not be because of the
% above fussing with imaginary and negative values.
psf = psf/sum(psf(:));

end