1function [varargout] = rotate(raxis, angle, varargin)
2%align set alignment of the objects in the block
3%
4% [...] = rotate(axis, angle, obj <, obj> ...);
5%
6% Rotates the corresponding gradinet object(s) about the given axis by
7% the specified amount. Gradients parallel to the rotation axis and
8% non-gradient objects are not affected.
9% Possible rotation axes are 'x', 'y' or 'z'.
10%
11% The objects that contain the field 'id' but are not modified by this
12% function are passed through without modification. For the gradients
13% that are modified, the field 'id' is removed automatically.
14%
15% Optional parameter list may include the keyword 'system' followed by a
16% system limits struct. The system can only be provided in the beginning or
17% at the ent of the list of optional parameters.
18%
19% Returns either a cell-array of objects if one return parameter is
20% provided or an explicit list of objects if multiple parameters are
21% given. Can be used directly as a parameter of seq.addBlock().
22%
23% See also mr.rotate3D, Sequence.addBlock
24%
25% Maxim Zaitsev <maxim.zaitsev@uniklinik-freiburg.de>
27axes={'x', 'y', 'z'};
29% cycle through the objects and rotate gradients non-parallel to the
30% given rotation axis. Rotated gradients assigned to the same axis are then
31% added together.
33% first create indexes of the objects to be bypassed or rotated
34irotate1=[];
35irotate2=[];
36ibypass=[];
37axes2rot=axes(~strcmp(axes,raxis));
38if length(axes2rot)~=2
39 error('incorrect axis specification');
40end
41if strcmp('y',raxis)
42 axes2rot=axes2rot(end:-1:1); % we need to reverse the list to preserve the correct handiness of the rotation matrix
43end
45if ~isscalar(angle) || ~isnumeric(angle)
46 error('Rotation angle needs to be a scalar value');
47end
49% parse out the optional parameter 'system', which can only be at the beginning
50% or in the end of the optional parameters
51system=[];
52if ischar(varargin{1}) && strcmp(lower(varargin{1}),'system')
53 if ~isstruct(varargin{2}) || ~isfield(varargin{2},'gradRasterTime')
54 error('Error parsing input parameters, keyword ''system'' is not followed by a valid system struct');
55 end
56 system=varargin{2};
57 varargin=varargin(3:end);
58elseif length(varargin)>1 && ischar(varargin{end-1}) && strcmp(lower(varargin{end-1}),'system')
59 if ~isstruct(varargin{end}) || ~isfield(varargin{end},'gradRasterTime')
60 error('Error parsing input parameters, keyword ''system'' is not followed by a valid system struct');
61 end
62 system=varargin{end};
63 varargin=varargin(1:end-2);
64end
65% make this function accept ready-made blocks
66if isstruct(varargin{1}) && isfield(varargin{1}, 'rf')
67 varargin=mr.block2events(varargin);
68end
69% we need this to allow for nested mr.rotate() calls
70if 1==length(varargin) && iscell(varargin{1})
71 va=varargin{1};
72else
73 va=varargin;
74end
76for i=1:length(va)
77 par = va{i};
78 if isempty(par)
79 continue;
80 end
81 if isnumeric(par) || ...
82 ((~strcmp(par.type,'grad') && ...
83 ~strcmp(par.type,'trap')) || ...
84 strcmp(par.channel, raxis))%['g' axis]
85 ibypass=[ibypass i];
86 else
87 if strcmp(par.channel, axes2rot(1)) %['g' axes2rot(1)]
88 irotate1=[irotate1 i];
89 else
90 if (strcmp(par.channel, axes2rot(2))) %['g' axes2rot(2)]
91 irotate2=[irotate2 i];
92 else
93 ibypass=[ibypass i]; % should never happen
94 end
95 end
96 end
97end
99% now every gradient to be rotated generates two new gradients, one on the
100% original axis and one on the other from the axes2rot list
102rotated1=cell(1,length(irotate1)+length(irotate2));
103rotated2=cell(1,length(irotate1)+length(irotate2));
104max_mag=0; % measure of the relevant amplitude
105for i=1:length(irotate1)
106 g=va{irotate1(i)};
107 if isfield(g,'id'), g=rmfield(g,'id'); end
108 max_mag=max(max_mag, getGradAbsMag(g));
109 rotated1{i}=mr.scaleGrad(g,cos(angle));
110 g=mr.scaleGrad(g,sin(angle));
111 g.channel=axes2rot{2};
112 rotated2{i}=g;
113end
114o=length(irotate1);
115for i=1:length(irotate2)
116 g=va{irotate2(i)};
117 if isfield(g,'id'), g=rmfield(g,'id'); end
118 max_mag=max(max_mag, getGradAbsMag(g));
119 rotated2{i+o}=mr.scaleGrad(g,cos(angle));
120 g=mr.scaleGrad(g,-sin(angle));
121 g.channel=axes2rot{1};
122 rotated1{i+o}=g;
123end
125% eliminate zero-amplitude gradients
126thresh=1e-6*max_mag;
127for i=length(rotated1):-1:1
128 if getGradAbsMag(rotated1{i})<thresh
129 rotated1(i)=[];
130 end
131end
132for i=length(rotated2):-1:1
133 if getGradAbsMag(rotated2{i})<thresh
134 rotated2(i)=[];
135 end
136end
138g=cell(1,2);
139% now we add gradients on the corresponding axis together
140if (length(rotated1)>1)
141 if isempty(system)
142 g{1}=mr.addGradients(rotated1);
143 else
144 g{1}=mr.addGradients(rotated1,system);
145 end
146else
147 if (~isempty(rotated1))
148 g{1}=rotated1{1};
149 end
150end
152if (length(rotated2)>1)
153 if isempty(system)
154 g{2}=mr.addGradients(rotated2);
155 else
156 g{2}=mr.addGradients(rotated2,system);
157 end
158else
159 if (~isempty(rotated2))
160 g{2}=rotated2{1};
161 end
162end
164% eliminate zero-amplitude gradients
165for i=length(g):-1:1
166 if isempty(g{i}) || getGradAbsMag(g{i})<thresh
167 g(i)=[];
168 end
169end
171% export
172bypass=va(ibypass);
173out={bypass{:},g{:}};
175nout = nargout;
176varargout = cell(1,nout);
177if nout==1
178 varargout{1}=out;
179else
180 nr=min(nout,length(out));
181 if nout<length(out)
182 warning('insufficient number of return parameters, some rotated gradient components might go lost');
183 end
184 for k=1:nr
185 varargout{k}=out{k};
186 end
187end
189end
192function [out] = getGradAbsMag(grad)
193 if strcmp(grad.type,'trap')
194 out=abs(grad.amplitude);
195 else
196 out=max(abs(grad.waveform));
197 end
198end