1 function [F,I,N] = dpg_cal( varargin );
3 % [F,I,N ] = dpg_cal( Wk, Sk, Y, [opt args]);
6 % 'v', {0,1} - verbosity off/on
7 % 'kernel', string - grappa kernel size, x by y. e.g. '2x5', '4x1'
8 % 'N', (xy)-by-num_patterns-L matrix - grappa reconstruction parameters
9 % 'acs', array of indices - index locations to use for ACS lines
11 % This implementation is derived from
12 % "Generalized autocalibrating partially parallel acquisitions"
13 % by M. A. Griswold, P. M. Jakob, et. al..
14 % Mag. Reson. Med, 47(6):1202-1210, Jun 2002
18 % Copyright 2004-2016 W. Scott Hoge (wsh032580 at proton dot me)
20 % Licensed under the terms of the MIT License
21 % (https://opensource.org/licenses/MIT)
24 % GRAPPA: data from all coils used to patch data in one coil
26 % coil 1 o * o * a * o *
27 % coil 2 o * o * a * o *
28 % coil 3 o * o * a * o *
29 % coil 4 o * o * x * o *
31 % *: acquired line, o: unacquired line, a: auto-callibration line
32 % x: reconstructed line
34 % S_j (k_y - m\Delta k_y ) = \sum_{l=1}^L \sum_{b=0}^{N_b-1}
35 % n(j,b,l,m) S_l(k_y - b A \Delta k_y)
42 acs = []; N = []; verbose=0; kernel = '2x5'; dks = []; prnt = 0;
46 vararg = varargin(4:end);
47 for cnt=1:2:length(vararg),
48 if strcmp( vararg{cnt}, 'v' ),
49 verbose = cell2mat(vararg(cnt+1));
50 elseif strcmp( vararg{cnt}, 'N' ),
52 elseif strcmp( vararg{cnt}, 'dks' ),
54 elseif strcmp( vararg{cnt}, 'kernel' ),
55 kernel = vararg{cnt+1};
56 elseif strcmp( vararg{cnt}, 'acs' ),
58 elseif (strcmp( vararg{cnt}, 'report' ) || strcmp( vararg{cnt}, 'r' )),
63 if length(size(Wk)) == 3,
64 [Wm,Wn,ncoils] = size(Wk);
66 Wm = Wk(1); Wn = Wk(2); ncoils = Wk(3);
70 %% zero-pad the k-space data
71 Sk = zeros( Wm, Wn, ncoils );
81 rptupd(prnt,['* ' mfilename ': kernel size = ' kernel '\n']);
85 prfl = max( sum(abs( Sk(:,:,:)),3).' );
87 if length( d == 1) == 1,
88 acs = find( prfl == max(prfl(Y(find(d==1)))) );
90 acs = Y([ min(find(d==1)); find( d == 1)+1 ]);
92 rptupd(prnt,['* ' mfilename ': acs =']);
93 rptupd(prnt,sprintf(' %d', acs) );
98 %% determine which delta-k's to repair.
101 if (sum( cnt == d ) > 0),
107 % set the kernel spacings based on 'kernel' string
108 nky = str2double(kernel(1));
109 nkx = str2double(kernel(3:end)); % should be odd
111 indx3 = ceil((0-nkx)/2) -1 + (1:nkx); % [-2:2];
117 pattern{cnt} = N(cnt).pattern;
122 %% foreach dk spacing
123 for cnt=1:length(dks),
126 % for no acceleration, just specify the pattern
127 tmppat = repmat('*',[1 nky]);
129 pattern{length(pattern)+1} = tmppat;
130 pattern{length(pattern)}(patcnt) = '<';
134 pattern{length(pattern)+1} = tmppat;
135 pattern{length(pattern)}(patcnt) = '>';
141 tmp = [ repmat([ '*' repmat('o',1,dks(cnt)-1) ],1,nky-1) '*' ];
142 z2 = strfind(tmp,'o');
144 % pad with zeros, if the kernel is 1-by-N
145 tmp = [ 'o' tmp 'o' ];
146 z2 = strfind(tmp,'o');
148 [tmp2,indx] = sort( abs(z2 - z2( floor(median(1:length(z2))) )) );
150 olength = length(pattern);
152 % for dual-kernels, we want to genereate coefficients for the sampled
154 npat = 2*length(tmp);
158 for cnt2 = 1:npat, % (dks(cnt)-1), %
162 pattern{length(pattern)+1} = tmp;
163 pattern{length(pattern)}(z2(cnt3)) = 'x';
165 cnt3 = mod( cnt2-1, length(tmp) ) + 1;
166 pattern{length(pattern)+1} = tmp;
167 if ( cnt2 <= npat/2 )
168 pattern{length(pattern)}( cnt3 ) = '<';
170 pattern{length(pattern)}( cnt3 ) = '>';
180 % find the recon patterns we can use
181 plist = cell(length(pattern),1);
183 for cnt = 1:length(pattern),
184 v = extrapolate_pattern(pattern{cnt},nky);
186 % if cnt == 14, keyboard; end;
188 for cnt2 = 1:length(acs);
189 %% check if the GRAPPA pattern...
190 indx = acs(cnt2) + v;
192 %% can be accomodated by the sampling pattern
193 tmp = find( Y(:)*ones(1,length(indx)) == ones(length(Y),1)*indx );
194 if ( length(tmp) == length(indx) ),
195 plist{cnt} = [ plist{cnt} acs(cnt2) ];
200 rptupd(prnt,['* ' mfilename ': sampling patterns in use = \n' ]);
201 for cnt1=1:length(pattern);
202 rptupd(prnt,sprintf(' %s: %d acs lines =', pattern{cnt1}, length(plist{cnt1})) );
203 rptupd(prnt,sprintf(' %3d', plist{cnt1}) );
204 % for cnt2 = 1:length( plist{cnt1} ),
205 % rptupd(prnt,(' %s', pattern{plist{cnt1}} );
206 % % v = extrapolate_pattern(pattern{plist{cnt1}(cnt2)},nky);
207 % % norm(vec(Sk( acs(cnt1)+v, :, : )));
211 if (length(plist) == 0),
212 error(['sorry, acquisition pattern is incompatible with available GRAPPA recon patterns']);
217 if verbose, rptupd(prnt,'\n'); end;
219 mss = zeros( size(Sk,1), 1 ); % set the missing lines index vector
223 % N = zeros( length(indx3)*nky*size(s,3),length(pattern),size(s,3));
225 rptupd(prnt,'calc recon param\n');
226 for cnt = 1:length(pattern),
227 v = extrapolate_pattern(pattern{cnt},nky);
229 %% and for each pattern ...
232 %% A is data from the expanded input data matrix.
233 %% size(A) = [ kxnum * nacs, ncoils * nky * nkx ]
235 %% in the matlab (and c-code) structure is:
236 %% for each element of indx3
237 %% coil 1 | coil 2 | coil 3 | ...
238 %% -3 -1 1 3 | -3 -1 1 3 | -3 -1 1 3 | ...
239 %% o o o o | o o o o | o o o o | ...
250 %% the older version of matlab code sorted the columns by
252 %% | c1 c2 c3 ... | c1 c2 c3 ... | c1 c2 c3 ... | c1 c2 c3 ...
253 %% | -3 -3 -3 ... | -1 -1 -1 ... | 1 1 1 ... | 3 3 3 ...
254 %% | o o o ... | o o o ... | o o o ... | o o o ...
256 %% the order is immaterial, as long at it is consistent...
257 %% UPDATE (12 Jan 2011): the order does matter for GROG, so we have
258 %% now changed it here so that the c-code and matlab code match.
260 %% The old way to switch between the two was:
261 %% Accode(:, permute( reshape(1:size(Accode,2),YKS,Ncoils,XKS), [3 2 1]) ) == Amatlab
262 %% Accode = Amatlab(:, permute( reshape(1:size(Accode,2),XKS,Ncoils,YKS), [3 2 1]))
263 A = zeros( length(plist{cnt})*size(s,2), length(indx3)*nky*size(s,3) );
265 rptupd(prnt,sprintf('%3d %s: ', cnt, pattern{cnt}));
267 % v = extrapolate_pattern(pattern{cnt});
268 N(length(N)+1).pattern = pattern{cnt};
270 n = zeros( nky*size(s,3)*length(indx3),size(s,3));
272 for cnt2 = 1:length(plist{cnt}),
275 %% if the sampling pattern accomodates the GRAPPA pattern...
276 indx = plist{cnt}(cnt2) + v; % feedforward indices
277 % indx2 = plist{cnt}(cnt2) + f; % feedback indices
279 %% determine the k-space filling coefficients
280 for cnt3=1:length(indx3),
281 y0 = mod( indx3(cnt3) + [1:size(Sk,2)] , size(Sk,2) );
282 y0( y0 == 0 ) = size(Sk,2);
284 % generate a hybrid source matrix
286 tmpSksub = zeros( size( Sk(indx,y0,:) ) );
287 if ( sum(pattern{cnt}=='<') ),
288 tmpSksub( 1:2:length(indx), :, : ) = kin{1}( indx(1:2:end),y0,: );
289 tmpSksub( 2:2:length(indx), :, : ) = kin{2}( indx(2:2:end),y0,: );
290 % tmpSksub( (v < 0), :, : ) = kin{2}( indx( (v < 0) ),y0,: );
291 % tmpSksub( (v >= 0), :, : ) = kin{3}( indx( (v >= 0) ),y0,: );
293 tmpSksub( 1:2:length(indx), :, : ) = kin{2}( indx(1:2:end),y0,: );
294 tmpSksub( 2:2:length(indx), :, : ) = kin{1}( indx(2:2:end),y0,: );
295 % tmpSksub( (v >= 0), :, : ) = kin{2}( indx( (v >= 0) ),y0,: );
296 % tmpSksub( (v < 0), :, : ) = kin{3}( indx( (v < 0) ),y0,: );
299 tmpSksub = Sk(indx,y0,:);
301 % A( size(s,2)*(cnt2-1) + (1:size(s,2)), cnt3:length(indx3):size(A,2) ) = ...
302 A( size(s,2)*(cnt2-1) + (1:size(s,2)), (cnt3-1)*nky*ncoils + (1:nky*ncoils) ) = ...
303 reshape( permute( tmpSksub, [ 2 1 3 ]), size(Sk,2), size(Sk,3)*length(indx) );
306 %% for an N-by-1 kernel, these next lines are enough...
308 % reshape( permute(squeeze( Sk(indx,:,:) ),[ 2 3 1 ]), ...
309 % size(s,2), size(s,3)*length(indx) )
312 % b2 = [b2; squeeze( Sk( plist{cnt}(cnt2), :, l ) ).' ];
315 % normalize, following to Algo 4.2 of M. Schneider, "GPGPU for Accelerated GRAPPA Autocalibration in
316 % Magnetic Resonance Imaging," Masters Thesis, U of Erlangen, Apr 2008
318 p_src = sum(abs(A),2).^(-eta/2); % compute the power in each line of the linsys
319 A = tmult(A,diag(p_src),1); % normalize by the power in each linsys line
328 % A is the same for each coil ...
329 b = vec( squeeze(Sk( plist{cnt}, :, l )).' ); % for dual-kernel, this defaults
332 b = b.*p_src(:); % normalize by the power in each linsys line
337 if (verbose>2), pltcmplx( At'*n(:,l), b ); keyboard; end;
346 rptupd(prnt,'recon missing lines\n');
347 %% now go recon the missing lines of k-space:
349 if (verbose); keyboard; end;
351 kypts = 1:length(mss);
352 kypts( 1:floor(length(mss)/2) ) = kypts( (floor(length(mss)/2)):-1:1 );
354 linerpt = repmat(' ',1,length(mss));
356 for kycnt=1:length(mss),
359 if verbose, rptupd(prnt,sprintf(' line %3d, from lines ',cnt2)); end;
361 if ( find( Y == cnt2) );
364 if verbose, rptupd(prnt,'\n'); end;
369 %% determine which recon pattern we can use:
370 for cnt4 = 1:length(pattern),
371 v = extrapolate_pattern(pattern{cnt4},nky);
373 if ( (kycnt < length(mss)/2) && sum(find( pattern{cnt4} == '-')) ), continue, end;
374 if ( (kycnt > length(mss)/2) && sum(find( pattern{cnt4} == '+')) ), continue, end;
378 % tmp = find( Y(:)*ones(1,length(indx)) == ones(length(Y),1)*indx );
379 % if ( ... % ( (min(indx) > 1) & (max(indx) < size(Sk,1)) ) & ...
380 % ( length(tmp) == length(indx) ) ),
381 % break; % jump out of the loop
383 if ( indx(1) < 1) | (indx(end) > size(Sk,1)); continue; end;
385 tmp = (mss( indx ) == ones(length(indx),1));
386 if ( sum(tmp) == length(tmp) ),
392 if ( ( (min(indx) < 1) | (max(indx) > size(Sk,1)) ) | ...
393 ( sum(tmp) ~= length(indx) ) ),
396 if verbose, rptupd(prnt,'o'); rptupd(prnt,'\n'); end;
400 %% recon k-space line based on that pattern:
402 A = zeros( rsz, length(indx3)*length(v)*size(s,3) );
403 B = []; % zeros( rsz, length(indx3)*length(f)*size(s,3) );
405 for cnt3=1:length(indx3),
406 y0 = mod( indx3(cnt3) + [(size(Sk,2)-rsz)/2+(1:rsz)] , size(Sk,2) );
407 y0( y0 == 0 ) = size(Sk,2);
409 A( (1:rsz), (cnt3-1)*nky*ncoils + (1:nky*ncoils) ) = ...
410 reshape( permute( Sk(indx,y0,:), [ 2 1 3 ]), ...
411 rsz, size(Sk,3)*length(indx) );
414 if (verbose), % ( sum(Sk(Y(mss(cnt2))+1,:,l)) ~= 0 );
415 rptupd(prnt,sprintf(' %3d',[ indx(:) ]));
416 rptupd(prnt,sprintf(' (pattern: %s)', pattern{cnt4}) );
419 figure(1); imagesc( log10(abs(Sk(:,:,1)) + 1e-1) ); pause(0.5);
425 for cnt3=1:length(N),
426 if strcmp( N(cnt3).pattern, pattern{cnt4} ),
427 break; % jump out of the loop
435 % if cnt2==109, keyboard; end;
436 % rptupd(prnt,(' line %3d, pattern %3d\n', cnt2, plist(cnt3) );
437 Sk( cnt2, :, l ) = [A B] * n(:,l);
440 if (verbose>1), keyboard; end;
442 rptupd(prnt,linerpt);
445 % Sk(1:2:size(Sk,1),:,:) = -Sk(1:2:size(Sk,1),:,:);
446 % Sk(:,1:2:size(Sk,2),:) = -Sk(:,1:2:size(Sk,2),:);
449 I = sqrt( sum(abs(ifft2(Sk)).^2,3) );
450 I = I./(prod(size(I)));
453 rpttime = etime(clock,st);
454 rptupd(prnt,sprintf(' GRAPPA recon time: %g (avg per kernel: %g)\n', ...
455 rpttime, rpttime/length(pattern)) );
459 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
460 function rptupd( prnt, str )
468 %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
469 function v = extrapolate_pattern(p,YKS)
471 r = find( (p == 'x') | (p == '<') | (p == '>') );
472 v = find( (p == '*') | (p == '<') | (p == '>') ) - r;
475 v = v( find( v~= 0 ) );