OSCR

Laterally Oscillating Trajectory for Undersampling Slices: LOTUS.

Code ↔ Paper

10 matches between paragraphs of the paper and lines of its authors' code, computed by the harvester (lexical-v1). Click a colored paragraph or line to see its counterpart.

The 10 matches · 1 of them tie a paragraph to a whole file, not to given lines: a weak match, whose lines are not tinted
  1. [1] § Theory › G‐Factor Estimation for Non‐Cartesian MRI ↔ operators/mrSampFuncMat.m, the whole file · a weak match · score 0.77 · receiver sensitivity profiles, sampling operator, B0 inhomogeneity, Iterative, masking, MRI
  2. [2] § Methods › Reconstruction ↔ demos/demo_denseMat.m, lines 1–68 · score 0.77 · expanded encoding model, ground truth image, field probe, GB, memory, GPU
  3. [3] § Methods › In Vivo ↔ demos/demo_denseMat.m, lines 1–68 · score 0.73 · field probe system, virtual coils, coil compressed, speed, receivers, noise
  4. [4] § Methods › In Vivo ↔ demos/demo_autoDel.m, lines 1–62 · score 0.71 · field probe system, virtual coils, coil compressed, receivers, noise
  5. [5] § Theory › G‐Factor Estimation for Non‐Cartesian MRI ↔ operators/mrSampFunc.m, lines 1–44 · score 0.65 · receiver sensitivity, domain image, Tikhonov, compensate, Cartesian, operator
  6. [6] § Methods › Reconstruction ↔ computeHarmonics/harmonicsFromRaw.m, lines 1–71 · score 0.60 · encoding model, field probe, coefficients, fitted, iterative, MRI
  7. [7] § Methods › In Vivo ↔ dMRI/nii2kurt.m, lines 1–55 · score 0.55 · matMRI, diffusion tensor, FA, masking
  8. [8] § Methods › Simulations › Investigation of k z Oscillation Period ↔ trajectory/spiralGen.m, lines 90–194 · score 0.55 · Golden ratio, 0–1, oscillation, LOTUS, trajectories
  9. [9] § Theory › Trajectory Design ↔ trajectory/spiralGen.m, lines 1–85 · score 0.54 · gradient relative, Pipe, Nyquist, magnitude, angle, numerical
  10. [10] § Methods › Simulations › Trajectory Comparisons › G‐Factor Maps and Error Metrics ↔ trajectory/spiralGen.m, lines 90–194 · score 0.53 · slew rate, simultaneous slices, msec, resolution, gradient

Paper

Loaded from Europe PMC by your browser, not stored by OSCR: doi.org · Europe PMC

The paper is loaded when this pane is shown.

The authors' code

