DPG demo version from 2019-08-20
[dpg] / phzshift.m
1 function varargout = phzshift(a,b,mode,verbose,phz,thrsh);
2 % [c_out,y_out] = phzshift( a, b, [mode, verbose, phz]);
3 %
4 % calculates the coordinate shift between two k-space data sets, a and b.
5 % Using the Phase Correlation Method, each k-space set is transformed
6 % to the image domain, the phase difference is calculated and then
7 % applied to the second set.  After this correction, the two data sets
8 % are added together and output.
9 %
10 % inputs can be any number of dimensions, but 'a' and 'b' must be the
11 % same size
12 %
13 % c_out is the combination of (a+b)/2, after phase correction.
14
15 % y_out contains the estimated phase shift between 'a' and 'b' for
16 % each frame.
17 %
18 % Advanced (optional) options:
19 %    mode - a cell variable containing flags to various settings.
20 %           'nofft'    : The input data is already in the spatial domain
21 %           'nocombo'  : Returns  b with the phase correction applied.
22 %           'nofilter' : use the full data to estimate the phase shift, 
23 %                        instead of the default setting which uses a
24 %                        low-passed filtered version.
25 %           'center'   : center the low-pass filter.
26 % verbose - set to '1' to show debugging information
27 %     phz~ - if nonempty, use this matrix instead of estimating the phase
28 %           shift.  (i.e. y_out from a previous run)
29 %   thrsh - the mask threshold for the svd calculation. range: [0:1]
30
31 %
32 % Copyright 2004-2016  W. Scott Hoge  (wsh032580 at proton dot me)
33 %
34 % Licensed under the terms of the MIT License 
35 % (https://opensource.org/licenses/MIT)
36
37
38 % 2013/nov/07: jonathan polimeni <jonp at nmr dot mgh dot harvard dot edu>
39 % fixed non-integer index, fixed missing conditional for "nofilter" option
40
41 % 2014/sep/25: jonathan polimeni <jonp at nmr dot mgh dot harvard dot edu>
42 % replaced phase difference fitting with Local Phase Correction (LPC) method
43 % based on the methods described in:
44 %
45 %   Feiweier T. Magnetic Resonance Method and Apparatus to Determine Phase
46 %   Correction Parameters. U.S. Patent No. 8,497,681, Issued 7/30/2013.
47
48   
49 [m,n,c]=size(a);
50 c_out = zeros(m,n,c);
51 if nargout > 1,
52   y_out = zeros(2,c);
53 end;
54
55 if nargin < 3,
56   mode = {''};
57 end;
58
59 if nargin < 4,
60   verbose = 0;
61 end;
62
63 if nargin < 5,
64   phz = [];
65 end;
66 if ~isempty(phz),
67   phz = reshape(phz,[2 c]);
68 end;
69
70 if nargin < 6,
71   thrsh = 0.2;
72 end;
73
74
75 if ( sum(strcmp(mode,'nofilter')) || sum(strcmp(mode,'nofft')) ),
76   f1 = 1; f2 = 1;
77 else
78   % define a low-pass envelope to apply in kspace
79   
80   alph = 20;
81   
82   if (alph > m), alph2 = m; else, alph2 = alph; end;
83   f1 = [ zeros(floor((m-alph2)/2),1); gausswin(alph2); zeros(ceil((m-alph2)/2),1)]; 
84
85   if (alph > n), alph2 = n; else, alph2 = alph; end;
86   f2 = [ zeros(floor((n-alph2)/2),1); gausswin(alph2); zeros(ceil((n-alph2)/2),1)]; 
87
88   [tmp,maxx] = max(max(sum(abs(a(:,:,:)),3) ));
89   [tmp,maxy] = max(max(sum(abs(a(:,:,:)),3)'));
90
91   %%% correct the window coordinates to set ontop of DC (which may be off center)
92   [ans,j2]=max(f1);
93   [ans,j3]=max(f2);
94   
95   if sum( strcmp(mode,'center') )
96     c1 = 0; c2 = 0;
97   else,
98     c1 = j2 - maxy;
99     c2 = j3 - maxx;
100   end;
101   % fprintf('c1: %d c2: %d\n',c1,c2);
102   
103   if c1>0;
104     f1 = f1( [ c1:length(f1) 1:(c1-1)]);
105   elseif c1<0
106     f1 = f1( [ length(f1)+(c1:0) 1:(length(f1)+c1-1)]);
107   end;
108   
109   if c2>0;
110     f2 = f2( [ c2:length(f2) 1:(c2-1)]);
111   elseif c2<0
112     f2 = f2( [ length(f2)+(c2:0) 1:(length(f2)+c2-1)]);
113   end;
114
115 end;
116
117
118 % convert k-space to image-space
119 if sum(strcmp(mode,'nofft'));
120   A = a;
121   B = b;
122 elseif sum(strcmp(mode,'nofilter')),
123   A = fftshifthift(ifft2(fftshifthift( reshape(a,[m n c]) )));
124   B = fftshifthift(ifft2(fftshifthift( reshape(b,[m n c]) )));
125 else,
126   A = fftshifthift(ifft2(fftshifthift( repmat( (f1*f2'),[1 1 c]).*reshape(a,[m n c]) )));
127   B = fftshifthift(ifft2(fftshifthift( repmat( (f1*f2'),[1 1 c]).*reshape(b,[m n c]) )));
128 end;
129
130 mask = ( abs(A) > thrsh*max( abs(A(:)) ) );
131
132 A1 = reshape( permute(A,[2 1 3 4]),[n m*c]).'; 
133 B1 = reshape( permute(B,[2 1 3 4]),[n m*c]).';
134 M1 = reshape( permute(mask,[2 1 3 4]),[n m*c]).';
135
136 if (1),
137   if isempty(phz)
138     % calculate the normalized cross-correlation
139     C = zeros(size(A1));
140     % [JRP] why does next definition of "tt" below use a threshold of 0.1*max?
141     tt = find( abs(A1(:)) > 0.01*max(abs(A1(:))) );
142     C(tt) = A1(tt).*conj(B1(tt))./abs(A1(tt).*conj(A1(tt)));
143   
144     % extract out the dominant x and y shift vectors
145     [u d v]=svds(M1.*C,1);
146     % v = rsvd( mask.*C, 1 );
147   
148
149     m2 = sum(M1,1).';
150     t2 = find(m2~=0);
151
152     % [JRP] previously the vector "v" was unwrapped after extracting a
153     % subset defined by vector "t2", but since the subset of samples may be
154     % non-contiguous the unwrapping could add erroroneous phase
155     % jumps---better to unwrap the vector first then extract the subset
156     % afterwards
157     unv = unwrap(angle(v));
158     
159     % calculate the shift from the slope of the phase.
160     % here, we are only concerned with shifts along rows
161     if (verbose),
162       fprintf(' %d ',t2);
163       fprintf('\n');
164       Atmp = [ ones(length(t2),1) t2(:)-size(B,2) ];
165       fprintf('A''*A  =');
166       fprintf(' %d ', Atmp'*Atmp );
167       fprintf('\n');
168       fprintf('A''*v2 =');
169       fprintf(' %f ', Atmp'*unv(t2) );
170       fprintf('\n');
171     end;
172     if (0),
173       % [JRP] "robustfit" performs better than backslash when outliers are
174       % present and are not properly masked
175       %y2 = ( [ ones(length(t2),1) t2(:)-size(B,2) ] ) \ unv(t2);
176       y2 = robustfit(t2(:)-size(B,2), unv(t2) );
177     else,
178       % [JRP] based on "Local Phase Correction" of Feiweier
179       dphz = angle(sum(v(2:end) .* conj(v(1:end-1))));
180       y2 = [1; dphz];
181     end;
182     if (verbose)
183       figure; plot( t2-size(B,2), unv(t2), 'bx-', t2-size(B,2), [ ones(length(t2),1) t2(:) ]*y2,'k-.' );
184
185       fprintf(' x = %f  %f \n', y2 );
186       keyboard;
187     end;
188   else,
189     y2(2) = phz(1);
190   end; % if exist('phz')
191   
192   % correct the phase of the second data set to match the first
193   % (i.e. shift the k-space grids to match)
194   if sum(strcmp(mode,'nofft'));
195     A = a;% (:,:,cnt);
196     B = b;% (:,:,cnt);
197   else,
198     A = fftshift(ifft2(fftshift( a,1:2 )), 1:2 );
199     B = fftshift(ifft2(fftshift( b,1:2 )), 1:2 );
200   end;
201   for cnt=1:c,
202     if sum(strcmp(mode,'nocombo'))
203       B(:,:,cnt) = B(:,:,cnt) * diag( exp( -j*y2(2)*( (1:size(B,2))-size(B,2)/2 ) ) );
204     else 
205       A(:,:,cnt) = A(:,:,cnt) * diag( exp( +j*y2(2)/2*( (1:size(A,2))-size(A,2)/2 ) ) );
206       B(:,:,cnt) = B(:,:,cnt) * diag( exp( -j*y2(2)/2*( (1:size(B,2))-size(B,2)/2 ) ) );
207    end;
208   end;
209 %  m1 = sum(mask,2).';
210 %  t1 = find(m1~=0);
211 %  % calculate the shift from the slope of the phase.
212 %  % here, we are only concerned with shifts along rows
213 %  y1 = ( [ ones(length(t1),1) t1(:) ] ) \ unv(t1);
214 %  
215 %  % correct the phase of the second data set to match the first
216 %  % (i.e. shift the k-space grids to match)
217 %  B = diag( exp( j*y1(2)*((1:size(B,1))-1) ) ) * B;
218
219   % calculate the scalar phase difference between the sets ...
220   if isempty(phz)
221     if (0),
222     P = zeros(size(A));
223     % [JRP] why does previous definition of "tt" above use a threshold of 0.01*max?
224     tt = find(A(:)>0.1*max(A(:)));
225     P(tt) = A(tt).*conj(B(tt))./abs(A(tt).*conj(A(tt)));
226     indx1 = round((size(P,1) - 10)/2) + (1:10); if ( sum(indx1<=0) > 0 ) indx1 = 1:size(P,1); end;
227     x = mean(vec(P(  indx1, (end-10)/2 +(1:10), : ) ));
228     else,
229       % [JRP] based on "Local Phase Correction" of Feiweier
230       x = sum(vec(A.*conj(B)));
231     end;
232   else,
233     x = phz(2);
234   end;
235
236   % and apply a scalar correction as well.
237   B = B.*exp(j*angle(x));
238
239   if nargout > 1,
240     y_out(:,cnt) = [ y2(2); exp(j*angle(x)) ];
241     if (verbose)
242       fprintf(' p = %f  %f + j %f\n',y_out(1,cnt), ...
243       real(y_out(2,cnt)),imag(y_out(2,cnt)) );
244     end;
245   end;
246
247   % add the two sets together, and convert back to k-space for output.
248   if sum(strcmp(mode,'nocombo'))
249     c_out = B;
250   else,
251     c_out = (A+B)/2;
252   end;
253   if sum(strcmp(mode,'nofft')),
254   else,
255     c_out = ifftshift(fft2(ifftshift(c_out,1:2)),1:2);
256   end;
257
258 end;
259
260
261 c_out = reshape( c_out, size(a) );
262 sz = size(c_out);
263 if ( (nargout > 1) && length(sz)>2),
264   y_out = reshape( y_out, [ 2 sz(3:end) ] );
265 end;
266
267 % return output
268 if nargout > 0
269   varargout{1}= c_out;
270 end
271
272 if nargout > 1
273   varargout{2}= y_out;
274 end
275  
276 if nargout > 2
277   varargout{3}= y2;
278 end
279
280 if nargout > 3
281   varargout{4}= x;
282 end