├── 01_from_FID_to_PRESS ├── README.md ├── data │ ├── 01_FID.dat │ ├── 01_FID.seq │ ├── 02_FID_MFA.dat │ ├── 02_FID_MFA.seq │ ├── 03_SE_noSpoil.dat │ ├── 03_SE_noSpoil.seq │ ├── 04_SE_BackgroundGrad.dat │ ├── 04_SE_BackgroundGrad.seq │ ├── 05_SE_withSpoilers.dat │ ├── 05_SE_withSpoilers.seq │ ├── 06_PRESS_center.dat │ ├── 06_PRESS_center.seq │ ├── 07_PRESS_offcenter.dat │ └── 07_PRESS_offcenter.seq ├── doc │ ├── 01_from_FID_to_PRESS.pdf │ ├── 01_from_FID_to_PRESS.pptx │ └── Fig_1.png ├── recon │ └── r01_FID.m └── seq │ ├── s01_FID.m │ ├── s02_FID_multipleFAs.m │ ├── s03_SE.m │ ├── s04_SE_withBackGrad.m │ ├── s05_SE_withSpolers.m │ └── s06_PRESS.m ├── 02_basic_gradient_echo ├── README.md ├── data │ ├── 01_GRE_example.dat │ ├── 01_GRE_example.seq │ ├── 02_GRE_noSpoil_noRephase.dat │ ├── 02_GRE_noSpoil_noRephase.seq │ ├── 03_GRE_withSpoil_noRephase.dat │ ├── 03_GRE_withSpoil_noRephase.seq │ ├── 04_GRE_xSpoil_yRephase.dat │ ├── 04_GRE_xSpoil_yRephase.seq │ ├── 05_GRE_xSpoil_yRephase_RFspoil.dat │ ├── 05_GRE_xSpoil_yRephase_RFspoil.seq │ ├── 06_GRE_xSpoil_yRephase_RFspoilADC.dat │ ├── 06_GRE_xSpoil_yRephase_RFspoilADC.seq │ ├── 07_GRE_xSpoil_yRephase_RFspoilADC_dummy.dat │ └── 07_GRE_xSpoil_yRephase_RFspoilADC_dummy.seq ├── doc │ ├── 02_basic_gradient_echo.pdf │ ├── 02_basic_gradient_echo.pptx │ ├── Fig_1.png │ ├── Fig_2.png │ └── Fig_3.png ├── recon │ └── r01_2DFFT.m └── seq │ ├── s01_GRE_tutorial_step0.m │ ├── s02_GRE_tutorial_step1.m │ ├── s03_GRE_tutorial_step2.m │ ├── s04_GRE_tutorial_step3.m │ ├── s05_GRE_tutorial_step4.m │ └── s06_GRE_tutorial_step5.m ├── 11_from_GRE_to_EPI ├── README.md ├── data │ ├── 01_GRE.dat │ ├── 01_GRE.seq │ ├── 02_GRE_3echoes.dat │ ├── 02_GRE_3echoes.seq │ ├── 03_GRE_3echoes_bipolar.dat │ ├── 03_GRE_3echoes_bipolar.seq │ ├── 04a_GRE_3seg.dat │ ├── 04a_GRE_3seg.seq │ ├── 04b_GRE_5seg_fast.dat │ ├── 04b_GRE_5seg_fast.seq │ ├── 04c_improvised_EPI.dat │ ├── 04c_improvised_EPI.seq │ ├── 04c_improvised_EPI_noPE.dat │ ├── 04c_improvised_EPI_noPE.seq │ ├── 04c_improvised_EPI_other_phantom_other scanner.dat │ ├── 04c_improvised_EPI_other_phantom_other scanner.seq │ ├── 04c_improvised_EPI_other_phantom_other scanner_noPE.dat │ ├── 04c_improvised_EPI_other_phantom_other scanner_noPE.seq │ ├── 05_EPI.dat │ ├── 05_EPI.seq │ ├── 05_EPI_noPE.dat │ ├── 05_EPI_noPE.seq │ ├── 06_EPI_singleTraj.dat │ └── 06_EPI_singleTraj.seq ├── doc │ ├── 11_from_GRE_to_EPI.pdf │ ├── 11_from_GRE_to_EPI.pptx │ ├── Handout.pdf │ └── mri_together_esmrmb_banner.png ├── recon │ ├── r01_2DFFT.m │ ├── r02_2DEPI.m │ └── r03_2DGD.m └── seq │ ├── s01_GradientEcho.m │ ├── s02_MultiGradientEcho.m │ ├── s03_BipolarMultiGradientEcho.m │ ├── s04a_SegmentedGradientEcho.m │ ├── s04b_SegmentedGradientEcho.m │ ├── s04c_SegmentedGradientEcho.m │ ├── s05_EchoPlanarImaging.m │ └── s06_EPI_SingleTraj.m ├── 12_Radial_and_nonCartesian ├── README.md ├── doc │ ├── 12_Radial_and_nonCartesian.pdf │ └── 12_Radial_and_nonCartesian.pptx ├── recon │ ├── r01_2DFFT.m │ ├── r02_2D_iRadon.m │ ├── r03_2D_Gridding.m │ └── r04_radial_delay_calculation.m └── seq │ ├── s01_CartesianSE.m │ ├── s02_RadialSE.m │ ├── s03_CartesianGradientEcho.m │ ├── s04_RadialGradientEcho.m │ ├── s05_FastRadialGradientEcho.m │ └── s06_Spiral.m ├── LICENSE ├── README.md └── doc ├── meld_comparison.png └── pulseq_Concepts_Nov2022.pdf /01_from_FID_to_PRESS/README.md: -------------------------------------------------------------------------------- 1 | # Pulseq tutorial "From FID to PRESS" 2 | 3 | Welcome to the "From FID to PRESS" tutorial repository! This was initially developed for the Pulseq software demonstration and hands-on session at the **Italian Chapter of ISMRM 2022** in Pisa. 4 | 5 | The tutorial starts with the very basic first steps from an FID and non-selective spin-echo sequence and moves on toward the spectroscopic PRESS sequence. The raw data were acquired in a fat-water phantom, which explains the appearance of the off-center press spectrum, which was deliberately placed to capture both fat and water. 6 | 7 | The slide deck entitled [doc/01_from_FID_to_PRESS.pdf](doc/01_from_FID_to_PRESS.pdf) explains the basic properties of the objects from which the pulse sequence is built, 8 | such as RF and gradient pulses, and shows the sequence diagrams of all 9 | steps and visualises the changes at each step. We recommend using Meld 10 | software to highlight the source code changes between steps 01 and 05. 11 | 12 | ***s01\_FID*** 13 | 14 | ***s01*** describes the simplest FID sequence with a single flip angle 15 | (FA). It is constructed with two blocks -- one with an RF event and the 16 | other with an ADC event. 17 | 18 | ***s02\_FID\_multipleFAs*** 19 | 20 | ***s02*** describes an FID sequence with 9 FAs, established based on the 21 | FID sequence described in ***s01***. The TR containing two blocks is 22 | repeated 9 times. The flip angle of the RF event is changed during each 23 | TR repetition. 24 | 25 | ***s03\_SE*** 26 | 27 | ***s03*** describes a spin-echo (SE) acquisition with 4 blocks. It 28 | contains an RF pulse for non-selective excitation, an RF pulse for 29 | refocusing and an ADC. 30 | 31 | ***s04\_SE\_withBackGrad*** 32 | 33 | A background gradient is added to ***s03*** during the entire sequence 34 | to construct ***s04***. 35 | 36 | ***s05\_SE\_withSpoilers*** 37 | 38 | ***s05*** is constructed based on ***s03***: two spoiler gradients are 39 | inserted during the delay of the refocusing RF pulse and the delay of 40 | the ADC in ***s03***, acting as a pair of crushers. 41 | 42 | ***s06\_PRESS*** 43 | 44 | ***s06*** establishes a Point RESolved Spectroscopy (PRESS) sequence. 45 | The core sequence consists of three slice-selective RF pulses 46 | (90°-180°-180°) applied concurrently with three orthogonal gradients (x, 47 | y, and z) (for more details, please go to 48 | [https://mriquestions.com/press.html\#](https://mriquestions.com/press.html)). 49 | 50 | The mr.makeSincPulse function makes a sinc pulse and gradients for slice 51 | selection. 52 | 53 | The mr.makeExtendedTrapezoidArea function makes a shortest possible 54 | extended trapezoid with a given area, which starts and ends as non-zero 55 | gradient values. 56 | 57 | The mr.makeExtendedTrapezoid function creates an "arbitrary" gradient by 58 | combing a set of amplitude points and the corresponding set of time 59 | points, as shown in Figure 1. 60 | 61 | 62 | 63 | **Figure** **1** slice-selective **sinc pulse** and **gradient** with a 64 | pair of crusher gradients for selecting desired coherence pathways. 65 | **a** (**c**) and **b** (**d**) are the first and last points of the 66 | **pre-** (**post-**) crusher gradient, which are both realised as 67 | Extended Trapezoids. 68 | 69 | The sequence diagram of ***s06*** includes 7 blocks per TR. Note that the TE is 70 | defined as 2 \* delay between the centres of the two refocusing RF 71 | pulses. 72 | 73 | ## Quick links 74 | 75 | Pulseq Matlab repository: 76 | https://github.com/pulseq/pulseq 77 | 78 | ## Quick instructions 79 | 80 | The source code of the demo sequences and reconstruction scripts is the core of this repository. Please download the files to your computer and make them available to Matlab (e.g. by saving them in a subdirectory inside your Pulseq-Matlab installation and adding them to the Matlab's path). There are two sub-directories: 81 | 82 | * seq : contains example pulse sequences specifically prepared for this demo 83 | * recon : contains the reconstruction scripts tested with the above sequences 84 | * data : contains raw MR data in the Siemens TWIX format and the corresponding pulse sequences in the Pulseq format 85 | 86 | ## How to follow 87 | 88 | We strongly recommend using a text compare tool like *meld* (see this [Wikipedia page](https://en.wikipedia.org/wiki/Meld_(software)) and compare sequences from subsequent steps to visualise the respective steps. 89 | 90 | ## Further links 91 | 92 | Check out the main *Pulseq* repository at https://github.com/pulseq/pulseq and familiarising yourself with the code, example sequences and reconstruction scripts (see 93 | [pulseq/matlab/demoSeq](https://github.com/pulseq/pulseq/tree/master/matlab/demoSeq) and [pulseq/matlab/demoRecon](https://github.com/pulseq/pulseq/tree/master/matlab/demoRecon)). If you already use Pulseq, consider updating to the current version. 94 | 95 | 96 | 97 | -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/data/01_FID.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/01_from_FID_to_PRESS/data/01_FID.dat -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/data/01_FID.seq: -------------------------------------------------------------------------------- 1 | # Pulseq sequence file 2 | # Created by MATLAB mr toolbox 3 | 4 | [VERSION] 5 | major 1 6 | minor 4 7 | revision 0 8 | 9 | [DEFINITIONS] 10 | AdcRasterTime 1e-07 11 | BlockDurationRaster 1e-05 12 | GradientRasterTime 1e-05 13 | RadiofrequencyRasterTime 1e-06 14 | TotalDuration 5 15 | 16 | # Format of blocks: 17 | # NUM DUR RF GX GY GZ ADC EXT 18 | [BLOCKS] 19 | 1 62 1 0 0 0 0 0 20 | 2 499938 0 0 0 0 1 0 21 | 22 | # Format of RF events: 23 | # id amplitude mag_id phase_id time_shape_id delay freq phase 24 | # .. Hz .... .... .... us Hz rad 25 | [RF] 26 | 1 500 1 2 3 100 0 0 27 | 28 | # Format of ADC events: 29 | # id num dwell delay freq phase 30 | # .. .. ns us Hz rad 31 | [ADC] 32 | 1 8192 31250 29730 0 0 33 | 34 | # Sequence Shapes 35 | [SHAPES] 36 | 37 | shape_id 1 38 | num_samples 2 39 | 1 40 | 1 41 | 42 | shape_id 2 43 | num_samples 2 44 | 0 45 | 0 46 | 47 | shape_id 3 48 | num_samples 2 49 | 0 50 | 500 51 | 52 | 53 | [SIGNATURE] 54 | # This is the hash of the Pulseq file, calculated right before the [SIGNATURE] section was added 55 | # It can be reproduced/verified with md5sum if the file trimmed to the position right above [SIGNATURE] 56 | # The new line character preceding [SIGNATURE] BELONGS to the signature (and needs to be sripped away for recalculating/verification) 57 | Type md5 58 | Hash 5caad2dc77321c47fe1cf56d9ef581b9 59 | -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/data/02_FID_MFA.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/01_from_FID_to_PRESS/data/02_FID_MFA.dat -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/data/02_FID_MFA.seq: -------------------------------------------------------------------------------- 1 | # Pulseq sequence file 2 | # Created by MATLAB mr toolbox 3 | 4 | [VERSION] 5 | major 1 6 | minor 4 7 | revision 0 8 | 9 | [DEFINITIONS] 10 | AdcRasterTime 1e-07 11 | BlockDurationRaster 1e-05 12 | GradientRasterTime 1e-05 13 | RadiofrequencyRasterTime 1e-06 14 | 15 | # Format of blocks: 16 | # NUM DUR RF GX GY GZ ADC EXT 17 | [BLOCKS] 18 | 1 112 1 0 0 0 0 0 19 | 2 499888 0 0 0 0 1 0 20 | 3 112 2 0 0 0 0 0 21 | 4 499888 0 0 0 0 1 0 22 | 5 112 3 0 0 0 0 0 23 | 6 499888 0 0 0 0 1 0 24 | 7 112 4 0 0 0 0 0 25 | 8 499888 0 0 0 0 1 0 26 | 9 112 5 0 0 0 0 0 27 | 10 499888 0 0 0 0 1 0 28 | 11 112 6 0 0 0 0 0 29 | 12 499888 0 0 0 0 1 0 30 | 13 112 7 0 0 0 0 0 31 | 14 499888 0 0 0 0 1 0 32 | 15 112 8 0 0 0 0 0 33 | 16 499888 0 0 0 0 1 0 34 | 17 112 9 0 0 0 0 0 35 | 18 499888 0 0 0 0 1 0 36 | 37 | # Format of RF events: 38 | # id amplitude mag_id phase_id time_shape_id delay freq phase 39 | # .. Hz .... .... .... us Hz rad 40 | [RF] 41 | 1 55.5556 1 2 3 100 0 0 42 | 2 111.111 1 2 3 100 0 0 43 | 3 166.667 1 2 3 100 0 0 44 | 4 222.222 1 2 3 100 0 0 45 | 5 277.778 1 2 3 100 0 0 46 | 6 333.333 1 2 3 100 0 0 47 | 7 388.889 1 2 3 100 0 0 48 | 8 444.444 1 2 3 100 0 0 49 | 9 500 1 2 3 100 0 0 50 | 51 | # Format of ADC events: 52 | # id num dwell delay freq phase 53 | # .. .. ns us Hz rad 54 | [ADC] 55 | 1 8192 31250 29480 0 0 56 | 57 | # Sequence Shapes 58 | [SHAPES] 59 | 60 | shape_id 1 61 | num_samples 2 62 | 1 63 | 1 64 | 65 | shape_id 2 66 | num_samples 2 67 | 0 68 | 0 69 | 70 | shape_id 3 71 | num_samples 2 72 | 0 73 | 1000 74 | 75 | 76 | [SIGNATURE] 77 | # This is the hash of the Pulseq file, calculated right before the [SIGNATURE] section was added 78 | # It can be reproduced/verified with md5sum if the file trimmed to the position right above [SIGNATURE] 79 | # The new line character preceding [SIGNATURE] BELONGS to the signature (and needs to be sripped away for recalculating/verification) 80 | Type md5 81 | Hash 7e2b89c1ae6eca64fc825f6ccdc035ef 82 | -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/data/03_SE_noSpoil.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/01_from_FID_to_PRESS/data/03_SE_noSpoil.dat -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/data/03_SE_noSpoil.seq: -------------------------------------------------------------------------------- 1 | # Pulseq sequence file 2 | # Created by MATLAB mr toolbox 3 | 4 | [VERSION] 5 | major 1 6 | minor 4 7 | revision 0 8 | 9 | [DEFINITIONS] 10 | AdcRasterTime 1e-07 11 | BlockDurationRaster 1e-05 12 | GradientRasterTime 1e-05 13 | RadiofrequencyRasterTime 1e-06 14 | TotalDuration 0.25 15 | 16 | # Format of blocks: 17 | # NUM DUR RF GX GY GZ ADC EXT 18 | [BLOCKS] 19 | 1 112 1 0 0 0 0 0 20 | 2 5388 0 0 0 0 0 0 21 | 3 112 2 0 0 0 0 0 22 | 4 19388 0 0 0 0 1 0 23 | 24 | # Format of RF events: 25 | # id amplitude mag_id phase_id time_shape_id delay freq phase 26 | # .. Hz .... .... .... us Hz rad 27 | [RF] 28 | 1 250 1 2 3 100 0 0 29 | 2 500 1 2 3 100 0 0 30 | 31 | # Format of ADC events: 32 | # id num dwell delay freq phase 33 | # .. .. ns us Hz rad 34 | [ADC] 35 | 1 8192 12500 3280 0 0 36 | 37 | # Sequence Shapes 38 | [SHAPES] 39 | 40 | shape_id 1 41 | num_samples 2 42 | 1 43 | 1 44 | 45 | shape_id 2 46 | num_samples 2 47 | 0 48 | 0 49 | 50 | shape_id 3 51 | num_samples 2 52 | 0 53 | 1000 54 | 55 | 56 | [SIGNATURE] 57 | # This is the hash of the Pulseq file, calculated right before the [SIGNATURE] section was added 58 | # It can be reproduced/verified with md5sum if the file trimmed to the position right above [SIGNATURE] 59 | # The new line character preceding [SIGNATURE] BELONGS to the signature (and needs to be sripped away for recalculating/verification) 60 | Type md5 61 | Hash 67f9e34da9b97a9f23af82d17863462c 62 | -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/data/04_SE_BackgroundGrad.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/01_from_FID_to_PRESS/data/04_SE_BackgroundGrad.dat -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/data/04_SE_BackgroundGrad.seq: -------------------------------------------------------------------------------- 1 | # Pulseq sequence file 2 | # Created by MATLAB mr toolbox 3 | 4 | [VERSION] 5 | major 1 6 | minor 4 7 | revision 0 8 | 9 | [DEFINITIONS] 10 | AdcRasterTime 1e-07 11 | BlockDurationRaster 1e-05 12 | GradientRasterTime 1e-05 13 | RadiofrequencyRasterTime 1e-06 14 | TotalDuration 0.25006 15 | 16 | # Format of blocks: 17 | # NUM DUR RF GX GY GZ ADC EXT 18 | [BLOCKS] 19 | 1 3 0 1 0 0 0 0 20 | 2 112 1 2 0 0 0 0 21 | 3 5388 0 3 0 0 0 0 22 | 4 112 2 2 0 0 0 0 23 | 5 19388 0 4 0 0 1 0 24 | 6 3 0 5 0 0 0 0 25 | 26 | # Format of RF events: 27 | # id amplitude mag_id phase_id time_shape_id delay freq phase 28 | # .. Hz .... .... .... us Hz rad 29 | [RF] 30 | 1 250 3 4 5 100 0 0 31 | 2 500 3 4 5 100 0 0 32 | 33 | # Format of arbitrary gradients: 34 | # time_shape_id of 0 means default timing (stepping with grad_raster starting at 1/2 of grad_raster) 35 | # id amplitude amp_shape_id time_shape_id delay 36 | # .. Hz/m .. .. us 37 | [GRADIENTS] 38 | 1 5000 1 2 0 39 | 2 5000 3 6 0 40 | 3 5000 3 7 0 41 | 4 5000 3 8 0 42 | 5 5000 9 2 0 43 | 44 | # Format of ADC events: 45 | # id num dwell delay freq phase 46 | # .. .. ns us Hz rad 47 | [ADC] 48 | 1 8192 12500 3280 0 0 49 | 50 | # Sequence Shapes 51 | [SHAPES] 52 | 53 | shape_id 1 54 | num_samples 2 55 | 0 56 | 1 57 | 58 | shape_id 2 59 | num_samples 2 60 | 0 61 | 3 62 | 63 | shape_id 3 64 | num_samples 2 65 | 1 66 | 1 67 | 68 | shape_id 4 69 | num_samples 2 70 | 0 71 | 0 72 | 73 | shape_id 5 74 | num_samples 2 75 | 0 76 | 1000 77 | 78 | shape_id 6 79 | num_samples 2 80 | 0 81 | 112 82 | 83 | shape_id 7 84 | num_samples 2 85 | 0 86 | 5388 87 | 88 | shape_id 8 89 | num_samples 2 90 | 0 91 | 19388 92 | 93 | shape_id 9 94 | num_samples 2 95 | 1 96 | 0 97 | 98 | 99 | [SIGNATURE] 100 | # This is the hash of the Pulseq file, calculated right before the [SIGNATURE] section was added 101 | # It can be reproduced/verified with md5sum if the file trimmed to the position right above [SIGNATURE] 102 | # The new line character preceding [SIGNATURE] BELONGS to the signature (and needs to be sripped away for recalculating/verification) 103 | Type md5 104 | Hash aac1d624e8bf86af3edd1f06085c1ae2 105 | -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/data/05_SE_withSpoilers.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/01_from_FID_to_PRESS/data/05_SE_withSpoilers.dat -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/data/05_SE_withSpoilers.seq: -------------------------------------------------------------------------------- 1 | # Pulseq sequence file 2 | # Created by MATLAB mr toolbox 3 | 4 | [VERSION] 5 | major 1 6 | minor 4 7 | revision 0 8 | 9 | [DEFINITIONS] 10 | AdcRasterTime 1e-07 11 | BlockDurationRaster 1e-05 12 | GradientRasterTime 1e-05 13 | RadiofrequencyRasterTime 1e-06 14 | TotalDuration 0.25 15 | 16 | # Format of blocks: 17 | # NUM DUR RF GX GY GZ ADC EXT 18 | [BLOCKS] 19 | 1 112 1 0 0 0 0 0 20 | 2 5315 0 0 0 0 0 0 21 | 3 185 2 0 0 1 0 0 22 | 4 19388 0 0 0 1 1 0 23 | 24 | # Format of RF events: 25 | # id amplitude mag_id phase_id time_shape_id delay freq phase 26 | # .. Hz .... .... .... us Hz rad 27 | [RF] 28 | 1 250 1 2 3 100 0 0 29 | 2 500 1 2 3 830 0 0 30 | 31 | # Format of trapezoid gradients: 32 | # id amplitude rise flat fall delay 33 | # .. Hz/m us us us us 34 | [TRAP] 35 | 1 1.69492e+06 240 350 240 0 36 | 37 | # Format of ADC events: 38 | # id num dwell delay freq phase 39 | # .. .. ns us Hz rad 40 | [ADC] 41 | 1 8192 12500 3280 0 0 42 | 43 | # Sequence Shapes 44 | [SHAPES] 45 | 46 | shape_id 1 47 | num_samples 2 48 | 1 49 | 1 50 | 51 | shape_id 2 52 | num_samples 2 53 | 0 54 | 0 55 | 56 | shape_id 3 57 | num_samples 2 58 | 0 59 | 1000 60 | 61 | 62 | [SIGNATURE] 63 | # This is the hash of the Pulseq file, calculated right before the [SIGNATURE] section was added 64 | # It can be reproduced/verified with md5sum if the file trimmed to the position right above [SIGNATURE] 65 | # The new line character preceding [SIGNATURE] BELONGS to the signature (and needs to be sripped away for recalculating/verification) 66 | Type md5 67 | Hash 7a621205cecc36a2aadeeccf9d35399f 68 | -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/data/06_PRESS_center.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/01_from_FID_to_PRESS/data/06_PRESS_center.dat -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/data/07_PRESS_offcenter.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/01_from_FID_to_PRESS/data/07_PRESS_offcenter.dat -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/doc/01_from_FID_to_PRESS.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/01_from_FID_to_PRESS/doc/01_from_FID_to_PRESS.pdf -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/doc/01_from_FID_to_PRESS.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/01_from_FID_to_PRESS/doc/01_from_FID_to_PRESS.pptx -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/doc/Fig_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/01_from_FID_to_PRESS/doc/Fig_1.png -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/recon/r01_FID.m: -------------------------------------------------------------------------------- 1 | % very basic FID data handling example 2 | % 3 | % needs mapVBVD in the path 4 | 5 | %% Load data from the given directory sorted by name 6 | path='../data'; % directory to be scanned for data files 7 | nF=1; % the number of the file / data set to load 8 | 9 | pattern='*.dat'; 10 | D=dir([path filesep pattern]); 11 | [~,I]=sort(string({D(:).name})); 12 | data_file_path=[path filesep D(I(nF)).name]; 13 | 14 | fprintf(['loading `' data_file_path '´ ...\n']); 15 | twix_obj = mapVBVD(data_file_path); 16 | 17 | %% Load the latest file from a dir 18 | %path='../IceNIH_RawSend/'; % directory to be scanned for data files 19 | %pattern='*.dat'; 20 | %D=dir([path filesep pattern]); 21 | %[~,I]=sort([D(:).datenum]); 22 | %data_file_path=[path D(I(end-5)).name]; % use end-1 to reconstruct the second-last data set, etc. 23 | 24 | %% raw data preparation 25 | if iscell(twix_obj) 26 | data_unsorted = double(twix_obj{end}.image.unsorted()); 27 | else 28 | data_unsorted = double(twix_obj.image.unsorted()); 29 | end 30 | 31 | channels=size(data_unsorted,2); 32 | adc_len=size(data_unsorted,1); 33 | readouts=size(data_unsorted,3); 34 | 35 | rawdata = permute(data_unsorted, [1,3,2]); 36 | 37 | %% Load sequence from the file with the same name as the raw data 38 | seq_file_path = [data_file_path(1:end-3) 'seq']; 39 | fprintf(['loading `' seq_file_path '´ ...\n']); 40 | seq = mr.Sequence(); % Create a new sequence object 41 | seq.read(seq_file_path,'detectRFuse'); 42 | if strcmp(seq.getDefinition('Name'),'fid-mfa') 43 | fprintf('FID-MFA sequence detected, re-loading the sequence file ...\n'); 44 | seq = mr.Sequence(); % reload the sequence without detecting the RF pulse use because otherwise the code assumes that we have refocusing pulses... 45 | seq.read(seq_file_path); 46 | end 47 | 48 | %% we just want t_adc 49 | [ktraj_adc, t_adc, ktraj, t, t_excitation, t_refocusing]=seq.calculateKspacePP(); 50 | 51 | t_adc=reshape(t_adc,[adc_len,readouts]); 52 | % calc TE but account for dummy scans... 53 | %[~,iND]=max(t_excitation(t_excitation1 65 | for j=2:channels 66 | plot(t_e, abs(rawdata(:,:,j))); 67 | end 68 | end 69 | plot(t_e(1),0); % trick to force Y axis scaling 70 | xlabel('time since excitation /s'); 71 | 72 | if (readouts>1) 73 | figure; plot(abs(rawdata(4,:,1))); title('time evolution (4th FID point)'); 74 | if channels>1 75 | hold on; 76 | for j=2:channels 77 | plot(abs(rawdata(4,:,j))); 78 | end 79 | end 80 | 81 | dataavg=squeeze(mean(rawdata,2)); 82 | figure; plot(t_e, abs(dataavg(:,1))); title('averaged FIDs'); 83 | if channels>1 84 | hold on; 85 | for j=2:channels 86 | plot(t_e, abs(dataavg(:,2))); 87 | end 88 | end 89 | xlabel('time since excitation /s'); 90 | end 91 | 92 | %% plot spectr(um/a) 93 | data_fft=fftshift(fft(fftshift(rawdata,1)),1); 94 | w_axis=(-adc_len/2:(adc_len/2-1))/((t_adc(end,1,1)-t_adc(1,1,1))/(adc_len-1)*adc_len)/twix_obj.hdr.Meas.lFrequency*1e6'; 95 | figure; 96 | plot(w_axis, abs(data_fft(:,:,1))); title('abs spectr(um/a)'); 97 | xlim([-10 10]); xlabel('frequency /ppm'); 98 | set(gca,'Xdir','reverse') 99 | -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/seq/s01_FID.m: -------------------------------------------------------------------------------- 1 | system = mr.opts('rfRingdownTime', 20e-6, 'rfDeadTime', 100e-6, ... 2 | 'adcDeadTime', 20e-6); 3 | 4 | seq=mr.Sequence(system); % Create a new sequence object 5 | Nx=8192; 6 | Nrep=1; 7 | adcDur=512e-3; 8 | rfDur=500e-6; 9 | TR=5000e-3; 10 | TE=30e-3; % the used coil has a very long switching time! 11 | % todo1: change flip angle to reduce SNR 12 | % todo2: change repetitions to increase SNR 13 | 14 | % Create non-selective pulse 15 | rf = mr.makeBlockPulse(pi/2,'Duration',rfDur, 'system', system, 'use', 'excitation'); 16 | 17 | % Define delays and ADC events 18 | adc = mr.makeAdc(Nx,'Duration',adcDur, 'system', system, 'delay', TE-rf.shape_dur/2-system.rfRingdownTime); 19 | 20 | delayTR=TR-mr.calcDuration(rf); 21 | assert(delayTR>=0); 22 | 23 | % Loop over repetitions and define sequence blocks 24 | for i=1:Nrep 25 | seq.addBlock(rf); 26 | seq.addBlock(adc,mr.makeDelay(delayTR)); 27 | end 28 | 29 | seq.plot(); 30 | 31 | % check whether the timing of the sequence is compatible with the scanner 32 | [ok, error_report]=seq.checkTiming; 33 | 34 | if (ok) 35 | fprintf('Timing check passed successfully\n'); 36 | else 37 | fprintf('Timing check failed! Error listing follows:\n'); 38 | fprintf([error_report{:}]); 39 | fprintf('\n'); 40 | end 41 | 42 | seq.setDefinition('Name', 'fid'); 43 | seq.write('fid.seq') % Write to pulseq file 44 | %seq.install('siemens'); % copy to scanner 45 | 46 | % optional slow step, but useful for testing during development e.g. for the real TE, TR or for staying within slewrate limits 47 | rep = seq.testReport; 48 | fprintf([rep{:}]); 49 | -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/seq/s02_FID_multipleFAs.m: -------------------------------------------------------------------------------- 1 | system = mr.opts('rfRingdownTime', 20e-6, 'rfDeadTime', 100e-6, ... 2 | 'adcDeadTime', 20e-6); 3 | 4 | seq=mr.Sequence(system); % Create a new sequence object 5 | Nx=8192; 6 | Nrep=1; 7 | adcDur=256e-3; 8 | rfDur=1000e-6; % increased to 1ms 9 | TR=5000e-3; % increased to 5s avoid T1 saturation 10 | TE=30e-3; % the used coil has a very long switching time! 11 | flip_angles=9; 12 | % todo: change flip_angles to 18 and pi/2 to pi below 13 | 14 | % Create non-selective pulse 15 | rf = mr.makeBlockPulse(pi/2,'Duration',rfDur, 'system', system, 'use', 'excitation'); 16 | 17 | % Define delays and ADC events 18 | adc = mr.makeAdc(Nx,'Duration',adcDur, 'system', system, 'delay', TE-rf.shape_dur/2-system.rfRingdownTime); 19 | 20 | delayTR=TR-mr.calcDuration(rf); 21 | assert(delayTR>=0); 22 | 23 | for f=1:flip_angles 24 | rf = mr.makeBlockPulse(pi/flip_angles*f,'Duration',rfDur, 'system', system, 'use', 'excitation'); 25 | % Loop over repetitions and define sequence blocks 26 | for i=1:Nrep 27 | seq.addBlock(rf); 28 | seq.addBlock(adc,mr.makeDelay(delayTR)); 29 | end 30 | end 31 | 32 | seq.setDefinition('Name', 'fid-mfa'); 33 | seq.write('fid-mfa.seq') % Write to pulseq file 34 | %seq.install('siemens'); % copy to scanner 35 | 36 | seq.plot(); 37 | -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/seq/s03_SE.m: -------------------------------------------------------------------------------- 1 | system = mr.opts('rfRingdownTime', 20e-6, 'rfDeadTime', 100e-6, ... 2 | 'adcDeadTime', 20e-6); 3 | 4 | seq=mr.Sequence(system); % Create a new sequence object 5 | Nx=128; 6 | Nrep=1; 7 | adcDur=6.4e-3; 8 | rfDur=1000e-6; 9 | TR=13e-3; 10 | TE=10e-3; 11 | % todo: change ADC duration 12 | 13 | % Create non-selective excitation and refocusing pulses 14 | rf_ex = mr.makeBlockPulse(pi/2,'Duration',rfDur, 'system', system, 'use', 'excitation'); % for this phantom and this coil I had to reduce flip angle to avoid receiver saturation 15 | rf_ref = mr.makeBlockPulse(pi,'Duration',rfDur, 'system', system, 'use', 'refocusing'); % needed for the proper k-space calculation 16 | 17 | % Define delays and ADC events 18 | % delayTE1=TE/2-mr.calcDuration(rf_ex)/2-mr.calcDuration(rf_ref)/2; 19 | delayTE1 = TE/2 - rf_ex.shape_dur/2 - rf_ex.ringdownTime - rf_ref.delay - rf_ref.shape_dur/2 ; 20 | % delayTE2=TE/2-mr.calcDuration(rf_ref)+rf_ref.delay+mr.calcRfCenter(rf_ref)-adcDur/2; % this is not perfect, but -adcDur/2/Nx will break the raster alignment 21 | delayTE2 = TE/2 - rf_ref.shape_dur/2 - rf_ref.ringdownTime - adcDur / 2 ; 22 | %adc = mr.makeAdc(Nx,'Duration',adcDur, 'system', system, 'delay', delayTE2); 23 | adc = mr.makeAdc(Nx,'Duration',adcDur, 'system', system); 24 | 25 | delayTR=TR-mr.calcDuration(rf_ex)-delayTE1-mr.calcDuration(rf_ref); 26 | 27 | assert(delayTE1>=0); 28 | assert(delayTE2>=0); 29 | assert(delayTR>=0); 30 | 31 | % Loop over repetitions and define sequence blocks 32 | for i=1:Nrep 33 | seq.addBlock(rf_ex); 34 | seq.addBlock(delayTE1); 35 | seq.addBlock(rf_ref); 36 | seq.addBlock(delayTE2-adc.delay); 37 | seq.addBlock(adc,mr.makeDelay(delayTR)); 38 | end 39 | 40 | seq.plot(); 41 | 42 | % check whether the timing of the sequence is compatible with the scanner 43 | [ok, error_report]=seq.checkTiming; 44 | 45 | if (ok) 46 | fprintf('Timing check passed successfully\n'); 47 | else 48 | fprintf('Timing check failed! Error listing follows:\n'); 49 | fprintf([error_report{:}]); 50 | fprintf('\n'); 51 | end 52 | 53 | seq.setDefinition('Name', 'se'); 54 | seq.write('se.seq') % Write to pulseq file 55 | %seq.install('siemens'); % copy to scanner 56 | 57 | %% calculate k-space but only use it to check the TE calculation 58 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP('gradient_offset',[1 0 0]); 59 | 60 | assert(abs(t_refocusing-t_excitation-TE/2)<1e-6); % check that the refocusing happens at the 1/2 of TE 61 | assert(abs(t_adc(Nx/2)-t_excitation-TE)=0); 29 | assert(delayTE2>=0); 30 | assert(delayTR>=0); 31 | 32 | % ramp up the background gradient 33 | ramptime=abs(bg)/system.maxSlew; 34 | ramptime=ceil(ramptime/system.gradRasterTime)*system.gradRasterTime; % round-up to gradient raster 35 | ramptime=max(ramptime, 3*system.gradRasterTime); % and limit it to 3x system.gradRasterTime 36 | seq.addBlock(mr.makeExtendedTrapezoid('x','amplitudes',[0 bg],'times',[0 ramptime])); 37 | 38 | % Loop over repetitions and define sequence blocks 39 | for i=1:Nrep 40 | seq.addBlock(rf_ex,mr.makeExtendedTrapezoid('x','amplitudes',[bg bg],'times',[0 mr.calcDuration(rf_ex)])); 41 | seq.addBlock(mr.makeDelay(delayTE1),mr.makeExtendedTrapezoid('x','amplitudes',[bg bg],'times',[0 delayTE1])); 42 | seq.addBlock(rf_ref,mr.makeExtendedTrapezoid('x','amplitudes',[bg bg],'times',[0 mr.calcDuration(rf_ref)])); 43 | seq.addBlock(adc,mr.makeDelay(delayTR),mr.makeExtendedTrapezoid('x','amplitudes',[bg bg],'times',[0 delayTR])); 44 | end 45 | 46 | % ramp down the background gradient 47 | seq.addBlock(mr.makeExtendedTrapezoid('x','amplitudes',[bg 0],'times',[0 ramptime])); 48 | 49 | seq.plot(); 50 | 51 | % check whether the timing of the sequence is compatible with the scanner 52 | [ok, error_report]=seq.checkTiming; 53 | 54 | if (ok) 55 | fprintf('Timing check passed successfully\n'); 56 | else 57 | fprintf('Timing check failed! Error listing follows:\n'); 58 | fprintf([error_report{:}]); 59 | fprintf('\n'); 60 | end 61 | 62 | seq.setDefinition('Name', 'se-bg'); 63 | seq.write('se-bg.seq') % Write to pulseq file 64 | %seq.install('siemens'); % copy to scanner 65 | 66 | % calculate and plot k-spaces 67 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 68 | 69 | figure; plot(t_ktraj, ktraj'); % plot the entire k-space trajectory 70 | hold on; plot(t_adc,ktraj_adc(1,:),'.'); % and sampling points on the kx-axis 71 | 72 | % calculate real TE and TR, etc 73 | % reported TR will be slighlty higher for Nrep=1 becuse it uses TA instead 74 | rep = seq.testReport; 75 | fprintf([rep{:}]); 76 | -------------------------------------------------------------------------------- /01_from_FID_to_PRESS/seq/s05_SE_withSpolers.m: -------------------------------------------------------------------------------- 1 | system = mr.opts('rfRingdownTime', 20e-6, 'rfDeadTime', 100e-6, ... 2 | 'adcDeadTime', 20e-6); 3 | 4 | seq=mr.Sequence(system); % Create a new sequence object 5 | Nx=8192; 6 | Nrep=1; 7 | adcDur=102.4e-3; 8 | rfDur=1000e-6; 9 | TR=250e-3; 10 | TE=110e-3; 11 | spA=1000; % spoiler area in 1/m (=Hz/m*s) 12 | % todo: change spoiler area, remove one of spoilers and observe the signal 13 | 14 | % Create non-selective excitation and refocusing pulses 15 | rf_ex = mr.makeBlockPulse(pi/2,'Duration',rfDur, 'system', system, 'use', 'excitation'); % for this phantom and this coil I had to reduce flip angle to avoid receiver saturation 16 | rf_ref = mr.makeBlockPulse(pi,'Duration',rfDur, 'system', system, 'use', 'refocusing'); % needed for the proper k-space calculation 17 | 18 | % calculate spoiler gradient 19 | g_sp=mr.makeTrapezoid('z','Area',spA,'system',system); 20 | rf_ref.delay=max(mr.calcDuration(g_sp),rf_ref.delay); 21 | 22 | % Define delays and ADC events 23 | % delayTE1=TE/2-(mr.calcDuration(rf_ex)-mr.calcRfCenter(rf_ex)-rf_ex.delay)-rf_ref.delay-mr.calcRfCenter(rf_ref); 24 | delayTE1 = TE/2 - rf_ex.shape_dur/2 - rf_ex.ringdownTime - rf_ref.delay - rf_ref.shape_dur/2 ; 25 | % delayTE2=TE/2-mr.calcDuration(rf_ref)+rf_ref.delay+mr.calcRfCenter(rf_ref)-adcDur/2; % this is not perfect, but -adcDur/2/Nx will break the raster alignment 26 | delayTE2 = TE/2 - rf_ref.shape_dur/2 - rf_ref.ringdownTime - adcDur / 2 ; 27 | assert(delayTE2>mr.calcDuration(g_sp)); 28 | 29 | adc = mr.makeAdc(Nx,'Duration',adcDur, 'system', system, 'delay', delayTE2); 30 | 31 | delayTR=TR-mr.calcDuration(rf_ex)-delayTE1-mr.calcDuration(rf_ref); 32 | 33 | assert(delayTE1>=0); 34 | assert(delayTE2>=0); 35 | assert(delayTR>=0); 36 | 37 | % Loop over repetitions and define sequence blocks 38 | for i=1:Nrep 39 | seq.addBlock(rf_ex); 40 | seq.addBlock(mr.makeDelay(delayTE1)); 41 | seq.addBlock(rf_ref,g_sp); 42 | seq.addBlock(adc,g_sp,mr.makeDelay(delayTR)); 43 | end 44 | 45 | seq.plot(); 46 | 47 | % check whether the timing of the sequence is compatible with the scanner 48 | [ok, error_report]=seq.checkTiming; 49 | 50 | if (ok) 51 | fprintf('Timing check passed successfully\n'); 52 | else 53 | fprintf('Timing check failed! Error listing follows:\n'); 54 | fprintf([error_report{:}]); 55 | fprintf('\n'); 56 | end 57 | 58 | seq.setDefinition('Name', 'se-sp'); 59 | seq.write('se-sp.seq') % Write to pulseq file 60 | %seq.install('siemens'); % copy to scanner 61 | 62 | % calculate k-space but only use it to check timing 63 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 64 | 65 | assert(abs(t_refocusing-t_excitation-TE/2)<1e-6); % check that the refocusing happens at the 1/2 of TE 66 | assert(abs(t_adc(Nx/2)-t_excitation-TE)=0); 79 | % we start the ADC object right away after the spoiler 80 | adc = mr.makeAdc(Nx,'Duration',adcDur, 'system', system); 81 | 82 | % delayTR=TR-mr.calcDuration(g_ex)-mr.calcDuration(g_refC1)-delayTE1-delayTE2-mr.calcDuration(g_refC2)-mr.calcDuration(adc); 83 | delayTR=TR-max(mr.calcDuration(g_ex), mr.calcDuration(rf_ex))-mr.calcDuration(g_refC1)-delayTE1-delayTE2-mr.calcDuration(g_refC2)-mr.calcDuration(adc); 84 | assert(delayTR>=0); 85 | 86 | %% Loop over repetitions and define sequence blocks 87 | for i=(1-Ndummy):Nrep 88 | seq.addBlock(rf_ex,g_ex); 89 | seq.addBlock(mr.makeDelay(delayTE1)); 90 | seq.addBlock(rf_ref1,g_refC1,g_spAz,g_spAx); 91 | seq.addBlock(mr.makeDelay(delayTE2)); 92 | seq.addBlock(rf_ref2,g_refC2,g_spBy,g_spBx); 93 | if i>0 94 | seq.addBlock(adc); 95 | else 96 | seq.addBlock(mr.makeDelay(mr.calcDuration(adc))); 97 | end 98 | seq.addBlock(mr.makeDelay(delayTR)); 99 | end 100 | 101 | %% 102 | seq.plot('showBlocks',true,'timeDisp','us','stacked',true); 103 | 104 | % check whether the timing of the sequence is compatible with the scanner 105 | [ok, error_report]=seq.checkTiming; 106 | 107 | if (ok) 108 | fprintf('Timing check passed successfully\n'); 109 | else 110 | fprintf('Timing check failed! Error listing follows:\n'); 111 | fprintf([error_report{:}]); 112 | fprintf('\n'); 113 | end 114 | 115 | seq.setDefinition('FOV', voxel); 116 | seq.setDefinition('Name', 'press'); 117 | seq.write('press.seq') % Write to pulseq file 118 | 119 | %% calculate k-space but only use it to check timing 120 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP('gradient_offset',[0 0 1000]); 121 | 122 | figure; plot(t_ktraj,ktraj);title('k-space components as functions of time'); grid on; 123 | 124 | %% demo paperPlot 125 | 126 | seq.paperPlot(); 127 | -------------------------------------------------------------------------------- /02_basic_gradient_echo/README.md: -------------------------------------------------------------------------------- 1 | # Pulseq tutorial "Basic GRE" 2 | 3 | Welcome to the "Basic GRE" tutorial! It was initially developed for the Pulseq software demonstration and hands-on session at the **ISMRM 2019** in Montreal. 4 | 5 | This tutorial demonstrates how to expand a basic Gradient-Echo (GRE) 6 | sequence to an advanced GRE sequence with a desired steady-state 7 | magnetisation evolution needed to generate T1 contrast. It contains six 8 | steps from ***s01\_GRE\_tutorial\_step0*** to 9 | ***s06\_GRE\_tutorial\_step5***. We recommend using Meld software to 10 | highlight the changes at each step. The slide deck entitled 11 | [02_basic_gradient_echo.pdf](./doc/02_basic_gradient_echo.pdf) shows the 12 | sequence diagrams of all steps and visualises the changes at each step. 13 | 14 | The transverse magnetisation may persist from cycle to cycle in a GRE 15 | sequence with short repetition times (e.g. shorter than \~3\*T2). 16 | *Spoiling* tries to approximate a situation that the steady-state 17 | magnetisation has no transverse components immediately before each RF 18 | pulse. Three methods may be used alone or in combination to spoil 19 | transverse magnetisation. 20 | 21 | 1. Long TR spoiling. When TR\>\>T2\*, the transverse magnetisation will 22 | naturally decay to zero by the end of the cycle. Thus, any GRE 23 | sequence using TR values of several hundred milliseconds or longer 24 | will be "naturally" spoiled. 25 | 26 | 2. Gradient spoiling. In this method, spoiling is performed by applying 27 | the slice-selective (and sometimes readout) gradients at the end of 28 | each cycle just before the next RF pulse. Several gradient spoiling 29 | concepts have been tried in the MR history, with the strength of the 30 | spoiler gradient remaining constant or varied linearly or 31 | semi-randomly from TR to TR. In the present example a constant 32 | spoiler is used, and an appropriate phantom with a sufficiently long 33 | T2 demonstrates that gradient spoiling does not work unless the 34 | spoiler is so strong that the intrinsic diffusion weighting would 35 | kill the signal. 36 | 37 | 3. It is also demonstrated that it is necessary to refocus the phase 38 | encoding moment at the end of the TR cycle to avoid artefacts. 39 | 40 | 4. RF-spoiling. Here the phase of the RF carrier is changed according 41 | to a predefined formula from TR to TR. Using a completely randomised 42 | pattern of phase changes is not ideal because unintended spin 43 | clustering may occur, and the degree of spoiling may change from one 44 | interval to the next. A superior method is to increment the phase 45 | quadratically using a recursive formula. RF spoiling is always 46 | combined with a moderate constant spoiling in read and slice 47 | directions and phase-encoding refocusing. For further details about 48 | spoiling, please go to 49 | . 50 | 51 | ***s01\_GRE\_tutorial\_step0*** 52 | 53 | ***s01*** is a *basic* 2D slice-selective GRE sequence. Each TR of this 54 | sequence contains 5 blocks. The corresponding k-space is shown in Figure 55 | 1. 56 | 57 | 58 | 59 | **Figure** **1** K-space of s01\_GRE\_tutorial\_step0 sequence (6\*6 60 | encodes). 61 | 62 | ***s02\_GRE\_tutorial\_step1*** 63 | 64 | ***s02*** is a 2D slice-selective GRE sequence with three spoiler 65 | gradients added in slice-selective, readout and phase-encoding 66 | direction. Its k-space is shown in Figure 2. 67 | 68 | 69 | 70 | **Figure** **2** K-space of s02\_GRE\_tutorial\_step1 sequence (6\*6 71 | encodes). 72 | 73 | ***s03\_GRE\_tutorial\_step2*** 74 | 75 | ***s03*** is a 2D slice-selective GRE sequence with two spoiler 76 | gradients in slice-selective and readout directions and one rewinder 77 | gradient in the phase-encoding direction. It is built by altering the 78 | gyPost gradient in ***s01*** to rephase in the phase-encoding direction 79 | for subsequent phase-encoding steps. The k-space is shown in Figure 3. 80 | 81 | 82 | 83 | **Figure** **3** K-space of s03\_GRE\_tutorial\_step2 sequence (6\*6 84 | encodes). 85 | 86 | ***s04\_GRE\_tutorial\_step3*** 87 | 88 | ***s04*** is built by adding RF-spoiling to ***s03***. The RF-spoiling 89 | is achieved by quasi-randomly varying the RF phase offset in the *i*^th^ 90 | phase-encoding step, 91 | $\text{φ}_{\text{i}}\text{\ =\ mod}\left( \text{117\ *\ }\left( \text{i}^{\text{2}}\text{\ +\ }\text{i}\text{\ }\text{+\ 2} \right)\text{,\ 360} \right)\text{*}\frac{\text{π}}{\text{180}}$. 92 | 93 | ***s05\_GRE\_tutorial\_step4*** 94 | 95 | In ***s05***, the receiver phase offset is set to follow the transmitter 96 | phase offset, $\text{φ}_{\text{i}}$, in the *i*^th^ phase-encoding step. 97 | 98 | ***s06\_GRE\_tutorial\_step5*** 99 | 100 | ***s06*** adds some *dummy* scans (i.e. plays out multiple cycles of the 101 | sequence without recording a signal) to ***s05*** to establish (near) 102 | steady-state magnetisation in the GRE sequence prior to the beginning of the 103 | ADC recording. For more details about dummy scans, please go to 104 | . 105 | 106 | ## Quick links 107 | 108 | Pulseq Matlab repository: 109 | https://github.com/pulseq/pulseq 110 | 111 | ## Quick instructions 112 | 113 | The source code of the demo sequences and reconstruction scripts is the core of this repository. Please download the files to your computer and make them available to Matlab (e.g. by saving them in a subdirectory inside your Pulseq-Matlab installation and adding them to the Matlab's path). There are two sub-directories: 114 | 115 | * seq : contains example pulse sequences specifically prepared for this demo 116 | * recon : contains the reconstruction scripts tested with the above sequences 117 | * data : contains raw MR data in the Siemens TWIX format and the corresponding pulse sequences in the Pulseq format 118 | 119 | ## How to follow 120 | 121 | We strongly recommend using a text compare tool like *meld* (see this [Wikipedia page](https://en.wikipedia.org/wiki/Meld_(software)) and compare sequences from subsequent steps to visualise the respective steps. 122 | 123 | ## Further links 124 | 125 | Check out the main *Pulseq* repository at https://github.com/pulseq/pulseq and familarising yourself with the code, example sequences, and reconstruction scripts (see 126 | [pulseq/matlab/demoSeq](https://github.com/pulseq/pulseq/tree/master/matlab/demoSeq) and [pulseq/matlab/demoRecon](https://github.com/pulseq/pulseq/tree/master/matlab/demoRecon)). If you already use Pulseq, consider updating to the current version. 127 | -------------------------------------------------------------------------------- /02_basic_gradient_echo/data/01_GRE_example.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/02_basic_gradient_echo/data/01_GRE_example.dat -------------------------------------------------------------------------------- /02_basic_gradient_echo/data/02_GRE_noSpoil_noRephase.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/02_basic_gradient_echo/data/02_GRE_noSpoil_noRephase.dat -------------------------------------------------------------------------------- /02_basic_gradient_echo/data/03_GRE_withSpoil_noRephase.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/02_basic_gradient_echo/data/03_GRE_withSpoil_noRephase.dat -------------------------------------------------------------------------------- /02_basic_gradient_echo/data/04_GRE_xSpoil_yRephase.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/02_basic_gradient_echo/data/04_GRE_xSpoil_yRephase.dat -------------------------------------------------------------------------------- /02_basic_gradient_echo/data/05_GRE_xSpoil_yRephase_RFspoil.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/02_basic_gradient_echo/data/05_GRE_xSpoil_yRephase_RFspoil.dat -------------------------------------------------------------------------------- /02_basic_gradient_echo/data/06_GRE_xSpoil_yRephase_RFspoilADC.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/02_basic_gradient_echo/data/06_GRE_xSpoil_yRephase_RFspoilADC.dat -------------------------------------------------------------------------------- /02_basic_gradient_echo/data/07_GRE_xSpoil_yRephase_RFspoilADC_dummy.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/02_basic_gradient_echo/data/07_GRE_xSpoil_yRephase_RFspoilADC_dummy.dat -------------------------------------------------------------------------------- /02_basic_gradient_echo/doc/02_basic_gradient_echo.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/02_basic_gradient_echo/doc/02_basic_gradient_echo.pdf -------------------------------------------------------------------------------- /02_basic_gradient_echo/doc/02_basic_gradient_echo.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/02_basic_gradient_echo/doc/02_basic_gradient_echo.pptx -------------------------------------------------------------------------------- /02_basic_gradient_echo/doc/Fig_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/02_basic_gradient_echo/doc/Fig_1.png -------------------------------------------------------------------------------- /02_basic_gradient_echo/doc/Fig_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/02_basic_gradient_echo/doc/Fig_2.png -------------------------------------------------------------------------------- /02_basic_gradient_echo/doc/Fig_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/02_basic_gradient_echo/doc/Fig_3.png -------------------------------------------------------------------------------- /02_basic_gradient_echo/recon/r01_2DFFT.m: -------------------------------------------------------------------------------- 1 | % Reconstruction of 2D Cartesian Pulseq data 2 | % provides an example on how data reordering can be detected from the MR 3 | % sequence with almost no additional prior knowledge 4 | % 5 | % needs mapVBVD in the path 6 | 7 | %% Load data sorted by name 8 | path='../data'; % directory to be scanned for data files 9 | nF=1; % the number of the file / data set to load 10 | 11 | pattern='*.dat'; 12 | D=dir([path filesep pattern]); 13 | [~,I]=sort(string({D(:).name})); 14 | data_file_path=[path filesep D(I(nF)).name]; 15 | 16 | %% Load the latest file from a dir 17 | % path='../IceNIH_RawSend/'; % directory to be scanned for data files 18 | % %path='~/Dropbox/shared/data/siemens/'; 19 | % pattern='*.dat'; 20 | % 21 | % D=dir([path pattern]); 22 | % [~,I]=sort([D(:).datenum]); 23 | % % 24 | % data_file_path=[path D(I(end)).name]; % use end-1 to reconstruct the second-last data set, etc. 25 | % %data_file_path='../interpreters/siemens/data_example/gre_example.dat' 26 | %% 27 | twix_obj = mapVBVD(data_file_path); 28 | 29 | %% Load sequence from file (optional, you may still have it in memory) 30 | 31 | seq_file_path = [data_file_path(1:end-3) 'seq']; 32 | 33 | [~,out_name,~] = fileparts(seq_file_path); 34 | 35 | seq = mr.Sequence(); % Create a new sequence object 36 | seq.read(seq_file_path,'detectRFuse'); 37 | 38 | %TR = 20e-3; 39 | %seq.plot('timeRange', [0 2*TR]); 40 | %print( '-r150', '-dpng', [out_name '_seq.png']); 41 | 42 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 43 | figure; plot(ktraj(1,:),ktraj(2,:),'b',... 44 | ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % a 2D plot 45 | axis('equal'); 46 | title('k-space'); 47 | xlabel('kx / m^-^1'); 48 | ylabel('kx / m^-^1'); 49 | print( '-r150', '-dpng', [out_name '_kspace.png']); 50 | 51 | %% Analyze the trajectory data (ktraj_adc) 52 | k_extent=max(abs(ktraj_adc),[],2); 53 | k_scale=max(k_extent); 54 | k_threshold=k_scale/5000; 55 | 56 | % detect unused dimensions and delete them 57 | if any(k_extentk_threshold)=NaN; 69 | dk_all_cnt=sum(isfinite(dk_all),2); 70 | dk_all(~isfinite(dk_all))=0; 71 | dk=sum(dk_all,2)./dk_all_cnt; 72 | [~,k0_ind]=min(sum(ktraj_adc.^2,1)); 73 | kindex=round((ktraj_adc-ktraj_adc(:,k0_ind*ones(1,size(ktraj_adc,2))))./dk(:,ones(1,size(ktraj_adc,2)))); 74 | kindex_min=min(kindex,[],2); 75 | kindex_mat=kindex-kindex_min(:,ones(1,size(ktraj_adc,2)))+1; 76 | kindex_end=max(kindex_mat,[],2); 77 | sampler=zeros(kindex_end'); 78 | repeat=zeros(1,size(ktraj_adc,2)); 79 | for i=1:size(kindex_mat,2) 80 | if (size(kindex_mat,1)==3) 81 | ind=sub2ind(kindex_end,kindex_mat(1,i),kindex_mat(2,i),kindex_mat(3,i)); 82 | else 83 | ind=sub2ind(kindex_end,kindex_mat(1,i),kindex_mat(2,i)); 84 | end 85 | repeat(i)=sampler(ind); 86 | sampler(ind)=repeat(i)+1; 87 | end 88 | if (max(repeat(:))>0) 89 | kindex=[kindex;(repeat+1)]; 90 | kindex_mat=[kindex_mat;(repeat+1)]; 91 | kindex_end=max(kindex_mat,[],2); 92 | end 93 | %figure; plot(kindex(1,:),kindex(2,:),'.-'); 94 | 95 | %% sort in the k-space data 96 | if iscell(twix_obj) 97 | data_unsorted = twix_obj{end}.image.unsorted(); 98 | else 99 | data_unsorted = twix_obj.image.unsorted(); 100 | end 101 | 102 | % the incoming data order is [kx coils acquisitions] 103 | data_coils_last = permute(data_unsorted, [1, 3, 2]); 104 | nCoils = size(data_coils_last, 3); 105 | 106 | data_coils_last=reshape(data_coils_last, [size(data_coils_last,1)*size(data_coils_last,2), nCoils]); 107 | 108 | data=zeros([kindex_end' nCoils]); 109 | if (size(kindex,1)==3) 110 | for i=1:size(kindex,2) 111 | data(kindex_mat(1,i),kindex_mat(2,i),kindex_mat(3,i),:)=data_coils_last(i,:); 112 | end 113 | else 114 | for i=1:size(kindex,2) 115 | data(kindex_mat(1,i),kindex_mat(2,i),:)=data_coils_last(i,:); 116 | end 117 | end 118 | 119 | if size(kindex,1)==3 120 | nImages=size(data,3); 121 | else 122 | nImages=1; 123 | data=reshape(data, [size(data,1) size(data,2) 1 size(data,3)]); % we need a dummy images/slices dimension 124 | end 125 | 126 | %figure; imab(data); 127 | 128 | %% Reconstruct coil images 129 | 130 | images = zeros(size(data)); 131 | %figure; 132 | 133 | for ii = 1:nCoils 134 | %images(:,:,:,ii) = fliplr(rot90(fftshift(fft2(fftshift(data(:,:,:,ii)))))); 135 | images(:,:,:,ii) = fftshift(fft2(fftshift(data(end:-1:1,:,:,ii)))); 136 | %subplot(2,2,ii); 137 | %imshow(abs(images(:,:,ii)), []); 138 | %title(['RF Coil ' num2str(ii)]); 139 | %for ni = 1:nImages 140 | %tmp = abs(images(:,:,ni,ii)); 141 | %tmp = tmp./max(tmp(:)); 142 | %imwrite(tmp, ['img_coil_' num2str(ii) '.png']) 143 | %end 144 | end 145 | 146 | % Phase images (possibly channel-by-channel and echo-by-echo) 147 | %figure;imab(angle(images));colormap('jet'); 148 | %figure;imab(abs(images));colormap('gray'); 149 | 150 | %% Image display with optional sum of squares combination 151 | figure; 152 | if nCoils>1 153 | sos=abs(sum(images.*conj(images),ndims(images))).^(1/2); 154 | sos=sos./max(sos(:)); 155 | imab(sos); 156 | imwrite(rot90(sos), [out_name '_img_combined.png']); 157 | else 158 | imab(abs(images)); 159 | imwrite(rot90(abs(images)./max(abs(images(:)))), [out_name '_img.png']); 160 | end 161 | colormap('gray'); 162 | 163 | %% reconstruct field map (optional) 164 | 165 | if size(images,3)==2 166 | cmplx_diff=images(:,:,2,:).*conj(images(:,:,1,:)); 167 | phase_diff_image=angle(sum(cmplx_diff,4)); 168 | figure; 169 | imab(phase_diff_image);colormap('jet'); 170 | end 171 | 172 | %% gif movie export 173 | 174 | scale=1.2; 175 | filename='fftReconMovie'; 176 | fps=5; 177 | 178 | if size(images,3)>4 179 | 180 | clm=gray(256); 181 | 182 | if nCoils>1 183 | imex=sos; 184 | else 185 | imex=abs(images); 186 | imex=imex/max(imex(:)); 187 | end 188 | 189 | imex=permute(imex(:,end:-1:1,:),[2,1,3]); 190 | 191 | imind=uint8(scale*imex*(size(clm,1)-1)+1); 192 | imwrite(imind(:,:,1),clm, [filename, '.gif'],'DelayTime',1/fps,'Loopcount',inf); 193 | for i=2:size(imind,3), 194 | imwrite(imind(:,:,i),clm, [filename, '.gif'],'DelayTime',1/fps, 'WriteMode','append'); 195 | end 196 | 197 | end 198 | -------------------------------------------------------------------------------- /02_basic_gradient_echo/seq/s01_GRE_tutorial_step0.m: -------------------------------------------------------------------------------- 1 | % Define FOV and resolution 2 | fov = 256e-3; 3 | sliceThickness = 5e-3; 4 | Nx = 256; 5 | Ny = Nx; 6 | 7 | % Define sequence parameters 8 | TE = 8e-3; 9 | TR = 22e-3; 10 | alpha=30; 11 | 12 | % set system limits 13 | sys = mr.opts('MaxGrad',12,'GradUnit','mT/m',... 14 | 'MaxSlew',100,'SlewUnit','T/m/s',... 15 | 'rfRingdownTime', 20e-6, 'rfDeadtime', 100e-6); 16 | 17 | % Create a new sequence object 18 | seq=mr.Sequence(sys); 19 | 20 | % Create slice selective alpha-pulse and corresponding gradients 21 | [rf, gz, gzReph] = mr.makeSincPulse(alpha*pi/180, 'Duration', 4e-3,... 22 | 'SliceThickness', sliceThickness, 'apodization', 0.5,'timeBwProduct', 4, ... 23 | 'system' ,sys, 'use', 'excitation'); 24 | 25 | % Define other gradients and ADC events 26 | deltak = 1/fov; % Pulseq toolbox defaults to k-space units of m^-1 27 | gx = mr.makeTrapezoid('x', 'FlatArea', Nx*deltak, 'FlatTime', 6.4e-3); 28 | adc = mr.makeAdc(Nx, 'Duration', gx.flatTime, 'Delay', gx.riseTime); 29 | gxPre = mr.makeTrapezoid('x', 'Area', -gx.area/2, 'Duration', 2e-3); 30 | phaseAreas = ((0:Ny-1)-Ny/2)*deltak; 31 | 32 | 33 | % Calculate timing 34 | delayTE = round((TE - mr.calcDuration(gxPre) - mr.calcDuration(gz)/2 ... 35 | - mr.calcDuration(gx)/2)/seq.gradRasterTime)*seq.gradRasterTime; 36 | delayTR = round((TR - mr.calcDuration(gxPre) - mr.calcDuration(gz) ... 37 | - mr.calcDuration(gx) - delayTE)/seq.gradRasterTime)*seq.gradRasterTime; 38 | 39 | % Loop over phase encodes and define sequence blocks 40 | for i=1:Ny 41 | seq.addBlock(rf, gz); 42 | gyPre = mr.makeTrapezoid('y', 'Area', phaseAreas(i), 'Duration', 2e-3); 43 | seq.addBlock(gxPre, gyPre, gzReph); 44 | seq.addBlock(mr.makeDelay(delayTE)); 45 | seq.addBlock(gx, adc); 46 | seq.addBlock(mr.makeDelay(delayTR)); 47 | end 48 | 49 | % check whether the timing of the sequence is correct 50 | [ok, error_report]=seq.checkTiming; 51 | 52 | if (ok) 53 | fprintf('Timing check passed successfully\n'); 54 | else 55 | fprintf('Timing check failed! Error listing follows:\n'); 56 | fprintf([error_report{:}]); 57 | fprintf('\n'); 58 | end 59 | 60 | % export definitions 61 | seq.setDefinition('FOV', [fov fov sliceThickness]); 62 | seq.setDefinition('Name', 'DEMO_gre0'); 63 | 64 | seq.write(['DEMO_gre0.seq']) % Write to pulseq file 65 | 66 | seq.plot('timeRange', [0 2*TR]) 67 | 68 | % do not run the rest of the script automatically 69 | return 70 | 71 | %% plot gradients to check for gaps and optimality of the timing 72 | wave_data=seq.waveforms_and_times(); 73 | % plot the entire gradient shape 74 | figure; plot(wave_data{1}(1,:),wave_data{1}(2,:)); xlabel('time /s'); ylabel('gradient /(Hz/m)'); 75 | hold on; plot(wave_data{2}(1,:),wave_data{2}(2,:)); 76 | plot(wave_data{3}(1,:),wave_data{3}(2,:)); 77 | legend('G_x', 'G_y', 'G_z'); 78 | 79 | %% calculate k-space trajectory 80 | 81 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 82 | 83 | % plot k-spaces 84 | figure; plot(t_ktraj, ktraj'); % plot the entire k-space trajectory 85 | hold on; plot(t_adc,ktraj_adc(1,:),'.'); % and sampling points on the kx-axis 86 | title('k-space vector components as functions of time'); 87 | legend('k_x', 'k_y', 'k_z'); 88 | 89 | figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D plot 90 | hold on;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 91 | axis('equal'); % enforce aspect ratio for the correct trajectory display 92 | title('k-space trajectory (k_x/k_y)'); 93 | 94 | %% 95 | 96 | % [ktraj_adc, ktraj, t_excitation, t_refocusing, t_adc] = seq.calculateKspace(); 97 | 98 | % plot k-spaces 99 | % time_axis=(1:(size(ktraj,2)))*sys.gradRasterTime; 100 | % figure; plot(time_axis, ktraj'); % plot the entire k-space trajectory 101 | % hold; plot(t_adc,ktraj_adc(1,:),'.'); % and sampling points on the kx-axis 102 | % figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D plot 103 | % axis('equal'); % enforce aspect ratio for the correct trajectory display 104 | % hold;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 105 | 106 | %% very optional step, slow but useful for testing during development e.g. for the real TE, TR or for staying within slewrate limits 107 | rep = seq.testReport; 108 | fprintf([rep{:}]); 109 | 110 | %% listen to the sequence 111 | seq.sound(); 112 | -------------------------------------------------------------------------------- /02_basic_gradient_echo/seq/s02_GRE_tutorial_step1.m: -------------------------------------------------------------------------------- 1 | % Define FOV and resolution 2 | fov = 256e-3; 3 | sliceThickness = 5e-3; 4 | Nx = 256; 5 | Ny = Nx; 6 | 7 | % Define sequence parameters 8 | TE = 8e-3; 9 | TR = 22e-3; 10 | alpha=30; 11 | 12 | % set system limits 13 | sys = mr.opts('MaxGrad',12,'GradUnit','mT/m',... 14 | 'MaxSlew',100,'SlewUnit','T/m/s',... 15 | 'rfRingdownTime', 20e-6, 'rfDeadtime', 100e-6); 16 | 17 | % Create a new sequence object 18 | seq=mr.Sequence(sys); 19 | 20 | % Create slice selective alpha-pulse and corresponding gradients 21 | [rf, gz, gzReph] = mr.makeSincPulse(alpha*pi/180, 'Duration', 4e-3,... 22 | 'SliceThickness', sliceThickness, 'apodization', 0.5,'timeBwProduct', 4, ... 23 | 'system' ,sys, 'use', 'excitation'); 24 | 25 | % Define other gradients and ADC events 26 | deltak = 1/fov; % Pulseq toolbox defaults to k-space units of m^-1 27 | gx = mr.makeTrapezoid('x', 'FlatArea', Nx*deltak, 'FlatTime', 6.4e-3); 28 | adc = mr.makeAdc(Nx, 'Duration', gx.flatTime, 'Delay', gx.riseTime); 29 | gxPre = mr.makeTrapezoid('x', 'Area', -gx.area/2, 'Duration', 2e-3); 30 | phaseAreas = ((0:Ny-1)-Ny/2)*deltak; 31 | 32 | 33 | % Calculate timing 34 | delayTE = round((TE - mr.calcDuration(gxPre) - mr.calcDuration(gz)/2 ... 35 | - mr.calcDuration(gx)/2)/seq.gradRasterTime)*seq.gradRasterTime; 36 | delayTR = round((TR - mr.calcDuration(gxPre) - mr.calcDuration(gz) ... 37 | - mr.calcDuration(gx) - delayTE)/seq.gradRasterTime)*seq.gradRasterTime; 38 | 39 | spoilArea=4*gx.area(); % 4 "looks" good 40 | % Add spoilers in read, refocus in phase and spoiler in slice 41 | gxPost = mr.makeTrapezoid('x', 'Area', spoilArea, 'system', sys); % we pass 'system' here to calculate shortest time gradient 42 | gyPost = mr.makeTrapezoid('y', 'Area', spoilArea, 'system', sys); 43 | gzPost = mr.makeTrapezoid('z', 'Area', spoilArea, 'system', sys); 44 | 45 | delayTR = delayTR - mr.calcDuration(gxPost, gyPost, gzPost); 46 | 47 | % Loop over phase encodes and define sequence blocks 48 | for i=1:Ny 49 | seq.addBlock(rf, gz); 50 | gyPre = mr.makeTrapezoid('y', 'Area', phaseAreas(i), 'Duration', 2e-3); 51 | seq.addBlock(gxPre, gyPre, gzReph); 52 | seq.addBlock(mr.makeDelay(delayTE)); 53 | seq.addBlock(gx, adc); 54 | % Add spoilers in read and slice and may be in phase 55 | seq.addBlock(gxPost, gyPost, gzPost); 56 | seq.addBlock(mr.makeDelay(delayTR)); 57 | end 58 | 59 | % check whether the timing of the sequence is correct 60 | [ok, error_report]=seq.checkTiming; 61 | 62 | if (ok) 63 | fprintf('Timing check passed successfully\n'); 64 | else 65 | fprintf('Timing check failed! Error listing follows:\n'); 66 | fprintf([error_report{:}]); 67 | fprintf('\n'); 68 | end 69 | 70 | % export definitions 71 | seq.setDefinition('FOV', [fov fov sliceThickness]); 72 | seq.setDefinition('Name', 'DEMO_gre1'); 73 | 74 | seq.write(['DEMO_gre1.seq']) % Write to pulseq file 75 | 76 | seq.plot('timeRange', [0 2*TR]) 77 | 78 | % do not run the rest of the script automatically 79 | return 80 | 81 | %% plot gradients to check for gaps and optimality of the timing 82 | wave_data=seq.waveforms_and_times(); 83 | % plot the entire gradient shape 84 | figure; plot(wave_data{1}(1,:),wave_data{1}(2,:)); xlabel('time /s'); ylabel('gradient /(Hz/m)'); 85 | hold on; plot(wave_data{2}(1,:),wave_data{2}(2,:)); 86 | plot(wave_data{3}(1,:),wave_data{3}(2,:)); 87 | legend('G_x', 'G_y', 'G_z'); 88 | 89 | %% calculate k-space trajectory 90 | 91 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 92 | 93 | % plot k-spaces 94 | figure; plot(t_ktraj, ktraj'); % plot the entire k-space trajectory 95 | hold on; plot(t_adc,ktraj_adc(1,:),'.'); % and sampling points on the kx-axis 96 | title('k-space vector components as functions of time'); 97 | legend('k_x', 'k_y', 'k_z'); 98 | 99 | figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D plot 100 | hold on;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 101 | axis('equal'); % enforce aspect ratio for the correct trajectory display 102 | title('k-space trajectory (k_x/k_y)'); 103 | 104 | %% 105 | 106 | % [ktraj_adc, ktraj, t_excitation, t_refocusing, t_adc] = seq.calculateKspace(); 107 | 108 | % plot k-spaces 109 | % time_axis=(1:(size(ktraj,2)))*sys.gradRasterTime; 110 | % figure; plot(time_axis, ktraj'); % plot the entire k-space trajectory 111 | % hold; plot(t_adc,ktraj_adc(1,:),'.'); % and sampling points on the kx-axis 112 | % figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D plot 113 | % axis('equal'); % enforce aspect ratio for the correct trajectory display 114 | % hold;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 115 | 116 | %% very optional step, slow but useful for testing during development e.g. for the real TE, TR or for staying within slewrate limits 117 | rep = seq.testReport; 118 | fprintf([rep{:}]); 119 | 120 | %% listen to the sequence 121 | seq.sound(); 122 | -------------------------------------------------------------------------------- /02_basic_gradient_echo/seq/s03_GRE_tutorial_step2.m: -------------------------------------------------------------------------------- 1 | % Define FOV and resolution 2 | fov = 256e-3; 3 | sliceThickness = 5e-3; 4 | Nx = 256; 5 | Ny = Nx; 6 | 7 | % Define sequence parameters 8 | TE = 8e-3; 9 | TR = 22e-3; 10 | alpha=30; 11 | 12 | % set system limits 13 | sys = mr.opts('MaxGrad',12,'GradUnit','mT/m',... 14 | 'MaxSlew',100,'SlewUnit','T/m/s',... 15 | 'rfRingdownTime', 20e-6, 'rfDeadtime', 100e-6); 16 | 17 | % Create a new sequence object 18 | seq=mr.Sequence(sys); 19 | 20 | % Create slice selective alpha-pulse and corresponding gradients 21 | [rf, gz, gzReph] = mr.makeSincPulse(alpha*pi/180, 'Duration', 4e-3,... 22 | 'SliceThickness', sliceThickness, 'apodization', 0.5,'timeBwProduct', 4, ... 23 | 'system' ,sys, 'use', 'excitation'); 24 | 25 | % Define other gradients and ADC events 26 | deltak = 1/fov; % Pulseq toolbox defaults to k-space units of m^-1 27 | gx = mr.makeTrapezoid('x', 'FlatArea', Nx*deltak, 'FlatTime', 6.4e-3); 28 | adc = mr.makeAdc(Nx, 'Duration', gx.flatTime, 'Delay', gx.riseTime); 29 | gxPre = mr.makeTrapezoid('x', 'Area', -gx.area/2, 'Duration', 2e-3); 30 | phaseAreas = ((0:Ny-1)-Ny/2)*deltak; 31 | 32 | 33 | % Calculate timing 34 | delayTE = round((TE - mr.calcDuration(gxPre) - mr.calcDuration(gz)/2 ... 35 | - mr.calcDuration(gx)/2)/seq.gradRasterTime)*seq.gradRasterTime; 36 | delayTR = round((TR - mr.calcDuration(gxPre) - mr.calcDuration(gz) ... 37 | - mr.calcDuration(gx) - delayTE)/seq.gradRasterTime)*seq.gradRasterTime; 38 | 39 | spoilArea=4*gx.area(); % 4 "looks" good 40 | % Add spoilers in read, refocus in phase and spoiler in slice 41 | gxPost = mr.makeTrapezoid('x', 'Area', spoilArea, 'system', sys); % we pass 'system' here to calculate shortest time gradient 42 | gyPost = mr.makeTrapezoid('y', 'Area', -max(phaseAreas(:)), 'Duration', 2e-3); 43 | gzPost = mr.makeTrapezoid('z', 'Area', spoilArea, 'system', sys); 44 | 45 | delayTR = delayTR - mr.calcDuration(gxPost, gyPost, gzPost); 46 | 47 | % Loop over phase encodes and define sequence blocks 48 | for i=1:Ny 49 | seq.addBlock(rf, gz); 50 | gyPre = mr.makeTrapezoid('y', 'Area', phaseAreas(i), 'Duration', 2e-3); 51 | seq.addBlock(gxPre, gyPre, gzReph); 52 | seq.addBlock(mr.makeDelay(delayTE)); 53 | seq.addBlock(gx, adc); 54 | gyPost = mr.makeTrapezoid('y', 'Area', -gyPre.area, 'Duration', 2e-3); 55 | % Add spoilers in read and slice and may be in phase 56 | seq.addBlock(gxPost, gyPost, gzPost); 57 | seq.addBlock(mr.makeDelay(delayTR)); 58 | end 59 | 60 | % check whether the timing of the sequence is correct 61 | [ok, error_report]=seq.checkTiming; 62 | 63 | if (ok) 64 | fprintf('Timing check passed successfully\n'); 65 | else 66 | fprintf('Timing check failed! Error listing follows:\n'); 67 | fprintf([error_report{:}]); 68 | fprintf('\n'); 69 | end 70 | 71 | % export definitions 72 | seq.setDefinition('FOV', [fov fov sliceThickness]); 73 | seq.setDefinition('Name', 'DEMO_gre2'); 74 | 75 | seq.write(['DEMO_gre2.seq']) % Write to pulseq file 76 | 77 | seq.plot('timeRange', [0 2*TR]) 78 | 79 | % do not run the rest of the script automatically 80 | return 81 | 82 | %% plot gradients to check for gaps and optimality of the timing 83 | wave_data=seq.waveforms_and_times(); 84 | % plot the entire gradient shape 85 | figure; plot(wave_data{1}(1,:),wave_data{1}(2,:)); xlabel('time /s'); ylabel('gradient /(Hz/m)'); 86 | hold on; plot(wave_data{2}(1,:),wave_data{2}(2,:)); 87 | plot(wave_data{3}(1,:),wave_data{3}(2,:)); 88 | legend('G_x', 'G_y', 'G_z'); 89 | 90 | %% calculate k-space trajectory 91 | 92 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 93 | 94 | % plot k-spaces 95 | figure; plot(t_ktraj, ktraj'); % plot the entire k-space trajectory 96 | hold on; plot(t_adc,ktraj_adc(1,:),'.'); % and sampling points on the kx-axis 97 | title('k-space vector components as functions of time'); 98 | legend('k_x', 'k_y', 'k_z'); 99 | 100 | figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D plot 101 | hold on;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 102 | axis('equal'); % enforce aspect ratio for the correct trajectory display 103 | title('k-space trajectory (k_x/k_y)'); 104 | 105 | %% 106 | 107 | % [ktraj_adc, ktraj, t_excitation, t_refocusing, t_adc] = seq.calculateKspace(); 108 | 109 | % plot k-spaces 110 | % time_axis=(1:(size(ktraj,2)))*sys.gradRasterTime; 111 | % figure; plot(time_axis, ktraj'); % plot the entire k-space trajectory 112 | % hold; plot(t_adc,ktraj_adc(1,:),'.'); % and sampling points on the kx-axis 113 | % figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D plot 114 | % axis('equal'); % enforce aspect ratio for the correct trajectory display 115 | % hold;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 116 | 117 | %% very optional step, slow but useful for testing during development e.g. for the real TE, TR or for staying within slewrate limits 118 | rep = seq.testReport; 119 | fprintf([rep{:}]); 120 | 121 | %% listen to the sequence 122 | seq.sound(); 123 | -------------------------------------------------------------------------------- /02_basic_gradient_echo/seq/s04_GRE_tutorial_step3.m: -------------------------------------------------------------------------------- 1 | % Define FOV and resolution 2 | fov = 256e-3; 3 | sliceThickness = 5e-3; 4 | Nx = 256; 5 | Ny = Nx; 6 | 7 | % Define sequence parameters 8 | TE = 8e-3; 9 | TR = 22e-3; 10 | alpha=30; 11 | 12 | % set system limits 13 | sys = mr.opts('MaxGrad',12,'GradUnit','mT/m',... 14 | 'MaxSlew',100,'SlewUnit','T/m/s',... 15 | 'rfRingdownTime', 20e-6, 'rfDeadtime', 100e-6); 16 | 17 | % Create a new sequence object 18 | seq=mr.Sequence(sys); 19 | 20 | % Create slice selective alpha-pulse and corresponding gradients 21 | [rf, gz, gzReph] = mr.makeSincPulse(alpha*pi/180, 'Duration', 4e-3,... 22 | 'SliceThickness', sliceThickness, 'apodization', 0.5,'timeBwProduct', 4, ... 23 | 'system' ,sys, 'use', 'excitation'); 24 | 25 | % Define other gradients and ADC events 26 | deltak = 1/fov; % Pulseq toolbox defaults to k-space units of m^-1 27 | gx = mr.makeTrapezoid('x', 'FlatArea', Nx*deltak, 'FlatTime', 6.4e-3); 28 | adc = mr.makeAdc(Nx, 'Duration', gx.flatTime, 'Delay', gx.riseTime); 29 | gxPre = mr.makeTrapezoid('x', 'Area', -gx.area/2, 'Duration', 2e-3); 30 | phaseAreas = ((0:Ny-1)-Ny/2)*deltak; 31 | 32 | 33 | % Calculate timing 34 | delayTE = round((TE - mr.calcDuration(gxPre) - mr.calcDuration(gz)/2 ... 35 | - mr.calcDuration(gx)/2)/seq.gradRasterTime)*seq.gradRasterTime; 36 | delayTR = round((TR - mr.calcDuration(gxPre) - mr.calcDuration(gz) ... 37 | - mr.calcDuration(gx) - delayTE)/seq.gradRasterTime)*seq.gradRasterTime; 38 | 39 | spoilArea=4*gx.area(); % 4 "looks" good 40 | % Add spoilers in read, refocus in phase and spoiler in slice 41 | gxPost = mr.makeTrapezoid('x', 'Area', spoilArea, 'system', sys); % we pass 'system' here to calculate shortest time gradient 42 | gyPost = mr.makeTrapezoid('y', 'Area', -max(phaseAreas(:)), 'Duration', 2e-3); 43 | gzPost = mr.makeTrapezoid('z', 'Area', spoilArea, 'system', sys); 44 | 45 | delayTR = delayTR - mr.calcDuration(gxPost, gyPost, gzPost); 46 | 47 | % Loop over phase encodes and define sequence blocks 48 | for i=1:Ny 49 | % Vary RF phase quasi-randomly 50 | rand_phase = mod(117*(i^2 + i + 2), 360)*pi/180; 51 | [rf, gz] = mr.makeSincPulse(alpha*pi/180, 'Duration', 4e-3,... 52 | 'SliceThickness', 5e-3, ... 53 | 'apodization', 0.5, ... 54 | 'timeBwProduct', 4, ... 55 | 'system', sys, ... 56 | 'phaseOffset', rand_phase, ... 57 | 'use', 'excitation'); 58 | seq.addBlock(rf, gz); 59 | gyPre = mr.makeTrapezoid('y', 'Area', phaseAreas(i), 'Duration', 2e-3); 60 | seq.addBlock(gxPre, gyPre, gzReph); 61 | seq.addBlock(mr.makeDelay(delayTE)); 62 | seq.addBlock(gx, adc); 63 | gyPost = mr.makeTrapezoid('y', 'Area', -gyPre.area, 'Duration', 2e-3); 64 | % Add spoilers in read and slice and may be in phase 65 | seq.addBlock(gxPost, gyPost, gzPost); 66 | seq.addBlock(mr.makeDelay(delayTR)); 67 | end 68 | 69 | % check whether the timing of the sequence is correct 70 | [ok, error_report]=seq.checkTiming; 71 | 72 | if (ok) 73 | fprintf('Timing check passed successfully\n'); 74 | else 75 | fprintf('Timing check failed! Error listing follows:\n'); 76 | fprintf([error_report{:}]); 77 | fprintf('\n'); 78 | end 79 | 80 | % export definitions 81 | seq.setDefinition('FOV', [fov fov sliceThickness]); 82 | seq.setDefinition('Name', 'DEMO_gre3'); 83 | 84 | seq.write(['DEMO_gre3.seq']) % Write to pulseq file 85 | 86 | seq.plot('timeRange', [0 2*TR]) 87 | 88 | % do not run the rest of the script automatically 89 | return 90 | 91 | %% plot gradients to check for gaps and optimality of the timing 92 | wave_data=seq.waveforms_and_times(); 93 | % plot the entire gradient shape 94 | figure; plot(wave_data{1}(1,:),wave_data{1}(2,:)); xlabel('time /s'); ylabel('gradient /(Hz/m)'); 95 | hold on; plot(wave_data{2}(1,:),wave_data{2}(2,:)); 96 | plot(wave_data{3}(1,:),wave_data{3}(2,:)); 97 | legend('G_x', 'G_y', 'G_z'); 98 | 99 | %% calculate k-space trajectory 100 | 101 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 102 | 103 | % plot k-spaces 104 | figure; plot(t_ktraj, ktraj'); % plot the entire k-space trajectory 105 | hold on; plot(t_adc,ktraj_adc(1,:),'.'); % and sampling points on the kx-axis 106 | title('k-space vector components as functions of time'); 107 | legend('k_x', 'k_y', 'k_z'); 108 | 109 | figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D plot 110 | hold on;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 111 | axis('equal'); % enforce aspect ratio for the correct trajectory display 112 | title('k-space trajectory (k_x/k_y)'); 113 | 114 | %% 115 | 116 | % [ktraj_adc, ktraj, t_excitation, t_refocusing, t_adc] = seq.calculateKspace(); 117 | 118 | % plot k-spaces 119 | % time_axis=(1:(size(ktraj,2)))*sys.gradRasterTime; 120 | % figure; plot(time_axis, ktraj'); % plot the entire k-space trajectory 121 | % hold; plot(t_adc,ktraj_adc(1,:),'.'); % and sampling points on the kx-axis 122 | % figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D plot 123 | % axis('equal'); % enforce aspect ratio for the correct trajectory display 124 | % hold;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 125 | 126 | %% very optional step, slow but useful for testing during development e.g. for the real TE, TR or for staying within slewrate limits 127 | rep = seq.testReport; 128 | fprintf([rep{:}]); 129 | 130 | %% listen to the sequence 131 | seq.sound(); 132 | -------------------------------------------------------------------------------- /02_basic_gradient_echo/seq/s05_GRE_tutorial_step4.m: -------------------------------------------------------------------------------- 1 | % Define FOV and resolution 2 | fov = 256e-3; 3 | sliceThickness = 5e-3; 4 | Nx = 256; 5 | Ny = Nx; 6 | 7 | % Define sequence parameters 8 | TE = 8e-3; 9 | TR = 22e-3; 10 | alpha=30; 11 | 12 | % set system limits 13 | sys = mr.opts('MaxGrad',12,'GradUnit','mT/m',... 14 | 'MaxSlew',100,'SlewUnit','T/m/s',... 15 | 'rfRingdownTime', 20e-6, 'rfDeadtime', 100e-6); 16 | 17 | % Create a new sequence object 18 | seq=mr.Sequence(sys); 19 | 20 | % Create slice selective alpha-pulse and corresponding gradients 21 | [rf, gz, gzReph] = mr.makeSincPulse(alpha*pi/180, 'Duration', 4e-3,... 22 | 'SliceThickness', sliceThickness, 'apodization', 0.5,'timeBwProduct', 4, ... 23 | 'system' ,sys, 'use', 'excitation'); 24 | 25 | % Define other gradients and ADC events 26 | deltak = 1/fov; % Pulseq toolbox defaults to k-space units of m^-1 27 | gx = mr.makeTrapezoid('x', 'FlatArea', Nx*deltak, 'FlatTime', 6.4e-3); 28 | adc = mr.makeAdc(Nx, 'Duration', gx.flatTime, 'Delay', gx.riseTime); 29 | gxPre = mr.makeTrapezoid('x', 'Area', -gx.area/2, 'Duration', 2e-3); 30 | phaseAreas = ((0:Ny-1)-Ny/2)*deltak; 31 | 32 | 33 | % Calculate timing 34 | delayTE = round((TE - mr.calcDuration(gxPre) - mr.calcDuration(gz)/2 ... 35 | - mr.calcDuration(gx)/2)/seq.gradRasterTime)*seq.gradRasterTime; 36 | delayTR = round((TR - mr.calcDuration(gxPre) - mr.calcDuration(gz) ... 37 | - mr.calcDuration(gx) - delayTE)/seq.gradRasterTime)*seq.gradRasterTime; 38 | 39 | spoilArea=4*gx.area(); % 4 "looks" good 40 | % Add spoilers in read, refocus in phase and spoiler in slice 41 | gxPost = mr.makeTrapezoid('x', 'Area', spoilArea, 'system', sys); % we pass 'system' here to calculate shortest time gradient 42 | gyPost = mr.makeTrapezoid('y', 'Area', -max(phaseAreas(:)), 'Duration', 2e-3); 43 | gzPost = mr.makeTrapezoid('z', 'Area', spoilArea, 'system', sys); 44 | 45 | delayTR = delayTR - mr.calcDuration(gxPost, gyPost, gzPost); 46 | 47 | % Loop over phase encodes and define sequence blocks 48 | for i=1:Ny 49 | % Vary RF phase quasi-randomly 50 | rand_phase = mod(117*(i^2 + i + 2), 360)*pi/180; 51 | [rf, gz] = mr.makeSincPulse(alpha*pi/180, 'Duration', 4e-3,... 52 | 'SliceThickness', 5e-3, ... 53 | 'apodization', 0.5, ... 54 | 'timeBwProduct', 4, ... 55 | 'system', sys, ... 56 | 'phaseOffset', rand_phase, ... 57 | 'use', 'excitation'); 58 | seq.addBlock(rf, gz); 59 | gyPre = mr.makeTrapezoid('y', 'Area', phaseAreas(i), 'Duration', 2e-3); 60 | seq.addBlock(gxPre, gyPre, gzReph); 61 | seq.addBlock(mr.makeDelay(delayTE)); 62 | % Make receiver phase follow transmitter phase 63 | adc = mr.makeAdc(Nx, 'Duration', gx.flatTime,... 64 | 'Delay', gx.riseTime,... 65 | 'phaseOffset', rand_phase); 66 | seq.addBlock(gx, adc); 67 | gyPost = mr.makeTrapezoid('y', 'Area', -gyPre.area, 'Duration', 2e-3); 68 | % Add spoilers in read and slice and may be in phase 69 | seq.addBlock(gxPost, gyPost, gzPost); 70 | seq.addBlock(mr.makeDelay(delayTR)); 71 | end 72 | 73 | % check whether the timing of the sequence is correct 74 | [ok, error_report]=seq.checkTiming; 75 | 76 | if (ok) 77 | fprintf('Timing check passed successfully\n'); 78 | else 79 | fprintf('Timing check failed! Error listing follows:\n'); 80 | fprintf([error_report{:}]); 81 | fprintf('\n'); 82 | end 83 | 84 | % export definitions 85 | seq.setDefinition('FOV', [fov fov sliceThickness]); 86 | seq.setDefinition('Name', 'DEMO_gre4'); 87 | 88 | seq.write(['DEMO_gre4.seq']) % Write to pulseq file 89 | 90 | seq.plot('timeRange', [0 2*TR]) 91 | 92 | % do not run the rest of the script automatically 93 | return 94 | 95 | %% plot gradients to check for gaps and optimality of the timing 96 | wave_data=seq.waveforms_and_times(); 97 | % plot the entire gradient shape 98 | figure; plot(wave_data{1}(1,:),wave_data{1}(2,:)); xlabel('time /s'); ylabel('gradient /(Hz/m)'); 99 | hold on; plot(wave_data{2}(1,:),wave_data{2}(2,:)); 100 | plot(wave_data{3}(1,:),wave_data{3}(2,:)); 101 | legend('G_x', 'G_y', 'G_z'); 102 | 103 | %% calculate k-space trajectory 104 | 105 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 106 | 107 | % plot k-spaces 108 | figure; plot(t_ktraj, ktraj'); % plot the entire k-space trajectory 109 | hold on; plot(t_adc,ktraj_adc(1,:),'.'); % and sampling points on the kx-axis 110 | title('k-space vector components as functions of time'); 111 | legend('k_x', 'k_y', 'k_z'); 112 | 113 | figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D plot 114 | hold on;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 115 | axis('equal'); % enforce aspect ratio for the correct trajectory display 116 | title('k-space trajectory (k_x/k_y)'); 117 | 118 | %% 119 | 120 | % [ktraj_adc, ktraj, t_excitation, t_refocusing, t_adc] = seq.calculateKspace(); 121 | 122 | % plot k-spaces 123 | % time_axis=(1:(size(ktraj,2)))*sys.gradRasterTime; 124 | % figure; plot(time_axis, ktraj'); % plot the entire k-space trajectory 125 | % hold; plot(t_adc,ktraj_adc(1,:),'.'); % and sampling points on the kx-axis 126 | % figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D plot 127 | % axis('equal'); % enforce aspect ratio for the correct trajectory display 128 | % hold;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 129 | 130 | %% very optional step, slow but useful for testing during development e.g. for the real TE, TR or for staying within slewrate limits 131 | rep = seq.testReport; 132 | fprintf([rep{:}]); 133 | 134 | %% listen to the sequence 135 | seq.sound(); 136 | -------------------------------------------------------------------------------- /02_basic_gradient_echo/seq/s06_GRE_tutorial_step5.m: -------------------------------------------------------------------------------- 1 | % Define FOV and resolution 2 | fov = 256e-3; 3 | sliceThickness = 5e-3; 4 | Nx = 256; 5 | Ny = Nx; 6 | 7 | % Define sequence parameters 8 | TE = 8e-3; 9 | TR = 22e-3; 10 | alpha=30; 11 | 12 | % set system limits 13 | sys = mr.opts('MaxGrad',12,'GradUnit','mT/m',... 14 | 'MaxSlew',100,'SlewUnit','T/m/s',... 15 | 'rfRingdownTime', 20e-6, 'rfDeadtime', 100e-6); 16 | 17 | % Create a new sequence object 18 | seq=mr.Sequence(sys); 19 | 20 | % Create slice selective alpha-pulse and corresponding gradients 21 | [rf, gz, gzReph] = mr.makeSincPulse(alpha*pi/180, 'Duration', 4e-3,... 22 | 'SliceThickness', sliceThickness, 'apodization', 0.5,'timeBwProduct', 4, ... 23 | 'system' ,sys, 'use', 'excitation'); 24 | 25 | % Define other gradients and ADC events 26 | deltak = 1/fov; % Pulseq toolbox defaults to k-space units of m^-1 27 | gx = mr.makeTrapezoid('x', 'FlatArea', Nx*deltak, 'FlatTime', 6.4e-3); 28 | adc = mr.makeAdc(Nx, 'Duration', gx.flatTime, 'Delay', gx.riseTime); 29 | gxPre = mr.makeTrapezoid('x', 'Area', -gx.area/2, 'Duration', 2e-3); 30 | phaseAreas = ((0:Ny-1)-Ny/2)*deltak; 31 | 32 | 33 | % Calculate timing 34 | delayTE = round((TE - mr.calcDuration(gxPre) - mr.calcDuration(gz)/2 ... 35 | - mr.calcDuration(gx)/2)/seq.gradRasterTime)*seq.gradRasterTime; 36 | delayTR = round((TR - mr.calcDuration(gxPre) - mr.calcDuration(gz) ... 37 | - mr.calcDuration(gx) - delayTE)/seq.gradRasterTime)*seq.gradRasterTime; 38 | 39 | spoilArea=4*gx.area(); % 4 "looks" good 40 | % Add spoilers in read, refocus in phase and spoiler in slice 41 | gxPost = mr.makeTrapezoid('x', 'Area', spoilArea, 'system', sys); % we pass 'system' here to calculate shortest time gradient 42 | gyPost = mr.makeTrapezoid('y', 'Area', -max(phaseAreas(:)), 'Duration', 2e-3); 43 | gzPost = mr.makeTrapezoid('z', 'Area', spoilArea, 'system', sys); 44 | 45 | delayTR = delayTR - mr.calcDuration(gxPost, gyPost, gzPost); 46 | 47 | % Loop over phase encodes and define sequence blocks 48 | for i=-30:Ny % dummy scans 49 | % Vary RF phase quasi-randomly 50 | rand_phase = mod(117*(i^2 + i + 2), 360)*pi/180; 51 | [rf, gz] = mr.makeSincPulse(alpha*pi/180, 'Duration', 4e-3,... 52 | 'SliceThickness', 5e-3, ... 53 | 'apodization', 0.5, ... 54 | 'timeBwProduct', 4, ... 55 | 'system', sys, ... 56 | 'phaseOffset', rand_phase, ... 57 | 'use', 'excitation'); 58 | seq.addBlock(rf, gz); 59 | if (i>0) % negative or zero index -- dummy scans 60 | gyPre = mr.makeTrapezoid('y', 'Area', phaseAreas(i), 'Duration', 2e-3); 61 | else 62 | gyPre = mr.makeTrapezoid('y', 'Area', 0, 'Duration', 2e-3); 63 | end 64 | seq.addBlock(gxPre, gyPre, gzReph); 65 | seq.addBlock(mr.makeDelay(delayTE)); 66 | % Make receiver phase follow transmitter phase 67 | adc = mr.makeAdc(Nx, 'Duration', gx.flatTime,... 68 | 'Delay', gx.riseTime,... 69 | 'phaseOffset', rand_phase); 70 | if (i>0) % negative index -- dummy scans 71 | seq.addBlock(gx, adc); 72 | else 73 | seq.addBlock(gx); 74 | end 75 | gyPost = mr.makeTrapezoid('y', 'Area', -gyPre.area, 'Duration', 2e-3); 76 | % Add spoilers in read and slice and may be in phase 77 | seq.addBlock(gxPost, gyPost, gzPost); 78 | seq.addBlock(mr.makeDelay(delayTR)); 79 | end 80 | 81 | % check whether the timing of the sequence is correct 82 | [ok, error_report]=seq.checkTiming; 83 | 84 | if (ok) 85 | fprintf('Timing check passed successfully\n'); 86 | else 87 | fprintf('Timing check failed! Error listing follows:\n'); 88 | fprintf([error_report{:}]); 89 | fprintf('\n'); 90 | end 91 | 92 | % export definitions 93 | seq.setDefinition('FOV', [fov fov sliceThickness]); 94 | seq.setDefinition('Name', 'DEMO_gre5'); 95 | 96 | seq.write(['DEMO_gre5.seq']) % Write to pulseq file 97 | 98 | seq.plot('timeRange', [0 2*TR]) 99 | 100 | % do not run the rest of the script automatically 101 | return 102 | 103 | %% plot gradients to check for gaps and optimality of the timing 104 | wave_data=seq.waveforms_and_times(); 105 | % plot the entire gradient shape 106 | figure; plot(wave_data{1}(1,:),wave_data{1}(2,:)); xlabel('time /s'); ylabel('gradient /(Hz/m)'); 107 | hold on; plot(wave_data{2}(1,:),wave_data{2}(2,:)); 108 | plot(wave_data{3}(1,:),wave_data{3}(2,:)); 109 | legend('G_x', 'G_y', 'G_z'); 110 | 111 | %% calculate k-space trajectory 112 | 113 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 114 | 115 | % plot k-spaces 116 | figure; plot(t_ktraj, ktraj'); % plot the entire k-space trajectory 117 | hold on; plot(t_adc,ktraj_adc(1,:),'.'); % and sampling points on the kx-axis 118 | title('k-space vector components as functions of time'); 119 | legend('k_x', 'k_y', 'k_z'); 120 | 121 | figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D plot 122 | hold on;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 123 | axis('equal'); % enforce aspect ratio for the correct trajectory display 124 | title('k-space trajectory (k_x/k_y)'); 125 | 126 | %% 127 | 128 | % [ktraj_adc, ktraj, t_excitation, t_refocusing, t_adc] = seq.calculateKspace(); 129 | 130 | % plot k-spaces 131 | % time_axis=(1:(size(ktraj,2)))*sys.gradRasterTime; 132 | % figure; plot(time_axis, ktraj'); % plot the entire k-space trajectory 133 | % hold; plot(t_adc,ktraj_adc(1,:),'.'); % and sampling points on the kx-axis 134 | % figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D plot 135 | % axis('equal'); % enforce aspect ratio for the correct trajectory display 136 | % hold;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 137 | 138 | %% very optional step, slow but useful for testing during development e.g. for the real TE, TR or for staying within slewrate limits 139 | rep = seq.testReport; 140 | fprintf([rep{:}]); 141 | 142 | %% listen to the sequence 143 | seq.sound(); 144 | -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/README.md: -------------------------------------------------------------------------------- 1 | ![Pulseq live demo at MRI Together banner](./doc/mri_together_esmrmb_banner.png) 2 | # Pulseq tutorial "From GRE to EPI" 3 | 4 | Welcome to the "From GRE to EPI" tutorial repository! This was initially developed for the live Pulseq software demonstration and hands-on session at **MRI Together 2021** on-line conference. 5 | 6 | This tutorial introduces the way to establish an Echo-Planar Imaging (EPI) 7 | sequence based on a GRE sequence via multi-echo and segmented GRE sequences. 8 | 9 | [Handout materials](./doc/Handout.pdf) that accompanied the original session are available in the PDF format containing the slides from the presentation with some additional material and explanations. In particular, the second part of the document (page 18 and on) describes the code examples. Please keep in mind that these examples were specifically developed for the demo, so you might find some useful information there, even if you are familiar with the previous versions of Pulseq. 10 | 11 | Additionally, the slide deck entitled [11_from_GRE_to_EPI.pdf](./doc/11_from_GRE_to_EPI.pdf) shows sequence diagrams of all steps and visualises the changes at each step. 12 | 13 | ***s01\_GradientEcho*** 14 | 15 | ***s01*** is a single-echo GRE sequence with Ny\*numTE phase encodes. 16 | Its echo time (TE) cycles by the defined TEs (\[4 9 15\] \* 1e-3). numTE 17 | is the number of the defined TEs. Note: the mr.align function is very 18 | useful to set delays of events within a block to achieve desirable 19 | alignment. 20 | 21 | ***s02\_MultiGradientEcho*** 22 | 23 | ***s02*** is a monopolar multi-echo GRE sequence. The timing is 24 | calculated with the aid of helperT. gxFlyBack gradient is used to 25 | rephase the readout gradient. 26 | 27 | ***s03\_BipolarMultiGradientEcho*** 28 | 29 | ***s03*** is a bipolar multi-echo GRE sequence. It eliminates gxFlyBack 30 | gradients by reversing the polarity of the even readouts. 31 | 32 | ***s04a\_SegmentedGradientEcho*** 33 | 34 | ***s04a*** is a readout-segmented single-echo GRE sequence. Instead of 35 | sampling the k-space for one readout during each TR, it samples the 36 | k-space with multiple readouts during each TR (i.e. segmented readouts). 37 | Bipolar readout gradients are used to avoid time loss due to additional 38 | fly-back gradients. The area of phase-encoding (PE) gradients 39 | (pre-dephasing and blip) is determined based on the number of segmented 40 | readouts (nSeg). 41 | 42 | ***s04b\_SegmentedGradientEcho*** 43 | 44 | In s04b, the splitGradientAt function is used to split the gyBlip 45 | gradient of ***s04a*** into two parts, each in a separate block, which 46 | reduces the time interval between two adjacent segmented readouts. In 47 | the first segmented readout block, the first part of gyBlip is added 48 | after the readout gradient. In inner segmented readout blocks, the 49 | second and first parts of gyBlip are combined and added before and after 50 | the readout gradient. In the last segmented readout block, the second 51 | part of gyBlip is added before the readout gradient. 52 | 53 | In case the gyBlip.riseTime is shorter than the fallTime of the former 54 | block, gyBlip.delay is increased, and thus the peak of gyBlip is moved 55 | to the edge of the former block. In case gyBlip.riseTime is longer than 56 | the fallTime of the former block, a delay is added to the later readout 57 | gradient, such that the last point of gyBlip hits the last point of the 58 | ramp-up of the later readout gradient. 59 | 60 | ***s04c\_SegmentedGradientEcho*** 61 | 62 | ***s04c*** increases the nSeg of ***s04b*** from 5 to Ny, such that 63 | ***s04c*** traverses the whole k-space in one segmented scan (i.e. an 64 | improvised EPI scan). 65 | 66 | ***s05\_EchoPlanarImaging*** 67 | 68 | ***s05*** is a 2D EPI sequence with the shortest timing. It contains 69 | Ny+3 blocks. 70 | 71 | The mr.makeDigitalOutputPulse function defines the output trigger to 72 | play out with every slice excitation. 73 | 74 | The shortest timing is achieved by using maximum slew rate and ramp 75 | sampling. gyBlip is split into two parts and distributed to two adjacent 76 | blocks. The dead times of the readout trapezoid gradient are at the 77 | beginning and the end (align with two parts of gyBlip), each equal to 78 | half of gyBlip.Duration. The readout gradient is first calculated based 79 | on the maximum slew rate (with consideration of the dead times). Then, 80 | its amplitude is scaled down to fix the area to be Nx\*deltak. 81 | 82 | The ADC dwell time on the readout flat-top is calculated based on 83 | adcDwellNyquist=deltak/gx.amplitude. The dwell time is rounded down to 84 | system.adcRasterTime. Note that the number of ADC samples on Siemens 85 | should be divisible by 4. In addition, the ADC should be aligned with 86 | respect to the readout gradient: *both Pulseq and Siemens define the ADC 87 | samples to happen in the centre of the dwell period*. 88 | 89 | ***s06\_EPI\_SingleTraj*** 90 | 91 | ***s06*** is a 2D EPI sequence constructed from a single arbitrary 92 | trajectory. It is based on ***s05*** and contains 4 blocks. 93 | 94 | A dummy sequence object (seq\_d) creates and exports the single EPI 95 | trajectory in xy-plane with Ny phase encoding steps. A single ADC object 96 | is created to sample from the start to the end of the single trajectory 97 | (excluding the ADC dead times: 98 | adcDur=seq\_d.duration-2\*sys.adcDeadTime). A real sequence object 99 | (seq\_r) is created to combine the slice-selective excitation, Gz 100 | rephasing and Gx and Gy dephasing, the single EPI trajectory and the 101 | single ADC, and Gz spoiler together, for a total of 4 blocks. 102 | 103 | ## Quick instructions 104 | 105 | Source code of the demo sequences and reconstruction scripts is the core of this repository. Please download the files to your computer and make them available to Matlab (e.g. by saving them in a subdirectory inside your Pulseq-Matlab installation and adding them to the Matlab's path). There are three sub-directories: 106 | 107 | * seq : contains example pulse sequences specifically prepared for this demo 108 | * recon : contains the reconstruction scripts tested with the above sequences 109 | * data: raw MR data and sequences in Pulseq format 110 | 111 | ## Quick links 112 | 113 | Pulseq Matlab repository: 114 | https://github.com/pulseq/pulseq 115 | 116 | ## How to follow 117 | 118 | We strongly recommend using a text compare tool like *meld* (see this [Wikipedia page](https://en.wikipedia.org/wiki/Meld_(software)) and compare sequences from subsequent steps to visualise the respective steps. 119 | 120 | ## Further links 121 | 122 | Check out the main *Pulseq* repository at https://github.com/pulseq/pulseq and familarising yourself with the code, example sequences and reconstruction scripts (see 123 | [pulseq/matlab/demoSeq](https://github.com/pulseq/pulseq/tree/master/matlab/demoSeq) and [pulseq/matlab/demoRecon](https://github.com/pulseq/pulseq/tree/master/matlab/demoRecon)). If you already use Pulseq, consider updating to the current version. 124 | 125 | -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/data/01_GRE.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/11_from_GRE_to_EPI/data/01_GRE.dat -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/data/02_GRE_3echoes.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/11_from_GRE_to_EPI/data/02_GRE_3echoes.dat -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/data/03_GRE_3echoes_bipolar.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/11_from_GRE_to_EPI/data/03_GRE_3echoes_bipolar.dat -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/data/04a_GRE_3seg.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/11_from_GRE_to_EPI/data/04a_GRE_3seg.dat -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/data/04b_GRE_5seg_fast.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/11_from_GRE_to_EPI/data/04b_GRE_5seg_fast.dat -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/data/04c_improvised_EPI.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/11_from_GRE_to_EPI/data/04c_improvised_EPI.dat -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/data/04c_improvised_EPI_noPE.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/11_from_GRE_to_EPI/data/04c_improvised_EPI_noPE.dat -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/data/04c_improvised_EPI_other_phantom_other scanner.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/11_from_GRE_to_EPI/data/04c_improvised_EPI_other_phantom_other scanner.dat -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/data/04c_improvised_EPI_other_phantom_other scanner_noPE.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/11_from_GRE_to_EPI/data/04c_improvised_EPI_other_phantom_other scanner_noPE.dat -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/data/05_EPI.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/11_from_GRE_to_EPI/data/05_EPI.dat -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/data/05_EPI_noPE.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/11_from_GRE_to_EPI/data/05_EPI_noPE.dat -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/data/06_EPI_singleTraj.dat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/11_from_GRE_to_EPI/data/06_EPI_singleTraj.dat -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/doc/11_from_GRE_to_EPI.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/11_from_GRE_to_EPI/doc/11_from_GRE_to_EPI.pdf -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/doc/11_from_GRE_to_EPI.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/11_from_GRE_to_EPI/doc/11_from_GRE_to_EPI.pptx -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/doc/Handout.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/11_from_GRE_to_EPI/doc/Handout.pdf -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/doc/mri_together_esmrmb_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/11_from_GRE_to_EPI/doc/mri_together_esmrmb_banner.png -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/recon/r01_2DFFT.m: -------------------------------------------------------------------------------- 1 | % Reconstruction of 2D Cartesian Pulseq data 2 | % provides an example on how data reordering can be detected from the MR 3 | % sequence with almost no additional prior knowledge 4 | % 5 | % it loads Matlab .mat files with the rawdata in the format 6 | % adclen x channels x readouts 7 | % if Matlab .mat file not available it attempt to load Siemens .dat (which needs mapVBVD in the path) 8 | % but first it seeks the accompanying .seq file with the same name to interpret 9 | % the data 10 | 11 | %% Load data sorted by name 12 | path='./tutorials/11_from_GRE_to_EPI/data'; % directory to be scanned for data files 13 | nF=1; % the number of the data set to load; 14 | 15 | pattern='*.seq'; 16 | D=dir([path filesep pattern]); 17 | [~,I]=sort(string({D(:).name})); 18 | seq_file_path=[path filesep D(I(nF)).name]; 19 | 20 | % keep basic filename without the extension 21 | [p,n,e] = fileparts(seq_file_path); 22 | basic_file_path=fullfile(p,n); 23 | 24 | %% load raw data 25 | % try loading Matlab data 26 | data_file_path=[basic_file_path '.mat']; 27 | if isfile(data_file_path) 28 | fprintf(['loading `' data_file_path '´ ...\n']); 29 | data_unsorted = load(data_file_path); 30 | if isstruct(data_unsorted) 31 | fn=fieldnames(data_unsorted); 32 | assert(length(fn)==1); % we only expect a single variable 33 | data_unsorted=data_unsorted.(fn{1}); 34 | end 35 | else 36 | % revert to Siemens .dat file 37 | data_file_path=[basic_file_path '.dat']; 38 | fprintf(['loading `' data_file_path '´ ...\n']); 39 | twix_obj = mapVBVD(data_file_path); 40 | if iscell(twix_obj) 41 | data_unsorted = twix_obj{end}.image.unsorted(); 42 | seqHash_twix=twix_obj{end}.hdr.Dicom.tSequenceVariant; 43 | else 44 | data_unsorted = twix_obj.image.unsorted(); 45 | seqHash_twix=twix_obj.hdr.Dicom.tSequenceVariant; 46 | end 47 | if length(seqHash_twix)==32 48 | fprintf(['raw data contain pulseq-file signature ' seqHash_twix '\n']); 49 | end 50 | 51 | end 52 | [adc_len,channels,readouts]=size(data_unsorted); 53 | 54 | %% Load sequence from file 55 | fprintf(['loading `' seq_file_path '´ ...\n']); 56 | seq = mr.Sequence(); % Create a new sequence object 57 | seq.read(seq_file_path,'detectRFuse'); 58 | seqName=seq.getDefinition('Name'); 59 | if ~isempty(seqName), fprintf('sequence name: %s\n',seqName); end 60 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 61 | figure; plot(ktraj(1,:),ktraj(2,:),'b',... 62 | ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % a 2D plot 63 | axis('equal'); title('2D k-space trajectory'); 64 | xlabel('kx / m^-^1'); ylabel('kx / m^-^1'); 65 | %saveas(gcf, [basic_file_path '_kspace.png']); 66 | 67 | %% Analyze the trajectory data (ktraj_adc) 68 | fprintf('analyzing the k-space trajectory ...\n'); 69 | k_extent=max(abs(ktraj_adc),[],2); 70 | k_scale=max(k_extent); 71 | k_threshold=k_scale/5000; 72 | 73 | % detect unused dimensions and delete them 74 | if any(k_extentk_threshold)=NaN; 86 | dk_all_cnt=sum(isfinite(dk_all),2); 87 | dk_all(~isfinite(dk_all))=0; 88 | dk=sum(dk_all,2)./dk_all_cnt; 89 | dk(~isfinite(dk))=0; 90 | [~,k0_ind]=min(sum(ktraj_adc.^2,1)); 91 | kindex=round((ktraj_adc-ktraj_adc(:,k0_ind*ones(1,size(ktraj_adc,2))))./dk(:,ones(1,size(ktraj_adc,2)))); 92 | kindex(~isfinite(kindex))=0; 93 | kindex_min=min(kindex,[],2); 94 | kindex_mat=kindex-kindex_min(:,ones(1,size(ktraj_adc,2)))+1; 95 | kindex_end=max(kindex_mat,[],2); 96 | sampler=zeros(kindex_end'); 97 | repeat=zeros(1,size(ktraj_adc,2)); 98 | for i=1:size(kindex_mat,2) 99 | if (size(kindex_mat,1)==3) 100 | ind=sub2ind(kindex_end,kindex_mat(1,i),kindex_mat(2,i),kindex_mat(3,i)); 101 | else 102 | ind=sub2ind(kindex_end,kindex_mat(1,i),kindex_mat(2,i)); 103 | end 104 | repeat(i)=sampler(ind); 105 | sampler(ind)=repeat(i)+1; 106 | end 107 | if (max(repeat(:))>0) 108 | kindex=[kindex;(repeat+1)]; 109 | kindex_mat=[kindex_mat;(repeat+1)]; 110 | kindex_end=max(kindex_mat,[],2); 111 | end 112 | %figure; plot(kindex(1,:),kindex(2,:),'.-'); 113 | 114 | %% sort the k-space data into the data matrix 115 | % the incoming data order is [kx coils acquisitions] 116 | data_coils_last = permute(data_unsorted, [1, 3, 2]); 117 | data_coils_last = reshape(data_coils_last, [adc_len*readouts, channels]); 118 | 119 | data=zeros([kindex_end' channels]); 120 | if (size(kindex,1)==3) 121 | for i=1:size(kindex,2) 122 | data(kindex_mat(1,i),kindex_mat(2,i),kindex_mat(3,i),:)=data_coils_last(i,:); 123 | end 124 | else 125 | for i=1:size(kindex,2) 126 | data(kindex_mat(1,i),kindex_mat(2,i),:)=data_coils_last(i,:); 127 | end 128 | end 129 | 130 | if size(kindex,1)==3 131 | nImages=size(data,3); 132 | else 133 | nImages=1; 134 | data=reshape(data, [size(data,1) size(data,2) 1 size(data,3)]); % we need a dummy images/slices dimension 135 | end 136 | 137 | % account for the matlab's strange convention with data dimensions 138 | data=flip(data,1); % left-right orientation, works on TRIO 139 | data=flip(data,2); 140 | 141 | %figure; imab(log(abs(data))); title('k-space data'); 142 | 143 | %% Reconstruct coil images 144 | 145 | images = zeros(size(data)); 146 | %figure; 147 | 148 | for ii = 1:channels 149 | images(:,:,:,ii) = ifftshift(ifft2(ifftshift(data(:,:,:,ii)))); % 1.4.0 does not need read inversion 150 | end 151 | 152 | % Phase images (possibly channel-by-channel and echo-by-echo) 153 | %figure;imab(angle(images));colormap('jet'); 154 | %figure;imab(abs(images));colormap('gray'); 155 | 156 | %% Image display with optional sum of squares combination 157 | figure; 158 | if channels>1 159 | sos=abs(sum(images.*conj(images),ndims(images))).^(1/2); 160 | sos=sos./max(sos(:)); 161 | imab(sos); title('reconstructed image(s), sum-of-squares'); 162 | %imwrite(sos, ['img_combined.png'] 163 | else 164 | imab(abs(images)); title('reconstructed image(s)'); 165 | end 166 | colormap('gray'); 167 | saveas(gcf,[basic_file_path '_image_2dfft'],'png'); 168 | 169 | %% reconstruct field map (optional) 170 | 171 | if size(images,3)>=2 172 | cmplx_diff=images(:,:,2,:).*conj(images(:,:,1,:)); 173 | phase_diff_image=angle(sum(cmplx_diff,4)); 174 | figure; 175 | imab(phase_diff_image);colormap('jet'); 176 | title('phase difference(s) echo2-echo1'); 177 | end 178 | 179 | %% gif movie export 180 | 181 | scale=1.2; 182 | filename='fftReconMovie'; 183 | fps=5; 184 | 185 | if size(images,3)>4 186 | 187 | clm=gray(256); 188 | 189 | if channels>1 190 | imex=sos; 191 | else 192 | imex=abs(images); 193 | imex=imex/max(imex(:)); 194 | end 195 | 196 | imex=permute(imex(:,end:-1:1,:),[2,1,3]); 197 | 198 | imind=uint8(scale*imex*(size(clm,1)-1)+1); 199 | imwrite(imind(:,:,1),clm, [filename, '.gif'],'DelayTime',1/fps,'Loopcount',inf); 200 | for i=2:size(imind,3), 201 | imwrite(imind(:,:,i),clm, [filename, '.gif'],'DelayTime',1/fps, 'WriteMode','append'); 202 | end 203 | 204 | end 205 | -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/recon/r03_2DGD.m: -------------------------------------------------------------------------------- 1 | % very basic and crude non-Cartesian recon using griddata() 2 | % 3 | % needs mapVBVD in the path 4 | 5 | %% Load data sorted by name 6 | path='./tutorials/11_from_GRE_to_EPI/data'; % directory to be scanned for data files 7 | nF=12; % the number of the data set to load; you can grid any data with pulseq, but in this tutorial it is only necessary for #12 8 | 9 | pattern='*.seq'; 10 | D=dir([path filesep pattern]); 11 | [~,I]=sort(string({D(:).name})); 12 | seq_file_path=[path filesep D(I(nF)).name]; 13 | 14 | 15 | %% alternatively just provide the path to the .seq file 16 | %seq_file_path='../interpreters/siemens/data_example/gre_example.seq' 17 | %seq_file_path='/data/20211025-AMR/data/2021-10-26-093137.seq'; 18 | 19 | %% Load sequence from the file with the same name as the raw data 20 | fprintf(['loading `' seq_file_path '´ ...\n']); 21 | seq = mr.Sequence(); % Create a new sequence object 22 | seq.read(seq_file_path,'detectRFuse'); 23 | 24 | %% keep basic filename without the extension 25 | [p,n,e] = fileparts(seq_file_path); 26 | basic_file_path=fullfile(p,n); 27 | 28 | %% load the raw data file 29 | data_file_path= [basic_file_path '.mat']; % try to load a matlab file with raw data... 30 | try 31 | fprintf(['loading `' data_file_path '´ ...\n']); 32 | data_unsorted = load(data_file_path); 33 | if isstruct(data_unsorted) 34 | fn=fieldnames(data_unsorted); 35 | assert(length(fn)==1); % we only expect a single variable 36 | data_unsorted=double(data_unsorted.(fn{1})); 37 | end 38 | catch 39 | data_file_path= [basic_file_path '.dat']; % now try to load a raw data file... 40 | fprintf(['falling back to `' data_file_path '´ ...\n']); 41 | twix_obj = mapVBVD(data_file_path); 42 | if iscell(twix_obj) 43 | data_unsorted = double(twix_obj{end}.image.unsorted()); 44 | seqHash_twix=twix_obj{end}.hdr.Dicom.tSequenceVariant; 45 | else 46 | data_unsorted = double(twix_obj.image.unsorted()); 47 | seqHash_twix=twix_obj.hdr.Dicom.tSequenceVariant; 48 | end 49 | if length(seqHash_twix)==32 50 | fprintf(['raw data contain pulseq-file signature ' seqHash_twix '\n']); 51 | end 52 | clear twix_obj 53 | end 54 | 55 | %% calculate k-space trajectory 56 | %[3.5 4 0] % for AMR 2D-UTE 57 | traj_recon_delay=0.0e-6;% [0.527 -1.367 0]; % adjust this parameter to potentially improve resolution & geometric accuracy. It can be calibrated by inverting the spiral revolution dimension and making two images match. for our Prisma and a particular trajectory we found 1.75e-6 58 | grad_offsets=[0 0 0]; 59 | 60 | seq = mr.Sequence(); % Create a new sequence object 61 | seq.read(seq_file_path,'detectRFuse'); 62 | seqName=seq.getDefinition('Name'); 63 | if ~isempty(seqName), fprintf('sequence name: %s\n',seqName); end 64 | %[ktraj_adc, ktraj, t_excitation, t_refocusing, t_adc] = seq.calculateKspace('trajectory_delay', traj_recon_delay); 65 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP('trajectory_delay', traj_recon_delay, 'gradient_offset', grad_offsets); 66 | figure; plot(ktraj(1,:),ktraj(2,:),'b',... 67 | ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % a 2D plot 68 | axis('equal'); 69 | title('2D k-space trajectory'); drawnow; 70 | 71 | %% Define FOV and resolution and simple off-resonance frequency correction 72 | 73 | fov=256e-3; Nx=128; Ny=Nx; % define parameters explicitly 74 | Ns=1; % this is a number of slices (or contrasts) which needs to be specified manually for now 75 | %Na=1; 76 | 77 | def_fov=seq.getDefinition('FOV'); % try to read from definitions 78 | if numel(def_fov) 79 | fov=max(def_fov); 80 | end 81 | %fov=fov*1.5; 82 | deltak=1/fov; 83 | 84 | % or estimate from the k-space trajectory 85 | k_max=max(vecnorm(ktraj_adc)); 86 | Nx=round(k_max/deltak*2); 87 | Ny=Nx; 88 | 89 | os=2; % oversampling factor (we oversample both in image and k-space) 90 | offresonance=0; % global off-resonance in Hz 91 | 92 | %% 93 | rawdata = permute(data_unsorted, [1,3,2]); 94 | adc_len=size(rawdata,1); 95 | readouts=size(rawdata,2); 96 | channels=size(rawdata,3); 97 | 98 | Na=numel(rawdata(:,:,1))/Ns/numel(t_adc); % averages/acquisitions 99 | if Na>1 100 | nTRs=size(rawdata,2)/Na; 101 | rawdata = sum(permute(reshape(rawdata, [size(rawdata,1),size(rawdata,2)/Na,Na,size(rawdata,3)]),[1,2,4,3]),4); 102 | readouts=readouts/Na; 103 | end 104 | 105 | if strcmp('ute_rs',seq.getDefinition('Name')) 106 | % average every 2nd spoke because of the half-rf excitation 107 | ktraj_adc=reshape(ktraj_adc,[3,adc_len,readouts]); 108 | ktraj_adc=ktraj_adc(:,:,1:2:end-1); 109 | t_adc=reshape(t_adc,[1,adc_len,readouts]); 110 | t_adc=t_adc(:,:,1:2:end-1); 111 | %rawdata=rawdata(:,1:2:end-1,:); 112 | rawdata=rawdata(:,1:2:end-1,:)+rawdata(:,2:2:end,:); 113 | readouts=readouts/2; 114 | ktraj_adc=reshape(ktraj_adc,[3,adc_len*readouts]); 115 | t_adc=reshape(t_adc,[1,adc_len*readouts]); 116 | end 117 | 118 | rawdata = reshape(rawdata, [size(rawdata,1)*size(rawdata,2)/Ns,Ns,size(rawdata,3)]); 119 | ktraj_adc=ktraj_adc(:,1:end/Ns); 120 | t_adc=t_adc(1:end/Ns); 121 | 122 | for s=1:Ns 123 | for c=1:channels 124 | rawdata(:,s,c) = rawdata(:,s,c) .* exp(-1i*2*pi*t_adc'*offresonance); 125 | end 126 | end 127 | 128 | %% here we expect Nx, Ny, deltak to be set already 129 | % and rawdata ktraj_adc loaded (and having the same dimensions) 130 | 131 | kxm=round(os*os*Nx/2); 132 | kym=round(os*os*Ny/2); 133 | 134 | [kyy,kxx] = meshgrid(-kxm:(kxm-1), -kym:(kym-1)); 135 | kyy=-kyy*deltak/os; % we swap the order and invert one sign to account for Matlab's strange column/line convention 136 | kxx=-kxx*deltak/os; % this is needed to match Localizer images on TRIO 137 | 138 | kgd=zeros([size(kxx) Ns channels]); 139 | d1=1; d2=size(rawdata,1); 140 | %d1=320*64+1; d2=320*192; 141 | %d1=1; d2=320*128; 142 | %d1=320*128+1; d2=320*256; 143 | for s=1:Ns 144 | for c=1:channels 145 | kgd(:,:,s,c)=griddata(ktraj_adc(1,d1:d2),ktraj_adc(2,d1:d2),rawdata(d1:d2,s,c),kxx,kyy,'cubic'); 146 | end 147 | end 148 | kgd(isnan(kgd))=0; 149 | 150 | figure;imagesc(log(abs(kgd(:,:,1,1)))');axis('square'); 151 | title('2D k-space data, ch1 as log(abs())'); 152 | 153 | igd=ifftshift(ifft2(ifftshift(kgd))); 154 | 155 | Nxo=round(Nx*os); 156 | Nyo=round(Ny*os); 157 | Nxs=round((size(igd,1)-Nxo)/2); 158 | Nys=round((size(igd,2)-Nyo)/2); 159 | images = igd((Nxs+1):(Nxs+Nxo),(Nys+1):(Nys+Nyo),:,:); 160 | 161 | %% Image display with optional sum of squares combination 162 | figure; 163 | if channels>1 164 | sos=abs(sum(images.*conj(images),ndims(images))).^(1/2); 165 | sos=sos./max(sos(:)); 166 | imab(sos); title('reconstructed image(s), sum-of-squares'); 167 | %imwrite(sos, ['img_combined.png'] 168 | else 169 | imab(abs(images)); title('reconstructed image(s)'); 170 | end 171 | colormap('gray'); 172 | add=''; 173 | if abs(traj_recon_delay)>eps, add='_delay'; end 174 | saveas(gcf,[basic_file_path '_image_2d_gridding' add],'png'); 175 | 176 | -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/seq/s01_GradientEcho.m: -------------------------------------------------------------------------------- 1 | % set system limits 2 | sys = mr.opts('MaxGrad', 28, 'GradUnit', 'mT/m', ... 3 | 'MaxSlew', 150, 'SlewUnit', 'T/m/s', ... 4 | 'rfRingdownTime', 20e-6, 'rfDeadTime', 100e-6, 'adcDeadTime', 10e-6); 5 | 6 | % basic parameters 7 | seq=mr.Sequence(sys); % Create a new sequence object 8 | fov=256e-3; Nx=256; Ny=Nx; % Define FOV and resolution 9 | alpha=10; % flip angle 10 | sliceThickness=3e-3; % slice 11 | TR=21e-3; % TR, a single value 12 | %TE=4e-3; % TE 13 | TE=[4 9 15]*1e-3; % optionally give a vector here to have multiple TEs (e.g. for field mapping) 14 | % TODO: change to multiple contrasts, change order of loops... 15 | 16 | % more in-depth parameters 17 | rfSpoilingInc=117; % RF spoiling increment 18 | rfDuration=3e-3; 19 | roDuration=3.2e-3; % not all values are possible, watch out for the checkTiming output 20 | 21 | % Create alpha-degree slice selection pulse and corresponding gradients 22 | [rf, gz, gzReph] = mr.makeSincPulse(alpha*pi/180,'Duration',rfDuration,... 23 | 'SliceThickness',sliceThickness,'apodization',0.42,'timeBwProduct',4,'use','excitation','system',sys); 24 | 25 | % Define other gradients and ADC events 26 | deltak=1/fov; % Pulseq default units for k-space are inverse meters 27 | gx = mr.makeTrapezoid('x','FlatArea',Nx*deltak,'FlatTime',roDuration,'system',sys); % Pulseq default units for gradient amplitudes are 1/Hz 28 | adc = mr.makeAdc(Nx,'Duration',gx.flatTime,'Delay',gx.riseTime,'system',sys); 29 | gxPre = mr.makeTrapezoid('x','Area',-gx.area/2,'system',sys); % if no 'Duration' is provided shortest possible duration will be used 30 | phaseAreas = ((0:Ny-1)-Ny/2)*deltak; 31 | 32 | % gradient spoiling 33 | gxSpoil=mr.makeTrapezoid('x','Area',2*Nx*deltak,'system',sys); % 2 cycles over the voxel size in X 34 | gzSpoil=mr.makeTrapezoid('z','Area',4/sliceThickness,'system',sys); % 4 cycles over the slice thickness 35 | 36 | % Calculate timing (need to decide on the block structure already) 37 | delayTE=ceil((TE - gz.fallTime - gz.flatTime/2 ... 38 | - mr.calcDuration(gx)/2)/seq.gradRasterTime)*seq.gradRasterTime; 39 | delayTR=ceil((TR - mr.calcDuration(gz) - max(mr.calcDuration(gxPre,gzReph),delayTE) ... 40 | - mr.calcDuration(gx))/seq.gradRasterTime)*seq.gradRasterTime; 41 | assert(all(delayTE>=mr.calcDuration(gxPre,gzReph))); 42 | assert(all(delayTR>=mr.calcDuration(gxSpoil,gzSpoil))); 43 | 44 | % initialize the RF spoling counters 45 | rf_phase=0; 46 | rf_inc=0; 47 | 48 | % define sequence blocks 49 | for i=1:Ny % loop over phase encodes 50 | for c=1:length(TE) % loop over TEs 51 | rf.phaseOffset=rf_phase/180*pi; 52 | adc.phaseOffset=rf_phase/180*pi; 53 | rf_inc=mod(rf_inc+rfSpoilingInc, 360.0); 54 | rf_phase=mod(rf_phase+rf_inc, 360.0); 55 | % 56 | seq.addBlock(rf,gz); 57 | gyPre = mr.makeTrapezoid('y','Area',phaseAreas(i),'Duration',mr.calcDuration(gxPre),'system',sys); 58 | %seq.addBlock(mr.makeDelay(delayTE(c)),gxPre,gyPre,gzReph); 59 | seq.addBlock(mr.align('left', mr.makeDelay(delayTE(c)),gyPre,gzReph,'right',gxPre)); % stitch the read prephase to the readout gradient 60 | seq.addBlock(gx,adc); 61 | gyPre.amplitude=-gyPre.amplitude; % better to use mr.scaleGrad(gyPre,-1); 62 | seq.addBlock(mr.makeDelay(delayTR(c)),gxSpoil,gyPre,gzSpoil) 63 | end 64 | end 65 | 66 | %% check whether the timing of the sequence is correct 67 | [ok, error_report]=seq.checkTiming; 68 | 69 | if (ok) 70 | fprintf('Timing check passed successfully\n'); 71 | else 72 | fprintf('Timing check failed! Error listing follows:\n'); 73 | fprintf([error_report{:}]); 74 | fprintf('\n'); 75 | end 76 | 77 | %% prepare sequence export 78 | seq.setDefinition('FOV', [fov fov sliceThickness]); 79 | seq.setDefinition('Name', 'gre'); 80 | seq.write('gre.seq') % Write to pulseq file 81 | %seq.install('siemens'); 82 | 83 | %% plot sequence and k-space diagrams 84 | 85 | seq.plot('timeRange', [0 5]*TR); 86 | 87 | % k-space trajectory calculation 88 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 89 | 90 | % plot k-spaces 91 | figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D k-space plot 92 | axis('equal'); % enforce aspect ratio for the correct trajectory display 93 | hold;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 94 | title('full k-space trajectory (k_x x k_y)'); 95 | 96 | %% very optional slow step, but useful for testing during development e.g. for the real TE, TR or for staying within slewrate limits 97 | rep = seq.testReport; 98 | fprintf([rep{:}]); 99 | -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/seq/s02_MultiGradientEcho.m: -------------------------------------------------------------------------------- 1 | % set system limits 2 | sys = mr.opts('MaxGrad', 28, 'GradUnit', 'mT/m', ... 3 | 'MaxSlew', 150, 'SlewUnit', 'T/m/s', ... 4 | 'rfRingdownTime', 20e-6, 'rfDeadTime', 100e-6, 'adcDeadTime', 10e-6); 5 | 6 | % basic parameters 7 | seq=mr.Sequence(sys); % Create a new sequence object 8 | fov=256e-3; Nx=256; Ny=Nx; % Define FOV and resolution 9 | alpha=10; % flip angle 10 | sliceThickness=3e-3; % slice 11 | TR=21e-3; % TR, a single value 12 | TE=[4 9 15]*1e-3; % give a vector here to have multiple TEs 13 | 14 | % more in-depth parameters 15 | rfSpoilingInc=117; % RF spoiling increment 16 | rfDuration=3e-3; 17 | roDuration=3.2e-3; % not all values are possible, watch out for the checkTiming output 18 | 19 | % Create alpha-degree slice selection pulse and corresponding gradients 20 | [rf, gz, gzReph] = mr.makeSincPulse(alpha*pi/180,'Duration',rfDuration,... 21 | 'SliceThickness',sliceThickness,'apodization',0.42,'timeBwProduct',4,'use','excitation','system',sys); 22 | 23 | % Define other gradients and ADC events 24 | deltak=1/fov; % Pulseq default units for k-space are inverse meters 25 | gx = mr.makeTrapezoid('x','FlatArea',Nx*deltak,'FlatTime',roDuration,'system',sys); % Pulseq default units for gradient amplitudes are 1/Hz 26 | adc = mr.makeAdc(Nx,'Duration',gx.flatTime,'Delay',gx.riseTime,'system',sys); 27 | gxPre = mr.makeTrapezoid('x','Area',-gx.area/2,'system',sys); % if no 'Duration' is provided shortest possible duration will be used 28 | gxFlyBack = mr.makeTrapezoid('x','Area',-gx.area,'system',sys); 29 | phaseAreas = ((0:Ny-1)-Ny/2)*deltak; 30 | 31 | % gradient spoiling 32 | gxSpoil=mr.makeTrapezoid('x','Area',2*Nx*deltak,'system',sys); % 2 cycles over the voxel size in X 33 | gzSpoil=mr.makeTrapezoid('z','Area',4/sliceThickness,'system',sys); % 4 cycles over the slice thickness 34 | 35 | % Calculate timing (need to decide on the block structure already) 36 | helperT=ceil((gz.fallTime + gz.flatTime/2 + mr.calcDuration(gx)/2)/seq.gradRasterTime)*seq.gradRasterTime; 37 | delayTE = zeros(size(TE)) ; 38 | for c=1:length(TE) 39 | delayTE(c)=TE(c) - helperT; 40 | helperT=helperT+delayTE(c)+mr.calcDuration(gx); 41 | end 42 | assert(all(delayTE(1)>=mr.calcDuration(gxPre,gzReph))); 43 | assert(all(delayTE(2:end)>=mr.calcDuration(gxFlyBack))); 44 | delayTR=round((TR - mr.calcDuration(gz) - sum(delayTE) ... 45 | - mr.calcDuration(gx)*length(TE))/seq.gradRasterTime)*seq.gradRasterTime; 46 | assert(all(delayTR>=mr.calcDuration(gxSpoil,gzSpoil))); 47 | 48 | % initialize the RF spoling counters 49 | rf_phase=0; 50 | rf_inc=0; 51 | 52 | % define sequence blocks 53 | for i=1:Ny % loop over phase encodes 54 | rf.phaseOffset=rf_phase/180*pi; 55 | adc.phaseOffset=rf_phase/180*pi; 56 | rf_inc=mod(rf_inc+rfSpoilingInc, 360.0); 57 | rf_phase=mod(rf_phase+rf_inc, 360.0); 58 | % 59 | seq.addBlock(rf,gz); 60 | gyPre = mr.makeTrapezoid('y','Area',phaseAreas(i),'Duration',mr.calcDuration(gxPre),'system',sys); 61 | for c=1:length(TE) % loop over TEs 62 | if (c==1) 63 | seq.addBlock(mr.align('left', mr.makeDelay(delayTE(c)),gyPre,gzReph,'right',gxPre)); 64 | else 65 | seq.addBlock(mr.align('left', mr.makeDelay(delayTE(c)),'right',gxFlyBack)); 66 | end 67 | seq.addBlock(gx,adc); 68 | % to check/debug TE calculation with seq.testReport() comment out 69 | % the above line and uncommend the line below this comment block; 70 | % change 'c==3' statement to select the echo to test 71 | % if c==3, seq.addBlock(gx,adc); else, seq.addBlock(gx); end 72 | end 73 | gyPre.amplitude=-gyPre.amplitude; % better to use mr.scaleGrad(gyPre,-1); 74 | seq.addBlock(mr.makeDelay(delayTR),gxSpoil,gyPre,gzSpoil) 75 | end 76 | 77 | %% check whether the timing of the sequence is correct 78 | [ok, error_report]=seq.checkTiming; 79 | 80 | if (ok) 81 | fprintf('Timing check passed successfully\n'); 82 | else 83 | fprintf('Timing check failed! Error listing follows:\n'); 84 | fprintf([error_report{:}]); 85 | fprintf('\n'); 86 | end 87 | 88 | %% prepare sequence export 89 | seq.setDefinition('FOV', [fov fov sliceThickness]); 90 | seq.setDefinition('Name', 'mgre'); 91 | seq.write('mgre.seq') % Write to pulseq file 92 | %seq.install('siemens'); 93 | 94 | %% plot sequence and k-space diagrams 95 | 96 | seq.plot('timeRange', [0 5]*TR); 97 | 98 | % k-space trajectory calculation 99 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 100 | 101 | % plot k-spaces 102 | figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D k-space plot 103 | axis('equal'); % enforce aspect ratio for the correct trajectory display 104 | hold;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 105 | title('full k-space trajectory (k_x x k_y)'); 106 | 107 | %% very optional slow step, but useful for testing during development e.g. for the real TE, TR or for staying within slewrate limits 108 | rep = seq.testReport; 109 | fprintf([rep{:}]); 110 | -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/seq/s03_BipolarMultiGradientEcho.m: -------------------------------------------------------------------------------- 1 | % set system limits 2 | sys = mr.opts('MaxGrad', 28, 'GradUnit', 'mT/m', ... 3 | 'MaxSlew', 150, 'SlewUnit', 'T/m/s', ... 4 | 'rfRingdownTime', 20e-6, 'rfDeadTime', 100e-6, 'adcDeadTime', 10e-6); 5 | 6 | % basic parameters 7 | seq=mr.Sequence(sys); % Create a new sequence object 8 | fov=256e-3; Nx=256; Ny=Nx; % Define FOV and resolution 9 | alpha=10; % flip angle 10 | sliceThickness=3e-3; % slice 11 | TR=21e-3; % TR, a single value 12 | TE=[4 9 15]*1e-3; % give a vector here to have multiple TEs % TODO: reduce TEs 13 | %TODO: play with TEs to make them really minimal 14 | 15 | % more in-depth parameters 16 | rfSpoilingInc=117; % RF spoiling increment 17 | rfDuration=3e-3; 18 | roDuration=3.2e-3; % not all values are possible, watch out for the checkTiming output 19 | 20 | % Create alpha-degree slice selection pulse and corresponding gradients 21 | [rf, gz, gzReph] = mr.makeSincPulse(alpha*pi/180,'Duration',rfDuration,... 22 | 'SliceThickness',sliceThickness,'apodization',0.42,'timeBwProduct',4,'use','excitation','system',sys); 23 | 24 | % Define other gradients and ADC events 25 | deltak=1/fov; % Pulseq default units for k-space are inverse meters 26 | gxp = mr.makeTrapezoid('x','FlatArea',Nx*deltak,'FlatTime',roDuration,'system',sys); % Pulseq default units for gradient amplitudes are 1/Hz 27 | gxm=mr.scaleGrad(gxp,-1); 28 | adc = mr.makeAdc(Nx,'Duration',gxp.flatTime,'Delay',gxp.riseTime,'system',sys); 29 | gxPre = mr.makeTrapezoid('x','Area',-gxp.area/2,'system',sys); % if no 'Duration' is provided shortest possible duration will be used 30 | phaseAreas = ((0:Ny-1)-Ny/2)*deltak; 31 | 32 | % gradient spoiling 33 | if mod(length(TE),2)==0, spSign=-1; else, spSign=1; end 34 | gxSpoil=mr.makeTrapezoid('x','Area',2*Nx*deltak*spSign,'system',sys); % 2 cycles over the voxel size in X 35 | gzSpoil=mr.makeTrapezoid('z','Area',4/sliceThickness,'system',sys); % 4 cycles over the slice thickness 36 | 37 | % Calculate timing (need to decide on the block structure already) 38 | helperT=ceil((gz.fallTime + gz.flatTime/2 + mr.calcDuration(gxp)/2)/seq.gradRasterTime)*seq.gradRasterTime; 39 | delayTE = zeros(size(TE)) ; 40 | for c=1:length(TE) 41 | delayTE(c)=TE(c) - helperT; 42 | helperT=helperT+delayTE(c)+mr.calcDuration(gxp); 43 | end 44 | assert(all(delayTE(1)>=mr.calcDuration(gxPre,gzReph))); 45 | assert(all(delayTE(2:end)>=0)); 46 | delayTR=round((TR - mr.calcDuration(gz) - sum(delayTE) ... 47 | - mr.calcDuration(gxp)*length(TE))/seq.gradRasterTime)*seq.gradRasterTime; 48 | assert(all(delayTR>=mr.calcDuration(gxSpoil,gzSpoil))); 49 | 50 | % initialize the RF spoling counters 51 | rf_phase=0; 52 | rf_inc=0; 53 | 54 | % define sequence blocks 55 | for i=1:Ny % loop over phase encodes 56 | rf.phaseOffset=rf_phase/180*pi; 57 | adc.phaseOffset=rf_phase/180*pi; 58 | rf_inc=mod(rf_inc+rfSpoilingInc, 360.0); 59 | rf_phase=mod(rf_phase+rf_inc, 360.0); 60 | % 61 | seq.addBlock(rf,gz); 62 | gyPre = mr.makeTrapezoid('y','Area',phaseAreas(i),'Duration',mr.calcDuration(gxPre),'system',sys); 63 | for c=1:length(TE) % loop over TEs 64 | if (c==1) 65 | seq.addBlock(mr.align('left', mr.makeDelay(delayTE(c)),gyPre,gzReph,'right',gxPre)); 66 | else 67 | seq.addBlock(mr.makeDelay(delayTE(c))); 68 | end 69 | if mod(c,2)==0, gx=gxm; else, gx=gxp; end 70 | seq.addBlock(gx,adc); 71 | % to check/debug TE calculation with seq.testReport() comment out 72 | % the above line and uncommend the line below this comment block; 73 | % change 'c==3' statement to select the echo to test 74 | %if c==2, seq.addBlock(gx,adc); else, seq.addBlock(gx); end 75 | end 76 | gyPre.amplitude=-gyPre.amplitude; % better to use mr.scaleGrad(gyPre,-1); 77 | seq.addBlock(mr.makeDelay(delayTR),gxSpoil,gyPre,gzSpoil) 78 | end 79 | 80 | %% check whether the timing of the sequence is correct 81 | [ok, error_report]=seq.checkTiming; 82 | 83 | if (ok) 84 | fprintf('Timing check passed successfully\n'); 85 | else 86 | fprintf('Timing check failed! Error listing follows:\n'); 87 | fprintf([error_report{:}]); 88 | fprintf('\n'); 89 | end 90 | 91 | %% prepare sequence export 92 | seq.setDefinition('FOV', [fov fov sliceThickness]); 93 | seq.setDefinition('Name', 'bmgre'); 94 | seq.write('bmgre.seq') % Write to pulseq file 95 | %seq.install('siemens'); 96 | 97 | %% plot sequence and k-space diagrams 98 | 99 | seq.plot('timeRange', [0 5]*TR); 100 | 101 | % k-space trajectory calculation 102 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 103 | 104 | % plot k-spaces 105 | figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D k-space plot 106 | axis('equal'); % enforce aspect ratio for the correct trajectory display 107 | hold;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 108 | title('full k-space trajectory (k_x x k_y)'); 109 | 110 | %% very optional slow step, but useful for testing during development e.g. for the real TE, TR or for staying within slewrate limits 111 | rep = seq.testReport; 112 | fprintf([rep{:}]); 113 | -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/seq/s04a_SegmentedGradientEcho.m: -------------------------------------------------------------------------------- 1 | % set system limits 2 | sys = mr.opts('MaxGrad', 28, 'GradUnit', 'mT/m', ... 3 | 'MaxSlew', 150, 'SlewUnit', 'T/m/s', ... 4 | 'rfRingdownTime', 20e-6, 'rfDeadTime', 100e-6, 'adcDeadTime', 10e-6); 5 | 6 | % basic parameters 7 | seq=mr.Sequence(sys); % Create a new sequence object 8 | fov=256e-3; Nx=128; Ny=Nx; % Define FOV and resolution 9 | alpha=10; % flip angle 10 | sliceThickness=3e-3; % slice 11 | TR=21e-3; % TR, a single value 12 | TE=9*1e-3; % only a single TE is accepted now 13 | nSeg=5; % number of segments 14 | %TODO: lower matrix, increase segments, shorten RO (128x128, 5, 640us) 15 | 16 | % more in-depth parameters 17 | rfSpoilingInc=117; % RF spoiling increment 18 | rfDuration=3e-3; 19 | roDuration=640e-6; % not all values are possible, watch out for the checkTiming output 20 | 21 | % Create alpha-degree slice selection pulse and corresponding gradients 22 | [rf, gz, gzReph] = mr.makeSincPulse(alpha*pi/180,'Duration',rfDuration,... 23 | 'SliceThickness',sliceThickness,'apodization',0.42,'timeBwProduct',4,'use','excitation','system',sys); 24 | 25 | % Define other gradients and ADC events 26 | deltak=1/fov; % Pulseq default units for k-space are inverse meters 27 | gxp = mr.makeTrapezoid('x','FlatArea',Nx*deltak,'FlatTime',roDuration,'system',sys); % Pulseq default units for gradient amplitudes are 1/Hz 28 | gxm=mr.scaleGrad(gxp,-1); 29 | adc = mr.makeAdc(Nx,'Duration',gxp.flatTime,'Delay',gxp.riseTime,'system',sys); 30 | gxPre = mr.makeTrapezoid('x','Area',-gxp.area/2,'system',sys); % if no 'Duration' is provided shortest possible duration will be used 31 | 32 | % with segmentation it gets trickier 33 | if mod(nSeg,2)==0, warning('for even number of segments additional steps are required to avoid the segment edge hitting the k-space center, expect artifacts...'); end % the code will work but the images are likely to be affected by the discontinuity at the center of k-space 34 | phaseAreas = ((0:floor(Ny/nSeg)-1)-Ny/2)*deltak; 35 | 36 | % calculate the blip gradient 37 | gyBlip = mr.makeTrapezoid('y','Area',floor(Ny/nSeg)*deltak,'Delay',gxp.riseTime+gxp.flatTime,'system',sys); 38 | 39 | % gradient spoiling 40 | if mod(length(TE),2)==0, spSign=-1; else, spSign=1; end 41 | gxSpoil=mr.makeTrapezoid('x','Area',2*Nx*deltak*spSign,'system',sys); % 2 cycles over the voxel size in X 42 | gzSpoil=mr.makeTrapezoid('z','Area',4/sliceThickness,'system',sys); % 4 cycles over the slice thickness 43 | 44 | % Calculate timing (need to decide on the block structure already) 45 | delayTE=TE - ceil((gz.fallTime + gz.flatTime/2 + floor(nSeg/2)*mr.calcDuration(gxp,gyBlip)+mr.calcDuration(gxp)/2)/seq.gradRasterTime)*seq.gradRasterTime; 46 | assert(all(delayTE>=mr.calcDuration(gxPre,gzReph))); 47 | delayTR=round((TR - mr.calcDuration(gz) - delayTE ... 48 | - mr.calcDuration(gxp,gyBlip)*(nSeg-1)-mr.calcDuration(gxp))/seq.gradRasterTime)*seq.gradRasterTime; 49 | assert(all(delayTR>=mr.calcDuration(gxSpoil,gzSpoil))); 50 | 51 | % initialize the RF spoling counters 52 | rf_phase=0; 53 | rf_inc=0; 54 | 55 | % define sequence blocks 56 | for i=1:length(phaseAreas) % loop over phase encodes 57 | rf.phaseOffset=rf_phase/180*pi; 58 | adc.phaseOffset=rf_phase/180*pi; 59 | rf_inc=mod(rf_inc+rfSpoilingInc, 360.0); 60 | rf_phase=mod(rf_phase+rf_inc, 360.0); 61 | % 62 | seq.addBlock(rf,gz); 63 | gyPre = mr.makeTrapezoid('y','Area',phaseAreas(i),'Duration',mr.calcDuration(gxPre),'system',sys); 64 | seq.addBlock(mr.align('left', mr.makeDelay(delayTE),gyPre,gzReph,'right',gxPre)); 65 | for s=1:nSeg % loop over segments 66 | if mod(s,2)==0, gx=gxm; else, gx=gxp; end 67 | if s~=nSeg 68 | seq.addBlock(gx,adc,gyBlip); 69 | else 70 | seq.addBlock(gx,adc); 71 | end 72 | end 73 | gyPost = mr.makeTrapezoid('y','Area',-phaseAreas(i)-gyBlip.area*(nSeg-1),'Duration',mr.calcDuration(gxPre),'system',sys); 74 | seq.addBlock(mr.makeDelay(delayTR),gxSpoil,gyPost,gzSpoil) 75 | end 76 | 77 | %% check whether the timing of the sequence is correct 78 | [ok, error_report]=seq.checkTiming; 79 | 80 | if (ok) 81 | fprintf('Timing check passed successfully\n'); 82 | else 83 | fprintf('Timing check failed! Error listing follows:\n'); 84 | fprintf([error_report{:}]); 85 | fprintf('\n'); 86 | end 87 | 88 | %% prepare sequence export 89 | seq.setDefinition('FOV', [fov fov sliceThickness]); 90 | seq.setDefinition('Name', 'seg-gre'); 91 | seq.write('seg-gre.seq') % Write to pulseq file 92 | %seq.install('siemens'); 93 | 94 | %% plot sequence and k-space diagrams 95 | 96 | seq.plot('timeRange', [0 2]*TR); 97 | seq.plot('timeDisp','us','showBlocks',1,'timeRange',[0 2]*TR); %detailed view 98 | 99 | % k-space trajectory calculation 100 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 101 | 102 | % plot k-spaces 103 | figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D k-space plot 104 | axis('equal'); % enforce aspect ratio for the correct trajectory display 105 | hold;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 106 | title('full k-space trajectory (k_x x k_y)'); 107 | 108 | %% very optional slow step, but useful for testing during development e.g. for the real TE, TR or for staying within slewrate limits 109 | rep = seq.testReport; 110 | fprintf([rep{:}]); 111 | -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/seq/s04b_SegmentedGradientEcho.m: -------------------------------------------------------------------------------- 1 | % set system limits 2 | sys = mr.opts('MaxGrad', 28, 'GradUnit', 'mT/m', ... 3 | 'MaxSlew', 150, 'SlewUnit', 'T/m/s', ... 4 | 'rfRingdownTime', 20e-6, 'rfDeadTime', 100e-6, 'adcDeadTime', 10e-6); 5 | 6 | % basic parameters 7 | seq=mr.Sequence(sys); % Create a new sequence object 8 | fov=256e-3; Nx=128; Ny=128; % Define FOV and resolution 9 | alpha=10; % flip angle 10 | sliceThickness=3e-3; % slice 11 | TR=30e-3; % TR, a single value 12 | TE=20e-3; % only a single TE is accepted now 13 | nSeg=5; % number of segments 14 | %TODO: increase nSeq, (remember to increase TE), see how we hit the "blip limit" 15 | 16 | % more in-depth parameters 17 | rfSpoilingInc=117; % RF spoiling increment 18 | rfDuration=3e-3; 19 | roDuration=640e-6; % not all values are possible, watch out for the checkTiming output 20 | 21 | % Create alpha-degree slice selection pulse and corresponding gradients 22 | [rf, gz, gzReph] = mr.makeSincPulse(alpha*pi/180,'Duration',rfDuration,... 23 | 'SliceThickness',sliceThickness,'apodization',0.42,'timeBwProduct',4,'use','excitation','system',sys); 24 | 25 | % Define other gradients and ADC events 26 | deltak=1/fov; % Pulseq default units for k-space are inverse meters 27 | gxp = mr.makeTrapezoid('x','FlatArea',Nx*deltak,'FlatTime',roDuration,'system',sys); % Pulseq default units for gradient amplitudes are 1/Hz 28 | gxm=mr.scaleGrad(gxp,-1); 29 | adc = mr.makeAdc(Nx,'Duration',gxp.flatTime,'Delay',gxp.riseTime,'system',sys); 30 | gxPre = mr.makeTrapezoid('x','Area',-gxp.area/2,'system',sys); % if no 'Duration' is provided shortest possible duration will be used 31 | 32 | % with segmentation it gets trickier 33 | if mod(nSeg,2)==0, warning('for even number of segments additional steps are required to avoid the segment edge hitting the k-space center, expect artifacts...'); end % the code will work but the images are likely to be affected by the discontinuity at the center of k-space 34 | phaseAreas = ((0:floor(Ny/nSeg)-1)-Ny/2)*deltak; 35 | 36 | % calculate the blip gradient 37 | gyBlip = mr.makeTrapezoid('y','Area',floor(Ny/nSeg)*deltak,'Delay',gxp.riseTime+gxp.flatTime,'system',sys); 38 | if mr.calcDuration(gyBlip)-mr.calcDuration(gxp)gxp.riseTime 49 | % need to update the copy to allow for the split gradient to ramp down 50 | gxp.delay=mr.calcDuration(gyBlip_parts(2))-gxp.riseTime; 51 | gxm.delay=mr.calcDuration(gyBlip_parts(2))-gxm.riseTime; 52 | adc.delay=adc.delay+gxp.delay; 53 | gyBlip_part_tmp.delay=gyBlip_part_tmp.delay+gxp.delay; 54 | end 55 | % now for inner echos create a special gy gradient, that will ramp down to 0, stay at 0 for a while and ramp up again 56 | gyBlip_down_up=mr.addGradients({gyBlip_parts(2), gyBlip_part_tmp}, sys); % QC: gyBlip_parts(2) is inserted before gxp. gyBlip_part_tmp with a long delay is inserted after gxp. 57 | % copy for readability 58 | gyBlip_up=gyBlip_parts(1); 59 | gyBlip_down=gyBlip_parts(2); 60 | 61 | % gradient spoiling 62 | if mod(length(TE),2)==0, spSign=-1; else, spSign=1; end 63 | gxSpoil=mr.makeTrapezoid('x','Area',2*Nx*deltak*spSign,'system',sys); % 2 cycles over the voxel size in X 64 | gzSpoil=mr.makeTrapezoid('z','Area',4/sliceThickness,'system',sys); % 4 cycles over the slice thickness 65 | 66 | % Calculate timing (need to decide on the block structure already) 67 | delayTE=TE - ceil((gz.fallTime + gz.flatTime/2 + nSeg*mr.calcDuration(gxp0)/2 + floor((nSeg-1)/2)*gxp.delay)/seq.gradRasterTime)*seq.gradRasterTime; 68 | assert(all(delayTE>=mr.calcDuration(gxPre,gzReph))); 69 | delayTR=round((TR - mr.calcDuration(gz) - delayTE ... 70 | - nSeg*mr.calcDuration(gxp0) - floor((nSeg-1)/2)*gxp.delay)/seq.gradRasterTime)*seq.gradRasterTime; 71 | assert(all(delayTR>=mr.calcDuration(gxSpoil,gzSpoil))); 72 | 73 | % initialize the RF spoling counters 74 | rf_phase=0; 75 | rf_inc=0; 76 | 77 | % define sequence blocks 78 | for i=1:length(phaseAreas) % loop over phase encodes 79 | rf.phaseOffset=rf_phase/180*pi; 80 | adc.phaseOffset=rf_phase/180*pi; 81 | adc0.phaseOffset=rf_phase/180*pi; 82 | rf_inc=mod(rf_inc+rfSpoilingInc, 360.0); 83 | rf_phase=mod(rf_phase+rf_inc, 360.0); 84 | % 85 | seq.addBlock(rf,gz); 86 | gyPre = mr.makeTrapezoid('y','Area',phaseAreas(i),'Duration',mr.calcDuration(gxPre),'system',sys); 87 | seq.addBlock(mr.align('left', mr.makeDelay(delayTE),gyPre,gzReph,'right',gxPre)); 88 | for s=1:nSeg % loop over segments 89 | if s==1 90 | seq.addBlock(gxp0,adc0,gyBlip_up); 91 | else 92 | if mod(s,2)==0, gx=gxm; else, gx=gxp; end 93 | if s~=nSeg 94 | seq.addBlock(gx,adc,gyBlip_down_up); 95 | else 96 | seq.addBlock(gx,adc,gyBlip_down); 97 | end 98 | end 99 | end 100 | gyPost = mr.makeTrapezoid('y','Area',-phaseAreas(i)-gyBlip.area*(nSeg-1),'Duration',mr.calcDuration(gxPre),'system',sys); 101 | seq.addBlock(mr.makeDelay(delayTR),gxSpoil,gyPost,gzSpoil) 102 | end 103 | 104 | %% check whether the timing of the sequence is correct 105 | [ok, error_report]=seq.checkTiming; 106 | 107 | if (ok) 108 | fprintf('Timing check passed successfully\n'); 109 | else 110 | fprintf('Timing check failed! Error listing follows:\n'); 111 | fprintf([error_report{:}]); 112 | fprintf('\n'); 113 | end 114 | 115 | %% prepare sequence export 116 | seq.setDefinition('FOV', [fov fov sliceThickness]); 117 | seq.setDefinition('Name', 'seg-gre'); 118 | seq.write('seg-gre.seq') % Write to pulseq file 119 | %seq.install('siemens'); 120 | 121 | %% plot sequence and k-space diagrams 122 | 123 | %seq.plot('timeRange', [0 2]*TR); 124 | seq.plot('timeDisp','us','showBlocks',1,'timeRange',[0 2]*TR); %detailed view 125 | 126 | % k-space trajectory calculation 127 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 128 | 129 | % plot k-spaces 130 | figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D k-space plot 131 | axis('equal'); % enforce aspect ratio for the correct trajectory display 132 | hold;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 133 | title('full k-space trajectory (k_x x k_y)'); 134 | 135 | %% very optional slow step, but useful for testing during development e.g. for the real TE, TR or for staying within slewrate limits 136 | rep = seq.testReport; 137 | fprintf([rep{:}]); 138 | -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/seq/s04c_SegmentedGradientEcho.m: -------------------------------------------------------------------------------- 1 | % set system limits 2 | sys = mr.opts('MaxGrad', 28, 'GradUnit', 'mT/m', ... 3 | 'MaxSlew', 150, 'SlewUnit', 'T/m/s', ... 4 | 'rfRingdownTime', 20e-6, 'rfDeadTime', 100e-6, 'adcDeadTime', 10e-6); 5 | 6 | % basic parameters 7 | seq=mr.Sequence(sys); % Create a new sequence object 8 | fov=256e-3; Nx=128; Ny=Nx; % Define FOV and resolution 9 | alpha=90; % flip angle 10 | sliceThickness=3e-3; % slice 11 | TR=130e-3; % TR, a single value 12 | TE=61e-3; % only a single TE is accepted now 13 | nSeg=Ny; % number of segments, noy equals to the number of PE lines 14 | % run this improvised EPI sequence with and without PE, have a look a the k-space 15 | 16 | % more in-depth parameters 17 | rfSpoilingInc=117; % RF spoiling increment 18 | rfDuration=3e-3; 19 | roDuration=640e-6; % not all values are possible, watch out for the checkTiming output 20 | 21 | % Create alpha-degree slice selection pulse and corresponding gradients 22 | [rf, gz, gzReph] = mr.makeSincPulse(alpha*pi/180,'Duration',rfDuration,... 23 | 'SliceThickness',sliceThickness,'apodization',0.42,'timeBwProduct',4,'use','excitation','system',sys); 24 | 25 | % Define other gradients and ADC events 26 | deltak=1/fov; % Pulseq default units for k-space are inverse meters 27 | gxp = mr.makeTrapezoid('x','FlatArea',Nx*deltak,'FlatTime',roDuration,'system',sys); % Pulseq default units for gradient amplitudes are 1/Hz 28 | gxm=mr.scaleGrad(gxp,-1); 29 | adc = mr.makeAdc(Nx,'Duration',gxp.flatTime,'Delay',gxp.riseTime,'system',sys); 30 | gxPre = mr.makeTrapezoid('x','Area',-gxp.area/2,'system',sys); % if no 'Duration' is provided shortest possible duration will be used 31 | 32 | % with segmentation it gets trickier 33 | if mod(nSeg,2)==0, warning('for even number of segments additional steps are required to avoid the segment edge hitting the k-space center, expect artifacts...'); end % the code will work but the images are likely to be affected by the discontinuity at the center of k-space 34 | phaseAreas = ((0:floor(Ny/nSeg)-1)-Ny/2)*deltak; 35 | 36 | % calculate the blip gradient 37 | gyBlip = mr.makeTrapezoid('y','Area',floor(Ny/nSeg)*deltak,'Delay',gxp.riseTime+gxp.flatTime,'system',sys); 38 | if mr.calcDuration(gyBlip)-mr.calcDuration(gxp)gxp.riseTime 48 | % need to update the copy to allow for the split gradient to ramp down 49 | gxp.delay=mr.calcDuration(gyBlip_parts(2))-gxp.riseTime; 50 | gxm.delay=mr.calcDuration(gyBlip_parts(2))-gxm.riseTime; 51 | adc.delay=adc.delay+gxp.delay; 52 | gyBlip_part_tmp.delay=gyBlip_part_tmp.delay+gxp.delay; 53 | end 54 | % now for inner echos create a special gy gradient, that will ramp down to 0, stay at 0 for a while and ramp up again 55 | gyBlip_down_up=mr.addGradients({gyBlip_parts(2), gyBlip_part_tmp}, sys); 56 | % copy for readability 57 | gyBlip_up=gyBlip_parts(1); 58 | gyBlip_down=gyBlip_parts(2); 59 | 60 | % gradient spoiling 61 | if mod(length(TE),2)==0, spSign=-1; else, spSign=1; end 62 | gxSpoil=mr.makeTrapezoid('x','Area',2*Nx*deltak*spSign,'system',sys); % 2 cycles over the voxel size in X 63 | gzSpoil=mr.makeTrapezoid('z','Area',4/sliceThickness,'system',sys); % 4 cycles over the slice thickness 64 | 65 | % Calculate timing (need to decide on the block structure already) 66 | delayTE=TE - ceil((gz.fallTime + gz.flatTime/2 + (floor(nSeg/2)+0.5)*mr.calcDuration(gxp0) + floor((nSeg-1)/2)*gxp.delay)/seq.gradRasterTime)*seq.gradRasterTime; 67 | assert(all(delayTE>=mr.calcDuration(gxPre,gzReph))); 68 | delayTR=round((TR - mr.calcDuration(gz) - delayTE ... 69 | - nSeg*mr.calcDuration(gxp0) - floor((nSeg-1)/2)*gxp.delay)/seq.gradRasterTime)*seq.gradRasterTime; 70 | assert(all(delayTR>=mr.calcDuration(gxSpoil,gzSpoil))); 71 | 72 | % initialize the RF spoling counters 73 | rf_phase=0; 74 | rf_inc=0; 75 | 76 | % define sequence blocks 77 | for i=1:length(phaseAreas) % loop over phase encodes 78 | rf.phaseOffset=rf_phase/180*pi; 79 | adc.phaseOffset=rf_phase/180*pi; 80 | adc0.phaseOffset=rf_phase/180*pi; 81 | rf_inc=mod(rf_inc+rfSpoilingInc, 360.0); 82 | rf_phase=mod(rf_phase+rf_inc, 360.0); 83 | % 84 | seq.addBlock(rf,gz); 85 | gyPre = mr.makeTrapezoid('y','Area',phaseAreas(i),'Duration',mr.calcDuration(gxPre),'system',sys); 86 | seq.addBlock(mr.align('left', mr.makeDelay(delayTE),gyPre,gzReph,'right',gxPre)); 87 | for s=1:nSeg % loop over segments 88 | if s==1 89 | seq.addBlock(gxp0,adc0,gyBlip_up); 90 | else 91 | if mod(s,2)==0, gx=gxm; else, gx=gxp; end 92 | if s~=nSeg 93 | seq.addBlock(gx,adc,gyBlip_down_up); 94 | else 95 | seq.addBlock(gx,adc,gyBlip_down); 96 | end 97 | end 98 | end 99 | gyPost = mr.makeTrapezoid('y','Area',-phaseAreas(i)-gyBlip.area*(nSeg-1),'Duration',mr.calcDuration(gxPre),'system',sys); 100 | seq.addBlock(mr.makeDelay(delayTR),gxSpoil,gyPost,gzSpoil) 101 | end 102 | 103 | %% check whether the timing of the sequence is correct 104 | [ok, error_report]=seq.checkTiming; 105 | 106 | if (ok) 107 | fprintf('Timing check passed successfully\n'); 108 | else 109 | fprintf('Timing check failed! Error listing follows:\n'); 110 | fprintf([error_report{:}]); 111 | fprintf('\n'); 112 | end 113 | 114 | %% prepare sequence export 115 | seq.setDefinition('FOV', [fov fov sliceThickness]); 116 | seq.setDefinition('Name', 'seg-gre'); 117 | seq.write('seg-gre.seq') % Write to pulseq file 118 | %seq.install('siemens'); 119 | 120 | %% plot sequence and k-space diagrams 121 | 122 | %seq.plot('timeRange', [0 2]*TR); 123 | seq.plot('timeDisp','us','showBlocks',1,'timeRange',[0 2]*TR); %detailed view 124 | 125 | % k-space trajectory calculation 126 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 127 | 128 | % plot k-spaces 129 | figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D k-space plot 130 | axis('equal'); % enforce aspect ratio for the correct trajectory display 131 | hold;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 132 | title('full k-space trajectory (k_x x k_y)'); 133 | 134 | %% very optional slow step, but useful for testing during development e.g. for the real TE, TR or for staying within slewrate limits 135 | rep = seq.testReport; 136 | fprintf([rep{:}]); 137 | -------------------------------------------------------------------------------- /11_from_GRE_to_EPI/seq/s05_EchoPlanarImaging.m: -------------------------------------------------------------------------------- 1 | % set system limits 2 | sys = mr.opts('MaxGrad', 28, 'GradUnit', 'mT/m', ... 3 | 'MaxSlew', 150, 'SlewUnit', 'T/m/s', ... 4 | 'rfRingdownTime', 20e-6, 'rfDeadTime', 100e-6, 'adcDeadTime', 10e-6); 5 | 6 | % basic parameters 7 | seq=mr.Sequence(sys); % Create a new sequence object 8 | fov=256e-3; Nx=128; Ny=Nx; % Define FOV and resolution 9 | alpha=90; % flip angle 10 | sliceThickness=3e-3; % slice 11 | %TR=21e-3; % ignore TR, go as fast as possible 12 | %TE=60e-3; % ignore TE, go as fast as possible 13 | % TODO: run this EPI sequence with and without PE, to calibrate the delay 14 | % TODO: change MaxGrad/MaxSlew/roDuration to see what happends to the 15 | % stimulation (e.g. 80/200/500) 16 | 17 | % more in-depth parameters 18 | pe_enable=1; % a flag to quickly disable phase encoding (1/0) as needed for the delay calibration 19 | rfDuration=3e-3; 20 | roDuration=640e-6; % not all values are possible, watch out for the checkTiming output 21 | 22 | % Create alpha-degree slice selection pulse and corresponding gradients 23 | [rf, gz, gzReph] = mr.makeSincPulse(alpha*pi/180,'Duration',rfDuration,... 24 | 'SliceThickness',sliceThickness,'apodization',0.42,'timeBwProduct',4,'use','excitation','system',sys); 25 | 26 | % define the output trigger to play out with every slice excitation 27 | trig=mr.makeDigitalOutputPulse('ext1','duration', 100e-6,'delay', rf.delay+mr.calcRfCenter(rf)); % possible channels: 'osc0','osc1','ext1' 28 | 29 | % Define other gradients and ADC events 30 | deltak=1/fov; % Pulseq default units for k-space are inverse meters 31 | kWidth = Nx*deltak; 32 | 33 | % start with the blip 34 | blip_dur = ceil(2*sqrt(deltak/sys.maxSlew)/sys.gradRasterTime/2)*sys.gradRasterTime*2; % we round-up the duration to 2x the gradient raster time 35 | gyBlip = mr.makeTrapezoid('y',sys,'Area',-deltak,'Duration',blip_dur); % we use negative blips to save one k-space line on our way towards the k-space center 36 | 37 | % readout gradient is a truncated trapezoid with dead times at the beginnig 38 | % and at the end, each equal to a half of blip_dur 39 | % the area between the blips should be equal to kWidth 40 | % we do a two-step calculation: we first increase the area assuming maximum 41 | % slew rate and then scale down the amlitude to fix the area 42 | extra_area=blip_dur/2*blip_dur/2*sys.maxSlew; 43 | gx = mr.makeTrapezoid('x',sys,'Area',kWidth+extra_area,'duration',roDuration+blip_dur); 44 | actual_area=gx.area-gx.amplitude/gx.riseTime*blip_dur/2*blip_dur/2/2-gx.amplitude/gx.fallTime*blip_dur/2*blip_dur/2/2; 45 | gx=mr.scaleGrad(gx,kWidth/actual_area); 46 | %adc = mr.makeAdc(Nx,'Duration',roDurtion,'Delay',blip_dur/2,'system',sys); 47 | gxPre = mr.makeTrapezoid('x','Area',-gx.area/2,'system',sys); % if no 'Duration' is provided shortest possible duration will be used 48 | gyPre = mr.makeTrapezoid('y','Area',(Ny/2-1)*deltak,'system',sys); 49 | 50 | % calculate ADC - it is quite trickly 51 | % we use ramp sampling, so we have to calculate the dwell time and the 52 | % number of samples, which are will be quite different from Nx and 53 | % readoutTime/Nx, respectively. 54 | adcDwellNyquist=deltak/gx.amplitude; % dwell time on the top of the plato 55 | % round-down dwell time to sys.adcRasterTime (100 ns) 56 | adcDwell=floor(adcDwellNyquist/sys.adcRasterTime)*sys.adcRasterTime; 57 | adcSamples=floor(roDuration/adcDwell/4)*4; % on Siemens the number of ADC samples need to be divisible by 4 58 | adc = mr.makeAdc(adcSamples,'Dwell',adcDwell); 59 | % realign the ADC with respect to the gradient 60 | time_to_center=adc.dwell*((adcSamples-1)/2+0.5); % Pulseq (and Siemens) define the samples to happen in the center of the dwell period 61 | adc.delay=round((gx.riseTime+gx.flatTime/2-time_to_center)/sys.rfRasterTime)*sys.rfRasterTime; 62 | % above we adjust the delay to align the trajectory with the gradient. 63 | % We have to aligh the delay to seq.rfRasterTime (1us) 64 | % this rounding actually makes the sampling points on odd and even readouts 65 | % to appear misalligned. However, on the real hardware this misalignment is 66 | % much stronger anyways due to the grdient delays 67 | 68 | % finish the blip gradient calculation 69 | % split the blip into two halves and produce a combined synthetic gradient 70 | gyBlip_parts = mr.splitGradientAt(gyBlip, blip_dur/2, sys); 71 | [gyBlip_up,gyBlip_down,~]=mr.align('right',gyBlip_parts(1),'left',gyBlip_parts(2),gx); 72 | % now for inner echos create a special gy gradient, that will ramp down to 0, stay at 0 for a while and ramp up again 73 | gyBlip_down_up=mr.addGradients({gyBlip_down, gyBlip_up}, sys); 74 | 75 | % pe_enable support 76 | gyBlip_up=mr.scaleGrad(gyBlip_up,pe_enable); 77 | gyBlip_down=mr.scaleGrad(gyBlip_down,pe_enable); 78 | gyBlip_down_up=mr.scaleGrad(gyBlip_down_up,pe_enable); 79 | gyPre=mr.scaleGrad(gyPre,pe_enable); 80 | 81 | % gradient spoiling 82 | gzSpoil=mr.makeTrapezoid('z','Area',4/sliceThickness,'system',sys); % 4 cycles over the slice thickness 83 | 84 | % skip timing (TE/TR calculation), we'll accept the shortest TE/TR 85 | 86 | % define sequence blocks 87 | seq.addBlock(rf,gz,trig); 88 | seq.addBlock(mr.align('left',gyPre,gzReph,'right',gxPre)); 89 | for i=1:Ny % loop over phase encodes 90 | if i==1 91 | seq.addBlock(gx,gyBlip_up,adc); % Read the first line of k-space with a single half-blip at the end 92 | elseif i==Ny 93 | seq.addBlock(gx,gyBlip_down,adc); % Read the last line of k-space with a single half-blip at the beginning 94 | else 95 | seq.addBlock(gx,gyBlip_down_up,adc); % Read an intermediate line of k-space with a half-blip at the beginning and a half-blip at the end 96 | end 97 | gx = mr.scaleGrad(gx,-1); % Reverse polarity of read gradient),'Duration',mr.calcDuration(gxPre),'system',sys); 98 | end 99 | seq.addBlock(gzSpoil); 100 | 101 | %% check whether the timing of the sequence is correct 102 | [ok, error_report]=seq.checkTiming; 103 | 104 | if (ok) 105 | fprintf('Timing check passed successfully\n'); 106 | else 107 | fprintf('Timing check failed! Error listing follows:\n'); 108 | fprintf([error_report{:}]); 109 | fprintf('\n'); 110 | end 111 | 112 | %% prepare sequence export 113 | seq.setDefinition('FOV', [fov fov sliceThickness]); 114 | seq.setDefinition('Name', 'epi'); 115 | seq.write('epi.seq') % Write to pulseq file 116 | %seq.install('siemens'); 117 | 118 | %% plot sequence and k-space diagrams 119 | 120 | %seq.plot(); 121 | seq.plot('timeDisp','us','showBlocks',1); %detailed view 122 | 123 | % k-space trajectory calculation 124 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 125 | 126 | % plot k-spaces 127 | figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D k-space plot 128 | axis('equal'); % enforce aspect ratio for the correct trajectory display 129 | hold;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 130 | title('full k-space trajectory (k_x x k_y)'); 131 | 132 | % nice plot for a paper (looks nicer if you reduce the number of phase encoding steps) 133 | seq.paperPlot(); 134 | 135 | %% PNS calc 136 | 137 | [pns_ok, pns_n, pns_c, tpns]=seq.calcPNS('~/range_software/pulseq/matlab/idea/asc/MP_GPA_K2309_2250V_951A_AS82.asc'); % prisma 138 | %[pns_ok, pns_n, pns_c, tpns]=seq.calcPNS('idea/asc/MP_GPA_K2309_2250V_951A_GC98SQ.asc'); % aera-xq 139 | %[pns_ok, pns_n, pns_c, tpns]=seq.calcPNS('idea/asc/MP_GPA_K2298_2250V_793A_SC72CD_EGA.asc'); % TERRA-XR 140 | 141 | if (pns_ok) 142 | fprintf('PNS check passed successfully\n'); 143 | else 144 | fprintf('PNS check failed! The sequence will probably be stopped by the Gradient Watchdog\n'); 145 | end 146 | 147 | %% very optional slow step, but useful for testing during development e.g. for the real TE, TR or for staying within slewrate limits 148 | rep = seq.testReport; 149 | fprintf([rep{:}]); 150 | -------------------------------------------------------------------------------- /12_Radial_and_nonCartesian/README.md: -------------------------------------------------------------------------------- 1 | # Pulseq tutorial "Radial and Non-Cartesian" 2 | 3 | Welcome to the "Radial and Non-Cartesian" tutorial repository! This was initially developed as a part of a live demo at the virtual ISMRM meeting in 2020 and was extended somewhat further thereafter. 4 | 5 | This tutorial presents two simple radial sequences that are derived from the corresponding Cartesian counterparts. Additionally, a fast radial GRE sequence is presented, and a very basic 2D spiral is introduced. The slide deck entitled [12_Radial_and_nonCartesian.pdf](./doc/12_Radial_and_nonCartesian.pdf) shows sequence diagrams of all steps and visualises the changes at each step. 6 | 7 | ***s01\_CartesianSE*** 8 | 9 | ***s01*** describes a 2D slice-selective SE sequence with 7 blocks in 10 | each TR. Note that the polarity of the gy gradient in deltaTR (that is 11 | the polarity of both phase encoding and rephrasing gradients) remains 12 | unchanged due to the 180° refocusing RF pulse. 13 | 14 | ***s02\_RadialSE*** 15 | 16 | ***s02*** describes a 2D slice-selective radial sequence, built based on 17 | ***s01*** using the mr.rotate function. The mr.rotate function rotates 18 | the readout gradient and its pre-phaser around the *z*-axis to a certain 19 | angle, which projects the gradient into the *x*- and *y*-axis and thus 20 | produces two components. 21 | 22 | ***s03\_CartesianGradientEcho*** 23 | 24 | ***s03*** describes a 2D slice-selective Cartesian GRE sequence, similar 25 | as ***s01*** from the Tutorial 11\_from\_GRE\_to\_EPI. 26 | 27 | ***s04\_RadialGradientEcho*** 28 | 29 | ***s04*** describes a 2D slice-selective radial GRE sequence built based 30 | on ***s03***. 31 | 32 | ***s05\_FastRadialGradientEcho*** 33 | 34 | ***s05*** describes a 2D slice-selective radial GRE sequence with the 35 | shortest timing. The mr.addGradients function combines slice-selective 36 | gradient and slice-refocusing gradient into a single "ExtendedTrapezoid" 37 | gradient. The mr.align function is used to position the gxPre gradient 38 | right after the RF pulse and before the end of the slice-refocusing 39 | gradient. The flat time of the readout gradient is extended for 40 | spoiling. The whole sequence has a total of 2 blocks per TR. 41 | 42 | ***s06\_Spiral*** 43 | 44 | ***s06*** describes a 2D slice-selective spiral sequence with fat 45 | saturation. A Gaussian RF pulse followed by a spoiling gradient in the 46 | *z*-axis is used for fat saturation. A raw Archimedean spiral is 47 | generated and then resampled to stay directly under the slew rate and 48 | maximum gradient limits. The mr.traj2grad function calculates gradient 49 | strength and slew rate based on k-space trajectory. The 50 | mr.makeArbitraryGrad function generates an arbitrary gradient (e.g. 51 | spiral). 52 | 53 | ## Quick instructions 54 | 55 | Source code of the demo sequences and reconstruction scripts is the core of this repository. Please download the files to your computer and make them available to Matlab (e.g. by saving them in a subdirectory inside your Pulseq-Matlab installation and adding them to the Matlab's path). There are two sub-directories: 56 | 57 | * seq : contains example pulse sequences specifically prepared for this demo 58 | * recon : contains the reconstruction scripts tested with the above sequences 59 | 60 | ## Quick links 61 | 62 | Pulseq Matlab repository: 63 | https://github.com/pulseq/pulseq 64 | 65 | ## How to follow 66 | 67 | We strongly recommend using a text compare tool like *meld* (see this [Wikipedia page](https://en.wikipedia.org/wiki/Meld_(software)) and compare sequences from subsequent steps to visualise the respective steps. 68 | 69 | ## Further links 70 | 71 | Check out the main *Pulseq* repository at https://github.com/pulseq/pulseq and familarising yourself with the code, example sequences and reconstruction scripts (see 72 | [pulseq/matlab/demoSeq](https://github.com/pulseq/pulseq/tree/master/matlab/demoSeq) and [pulseq/matlab/demoRecon](https://github.com/pulseq/pulseq/tree/master/matlab/demoRecon)). If you already use Pulseq, consider updating to the current version. 73 | 74 | -------------------------------------------------------------------------------- /12_Radial_and_nonCartesian/doc/12_Radial_and_nonCartesian.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/12_Radial_and_nonCartesian/doc/12_Radial_and_nonCartesian.pdf -------------------------------------------------------------------------------- /12_Radial_and_nonCartesian/doc/12_Radial_and_nonCartesian.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pulseq/tutorials/651dcc7bb46353ceb05783acd39c603abca13ca3/12_Radial_and_nonCartesian/doc/12_Radial_and_nonCartesian.pptx -------------------------------------------------------------------------------- /12_Radial_and_nonCartesian/recon/r01_2DFFT.m: -------------------------------------------------------------------------------- 1 | % Reconstruction of 2D Cartesian Pulseq data 2 | % provides an example on how data reordering can be detected from the MR 3 | % sequence with almost no additional prior knowledge 4 | % 5 | % it loads Matlab .mat files with the rawdata in the format 6 | % adclen x channels x readouts 7 | % it also seeks an accompanying .seq file with the same name to interpret 8 | % the data 9 | 10 | %% Load the latest file from the specified directory 11 | path='/data/Dropbox/ismrm2021pulseq_liveDemo/dataLive/Vienna_7T_Siemens'; % directory to be scanned for data files 12 | 13 | pattern='*.mat'; 14 | D=dir([path filesep pattern]); 15 | [~,I]=sort([D(:).datenum]); 16 | data_file_path=[path filesep D(I(end-0)).name]; % use end-1 to reconstruct the second-last data set, etc... 17 | % or replace I(end-0) with I(1) to process the first dataset, I(2) for the second, etc... 18 | % load data 19 | fprintf(['loading `' data_file_path '´ ...\n']); 20 | data_unsorted = load(data_file_path); 21 | if isstruct(data_unsorted) 22 | fn=fieldnames(data_unsorted); 23 | assert(length(fn)==1); % we only expect a single variable 24 | data_unsorted=data_unsorted.(fn{1}); 25 | end 26 | % keep basic filename without the extension 27 | [p,n,e] = fileparts(data_file_path); 28 | basic_file_path=fullfile(p,n); 29 | 30 | %% Load sequence from file 31 | seq = mr.Sequence(); % Create a new sequence object 32 | seq_file_path = [basic_file_path '.seq']; 33 | seq.read(seq_file_path,'detectRFuse'); % detectRFuse is an important option for SE sequences 34 | fprintf(['loaded sequence `' seq.getDefinition('Name') '´\n']); 35 | 36 | %% raw data preparation 37 | 38 | if ndims(data_unsorted)<3 && size(data_unsorted,1)==1 39 | % OCRA data may need some fixing 40 | [~, ~, eventCount]=seq.duration(); 41 | readouts=eventCount(6); 42 | data_unsorted=reshape(data_unsorted,[length(data_unsorted)/readouts,1,readouts]); 43 | end 44 | 45 | [adc_len,channels,readouts]=size(data_unsorted); 46 | % the incoming data order is [kx coils acquisitions] 47 | data_coils_last = permute(data_unsorted, [1, 3, 2]); 48 | 49 | %% Plot and analyze the trajectory data (ktraj_adc) 50 | 51 | [ktraj_adc, ktraj, t_excitation, t_refocusing, t_adc] = seq.calculateKspace(); 52 | figure; plot(ktraj(1,:),ktraj(2,:),'b',... 53 | ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % a 2D plot 54 | axis('equal'); title('2D k-space trajectory'); 55 | 56 | % try to detect the data ordering 57 | k_extent=max(abs(ktraj_adc),[],2); 58 | k_scale=max(k_extent); 59 | k_threshold=k_scale/5000; 60 | 61 | % detect unused dimensions and delete them 62 | if any(k_extentk_threshold)=NaN; 74 | dk_all_cnt=sum(isfinite(dk_all),2); 75 | dk_all(~isfinite(dk_all))=0; 76 | dk=sum(dk_all,2)./dk_all_cnt; 77 | [~,k0_ind]=min(sum(ktraj_adc.^2,1)); 78 | kindex=round((ktraj_adc-ktraj_adc(:,k0_ind*ones(1,size(ktraj_adc,2))))./dk(:,ones(1,size(ktraj_adc,2)))); 79 | kindex_min=min(kindex,[],2); 80 | kindex_mat=kindex-kindex_min(:,ones(1,size(ktraj_adc,2)))+1; 81 | kindex_end=max(kindex_mat,[],2); 82 | sampler=zeros(kindex_end'); 83 | repeat=zeros(1,size(ktraj_adc,2)); 84 | for i=1:size(kindex_mat,2) 85 | if (size(kindex_mat,1)==3) 86 | ind=sub2ind(kindex_end,kindex_mat(1,i),kindex_mat(2,i),kindex_mat(3,i)); 87 | else 88 | ind=sub2ind(kindex_end,kindex_mat(1,i),kindex_mat(2,i)); 89 | end 90 | repeat(i)=sampler(ind); 91 | sampler(ind)=repeat(i)+1; 92 | end 93 | if (max(repeat(:))>0) 94 | kindex=[kindex;(repeat+1)]; 95 | kindex_mat=[kindex_mat;(repeat+1)]; 96 | kindex_end=max(kindex_mat,[],2); 97 | end 98 | %figure; plot(kindex(1,:),kindex(2,:),'.-'); 99 | 100 | %% sort the k-space data into the data matrix 101 | data_coils_last=reshape(data_coils_last, [adc_len*readouts, channels]); 102 | 103 | data=zeros([kindex_end' channels]); 104 | if (size(kindex,1)==3) 105 | for i=1:size(kindex,2) 106 | data(kindex_mat(1,i),kindex_mat(2,i),kindex_mat(3,i),:)=data_coils_last(i,:); 107 | end 108 | else 109 | for i=1:size(kindex,2) 110 | data(kindex_mat(1,i),kindex_mat(2,i),:)=data_coils_last(i,:); 111 | end 112 | end 113 | 114 | if size(kindex,1)==3 115 | nImages=size(data,3); 116 | else 117 | nImages=1; 118 | data=reshape(data, [size(data,1) size(data,2) 1 size(data,3)]); % we need a dummy images/slices dimension 119 | end 120 | 121 | %figure; imab(data); 122 | 123 | %% Reconstruct coil images 124 | 125 | images = zeros(size(data)); 126 | %figure; 127 | 128 | for ii = 1:channels 129 | %images(:,:,:,ii) = fliplr(rot90(fftshift(fft2(fftshift(data(:,:,:,ii)))))); 130 | images(:,:,:,ii) = fftshift(fft2(fftshift(data(end:-1:1,:,:,ii)))); 131 | end 132 | 133 | %% Image display with optional sum of squares combination 134 | figure; 135 | if channels>1 136 | sos=abs(sum(images.*conj(images),ndims(images))).^(1/2); 137 | imab(sos); title('reconstructed image(s), sum-of-squares'); 138 | %sos=sos./max(sos(:)); 139 | %imwrite(sos, ['img_combined.png'] 140 | else 141 | imab(abs(images));title('reconstructed image(s)'); 142 | end 143 | colormap('gray'); 144 | saveas(gcf,[basic_file_path '_image_2dfft'],'png'); 145 | -------------------------------------------------------------------------------- /12_Radial_and_nonCartesian/recon/r02_2D_iRadon.m: -------------------------------------------------------------------------------- 1 | % very basic inverse Radon radial reconstruction 2 | % 3 | % it loads Matlab .mat files with the rawdata in the format 4 | % adclen x channels x readouts 5 | % it also seeks an accompanying .seq file with the same name to interpret 6 | % the data 7 | 8 | %% Load the latest file from the specified directory 9 | %path='/data/Dropbox/ismrm2021pulseq_liveDemo/dataLive/Vienna_7T_Siemens'; % directory to be scanned for data files 10 | 11 | path='~/20211025-AMR/data'; 12 | 13 | pattern='*.seq'; 14 | D=dir([path filesep pattern]); 15 | [~,I]=sort([D(:).datenum]); 16 | data_file_path=[path filesep D(I(18)).name]; % use end-1 to reconstruct the second-last data set, etc... 17 | % or replace I(end-0) with I(1) to process the first dataset, I(2) for the second, etc... 18 | 19 | % basic path and filename without the extension 20 | [p,n,e] = fileparts(data_file_path); 21 | basic_file_path=fullfile(p,n); 22 | 23 | % load data 24 | data_file_path=[basic_file_path '.mat']; 25 | fprintf(['loading `' data_file_path '´ ...\n']); 26 | data_unsorted = load(data_file_path); 27 | if isstruct(data_unsorted) 28 | fn=fieldnames(data_unsorted); 29 | assert(length(fn)==1); % we only expect a single variable 30 | data_unsorted=data_unsorted.(fn{1}); 31 | end 32 | % keep basic filename without the extension 33 | [p,n,e] = fileparts(data_file_path); 34 | basic_file_path=fullfile(p,n); 35 | 36 | %% Load sequence from file 37 | seq = mr.Sequence(); % Create a new sequence object 38 | seq_file_path = [basic_file_path '.seq']; 39 | seq.read(seq_file_path,'detectRFuse'); % detectRFuse is an important option for SE sequences 40 | fprintf(['loaded sequence `' seq.getDefinition('Name') '´\n']); 41 | 42 | %% raw data preparation 43 | 44 | if ndims(data_unsorted)<3 && size(data_unsorted,1)==1 45 | % OCRA data may need some fixing 46 | [~, ~, eventCount]=seq.duration(); 47 | readouts=eventCount(6); 48 | data_unsorted=reshape(data_unsorted,[length(data_unsorted)/readouts,1,readouts]); 49 | elseif ndims(data_unsorted)==2 && size(data_unsorted,2)>1 50 | data_unsorted=reshape(data_unsorted,[size(data_unsorted,1),1,size(data_unsorted,2)]); 51 | end 52 | 53 | [adc_len,channels,readouts]=size(data_unsorted); 54 | rawdata = permute(data_unsorted, [1,3,2]); % channels last 55 | 56 | %% Analyze the nominal trajectory 57 | 58 | [ktraj_adc_nom,t_adc] = seq.calculateKspacePP('trajectory_delay',0); 59 | 60 | % detect slice dimension 61 | max_abs_ktraj_adc=max(abs(ktraj_adc_nom')); 62 | [~, slcDim]=min(max_abs_ktraj_adc); 63 | encDim=find([1 2 3]~=slcDim); 64 | 65 | ktraj_adc_nom = reshape(ktraj_adc_nom, [3, adc_len, size(ktraj_adc_nom,2)/adc_len]); 66 | 67 | prg_angle=unwrap(squeeze(atan2(ktraj_adc_nom(encDim(2),2,:)-ktraj_adc_nom(encDim(2),1,:),... 68 | ktraj_adc_nom(encDim(1),2,:)-ktraj_adc_nom(encDim(1),1,:)))); 69 | nproj=length(prg_angle); 70 | 71 | %% from k-space to projections (1D FFTs) 72 | 73 | data_fft1=ifftshift(ifft(ifftshift(rawdata,1)),1); 74 | 75 | figure; imab(abs(squeeze(data_fft1))); title('sinogramm view') 76 | 77 | %% crop the data to remove oversampling and adapt to the iradon 78 | target_matrix_size=256; 79 | shift=0; % 12 was found experimentally for the first Benjamin's data set 80 | cropLeft=(adc_len-target_matrix_size)/2+shift; 81 | cropRight=(adc_len-target_matrix_size)/2-shift; 82 | data_fft1c=data_fft1(2+cropLeft:end-cropRight,:,:); 83 | 84 | % visualize the matching of positive and negative directions 85 | p1=1; 86 | [~,p2]=min(abs(mod(prg_angle-prg_angle(p1)+2*pi,2*pi)-pi)); % look for the projection closest to the opposite to p1 87 | figure;plot(abs(data_fft1c(1:end,p1,1)));hold on;plot(abs(data_fft1c(end:-1:1,p2,1))); title('comparing opposite projections'); 88 | 89 | %% the actuall iRadon transform 90 | theta=270-prg_angle/pi*180; 91 | for c=1:channels 92 | % the classical (absolute value) transform 93 | irad_a=iradon(abs(data_fft1c(:,:,c)),theta,'linear','Hann'); 94 | %irad_a=iradon(abs(data_fft1c(:,:,c)),theta); 95 | %irad_a=iradon(abs(data_fft1c(:,:,c)),theta,'linear','Shepp-Logan'); 96 | if (c==1) 97 | irad_abs=zeros([size(irad_a) channels]); 98 | irad_cmpx=zeros([size(irad_a) channels]); 99 | end 100 | irad_abs(:,:,c)=irad_a; 101 | % MR-specific complex-valued transform 102 | irad_r=iradon(real(data_fft1c(:,:,c)),theta,'linear','Hann'); 103 | irad_i=iradon(imag(data_fft1c(:,:,c)),theta,'linear','Hann'); 104 | irad_cmpx(:,:,c)=irad_r + 1i*irad_i; 105 | end 106 | 107 | figure;imab(abs(irad_abs));colormap('gray'); title('abs iRadon recon') 108 | saveas(gcf,[basic_file_path '_image_iradon'],'png'); 109 | 110 | figure;imab(abs(irad_cmpx));colormap('gray'); title('complex iRadon recon') 111 | %axis('equal'); 112 | 113 | %% Sum of squares combination 114 | if channels>1 115 | sos=abs(sum(irad_cmpx.^2,ndims(irad_cmpx)).^(1/2)); 116 | sos=sos./max(sos(:)); 117 | figure;imab(sos);colormap('gray'); title('SOS complex iRadon recon') 118 | %imwrite(sos, ['img_combined.png']) 119 | end 120 | -------------------------------------------------------------------------------- /12_Radial_and_nonCartesian/recon/r03_2D_Gridding.m: -------------------------------------------------------------------------------- 1 | % very basic and crude non-Cartesian recon using griddata() 2 | % 3 | % it loads Matlab .mat files with the rawdata in the format 4 | % adclen x channels x readouts 5 | % it also seeks an accompanying .seq file with the same name to interpret 6 | % the data 7 | 8 | %% Load the latest file from the specified directory 9 | path='/data/Dropbox/ismrm2021pulseq_liveDemo/dataLive/Vienna_7T_Siemens'; % directory to be scanned for data files 10 | 11 | pattern='*.mat'; 12 | D=dir([path filesep pattern]); 13 | [~,I]=sort([D(:).datenum]); 14 | data_file_path=[path filesep D(I(end-0)).name]; % use end-1 to reconstruct the second-last data set, etc... 15 | % or replace I(end-0) with I(1) to process the first dataset, I(2) for the second, etc... 16 | % load data 17 | fprintf(['loading `' data_file_path '´ ...\n']); 18 | data_unsorted = load(data_file_path); 19 | if isstruct(data_unsorted) 20 | fn=fieldnames(data_unsorted); 21 | assert(length(fn)==1); % we only expect a single variable 22 | data_unsorted=data_unsorted.(fn{1}); 23 | end 24 | % keep basic filename without the extension 25 | [p,n,e] = fileparts(data_file_path); 26 | basic_file_path=fullfile(p,n); 27 | 28 | %% Load sequence from file 29 | seq = mr.Sequence(); % Create a new sequence object 30 | seq_file_path = [basic_file_path '.seq']; 31 | seq.read(seq_file_path,'detectRFuse'); % detectRFuse is an important option for SE sequences 32 | fprintf(['loaded sequence `' seq.getDefinition('Name') '´\n']); 33 | 34 | %% raw data preparation 35 | 36 | if ndims(data_unsorted)<3 && size(data_unsorted,1)==1 37 | % OCRA data may need some fixing 38 | [~, ~, eventCount]=seq.duration(); 39 | readouts=eventCount(6); 40 | data_unsorted=reshape(data_unsorted,[length(data_unsorted)/readouts,1,readouts]); 41 | elseif ndims(data_unsorted)==2 && size(data_unsorted,2)>1 42 | data_unsorted=reshape(data_unsorted,[size(data_unsorted,1),1,size(data_unsorted,2)]); 43 | end 44 | 45 | [adc_len,channels,readouts]=size(data_unsorted); 46 | rawdata = double(permute(data_unsorted, [1,3,2])); % channels last 47 | rawdata = reshape(rawdata, [adc_len*readouts,channels]); 48 | 49 | %% reconstruct the trajectory 50 | 51 | traj_recon_delay=[0 0 0]*1e-6; % adjust this parameter to potentially improve resolution & geometric accuracy. 52 | % It can be calibrated by inverting the spiral revolution dimension and making 53 | % two images match. for our Prisma and a particular trajectory we found 1.75e-6 54 | % it is also possisible to provide a vector of 3 delays (varying per axis) 55 | 56 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP('trajectory_delay',traj_recon_delay); 57 | 58 | % detect slice dimension 59 | max_abs_ktraj_adc=max(abs(ktraj_adc')); 60 | [~, slcDim]=min(max_abs_ktraj_adc); 61 | encDim=find([1 2 3]~=slcDim); 62 | 63 | % figure; plot(t_ktraj, ktraj'); % plot the entire k-space trajectory 64 | % 65 | figure; plot(ktraj(encDim(1),:),ktraj(encDim(2),:),'b',... 66 | ktraj_adc(encDim(1),:),ktraj_adc(encDim(2),:),'r.'); % a 2D plot 67 | axis('equal'); title('2D k-space trajectory'); 68 | 69 | %% Define FOV and resolution and simple off-resonance frequency correction 70 | 71 | %fov=30e-3; Nx=128; Ny=Nx; % OCRA 72 | fov=256e-3; Nx=256; Ny=Nx; % whole-body scanners 73 | deltak=1/fov; 74 | os=2; % oversampling factor (we oversample both in image and k-space) 75 | offresonance=0; % global off-resonance in Hz 76 | 77 | %% rudimentary off-resonance correction 78 | nex=length(t_excitation); 79 | t_adc_ex=t_adc; 80 | if nex>1 81 | for e=2:nex 82 | i1=find(t_adc>t_excitation(e),1); 83 | if e1 129 | sos=abs(sum(igdc.^2,ndims(igdc)).^(1/2)); 130 | sos=sos./max(sos(:)); 131 | figure;imab(sos);colormap('gray'); 132 | %imwrite(sos, ['img_combined.png']) 133 | end 134 | 135 | saveas(gcf,[basic_file_path '_image_2dgrd'],'png'); 136 | -------------------------------------------------------------------------------- /12_Radial_and_nonCartesian/recon/r04_radial_delay_calculation.m: -------------------------------------------------------------------------------- 1 | % very basic and crude non-Cartesian recon using griddata() 2 | % 3 | % it loads Matlab .mat files with the rawdata in the format 4 | % adclen x channels x readouts 5 | % it also seeks an accompanzing .seq file with the same name to interpret 6 | % the data 7 | 8 | %% Load the latest file from the specified directory 9 | %path='../IceNIH_RawSend/'; % directory to be scanned for data files 10 | %path='/data/Dropbox/ismrm2021pulseq_liveDemo/dataLive/Vienna_7T_Siemens'; % directory to be scanned for data files 11 | %path='/data/Dropbox/ismrm2021pulseq_liveDemo/dataPrerecorded/Vienna_7T_Siemens' 12 | path='~/20211025-AMR/data'; 13 | 14 | pattern='*.seq'; 15 | D=dir([path filesep pattern]); 16 | [~,I]=sort([D(:).datenum]); 17 | data_file_path=[path filesep D(I(18)).name]; % use end-1 to reconstruct the second-last data set, etc... 18 | % or replace I(end-0) with I(1) to process the first dataset, I(2) for the second, etc... 19 | 20 | % basic path and filename without the extension 21 | [p,n,e] = fileparts(data_file_path); 22 | basic_file_path=fullfile(p,n); 23 | 24 | % load data 25 | data_file_path=[basic_file_path '.mat']; 26 | fprintf(['loading `' data_file_path '´ ...\n']); 27 | data_unsorted = load(data_file_path); 28 | if isstruct(data_unsorted) 29 | fn=fieldnames(data_unsorted); 30 | assert(length(fn)==1); % we only expect a single variable 31 | data_unsorted=data_unsorted.(fn{1}); 32 | end 33 | 34 | %% Load sequence from file 35 | seq = mr.Sequence(); % Create a new sequence object 36 | seq_file_path = [basic_file_path '.seq']; 37 | seq.read(seq_file_path,'detectRFuse'); % detectRFuse is an important option for SE sequences 38 | fprintf(['loaded sequence `' seq.getDefinition('Name') '´\n']); 39 | 40 | %% raw data preparation 41 | 42 | if ndims(data_unsorted)<3 && size(data_unsorted,1)==1 43 | % OCRA data may need some fixing 44 | [~, ~, eventCount]=seq.duration(); 45 | readouts=eventCount(6); 46 | data_unsorted=reshape(data_unsorted,[length(data_unsorted)/readouts,1,readouts]); 47 | elseif ndims(data_unsorted)==2 && size(data_unsorted,2)>1 48 | data_unsorted=reshape(data_unsorted,[size(data_unsorted,1),1,size(data_unsorted,2)]); 49 | end 50 | 51 | [adc_len,channels,readouts]=size(data_unsorted); 52 | rawdata = permute(data_unsorted, [1,3,2]); % channels last 53 | 54 | %% Analyze the nominal trajectory 55 | 56 | [ktraj_adc_nom,t_adc] = seq.calculateKspacePP('trajectory_delay',0e-6); 57 | 58 | % detect slice dimension 59 | max_abs_ktraj_adc=max(abs(ktraj_adc_nom')); 60 | [~, slcDim]=min(max_abs_ktraj_adc); 61 | encDim=find([1 2 3]~=slcDim); 62 | 63 | ktraj_adc_nom = reshape(ktraj_adc_nom, [3, adc_len, size(ktraj_adc_nom,2)/adc_len]); 64 | 65 | prg_angle=squeeze(atan2(ktraj_adc_nom(encDim(2),2,:)-ktraj_adc_nom(encDim(2),1,:),ktraj_adc_nom(encDim(2),2,:)-ktraj_adc_nom(encDim(2),1,:))); 66 | nproj=length(prg_angle); 67 | 68 | i_pure2=find(abs(ktraj_adc_nom(encDim(2),1,:))=0); 46 | assert(delayTE2>mr.calcDuration(g_sp2)); 47 | assert(delayTR>=0); 48 | 49 | % Loop over repetitions and define sequence blocks 50 | for i=(1-Ndummy):Ny 51 | seq.addBlock(rf_ex,gs); 52 | if (i>0) % semi-negative index -- dummy scans 53 | gy = mr.makeTrapezoid('y', 'Area', phaseAreas(i), 'Duration', grPredur); 54 | else 55 | gy = mr.makeTrapezoid('y', 'Area', 0, 'Duration', grPredur); 56 | end 57 | seq.addBlock(grPre,gy); 58 | seq.addBlock(mr.makeDelay(delayTE1)); 59 | seq.addBlock(rf_ref,g_sp1); 60 | seq.addBlock(g_sp2, mr.makeDelay(delayTE2)); 61 | if (i>0) % semi-negative index -- dummy scans 62 | seq.addBlock(adc,gr); 63 | else 64 | seq.addBlock(gr); 65 | end 66 | seq.addBlock(gy,mr.makeDelay(delayTR)); 67 | end 68 | 69 | % show the first non-dummy TR with the block structure 70 | seq.plot('showBlocks',1,'timeRange',TR*(Ndummy+[0 1]),'timeDisp','us'); 71 | 72 | % check whether the timing of the sequence is compatible with the scanner 73 | [ok, error_report]=seq.checkTiming; 74 | 75 | if (ok) 76 | fprintf('Timing check passed successfully\n'); 77 | else 78 | fprintf('Timing check failed! Error listing follows:\n'); 79 | fprintf([error_report{:}]); 80 | fprintf('\n'); 81 | end 82 | 83 | seq.setDefinition('FOV', [fov fov sliceThickness]); 84 | seq.setDefinition('Name', 'se_2d'); 85 | 86 | seq.write('se_2d.seq') % Write to pulseq file 87 | %seq.install('siemens'); % copy to scanner 88 | 89 | % calculate k-space but only use it to check timing 90 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 91 | %[ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP('trajectory_delay',[0 0 0]*1e-6); % play with anisotropic trajectory delays -- zoom in to see the trouble ;-) 92 | 93 | if Ndummy==0 % if nDummy equals 0 we can do additional checks 94 | assert(abs(t_refocusing(1)-t_excitation(1)-TE/2)<1e-6); % check that the refocusing happens at the 1/2 of TE 95 | assert(abs(t_adc(Nx/2)-t_excitation(1)-TE)=0); 47 | assert(delayTE2>mr.calcDuration(g_sp2)); 48 | assert(delayTR>=0); 49 | 50 | % Loop over repetitions and define sequence blocks 51 | for i=(1-Ndummy):Nr 52 | seq.addBlock(rf_ex,gs); 53 | seq.addBlock(mr.rotate('z',delta*(i-1),grPre)); 54 | seq.addBlock(mr.makeDelay(delayTE1)); 55 | seq.addBlock(rf_ref,g_sp1); 56 | seq.addBlock(g_sp2, mr.makeDelay(delayTE2)); 57 | if (i>0) 58 | seq.addBlock(mr.rotate('z',delta*(i-1),adc,gr)); 59 | else 60 | seq.addBlock(mr.rotate('z',delta*(i-1),gr)); 61 | end 62 | seq.addBlock(mr.makeDelay(delayTR)); 63 | end 64 | 65 | seq.plot(); 66 | 67 | % check whether the timing of the sequence is compatible with the scanner 68 | [ok, error_report]=seq.checkTiming; 69 | 70 | if (ok) 71 | fprintf('Timing check passed successfully\n'); 72 | else 73 | fprintf('Timing check failed! Error listing follows:\n'); 74 | fprintf([error_report{:}]); 75 | fprintf('\n'); 76 | end 77 | 78 | seq.setDefinition('FOV', [fov fov sliceThickness]); 79 | seq.setDefinition('Name', 'se_rad'); 80 | 81 | seq.write('se_radial.seq') % Write to pulseq file 82 | %seq.install('siemens'); % copy to scanner 83 | 84 | % calculate k-space but only use it to check timing 85 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP(); 86 | %[ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP('trajectory_delay',[0 0 0]*1e-6); % play with anisotropic trajectory delays -- zoom in to see the trouble ;-) 87 | 88 | if Ndummy==0 89 | assert(abs(t_refocusing(1)-t_excitation(1)-TE/2)<1e-6); % check that the refocusing happens at the 1/2 of TE 90 | assert(abs(t_adc(Nx/2)-t_excitation(1)-TE)=0)); 40 | assert(all(delayTR>=mr.calcDuration(gxSpoil,gzSpoil))); 41 | 42 | rf_phase=0; 43 | rf_inc=0; 44 | 45 | for i=(-Ndummy+1):Ny 46 | for c=1:length(TE) 47 | rf.phaseOffset=rf_phase/180*pi; 48 | adc.phaseOffset=rf_phase/180*pi; 49 | rf_inc=mod(rf_inc+rfSpoilingInc, 360.0); 50 | rf_phase=mod(rf_phase+rf_inc, 360.0); 51 | % 52 | seq.addBlock(rf,gz); 53 | if (i>0) 54 | gyPre = mr.makeTrapezoid('y','Area',phaseAreas(i),'Duration',mr.calcDuration(gxPre),'system',sys); 55 | seq.addBlock(gxPre,gyPre,gzReph); 56 | else 57 | seq.addBlock(gxPre,gzReph); 58 | end 59 | seq.addBlock(mr.makeDelay(delayTE(c))); 60 | if (i>0) 61 | seq.addBlock(gx,adc); 62 | seq.addBlock(mr.makeDelay(delayTR(c)),gxSpoil,mr.scaleGrad(gyPre,-1),gzSpoil) 63 | else 64 | seq.addBlock(gx); 65 | seq.addBlock(mr.makeDelay(delayTR(c)),gxSpoil,gzSpoil) 66 | end 67 | end 68 | end 69 | 70 | % check whether the timing of the sequence is compatible with the scanner 71 | [ok, error_report]=seq.checkTiming; 72 | 73 | if (ok) 74 | fprintf('Timing check passed successfully\n'); 75 | else 76 | fprintf('Timing check failed! Error listing follows:\n'); 77 | fprintf([error_report{:}]); 78 | fprintf('\n'); 79 | end 80 | 81 | % 82 | seq.setDefinition('FOV', [fov fov sliceThickness]); 83 | seq.setDefinition('Name', 'gre_rad'); 84 | 85 | seq.write('gre_rad.seq') % Write to pulseq file 86 | %seq.install('siemens'); 87 | 88 | %% plot sequence and k-space diagrams 89 | 90 | %seq.plot('timeRange',[0 5]*TR); 91 | seq.plot(); 92 | 93 | % trajectory calculation 94 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing1] = seq.calculateKspacePP(); 95 | %[ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP('trajectory_delay',[0 0 0]*1e-6); % play with anisotropic trajectory delays -- zoom in to see the trouble ;-) 96 | 97 | % plot k-spaces 98 | figure; plot(t_ktraj, ktraj'); % plot the entire k-space trajectory 99 | hold on; plot(t_adc,ktraj_adc(1,:),'.'); % and sampling points on the kx-axis 100 | title('k-vector components as functions of time'); xlabel('time /s'); ylabel('k-component /m^-^1'); 101 | figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D plot 102 | axis('equal'); % enforce aspect ratio for the correct trajectory display 103 | hold on;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 104 | title('2D k-space trajectory'); xlabel('k_x /m^-^1'); ylabel('k_y /m^-^1'); 105 | -------------------------------------------------------------------------------- /12_Radial_and_nonCartesian/seq/s04_RadialGradientEcho.m: -------------------------------------------------------------------------------- 1 | % set system limits 2 | sys = mr.opts('MaxGrad', 28, 'GradUnit', 'mT/m', ... 3 | 'MaxSlew', 80, 'SlewUnit', 'T/m/s', ... 4 | 'rfRingdownTime', 20e-6, 'rfDeadTime', 100e-6, 'adcDeadTime', 10e-6); 5 | 6 | seq=mr.Sequence(sys); % Create a new sequence object 7 | fov=256e-3; Nx=256; % Define FOV and resolution 8 | alpha=30; % flip angle 9 | sliceThickness=3e-3; % slice 10 | TE=8e-3; % TE; give a vector here to have multiple TEs (e.g. for field mapping) 11 | TR=20e-3; % only a single value for now 12 | Nr=256; % number of radial spokes 13 | Ndummy=10; % number of dummy scans 14 | delta=pi/Nr; % angular increment; try golden angle pi*(3-5^0.5) or 0.5 of it 15 | 16 | % more in-depth parameters 17 | rfSpoilingInc=117; % RF spoiling increment 18 | 19 | % Create alpha-degree slice selection pulse and gradient 20 | [rf, gz] = mr.makeSincPulse(alpha*pi/180,'Duration',4e-3,... 21 | 'SliceThickness',sliceThickness,'apodization',0.5,'timeBwProduct',4,'system',sys); 22 | 23 | % Define other gradients and ADC events 24 | deltak=1/fov; 25 | gx = mr.makeTrapezoid('x','FlatArea',Nx*deltak,'FlatTime',3.2e-3,'system',sys); 26 | adc = mr.makeAdc(Nx,'Duration',gx.flatTime,'Delay',gx.riseTime,'system',sys); 27 | gxPre = mr.makeTrapezoid('x','Area',-gx.area/2-deltak/2,'Duration',2e-3,'system',sys); % we need this "deltak/2" because of the ADC sampling taking place in the middle of the dwell time 28 | gzReph = mr.makeTrapezoid('z','Area',-gz.area/2,'Duration',2e-3,'system',sys); 29 | 30 | % gradient spoiling 31 | gxSpoil=mr.makeTrapezoid('x','Area',1*Nx*deltak,'system',sys); 32 | gzSpoil=mr.makeTrapezoid('z','Area',4/sliceThickness,'system',sys); 33 | 34 | % Calculate timing 35 | delayTE=ceil((TE - mr.calcDuration(gxPre) - gz.fallTime - gz.flatTime/2 ... 36 | - mr.calcDuration(gx)/2)/seq.gradRasterTime)*seq.gradRasterTime; 37 | delayTR=ceil((TR - mr.calcDuration(gxPre) - mr.calcDuration(gz) ... 38 | - mr.calcDuration(gx) - delayTE)/seq.gradRasterTime)*seq.gradRasterTime; 39 | assert(all(delayTE>=0)); 40 | assert(all(delayTR>=mr.calcDuration(gxSpoil,gzSpoil))); 41 | 42 | % start the sequence 43 | rf_phase=0; 44 | rf_inc=0; 45 | 46 | for i=(-Ndummy+1):Nr 47 | for c=1:length(TE) 48 | rf.phaseOffset=rf_phase/180*pi; 49 | adc.phaseOffset=rf_phase/180*pi; 50 | rf_inc=mod(rf_inc+rfSpoilingInc, 360.0); 51 | rf_phase=mod(rf_phase+rf_inc, 360.0); 52 | % 53 | seq.addBlock(rf,gz); 54 | phi=delta*(i-1); 55 | seq.addBlock(mr.rotate('z',phi,gxPre,gzReph)); 56 | seq.addBlock(mr.makeDelay(delayTE(c))); 57 | if i>0 58 | seq.addBlock(mr.rotate('z',phi,gx,adc)); 59 | else 60 | seq.addBlock(mr.rotate('z',phi,gx)); 61 | end 62 | seq.addBlock(mr.rotate('z',phi,gxSpoil,gzSpoil,mr.makeDelay(delayTR))); 63 | end 64 | end 65 | 66 | % check whether the timing of the sequence is compatible with the scanner 67 | [ok, error_report]=seq.checkTiming; 68 | 69 | if (ok) 70 | fprintf('Timing check passed successfully\n'); 71 | else 72 | fprintf('Timing check failed! Error listing follows:\n'); 73 | fprintf([error_report{:}]); 74 | fprintf('\n'); 75 | end 76 | 77 | % 78 | seq.setDefinition('FOV', [fov fov sliceThickness]); 79 | seq.setDefinition('Name', 'gre_rad'); 80 | 81 | seq.write('gre_rad.seq') % Write to pulseq file 82 | %seq.install('siemens'); 83 | 84 | %% plot sequence and k-space diagrams 85 | 86 | %seq.plot('timeRange',[0 5]*TR); 87 | seq.plot(); 88 | 89 | % trajectory calculation 90 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing1] = seq.calculateKspacePP(); 91 | %[ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP('trajectory_delay',[0 0 0]*1e-6); % play with anisotropic trajectory delays -- zoom in to see the trouble ;-) 92 | 93 | % plot k-spaces 94 | figure; plot(t_ktraj, ktraj'); % plot the entire k-space trajectory 95 | hold on; plot(t_adc,ktraj_adc(1,:),'.'); % and sampling points on the kx-axis 96 | title('k-vector components as functions of time'); xlabel('time /s'); ylabel('k-component /m^-^1'); 97 | figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D plot 98 | axis('equal'); % enforce aspect ratio for the correct trajectory display 99 | hold on;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 100 | title('2D k-space trajectory'); xlabel('k_x /m^-^1'); ylabel('k_y /m^-^1'); 101 | -------------------------------------------------------------------------------- /12_Radial_and_nonCartesian/seq/s05_FastRadialGradientEcho.m: -------------------------------------------------------------------------------- 1 | % set system limits (slew rate 130 and max_grad 30 work on Prisma) 2 | sys = mr.opts('MaxGrad', 28, 'GradUnit', 'mT/m', ... 3 | 'MaxSlew', 120, 'SlewUnit', 'T/m/s', ... 4 | 'rfRingdownTime', 10e-6,'rfDeadTime', 100e-6, 'adcDeadTime', 10e-6); 5 | 6 | seq=mr.Sequence(sys); % Create a new sequence object 7 | fov=240e-3; Nx=240; % Define FOV and resolution 8 | alpha=5; % flip angle 9 | sliceThickness=3e-3; % slice 10 | % TE & TR are as short as possible derived from the above parameters and 11 | % the system specs above 12 | Nr=30; % number of radial spokes 13 | Ndummy=1; % number of dummy scans 14 | delta= pi / Nr; % angular increment; try golden angle pi*(3-5^0.5) or 0.5 of it 15 | % more in-depth parameters 16 | ro_dur=1200e-6; % RO duration 17 | ro_os=2; % readout oversampling 18 | ro_spoil=0.5; % additional k-max excursion for RO spoiling 19 | rf_dur = 600e-6; 20 | sl_spoil=2; % spoil area compared to the slice thickness 21 | 22 | 23 | 24 | % more in-depth parameters 25 | rfSpoilingInc=117; % RF spoiling increment 26 | 27 | % Create alpha-degree slice selection pulse and gradient 28 | [rf, gz, gzReph] = mr.makeSincPulse(alpha*pi/180,'Duration',rf_dur,... 29 | 'SliceThickness',sliceThickness,'apodization',0.5,'timeBwProduct',2,'system',sys); 30 | gzReph.delay=mr.calcDuration(gz); 31 | gzComb=mr.addGradients({gz, gzReph}, 'system', sys); 32 | 33 | % Define other gradients and ADC events 34 | deltak=1/fov; 35 | gx = mr.makeTrapezoid('x','Amplitude',Nx*deltak/ro_dur,'FlatTime',ceil(ro_dur/sys.gradRasterTime)*sys.gradRasterTime,'system',sys); 36 | adc = mr.makeAdc(Nx*ro_os,'Duration',ro_dur,'Delay',gx.riseTime,'system',sys); 37 | gxPre = mr.makeTrapezoid('x','Area',-gx.amplitude*(ro_dur/Nx/ro_os*(Nx*ro_os/2-0.5)+0.5*gx.riseTime),'system',sys); % 0.5 is necessary to acount for the Siemens sampling in the center of the dwell periods 38 | % start gxPre at least right after the RF pulse and when possible end it at the same time as the end of the slice refocusing gradient 39 | [gxPre,~,~]=mr.align('right', gxPre, 'right', gzComb, 'left', mr.makeDelay(mr.calcDuration(rf)+mr.calcDuration(gxPre))); 40 | 41 | % gradient spoiling 42 | if sl_spoil>0 43 | sp_area_needed=sl_spoil/sliceThickness-gz.area/2; 44 | gzSpoil=mr.makeTrapezoid('z','Area',sp_area_needed,'system',sys,'Delay',gx.riseTime+gx.flatTime); 45 | else 46 | gzSpoil=[]; 47 | end 48 | 49 | if ro_spoil>0 50 | ro_add_time=ceil(((gx.area/Nx*(Nx/2+1)*ro_spoil)/gx.amplitude)/sys.gradRasterTime)*sys.gradRasterTime; 51 | gx.flatTime=gx.flatTime+ro_add_time; % careful, areas stored in the object are now wrong 52 | end 53 | 54 | 55 | % we don't calculate timing but just accept what is achievable 56 | TR=0; 57 | TE=0; 58 | 59 | % start the sequence 60 | rf_phase=0; 61 | rf_inc=0; 62 | for i=(1-Ndummy):3 63 | rf.phaseOffset=rf_phase/180*pi; 64 | adc.phaseOffset=rf_phase/180*pi; 65 | rf_inc=mod(rf_inc+rfSpoilingInc, 360.0); 66 | rf_phase=mod(rf_phase+rf_inc, 360.0); 67 | % 68 | phi=delta*(i-1); 69 | seq.addBlock(mr.rotate('z',phi,rf,gzComb,gxPre)); 70 | if TE<=0, TE=seq.duration+adc.delay+adc.dwell*(adc.numSamples/2+0.5); end 71 | if i>0 72 | seq.addBlock(mr.rotate('z',phi,gx,adc,gzSpoil)); 73 | else 74 | seq.addBlock(mr.rotate('z',phi,gx,gzSpoil)); 75 | end 76 | if TR<=0, TR=seq.duration; end 77 | end 78 | 79 | % check whether the timing of the sequence is compatible with the scanner 80 | [ok, error_report]=seq.checkTiming; 81 | 82 | if (ok) 83 | fprintf('Timing check passed successfully\n'); 84 | else 85 | fprintf('Timing check failed! Error listing follows:\n'); 86 | fprintf([error_report{:}]); 87 | fprintf('\n'); 88 | end 89 | 90 | % 91 | seq.setDefinition('FOV', [fov fov sliceThickness]); 92 | seq.setDefinition('Name', 'gre_rad'); 93 | 94 | seq.write('gre_rad.seq') % Write to pulseq file 95 | %seq.install('siemens'); 96 | 97 | %% plot sequence and k-space diagrams 98 | 99 | %seq.plot('timeRange',[0 5]*TR); 100 | seq.plot(); 101 | 102 | % trajectory calculation 103 | [ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing1] = seq.calculateKspacePP(); 104 | %[ktraj_adc, t_adc, ktraj, t_ktraj, t_excitation, t_refocusing] = seq.calculateKspacePP('trajectory_delay',[0 0 0]*1e-6); % play with anisotropic trajectory delays -- zoom in to see the trouble ;-) 105 | 106 | % plot k-spaces 107 | figure; plot(t_ktraj, ktraj'); % plot the entire k-space trajectory 108 | hold on; plot(t_adc,ktraj_adc(1,:),'.'); % and sampling points on the kx-axis 109 | title('k-vector components as functions of time'); xlabel('time /s'); ylabel('k-component /m^-^1'); 110 | figure; plot(ktraj(1,:),ktraj(2,:),'b'); % a 2D plot 111 | axis('equal'); % enforce aspect ratio for the correct trajectory display 112 | hold on;plot(ktraj_adc(1,:),ktraj_adc(2,:),'r.'); % plot the sampling points 113 | title('2D k-space trajectory'); xlabel('k_x /m^-^1'); ylabel('k_y /m^-^1'); 114 | 115 | return; 116 | 117 | %% very optional slow step, but useful for testing during development e.g. for the real TE, TR or for staying within slew rate limits 118 | 119 | rep = seq.testReport; 120 | fprintf([rep{:}]); 121 | 122 | -------------------------------------------------------------------------------- /12_Radial_and_nonCartesian/seq/s06_Spiral.m: -------------------------------------------------------------------------------- 1 | % this is an experimental spiral sequence 2 | 3 | fov=256e-3; Nx=96; Ny=Nx; % Define FOV and resolution 4 | sliceThickness=3e-3; % slice thinckness 5 | Nslices=1; 6 | Oversampling=2; % oversampling along the readout (trajectory) dimension; I would say it needs to be at least 2 7 | phi=pi/2; % orientation of the readout e.g. for interleaving 8 | 9 | % Set system limits 10 | sys = mr.opts('MaxGrad',22,'GradUnit','mT/m',... 11 | 'MaxSlew',160,'SlewUnit','T/m/s',... 12 | 'rfRingdownTime', 30e-6, 'rfDeadtime', 100e-6, 'adcDeadTime', 10e-6,... 13 | 'adcSamplesLimit', 8192); % adcSamplesLimit is important on many Siemens platforms 14 | 15 | seq=mr.Sequence(sys); % Create a new sequence object 16 | warning('OFF', 'mr:restoreShape'); % restore shape is not compatible with spirals and will throw a warning from each plot() or calcKspace() call 17 | 18 | % Create fat-sat pulse 19 | % (in Siemens interpreter from January 2019 duration is limited to 8.192 ms, and although product EPI uses 10.24 ms, 8 ms seems to be sufficient) 20 | B0=2.89; % 1.5 2.89 3.0 21 | sat_ppm=-3.45; 22 | sat_freq=sat_ppm*1e-6*B0*sys.gamma; 23 | rf_fs = mr.makeGaussPulse(110*pi/180,'system',sys,'Duration',8e-3,... 24 | 'bandwidth',abs(sat_freq),'freqOffset',sat_freq); 25 | gz_fs = mr.makeTrapezoid('z',sys,'delay',mr.calcDuration(rf_fs),'Area',1/1e-4); % spoil up to 0.1mm 26 | 27 | % Create 90 degree slice selection pulse and gradient 28 | [rf, gz] = mr.makeSincPulse(pi/2,'system',sys,'Duration',3e-3,... 29 | 'SliceThickness',sliceThickness,'apodization',0.5,'timeBwProduct',4); 30 | 31 | % define k-space parameters 32 | deltak=1/fov; 33 | kRadius = round(Nx/2); 34 | kSamples=round(2*pi*kRadius)*Oversampling; 35 | 36 | % calculate a raw Archimedian spiral trajectory 37 | clear ka; 38 | ka(kRadius*kSamples+1)=1i; % init as complex 39 | for c=0:kRadius*kSamples 40 | r=deltak*c/kSamples; 41 | a=mod(c,kSamples)*2*pi/kSamples; 42 | ka(c+1)=r*exp(1i*a); 43 | end 44 | ka=[real(ka); imag(ka)]; 45 | % calculate gradients and slew rates 46 | [ga, sa]=mr.traj2grad(ka); 47 | 48 | % limit analysis 49 | safety_magrin=0.94; % we need that otherwise we just about violate the slew rate due to the rounding errors 50 | dt_gabs=abs(ga(1,:)+1i*ga(2,:))/(sys.maxGrad*safety_magrin)*sys.gradRasterTime; 51 | dt_sabs=sqrt(abs(sa(1,:)+1i*sa(2,:))/(sys.maxSlew*safety_magrin))*sys.gradRasterTime; 52 | dt_smooth=max([dt_gabs;dt_sabs]); 53 | 54 | % apply the lower limit not to lose the trajectory detail 55 | dt_min=4*sys.gradRasterTime/kSamples; % we want at least 4 points per revolution 56 | dt_smooth0=dt_smooth; 57 | dt_smooth(dt_smooth