MATLAB · 720 lines · 25 KB · MIT · 3 matches

  1. function [grads,slews,opt] = spiralGen(fovxy,resxy,opt,plotting)
  2. %
  3. % This code is adapted from spiralgen_jgp_12oct.c by Jim Pipe, found at
  4. % https://www.ismrm.org/mri_unbound/sequence.htm
  5. %
  6. % Changes by C. A. Baron:
  7. % - addition of two new spiral types:
  8. % sptype = 4; blipped on z to make multiple spiral planes
  9. % sptype = 5; LOTUS see ISMRM 2025 abstract 1371 (Sothynathan et al)
  10. %
  11. % Inputs:
  12. % fovxy in-plane field of view, in m
  13. % resxy in-plane resolution, in m
  14. % opt options structure. See code for descriptions
  15. %
  16. % Original comments from Pipe:
  17. % /*********************************************
  18. % // Spiral Generation code
  19. % **********************************************
  20. % // Author: Jim Pipe
  21. % // Date: May 2011
  22. % // Rev: Oct 2012
  23. % *********************************************/
  24. % // A Subset of Relevant Literature
  25. % //
  26. % // Spiral Invented:
  27. % // High-speed spiral-scan echo planar NMR imaging-I.
  28. % // Ahn, C.B., Kim, J.H. & Cho, Z.H., IEEE Transactions on Medical Imaging, 5(1) 1986.
  29. % //
  30. % // Spiral Improved:
  31. % // Fast Spiral Coronary Artery Imaging.
  32. % // Meyer CH, Hu BS, Nishimura DG, Macovski A, Magnetic Resonance in Medicine, 28(2) 1992.
  33. % //
  34. % // Variable Density Spiral
  35. % // Reduced aliasing artifacts using variable-density k-space sampling trajectories.
  36. % // Tsai CM, Nishimura DG, Magnetic Resonance in Medicine, 43(3), 2000
  37. % //
  38. % // "SLOPPY" SPIRAL
  39. % // Faster Imaging with Randomly Perturbed Undersampled Spirals and L_1 Reconstruction
  40. % // M. Lustig, J.H. Lee, D.L. Donoho, J.M. Pauly, Proc. of the ISMRM '05
  41. % //
  42. % // FLORET
  43. % // A new design and rationale for 3D orthogonally oversampled k-space trajectories
  44. % // Pipe JG, Zwart NR, Aboussouan EA, Robison RK, Devaraj A, Johnson KO, Mag Res Med 66(5) 2011
  45. % //
  46. % // Distributed Spirals
  47. % // Distributed Spirals: A New Class of 3D k-Space Trajectories
  48. % // Turley D, Pipe JG, Magnetic Resonance in Medicine, in press (also proc of ISMRM '12)
  49. %
  50. % This function
  51. % returns a single spiral arm calculated numerically
  52. %
  53. % The corresponding gradient waveforms are in gxarray and gyarray
  54. % spgrad_na reflects the number of gradient points to reach the end of k-space
  55. % spgrad_nb = spgrad_na + the number of gradient points to ramp G to zero
  56. % spgrad_nc = spgrad_nb + the number of gradient points to rewind k to zero
  57. % spgrad_nd = spgrad_nc + the number of gradient points for first moment compensation
  58. %
  59. % Assignments below indicate units of input parameters
  60. % All units input using kHz, msec, mT, and m!
  61. %
  62. % grad = gm exp(i theta) i.e. gm, theta are magnitude and angle of gradient
  63. % kloc = kr exp(i phi) i.e. kr, phi are magnitude and angle of k-space
  64. % alpha = theta - phi the angle of the gradient relative to that of k-space
  65. % (alpha = Pi/2, you go in a circle
  66. % alpha = 0, you go out radially)
  67. %
  68. % The variable rad_spacing determines the radial spacing
  69. % in units of the Nyquist distance.
  70. % rad_spacing = 1 gives critical sampling
  71. % rad_spacing > 1 gives undersampling
  72. % rad_spacing can vary throughout spiral generation to create variable density spirals
  73. %
  74. % KEY EQUATIONS:
  75. % (1) dkr/dphi = rad_spacing*Nyquist/(2 pi)
  76. % (2) dphi/dt = gamma gm Sin(alpha)/kr
  77. % (3) dkr/dt = gamma gm Cos(alpha)
  78. %
  79. % Solving (1)*(2) = (3) gives
  80. % (4) Tan(alpha) = (2*pi*kr)/(rad_spacing*Nyquist)
  81. %
  82. % *************************************************************/
  83. % /* Initializations */
  84. % /************************************************************/
  85. %%%%% Set limits
  86. maxarray = 128000;
  87. %%%%% Set input defaults
  88. if nargin < 1
  89. fovxy = 0.220/3;
  90. end
  91. if nargin < 2
  92. resxy = 0.002;
  93. end
  94. if nargin < 3
  95. opt = [];
  96. end
  97. if nargin<4 || isempty(plotting)
  98. plotting = true;
  99. end
  100. if ~isfield(opt,'subrast') || isempty(opt.subrast)
  101. opt.subrast = 5; %/* number of numerical cycles per gradient raster time */
  102. end
  103. if ~isfield(opt,'m_dGRast') || isempty(opt.m_dGRast)
  104. opt.m_dGRast = 0.01; % base raster time [msec]
  105. end
  106. if ~isfield(opt,'gamma') || isempty(opt.gamma)
  107. opt.gamma = 42.577; %/* typically 42.577 kHz/mT */
  108. end
  109. if ~isfield(opt,'gmax') || isempty(opt.gmax)
  110. opt.gmax = 30; %/* max gradient amplitude in mT/m */
  111. end
  112. if ~isfield(opt,'slewmax') || isempty(opt.slewmax)
  113. opt.slewmax = 120; %/* max slew rate, in mT/m/msec*/
  114. end
  115. if ~isfield(opt,'gtype') || isempty(opt.gtype)
  116. % 0 = calculate through readout
  117. % 1 = include grad ramp-down
  118. % 2 = include rewinder to end at k=0
  119. % 3 = include first moment comp
  120. opt.gtype = 1;
  121. end
  122. if ~isfield(opt,'fovz') || isempty(opt.fovz)
  123. % Only relevant for opt.sptype == 2
  124. opt.fovz = 0.256; % /* enter in m */
  125. end
  126. if ~isfield(opt,'resz') || isempty(opt.resz)
  127. opt.resz = 0.002; % /* enter in m : this should be true resolution */
  128. end
  129. if ~isfield(opt,'arms') || isempty(opt.arms)
  130. opt.arms = 1; % /* number of spiral interleaves*/
  131. end
  132. if ~isfield(opt,'sptype') || isempty(opt.sptype)
  133. % 0 = Archimedean
  134. % 1 = Cylinder DST
  135. % 2 = Spherical DST
  136. % 3 = Fermat:Floret
  137. % 4 = Archimedean with SMS CAIPI blips (CB 202301)
  138. % 5 = Archimedean with sinusoidal kz for SMS (CB 202301).
  139. % "LOTUS": Laterally Oscillating Trajectory for Undersampling Slices
  140. opt.sptype = 0;
  141. end
  142. % /* the next 4 variables are for variable density spirals */
  143. % /* they create a transition in the radial spacing as the k-space radius goes from 0 to 1, i.e.*/
  144. % /* 0 < kr < us_0 : spacing = Nyquist distance */
  145. % /* us_0 < kr < us_1 : spacing increases to us_r (affected by opt.ustype)*/
  146. % /* us_1 < kr < 1 : spacing = us_r*/
  147. if ~isfield(opt,'ustype') || isempty(opt.ustype)
  148. % rate of change in undersampling
  149. % 0 = linear
  150. % 1 = quadratic
  151. % 2 = hanning */
  152. opt.ustype = 0;
  153. end
  154. if ~isfield(opt,'us_0') || isempty(opt.us_0)
  155. opt.us_0 = 0;
  156. end
  157. if ~isfield(opt,'us_1') || isempty(opt.us_1)
  158. opt.us_1 = 0;
  159. end
  160. if ~isfield(opt,'us_r') || isempty(opt.us_r)
  161. opt.us_r = 1;
  162. end
  163. if ~isfield(opt,'slop_per') || isempty(opt.slop_per)
  164. % For sloppy spirals, this lets us define periodicity in units of iteration loop time */
  165. % set this to zero if you do not want sloppy spirals */
  166. opt.slop_per = 0;
  167. end
  168. % Params for SMS
  169. if ~isfield(opt,'Crate') || isempty(opt.Crate)
  170. opt.Crate = 2; % number of simultaneous slices
  171. end
  172. if ~isfield(opt,'Cdz') || isempty(opt.Cdz)
  173. opt.Cdz = 0.1; % spacing between slices [m]
  174. end
  175. % Params for kz blips for CAIPI-like waveform (sptype == 4)
  176. if ~isfield(opt,'Cf') || isempty(opt.Cf)
  177. opt.Cf = 0.3; % fractional slew rate reserved for blips. 0.2 or 0.3 seems to be a good tradeoff
  178. end
  179. if ~isfield(opt,'CstaggerBlip') || isempty(opt.CstaggerBlip)
  180. opt.CstaggerBlip = 0; %// to stagger blips to try to have fewer k-space gaps
  181. end
  182. % Params for kz oscillations for CAIPI-like waveform (sptype == 5)
  183. if ~isfield(opt,'CphiFact') || isempty(opt.CphiFact)
  184. % period of kz wave is 1/CphiFact larger than kxy rotation period.
  185. % Good choices take many repetitions to get back to an integer to
  186. % moreorless isotropically sample kz. It may also be a good idea to
  187. % choose a value close to 1 so that all channels have similar freq
  188. % content, which makes it easier to avoid vibrational
  189. % resonances.
  190. opt.CphiFact = 0.618; % Equal to 1/golden ratio.
  191. end
  192. %%%%% Internal variable calculation
  193. rast = opt.m_dGRast / opt.subrast; %/* calculation "raster time" in msec */
  194. if ( (opt.sptype >= 4) && (opt.Crate < 2) )
  195. % If only one slice, no need to account for SMS
  196. opt.sptype = 0;
  197. end
  198. %%%%% Error checking
  199. if (opt.CstaggerBlip)
  200. error('Proper handling of rewinding time not tested for staggered blips')
  201. end
  202. %%%%% Start computations
  203. nyquist = opt.arms/fovxy; %/* radial distance per arm to meet the Nyquist limit*/
  204. gamrast = opt.gamma*rast; %/* gamrast*g = dk*/
  205. dgc = opt.slewmax*rast; %/* the most the gradients can change in 1 raster period*/
  206. sub_gamrast = opt.subrast*gamrast;
  207. sub_dgc = opt.subrast*dgc;
  208. uz=0;
  209. gx=0;
  210. gy=0;
  211. gz=0;
  212. kx = zeros(opt.subrast*maxarray,1);
  213. ky = zeros(opt.subrast*maxarray,1);
  214. kz = zeros(opt.subrast*maxarray,1);
  215. gsign = ones(opt.subrast*maxarray,1);
  216. gxarray = zeros(maxarray,1);
  217. gyarray = zeros(maxarray,1);
  218. gzarray = zeros(maxarray,1);
  219. krmax = 0.5/resxy;
  220. kzmax = 0.5/opt.resz;
  221. krmax2 = krmax*krmax;
  222. kzmax2 = kzmax*kzmax;
  223. krlim = krmax*(1.-(resxy/fovxy));
  224. if (opt.sptype==4)
  225. %/* Determine k-space spacing based on "FOV" Crate*dz. This is the
  226. % typical dkz provided by a blip.
  227. CkbTot = 1.0 / (opt.Crate*opt.Cdz);
  228. %/* Determine total k-space step provided by largest blip, which
  229. % brings you back to the starting k-space position. We design to
  230. % this, then scale the smaller blips back down. */
  231. CkbTot = (opt.Crate - 1.0) * CkbTot;
  232. %// Determine duration based on basic k and grad relations [ms]
  233. Ct = 2*sqrt(CkbTot / (opt.gamma * opt.Cf * opt.slewmax));
  234. %// Find total number of points. Make total time a multiple of the twice the raster time
  235. Cnp = floor(Ct/(2.0*opt.m_dGRast) + 1.0) * 2 * opt.subrast;
  236. Ct = Cnp * rast;
  237. %// Find gradient change per point in array using k to grad relationship
  238. Cgmax = 2.0 * CkbTot / (opt.gamma * Ct);
  239. CGstep = Cgmax / (Cnp/2);
  240. %// Fill array with k-space values
  241. Cgb = zeros(Cnp,1);
  242. Cgb(1) = 0;
  243. for i=2:Cnp %(i=1;i<Cnp;i++)
  244. if (i<=Cnp/2)
  245. Cgb(i) = (i-1)*CGstep;
  246. else
  247. Cgb(i) = (Cnp-i+1)*CGstep;
  248. end
  249. end
  250. Cbstart = -1;
  251. CbstartPrev = -1;
  252. Ccurrblip = -1;
  253. CcurrblipPrev = -1;
  254. Cadd = 0; %// To stagger where the blips start a bit
  255. elseif (opt.sptype == 5)
  256. %// Determine k-space spacing based on "FOV" Crate*dz;
  257. CkbTot = 1.0 / (opt.Crate*opt.Cdz);
  258. %// Determine total span to go from min to max k
  259. CkbTot = (opt.Crate - 1.0) * CkbTot;
  260. isStart = 1;
  261. isStartRecord = ones(opt.subrast*maxarray,1);
  262. phiUnwrapped = zeros(opt.subrast*maxarray,1);
  263. end
  264. %/* start out spiral going radially at max slew-rate for 2 time-points */
  265. kx(1) = 0;
  266. ky(1) = 0;
  267. kx(2) = gamrast*dgc;
  268. ky(2) = 0;
  269. kx(3) = 3*gamrast*dgc;
  270. ky(3) = 0;
  271. %// IF SPHERE
  272. if (opt.sptype == 2)
  273. kz(1) = kzmax;
  274. kz(2) = sqrt(kzmax2*(1-((kx(1)*kx(1)+ky(1)*ky(1))/krmax2))); %// stay on surface of ellipsoid
  275. kz(3) = sqrt(kzmax2*(1-((kx(2)*kx(2)+ky(2)*ky(2))/krmax2))); %// stay on surface of ellipsoid
  276. end
  277. nLoops = 0;
  278. i = 3;
  279. kr = kx(3);
  280. % /******************************/
  281. % /* LOOP UNTIL YOU HIT MAX RES */
  282. % /******************************/
  283. while ((kr <= krlim) && (i < opt.subrast*maxarray-1) )
  284. if (nLoops > 10*opt.subrast*maxarray)
  285. error('Spiral gen failure')
  286. end
  287. if (i<2)
  288. error('Spiral gen failure on rewinding time')
  289. end
  290. % /**************************/
  291. % /*** STEP 1: Determine the direction (ux,uy) of the gradient at ~(i+0.5) */
  292. % /**************************/
  293. % /* calculate dk/rast = opt.gamma G*/
  294. kmx = 1.5*kx(i) - 0.5*kx(i-1);
  295. kmy = 1.5*ky(i) - 0.5*ky(i-1);
  296. kmr = sqrt(kmx*kmx + kmy*kmy);
  297. % /////////////////////////////
  298. % // Start rad_spacing logic //
  299. % /////////////////////////////
  300. rnorm = 2*resxy*kmr; %/* the k-space radius, normalized to go from 0 to 1 */
  301. %/* determine the undersample factor */
  302. if (rnorm <= opt.us_0)
  303. rad_spacing = 1;
  304. elseif (rnorm < opt.us_1)
  305. us_i = (rnorm-opt.us_0)/(opt.us_1 - opt.us_0); %/* goes from 0 to 1 as rnorm goes from us_0 to us_1*/
  306. if (opt.ustype == 0)
  307. %/* linearly changing undersampling*/
  308. rad_spacing = 1. + (opt.us_r - 1.)*us_i;
  309. elseif (opt.ustype == 1)
  310. %/* quadratically changing undersampling*/
  311. rad_spacing = 1. + (opt.us_r - 1.)*us_i*us_i;
  312. elseif (opt.ustype == 2)
  313. %/* Hanning-type change in undersampling */
  314. rad_spacing = 1. + (opt.us_r - 1.)*0.5*(1.-cos(us_i*M_PI));
  315. end
  316. else
  317. rad_spacing = opt.us_r;
  318. end
  319. %/* Undersample spiral for Spherical-Distributed Spiral */
  320. if (opt.sptype == 2)
  321. if (rnorm < 1.0)
  322. rad_spacing = min(opt.fovz/opt.resz, rad_spacing/sqrt(1.0 - (rnorm*rnorm)));
  323. else
  324. rad_spacing = opt.fovz/opt.resz;
  325. end
  326. end
  327. %/* MAKE FERMAT SPIRAL FOR FLORET*/
  328. if (opt.sptype == 3 && rnorm > 0)
  329. rad_spacing = rad_spacing / rnorm;
  330. end
  331. %/* Sloppy Spirals - add variability to rad_spacing for reduced aliasing coherence */
  332. % // A couple different options here are commented out
  333. % // Lots of ways to be sloppy
  334. if (opt.slop_per > 0)
  335. % // rad_spacing = MAX(1., (rad_spacing + ((rad_spacing-1.)*sin(2.*M_PI*(double)(i)/opt.slop_per))));
  336. % // rad_spacing += (rad_spacing-1.)*sin(2.*M_PI*opt.slop_per*atan2(ky(i),kx(i)));
  337. rad_spacing = rad_spacing + (rad_spacing-1)*sin(2*pi*opt.slop_per*rnorm);
  338. end
  339. % ///////////////////////////
  340. % // End rad_spacing logic //
  341. % ///////////////////////////
  342. %/* See the Key Equation 4 at the beginning of the code */
  343. alpha = atan(2*pi*kmr/(rad_spacing*nyquist));
  344. phi = atan2(kmy,kmx);
  345. theta = phi + alpha;
  346. ux = cos(theta);
  347. uy = sin(theta);
  348. % // IF SPHERICAL DST
  349. % // u dot km is zero if moving on a sphere (km is radial, u is tangential,
  350. % // thus km stays on the sphere)
  351. % // We are on an ellipsoid, but can normalize u and km by krmax and kzmax to make this work
  352. % // The final gradient vector (ux uy uz) will be tangential to the sphere
  353. if (opt.sptype == 2)
  354. kmz = 1.5*kz(i) - 0.5*kz(i-1);
  355. uz = -((ux*kmx + uy*kmy)/krmax2)*(kzmax2/kmz);
  356. umag = sqrt(ux*ux + uy*uy + uz*uz);
  357. ux = ux/umag;
  358. uy = uy/umag;
  359. uz = uz/umag;
  360. gz = (kz(i) - kz(i-1))/gamrast;
  361. elseif (opt.sptype == 4)
  362. % Set gradient for pre-defined CAIPI blip
  363. gz = (kz(i) - kz(i-1))/gamrast;
  364. %// Start a blip whenever kx goes from pos to neg
  365. if ( (kx(i-1) > 0) && (kx(i) < 0) )
  366. if ( (Cbstart > 0) && (i >= Cbstart) && (i < Cbstart + Cnp ) )
  367. error('CAIPI blips overlapping')
  368. end
  369. CbstartPrev = Cbstart;
  370. Cbstart = i + Cadd;
  371. CcurrblipPrev = Ccurrblip;
  372. Ccurrblip = Ccurrblip + 1;
  373. if (Ccurrblip > opt.Crate)
  374. Ccurrblip = 1;
  375. end
  376. if opt.CstaggerBlip
  377. Cadd = Cadd + round(Cnp/opt.Crate);
  378. if Cadd > Cnp
  379. Cadd = 0;
  380. end
  381. end
  382. end
  383. %// Check if in blip
  384. if ( (Cbstart > 0) && (i >= Cbstart) && (i < Cbstart + Cnp ) )
  385. %// Scale blip and set polarity
  386. if (Ccurrblip == 0)
  387. %// First blip is scaled differently to make kz symmetric
  388. Cfact = -0.5;
  389. elseif (Ccurrblip < opt.Crate)
  390. Cfact = 1/(opt.Crate-1);
  391. else
  392. Cfact = -1;
  393. end
  394. gznext = Cfact*Cgb(i-Cbstart+1);
  395. else
  396. gznext = 0.0;
  397. end
  398. elseif (opt.sptype == 5)
  399. %/* Find unwrapped dphi. */
  400. dphi = atan2(ky(i),kx(i)) - atan2(ky(i-1),kx(i-1));
  401. if abs(dphi)>3*pi/4
  402. if (dphi<0)
  403. dphi = dphi + 2*pi;
  404. else
  405. dphi = dphi - 2*pi;
  406. end
  407. end
  408. phiUnwrapped(i) = phiUnwrapped(i-1) + dphi;
  409. % Our target is kz = CkbTot/2*cos(opt.CphiFact*phi).
  410. % Thus, dk/dphi = CkbTot/2*opt.CphiFact*sin(opt.CphiFact*phi)
  411. % From comments at top of file, dphi/dt = gamma*Gxy*sin(alpha)/kmr
  412. % Multiplying these equations: dk/dt = gamma*Gxy*sin(alpha)/kmr*CkbTot/2*opt.CphiFact*sin(opt.CphiFact*phi)
  413. % Recognizing that gamma*Gz = dk/dt, using ux,uy,uz for Gx,Gy,Gz, and rearranging yields:
  414. uz = sqrt(ux^2+uy^2)*opt.CphiFact*CkbTot/2/kmr*...
  415. sin(opt.CphiFact*phiUnwrapped(i))*sin(alpha);
  416. % If we just use the above uz, the kz span will not be centered.
  417. % So, we use the first half-period of cos(opt.CphiFact*phi) as a
  418. % "prephasor" to get to the edge of the desired span. We can do
  419. % this by scaling uz by 0.5 during the first pi radians of opt.CphiFact*phi
  420. isStartRecord(i) = isStart;
  421. if (isStart)
  422. uz = 0.5*uz;
  423. if (opt.CphiFact*phiUnwrapped(i) >= pi)
  424. isStart = 0;
  425. end
  426. end
  427. %// Normalize the unit vector
  428. umag = sqrt(ux*ux + uy*uy + uz*uz);
  429. ux = ux/umag;
  430. uy = uy/umag;
  431. uz = uz/umag;
  432. gz = (kz(i) - kz(i-1))/gamrast;
  433. end
  434. % /**************************/
  435. % /*** STEP 2: Find largest gradient magnitude with available slew */
  436. % /**************************/
  437. %/* Current gradient*/
  438. gx = (kx(i) - kx(i-1))/gamrast;
  439. gy = (ky(i) - ky(i-1))/gamrast;
  440. % /*
  441. % // solve for gm using the quadratic equation |gm u - g| = dgc
  442. % // which is
  443. % // (gm u - g)(gm u* - g*) = dgc^2
  444. % // which gives
  445. % // gm^2 (u u*) - gm (g u* + u g*) + g g* - dgc^2 = 0
  446. %
  447. % // Replacing u u* with 1 (i.e. u is a unit vector) and
  448. % // replacing (g u* + u g*) with 2 Real[g u*]
  449. % // this is
  450. % // gm^2 + gm (2 b) + c = 0
  451. % // giving
  452. % // gm = -b +/- Sqrt(b^2 - c)
  453. % // The variable "term" = (b^2 - c) will be positive if we can meet the desired new gradient
  454. % */
  455. if (opt.sptype == 4)
  456. %// Only allow slew not reserved for blips. Ignore gz
  457. %// keep slew reduced for whole waveform. Could be more efficient, but this is easy
  458. term = dgc*dgc*(1-opt.Cf^2) - (gx*gx + gy*gy) + (ux*gx + uy*gy)*(ux*gx + uy*gy);
  459. else
  460. term = dgc*dgc - (gx*gx + gy*gy + gz*gz) + (ux*gx + uy*gy + uz*gz)*(ux*gx + uy*gy + uz*gz);
  461. end
  462. if (term >= 0)
  463. % // Slew constraint is met! Now assign next gradient and then next k value
  464. % // NOTE gsign is +1 or -1
  465. % // if gsign is positive, we are using slew to speed up (increase gm) as much as possible
  466. % // if gsign is negative, we are using slew to slow down (decrease gm) as much as possible
  467. if (opt.sptype == 4)
  468. %// Account for fixed blip gradient contributing to net grad
  469. %// keep xy max grad reduced for whole waveform. Could be more efficient, but this is easy
  470. gm = min((ux*gx + uy*gy) + gsign(i)*sqrt(term),sqrt(opt.gmax^2-Cgmax^2));
  471. else
  472. gm = min((ux*gx + uy*gy + uz*gz) + gsign(i)*sqrt(term),opt.gmax);
  473. end
  474. gx = gm*ux;
  475. gy = gm*uy;
  476. kx(i+1) = kx(i) + gx*gamrast;
  477. ky(i+1) = ky(i) + gy*gamrast;
  478. %// If SPHERE
  479. if (opt.sptype == 2)
  480. kz(i+1) = sqrt(kzmax2*(1.-((kx(i+1)*kx(i+1)+ky(i+1)*ky(i+1))/krmax2))); %// stay on surface of ellipsoid
  481. elseif (opt.sptype == 4)
  482. kz(i+1) = kz(i) + gznext*gamrast;
  483. elseif (opt.sptype == 5)
  484. gz = gm*uz;
  485. kz(i+1) = kz(i) + gz*gamrast;
  486. end
  487. i = i+1;
  488. else
  489. % // We can't go further without violating the slew rate
  490. % // This means that we've sped up too fast to turn here at the desired curvature
  491. % // We are going to iteratively go back in time and slow down, rather than speed up, at max slew
  492. % // Here we'll keep looking back until gsign is positive, then add another negative gsign, just far enough to make the current corner
  493. while ((i>4) && (gsign(i-1) == -1))
  494. i = i-1;
  495. end
  496. gsign(i-1) = -1;
  497. i = i-2;
  498. if (opt.sptype == 4) && (i<=Cbstart)
  499. % We rewound past a blip start
  500. Cbstart = CbstartPrev;
  501. Ccurrblip = CcurrblipPrev;
  502. elseif (opt.sptype == 5)
  503. isStart = isStartRecord(i);
  504. end
  505. end
  506. kr = sqrt(kx(i)*kx(i) + ky(i)*ky(i));
  507. nLoops = nLoops + 1;
  508. end % End main kr while loop
  509. i_end = i;
  510. % //********************************************
  511. % // DONE LOOPING FOR SAMPLING PORTION
  512. % // recast k to g while subsampling by opt.subrast
  513. % //********************************************
  514. % TODO: vectorize this
  515. gxsum = 0;
  516. gysum = 0;
  517. gzsum = 0;
  518. for j = 1:floor(i_end/opt.subrast) %(j=1;j<=(i_end/opt.subrast);j++)
  519. i1 = j*opt.subrast + 1;
  520. i0 = (j-1)*opt.subrast + 1;
  521. gxarray(j) = ( (kx(i1)-kx(i0))/sub_gamrast );
  522. gyarray(j) = ( (ky(i1)-ky(i0))/sub_gamrast );
  523. gzarray(j) = ( (kz(i1)-kz(i0))/sub_gamrast );
  524. gxsum = gxsum + gxarray(j);
  525. gysum = gysum + gyarray(j);
  526. gzsum = gzsum + gzarray(j);
  527. end
  528. spgrad_na = j;
  529. %// recalculate these ending gradient points
  530. gm = sqrt(gxarray(spgrad_na-1)*gxarray(spgrad_na-1) +...
  531. gyarray(spgrad_na-1)*gyarray(spgrad_na-1) +...
  532. gzarray(spgrad_na-1)*gzarray(spgrad_na-1));
  533. ux = gxarray(spgrad_na-1)/gm;
  534. uy = gyarray(spgrad_na-1)/gm;
  535. uz = gzarray(spgrad_na-1)/gm;
  536. % //**************************************************
  537. % // NOW, if requested via gtype, go to g=0 and k=0
  538. % // I've tried other ways to be faster, can't find them
  539. % //**************************************************
  540. % // first we'll ramp gradients to zero
  541. % // note {ux,uy} is still pointing in the gradient direction
  542. % TODO: vectorize
  543. if (opt.gtype > 0)
  544. gz_sum_ramp = 0;
  545. while ((gm > 0) && (j < maxarray))
  546. gm = max(0,gm - sub_dgc);
  547. gxarray(j) = gm*ux;
  548. gyarray(j) = gm*uy;
  549. gzarray(j) = gm*uz;
  550. gxsum = gxsum + gxarray(j);
  551. gysum = gysum + gyarray(j);
  552. gzsum = gzsum + gzarray(j);
  553. gz_sum_ramp = gz_sum_ramp + gzarray(j);
  554. j = j+1;
  555. end
  556. end
  557. spgrad_nb = j;
  558. % // now point gradient towards the k-space origin
  559. % // {ux,uy} will be a unit vector in that direction
  560. if (opt.gtype > 1)
  561. % /* NOTE: spherical needs a prephaser not a rewinder
  562. % * so just rewind x and y in that case */
  563. gsum = sqrt(gxsum*gxsum + gysum*gysum + gzsum*gzsum);
  564. if (opt.sptype == 2 )
  565. gsum = sqrt(gxsum*gxsum + gysum*gysum + gz_sum_ramp*gz_sum_ramp);
  566. end
  567. gsum0 = gsum;
  568. ux = -gxsum/gsum;
  569. uy = -gysum/gsum;
  570. uz = -gzsum/gsum;
  571. if (opt.sptype == 2)
  572. uz = -gz_sum_ramp/gsum;
  573. end
  574. gsum_ramp = 0.5*gm*(gm/sub_dgc); %/* this is *roughly* how much the area changes if we ramp down the gradient NOW*/
  575. %/* this value is zero right now (gm = 0), but it will make sense below */
  576. %// increase gm while we can
  577. while ((gsum_ramp < gsum) && (j < maxarray))
  578. gm = min(opt.gmax,gm+sub_dgc);
  579. gxarray(j) = gm*ux;
  580. gyarray(j) = gm*uy;
  581. gzarray(j) = gm*uz;
  582. gsum = gsum - gm;
  583. j = j+1;
  584. gsum_ramp = 0.5*gm*(gm/sub_dgc); %/* see - now this makes sense; this tells us when to start ramping down */
  585. end
  586. % // We've overshot it by a tiny bit, but we'll fix that later
  587. % // Ramp down for now
  588. while ((gm > 0) && (j < maxarray))
  589. gm = max(0,gm-sub_dgc);
  590. gxarray(j) = gm*ux;
  591. gyarray(j) = gm*uy;
  592. gzarray(j) = gm*uz;
  593. gsum = gsum - gm;
  594. j = j+1;
  595. end
  596. spgrad_nc = j;
  597. %// OK - gm is zero, but gsum is probably not EXACTLY zero. Now scale the rewinder to make the sum exactly zero
  598. gradtweak = gsum0/(gsum0-gsum);
  599. for j = spgrad_nb+1:spgrad_nc %(j=(spgrad_nb); j<(spgrad_nc); j++)
  600. gxarray(j) = (gradtweak)*gxarray(j);
  601. gyarray(j) = (gradtweak)*gyarray(j);
  602. gzarray(j) = (gradtweak)*gzarray(j);
  603. end
  604. end
  605. gxarray = gxarray(1:j);
  606. gyarray = gyarray(1:j);
  607. gzarray = gzarray(1:j);
  608. grads = cat(2, gxarray, gyarray, gzarray);
  609. slews = diff(grads,1,1)/opt.m_dGRast;
  610. %% Plotting
  611. if plotting
  612. figure;
  613. n1 = 2;
  614. n2 = 4;
  615. subplot(n1,n2,1);
  616. plot(grads);
  617. title('gradients')
  618. subplot(n1,n2,2)
  619. kx = cumsum(gxarray)*opt.gamma*opt.m_dGRast;
  620. ky = cumsum(gyarray)*opt.gamma*opt.m_dGRast;
  621. kz = cumsum(gzarray*opt.gamma*opt.m_dGRast);
  622. plot(kx, ky)
  623. if opt.sptype == 5
  624. zc = [abs(diff(sign(kz)))>0;false];
  625. kx_a = kx(zc);
  626. ky_a = ky(zc);
  627. hold('all');
  628. plot(kx_a,ky_a,'o')
  629. end
  630. title('k-space traj xy with kz zero crossings')
  631. subplot(n1,n2,3)
  632. plot3(cumsum(gxarray*opt.gamma*opt.m_dGRast), cumsum(gyarray*opt.gamma*opt.m_dGRast), cumsum(gzarray*opt.gamma*opt.m_dGRast))
  633. title('k-space traj xyz')
  634. subplot(n1,n2,4)
  635. % plot(cumsum(gxarray*opt.gamma*opt.m_dGRast))
  636. % hold('all')
  637. % plot(cumsum(gyarray*opt.gamma*opt.m_dGRast))
  638. plot(cumsum(gzarray*opt.gamma*opt.m_dGRast))
  639. title('k-space traj z')
  640. subplot(n1,n2,n2+1);
  641. plot(sqrt(sum(slews.^2,2)));
  642. title('net slew')
  643. subplot(n1,n2,n2+2);
  644. plot(sqrt(sum(grads.^2,2)));
  645. title('net grad')
  646. % Freq analysis notes:
  647. % Gmax and slew has the largest effect.
  648. % Undersampling and variable density has little effect
  649. % CAIPI grads are negligible compared to others.
  650. subplot(n1,n2,n2+3)
  651. fmaxp = 3000;
  652. Nf = 10*length(gxarray);
  653. fmax = 1000*0.5/opt.m_dGRast; % Hz
  654. psd = fft([gxarray,gyarray,gzarray],Nf,1);
  655. psd = psd(1:ceil(Nf/2),:).*conj(psd(1:ceil(Nf/2),:));
  656. f = linspace(0,fmax,size(psd,1))';
  657. plot(f,psd)
  658. xlim([0,fmaxp])
  659. cent = sum(f.*psd,1)./sum(psd,1);
  660. [~, pk] = max(psd(:,1)); pk = f(pk);
  661. bw = find(psd(:,1) > max(psd(:,1))/2);
  662. bw = f(bw(end)) - f(bw(1));
  663. text(0.5*fmaxp, 0.9*max(psd(:,1)),...
  664. sprintf('cent = %d Hz\npeak = %d Hz\nbw = %d Hz\nrate %d\nvd %.2f',round(cent(1)),round(pk(1)),round(bw),opt.us_r,opt.us_1))
  665. end

spiralGen.m at commit 17825cc, under MIT · at the source

Overview

Authors: Mayuri Sothynathan1,2, Paul I. Dubovan3,4, Corey A. Baron1,5
  1. Centre for Functional and Metabolic Mapping (CFMM), Robarts Research Institute, Western University London Ontario Canada
  2. Department of Biomedical Engineering Faculty of Engineering, Western University London Ontario Canada
  3. Athinoula A. Martinos Center for Biomedical Imaging, Massachusetts General Hospital Charlestown Massachusetts USA
  4. Department of Radiology Harvard Medical School Boston Massachusetts USA
  5. Department of Medical Biophysics Schulich School of Medicine and Dentistry, Western University London Ontario Canada
Journal: Magnetic resonance in medicine, volume 96, issue 4, pages 1682-1695
Dates: received 4 February 2026; accepted 1 June 2026; published online 9 June 2026; in print October 2026
Type: Research article · Language: English
License: CC BY-NC-ND
Identifiers: DOI 10.1002/mrm.70469 · PMID 42265901 · PMCID PMC13421052 · OpenAlex W7164123668
Open access: hybrid, a free copy (OpenAlex)
Status: code verified
Categories: structural MRI / diffusion (modality), human (organism), computational (subfield)
Methods: Machine learning, fMRI & imaging
Keywords: diffusion MRI, g‐factor, non‐Cartesian, simultaneous multislice, spiral
MeSH: Brain*, Diffusion Magnetic Resonance Imaging*, Image Processing, Computer-Assisted*, Imaging, Three-Dimensional*, Algorithms, Anisotropy, Computer Simulation, Humans, Image Enhancement, Image Interpretation, Computer-Assisted, Phantoms, Imaging, Reproducibility of Results, Signal-To-Noise Ratio (* major topic)
Journal subjects: Imaging Methodology
Topic: Advanced Neuroimaging Techniques and Applications (Radiology, Nuclear Medicine and Imaging, Medicine), according to OpenAlex
Funding: Ontario Graduate Scholarship; NSERC Discovery Grants (RGPIN‐2025‐05597)
Citations: not cited yet (Europe PMC); 42 references in the paper

Abstract

The abstract is not reproduced here: the paper's license (CC BY-NC-ND) does not allow it. Read it in the paper, at the publisher or on Europe PMC.

Repositories

Its files are read in the Code ↔ Paper reader above, with 10 matches between paragraphs and lines of code.

cfmm/matlab/matmri

License: MIT
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Commit: 17825cc25a717696e95e921018e7487d1c156fbf, 10 August 2026
Languages: MATLAB (65)
Size: 76 files, 65 scripts
Software Heritage: not archived
Found in: “Data Availability Statement”
Holds: README, license file, tests
Not found: CITATION.cff, environment file, continuous integration, documentation
Availability: 1 check, the latest on 27 September 2026: the link answers
  • 27 September 2026: the link answers
67 files

Zenodo 4495476

License: CC-BY-4.0
State: the link answers, verified on 27 September 2026
Evidence: files inventoried
Size: 1 file
Software Heritage: not checked
Found in: the references
Not found: README, license file, CITATION.cff, environment file, tests, continuous integration, documentation
Availability: 1 check, the latest on 27 September 2026: the link answers (HTTP 200)
  • 27 September 2026: the link answers (HTTP 200)
At the source:

The paper's code and data availability statement is in the Data section.

Tracing map

Proposed by the machine: these links were found in the paper and verified at the source, without human review. The map will receive a Zenodo DOI once one of the paper's authors has validated it with their ORCID.

What the map holds:

  • 2 repositories of the authors' code, each at its verified commit, with its license and how the link was found in the paper;
  • 65 scripts, each with its path and the digest of its content;
  • 10 matches between paragraphs of the paper and lines of the code (method lexical-v1);
  • neither the text of the paper nor the code itself.

Its JSON (tracing-map.json) is deposited on Zenodo with its DOI once the map is validated.

Data

No dataset and no data link were found in the paper.

Code and data availability statement

The paper has a code and data availability statement. Its license (CC BY-NC-ND) does not allow reproducing it here; in short, from what the harvester recognized in it:

Read it in the paper: doi.org/10.1002/mrm.70469.

Versions

The history of this record: each version stored by the harvester or made by a correction of its authors or of the maintainers of its code, and what changed in its facts. The texts of the paper (its abstract, its availability statements) are not part of it; versions that changed only those are not listed.

Version 2, 28 September 2026

  • Publisher: n/a → Wiley

Version 1, 27 September 2026: the first record

Recorded: type, language, journal, volume, issue, pages, dates, 3 authors, 5 keywords, 13 MeSH terms, 2 funders, 38 references.

Cite

This paper

Sothynathan, M., Dubovan, P. I., & Baron, C. A. (2026). Laterally Oscillating Trajectory for Undersampling Slices: LOTUS. Magnetic resonance in medicine, 96(4), 1682-1695. https://doi.org/10.1002/mrm.70469

BibTeX

@article{sothynathan2026laterally,
author = {Sothynathan, Mayuri and Dubovan, Paul I. and Baron, Corey A.},
title = {{Laterally Oscillating Trajectory for Undersampling Slices: LOTUS}},
journal = {Magnetic resonance in medicine},
year = {2026},
month = jun,
volume = {96},
number = {4},
pages = {1682--1695},
publisher = {Wiley},
issn = {0740-3194},
doi = {10.1002/mrm.70469},
url = {https://doi.org/10.1002/mrm.70469},
pmid = {42265901},
pmcid = {PMC13421052}
}

RIS

TY - JOUR
AU - Sothynathan, Mayuri
AU - Dubovan, Paul I.
AU - Baron, Corey A.
TI - Laterally Oscillating Trajectory for Undersampling Slices: LOTUS
T2 - Magnetic resonance in medicine
J2 - Magn Reson Med
PY - 2026
DA - 2026/06/09
VL - 96
IS - 4
SP - 1682
EP - 1695
SN - 0740-3194
PB - Wiley
DO - 10.1002/mrm.70469
UR - https://doi.org/10.1002/mrm.70469
LA - en
ER -

CSL-JSON

{
"id": "10.1002/mrm.70469",
"type": "article-journal",
"title": "Laterally Oscillating Trajectory for Undersampling Slices: LOTUS",
"container-title": "Magnetic resonance in medicine",
"author": [
{
"family": "Sothynathan",
"given": "Mayuri"
},
{
"family": "Dubovan",
"given": "Paul I."
},
{
"family": "Baron",
"given": "Corey A."
}
],
"container-title-short": "Magn Reson Med",
"volume": "96",
"issue": "4",
"page": "1682-1695",
"DOI": "10.1002/mrm.70469",
"PMID": "42265901",
"PMCID": "PMC13421052",
"ISSN": "0740-3194",
"publisher": "Wiley",
"URL": "https://doi.org/10.1002/mrm.70469",
"language": "en",
"issued": {
"date-parts": [
[
2026,
6,
9
]
]
}
}

The tracing map gets a citation of its own once an author has validated it and it has a DOI.

Similar papers

The papers with a page that share the most with this one: the tools found in their code, their categories, datasets, cited references and authors, the rarest counting most.

[1] doi:10.1002/mrm.70439 [code]
Group-Patch Joint Compression: Compressing Dynamic B&lt;sub&gt;0&lt;/sub&gt; and Static RF Spatial Modulations Across k-Space Subregion Groups for Highly Accelerated MRI.
Journal: Magnetic resonance in medicine
In common: Image Processing Toolbox, structural MRI / diffusion, 8 references
[2] doi:10.1162/imag.a.1262 [code]
Frame-wise multi-echo distortion correction for superior functional MRI.
Journal: Imaging neuroscience (Cambridge, Mass.)
In common: Optimization Toolbox, Parallel Computing Toolbox, Image Processing Toolbox, 1 other tool, 1 reference
[3] doi:10.1038/s41467-026-74215-5 [code]
Multi-metric evaluations of acute psychedelic effects on fMRI brain entropy.
Journal: Nature communications
In common: Optimization Toolbox, Parallel Computing Toolbox, Image Processing Toolbox, 1 other tool, computational
[4] doi:10.1002/mrm.70433 [code]
Single-Shot 2D Radial Echo Planar Imaging for Functional MRI.
Journal: Magnetic resonance in medicine
In common: 4 references
[5] doi:10.1002/hbm.70553 [code]
Axon Diameter Mapping in the Living Human Brain with Ultra-High-Gradient Diffusion MRI at 500 mT/m Gradient Strength.
Journal: Human brain mapping
In common: Optimization Toolbox, Parallel Computing Toolbox, Image Processing Toolbox, 1 other tool, structural MRI / diffusion
[6] doi:10.1038/s42003-026-10276-y [code]
The cellular correlates and adolescent reorganisation of cortical myelination networks in the common marmoset.
Journal: Communications biology
In common: Optimization Toolbox, Parallel Computing Toolbox, Image Processing Toolbox, 1 other tool, structural MRI / diffusion
[7] doi:10.1002/mrm.70366 [code]
Mesoscale Whole-Brain T&lt;sub&gt;2&lt;/sub&gt;*-Weighted and Associated Quantitative MRI in Humans at 10.5 T.
Journal: Magnetic resonance in medicine
In common: Image Processing Toolbox, Statistics and Machine Learning Toolbox, structural MRI / diffusion, 2 references
[8] doi:10.1158/2767-9764.crc-25-0579 [code]
Glioblastoma Subtypes Exhibit Distinct Migration Mechanics and Immune Responses.
Journal: Cancer research communications
In common: Optimization Toolbox, Parallel Computing Toolbox, Image Processing Toolbox, 1 other tool
[9] doi:10.1371/journal.pcbi.1014563 [code]
Single pulse electrical stimulation in white matter modulates iEEG visual responses in human early visual cortex.
Journal: PLoS computational biology
In common: Optimization Toolbox, Parallel Computing Toolbox, Image Processing Toolbox, 1 other tool
[10] doi:10.1007/s00429-026-03152-2 [code]
Limb-selective regions in the lateral temporal lobe shrink from childhood to adulthood.
Journal: Brain structure & function
In common: Optimization Toolbox, Parallel Computing Toolbox, Image Processing Toolbox, 1 other tool

Contribute

The authors of this paper can claim it, correct its record and validate its tracing map, and the maintainers of its code (its owner, or a public member of its organization) correct what it says of their repository; anyone signed in can ask for its removal. Every request goes to OSCR's own machine, which answers it; your account page follows them.

Sign in with ORCID to claim this paper as one of its authors, correct its record or validate its tracing map: when the paper's metadata lists your ORCID iD, you are recognized at once. Maintainers of its code: sign in with GitHub, then claim the repository on your account page.

Request its removal

To ask OSCR to remove this record, the copies of its authors' scripts or its tracing map, use the removal request page: signed in, you say who you are, what to remove and why, then review and confirm the request. Published rules decide every request (how).

Discussion, reproductions, activity

Discussion: questions and error reports about this paper and its code, from signed-in readers and its authors. It opens with sign-in.

Reproductions: reports from readers who ran the authors' code: what they reproduced, with which environment, commit and data. It opens with sign-in.

Activity: what happens around this paper: new versions of its record, its map's validation, discussions and reproductions. It opens with sign-in.