mrzerocore 0.2.9__tar.gz → 0.2.11__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (95) hide show
  1. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/.gitignore +1 -0
  2. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/CHANGELOG.md +10 -1
  3. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/Cargo.lock +1 -1
  4. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/Cargo.toml +1 -1
  5. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/PKG-INFO +2 -2
  6. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/_toc.yml +1 -0
  7. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/api.md +15 -6
  8. MRzeroCore-0.2.11/documentation/playground_mr0/flash_DWI.ipynb +201 -0
  9. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/mr00_FLASH_2D_ernstAngle_opt.ipynb +1 -1
  10. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/mr0_CS_cartesian_seq.ipynb +1 -1
  11. MRzeroCore-0.2.11/documentation/playground_mr0/mr0_CS_radial_seq.ipynb +1 -0
  12. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/mr0_DREAM_STE_seq.ipynb +1 -1
  13. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/mr0_DREAM_STID_seq.ipynb +1 -1
  14. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/mr0_DWI_GRE_2D_seq.ipynb +1 -1
  15. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/mr0_EPI_2D_seq.ipynb +1 -1
  16. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/mr0_FID_seq.ipynb +1 -1
  17. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/mr0_FLASH_2D_seq.ipynb +1 -1
  18. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/mr0_GRE_to_FLASH.ipynb +1 -1
  19. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/mr0_RARE_2D_seq.ipynb +1 -1
  20. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/mr0_SE_CPMG_seq.ipynb +1 -1
  21. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/mr0_STE_3pulses_5echoes_seq.ipynb +1 -1
  22. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/mr0_bSSFP_2D_seq.ipynb +1 -1
  23. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/mr0_opt_FLASH_2D_IR_Fit_T1.ipynb +1 -1
  24. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/mr0_opt_FLASH_2D_IR_voxelNN_T1.ipynb +1 -1
  25. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/mr0_pypulseq_exmpls_seq.ipynb +1 -1
  26. MRzeroCore-0.2.11/documentation/playground_mr0/mr0_upload_seq.ipynb +1 -0
  27. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/overview.md +33 -32
  28. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/pulseq_flash.ipynb +1 -1
  29. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/pulseq_rf_shim.ipynb +2 -2
  30. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/phantom/brainweb/brainweb_data.json +1 -1
  31. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/sequence.py +2 -2
  32. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/simulation/main_pass.py +24 -10
  33. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/src/lib.rs +1 -1
  34. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/src/pre_pass.rs +6 -21
  35. MRzeroCore-0.2.9/documentation/playground_mr0/mr0_CS_radial_seq.ipynb +0 -1
  36. MRzeroCore-0.2.9/documentation/playground_mr0/mr0_upload_seq.ipynb +0 -1
  37. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/.github/workflows/pypi_publish.yml +0 -0
  38. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/.readthedocs.yaml +0 -0
  39. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/EULA.txt +0 -0
  40. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/LICENSE +0 -0
  41. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/README.md +0 -0
  42. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/_config.yml +0 -0
  43. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/api/phantom.md +0 -0
  44. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/api/reco.md +0 -0
  45. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/api/sequence.md +0 -0
  46. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/api/simulation/isochromat_sim.md +0 -0
  47. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/api/simulation/pdg_sim.md +0 -0
  48. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/api/simulation.md +0 -0
  49. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/api/util.md +0 -0
  50. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/intro.md +0 -0
  51. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/logo.png +0 -0
  52. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/phantom_generation.md +0 -0
  53. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/AdjDataUser2gB0_transversal_0.08moving_average.mat +0 -0
  54. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/flash.ipynb +0 -0
  55. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/mr0_burst_TSE.ipynb +0 -0
  56. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/numerical_brain_cropped.mat +0 -0
  57. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/ptx_phantom.p +0 -0
  58. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/pulseq_sim_pTx.ipynb +0 -0
  59. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/seqs/flash pTx CP.seq +0 -0
  60. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/seqs/flash pTx EP.seq +0 -0
  61. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/seqs/flash pTx QM.seq +0 -0
  62. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/playground_mr0/subject05.npz +0 -0
  63. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/requirements.txt +0 -0
  64. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/resources/logo.blend +0 -0
  65. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/resources/logo.png +0 -0
  66. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/documentation/resources/studio_small_09_2k.hdr +0 -0
  67. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/pyproject.toml +0 -0
  68. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/__init__.py +0 -0
  69. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/phantom/brainweb/__init__.py +0 -0
  70. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/phantom/brainweb/brainweb_data_sources.txt +0 -0
  71. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/phantom/brainweb/output/.gitkeep +0 -0
  72. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/phantom/custom_voxel_phantom.py +0 -0
  73. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/phantom/sim_data.py +0 -0
  74. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/phantom/voxel_grid_phantom.py +0 -0
  75. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/pulseq/exporter.py +0 -0
  76. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/pulseq/exporter_v2.py +0 -0
  77. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/pulseq/helpers.py +0 -0
  78. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/pulseq/pulseq_exporter.py +0 -0
  79. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/pulseq/pulseq_loader/__init__.py +0 -0
  80. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/pulseq/pulseq_loader/adc.py +0 -0
  81. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/pulseq/pulseq_loader/helpers.py +0 -0
  82. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/pulseq/pulseq_loader/pulse.py +0 -0
  83. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/pulseq/pulseq_loader/pulseq_file/__init__.py +0 -0
  84. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/pulseq/pulseq_loader/pulseq_file/adc.py +0 -0
  85. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/pulseq/pulseq_loader/pulseq_file/block.py +0 -0
  86. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/pulseq/pulseq_loader/pulseq_file/definitons.py +0 -0
  87. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/pulseq/pulseq_loader/pulseq_file/gradient.py +0 -0
  88. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/pulseq/pulseq_loader/pulseq_file/helpers.py +0 -0
  89. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/pulseq/pulseq_loader/pulseq_file/rf.py +0 -0
  90. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/pulseq/pulseq_loader/pulseq_file/trap.py +0 -0
  91. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/pulseq/pulseq_loader/spoiler.py +0 -0
  92. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/reconstruction.py +0 -0
  93. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/simulation/isochromat_sim.py +0 -0
  94. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/simulation/pre_pass.py +0 -0
  95. {MRzeroCore-0.2.9 → MRzeroCore-0.2.11}/python/MRzeroCore/util.py +0 -0
@@ -4,6 +4,7 @@
4
4
  out/
5
5
  *.seq
6
6
  *.npz
7
+ *.env
7
8
 
8
9
  *.pdf
9
10
 
@@ -1,3 +1,12 @@
1
+ - 0.2.11
2
+ - (re-)introduced return_mag_z parameter in pdg simulation
3
+ - 0.2.10
4
+ - change default FOV in all notebooks to phantom size (200 mm)
5
+ - Use Open in Colab badges in the playground instead of links
6
+ - Documented how to run on GPU
7
+ - Added another pure mr0 flash DWI sequence to the playground
8
+ - Bugfix: normalized_grads setting was not passed in Sequence.cuda() and .cpu()
9
+ - Bugfix: removed wrong 2*pi factor from Diffusion b-value calculations
1
10
  - 0.2.9
2
11
  - Change sign in gradient simulation - flips orientation of old reconstructions
3
12
  - Included util file in mr0
@@ -11,4 +20,4 @@
11
20
  - 0.2.6
12
21
  - New pulseq importer (correctly implements 1.2 - 1.4 spec)
13
22
  - FOV / phantom size now in SI units per default
14
- - Normalized gradients (when defining sequences in mr0) is now an explicit sequence flag
23
+ - Normalized gradients (when defining sequences in mr0) is now an explicit sequence flag
@@ -53,7 +53,7 @@ dependencies = [
53
53
 
54
54
  [[package]]
55
55
  name = "mrzero_core"
56
- version = "0.2.9"
56
+ version = "0.2.11"
57
57
  dependencies = [
58
58
  "num-complex",
59
59
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "mrzero_core"
3
- version = "0.2.9"
3
+ version = "0.2.11"
4
4
  edition = "2021"
5
5
 
6
6
  # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: MRzeroCore
3
- Version: 0.2.9
3
+ Version: 0.2.11
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -18,9 +18,9 @@ Summary: Core functionality of MRzero
18
18
  Author-email: Jonathan Endres <jonathan.endres@uk-erlangen.de>
19
19
  Requires-Python: >=3.9
20
20
  Description-Content-Type: text/markdown; charset=UTF-8; variant=GFM
21
- Project-URL: Documentation, https://mrzero-core.readthedocs.io/
22
21
  Project-URL: MRzero-Paper, https://arxiv.org/abs/2002.04265
23
22
  Project-URL: Repository, https://github.com/MRsources/MRzero-Core
23
+ Project-URL: Documentation, https://mrzero-core.readthedocs.io/
24
24
 
25
25
  [![Documentation Status](https://readthedocs.org/projects/mrzero-core/badge/?version=latest)](https://mrzero-core.readthedocs.io/en/latest/?badge=latest)
26
26
 
@@ -42,5 +42,6 @@ chapters:
42
42
  - file: playground_mr0/mr00_FLASH_2D_ernstAngle_opt.ipynb
43
43
 
44
44
  - file: playground_mr0/flash
45
+ - file: playground_mr0/flash_DWI
45
46
  - file: playground_mr0/pulseq_flash
46
47
  - file: playground_mr0/pulseq_sim_pTx
@@ -1,24 +1,29 @@
1
1
  # API Reference
2
2
 
3
- ```{warning}
4
- NOTE: Don't use autosummary as it is impossible to configure to produce the desired output. Instead, write all documentation pages yourself and use autodoc to include the dcstrings.
5
- ```
6
-
7
3
  All functionality provided by MRzeroCore is re-exported at the top level. It is recommended to import MRzeroCore as follows:
8
4
 
9
- ```
5
+ ```python
10
6
  import MRzeroCore as mr0
11
7
 
12
8
  # Example: build a sequence
13
9
  seq = mr0.Sequence()
14
10
  rep = seq.new_rep(65)
15
11
  rep.pulse.usage = mr0.PulseUsage.EXCIT
12
+ ...
13
+ ```
14
+
15
+ To run simulations on the GPU, the approach is similar to when using pyTorch:
16
+
17
+ ```python
18
+ graph = mr0.compute_graph(seq, obj)
19
+ # Calculate signal on the GPU, move returned tensor back to the CPU:
20
+ signal = mr0.execute_graph(seq.cuda(), obj.cuda()).cpu()
16
21
  ```
17
22
 
18
23
  The following pages list all functionality provided by `MRzeroCore`
19
24
 
20
25
  ::::{grid}
21
- :gutter: 3
26
+ :gutter: 2
22
27
 
23
28
  :::{grid-item-card} [Sequence](sequence)
24
29
  Create MRI sequences
@@ -28,6 +33,10 @@ Create MRI sequences
28
33
  Reconstruct images
29
34
  :::
30
35
 
36
+ :::{grid-item-card} [Util](util)
37
+ Utilitary functions
38
+ :::
39
+
31
40
  :::{grid-item-card} [Phantom](phantom)
32
41
  Various virtual subjects
33
42
  :::
@@ -0,0 +1,201 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "metadata": {
7
+ "tags": [
8
+ "hide-cell"
9
+ ]
10
+ },
11
+ "outputs": [],
12
+ "source": [
13
+ "!pip install MRzeroCore &> /dev/null\n",
14
+ "!wget https://github.com/MRsources/MRzero-Core/raw/main/documentation/playground_mr0/subject05.npz &> /dev/null"
15
+ ]
16
+ },
17
+ {
18
+ "cell_type": "code",
19
+ "execution_count": null,
20
+ "metadata": {},
21
+ "outputs": [],
22
+ "source": [
23
+ "import MRzeroCore as mr0\n",
24
+ "import matplotlib.pyplot as plt\n",
25
+ "import numpy as np\n",
26
+ "from numpy import pi\n",
27
+ "import torch"
28
+ ]
29
+ },
30
+ {
31
+ "attachments": {},
32
+ "cell_type": "markdown",
33
+ "metadata": {},
34
+ "source": [
35
+ "(flash_dwi)=\n",
36
+ "# DWI preparation with FLASH readout\n",
37
+ "\n",
38
+ "Change the b-value in build_seq() to the desired value"
39
+ ]
40
+ },
41
+ {
42
+ "cell_type": "code",
43
+ "execution_count": null,
44
+ "metadata": {},
45
+ "outputs": [],
46
+ "source": [
47
+ "def build_seq(fov=0.2) -> mr0.Sequence:\n",
48
+ " seq = mr0.Sequence()\n",
49
+ "\n",
50
+ " # Needed, otherwise prep produces stronger first readout\n",
51
+ " dummies = 1\n",
52
+ "\n",
53
+ " # Add DWI with a 90-180-90 prep sequence\n",
54
+ " b = 1000 # s / mm^2\n",
55
+ " t_grad = 10e-3\n",
56
+ " t_rf = 2e-3\n",
57
+ " # We manually scale xy grads for FOV and leave z alone.\n",
58
+ " # Convert mm to m -> 1e3\n",
59
+ " k = 1e3 * np.sqrt(b / (2/3 * t_grad + t_rf))\n",
60
+ " print(f\"b-value: {b} -> gradient moment: {k}\")\n",
61
+ "\n",
62
+ " rep = seq.new_rep(2)\n",
63
+ " rep.pulse.angle = pi/2\n",
64
+ " rep.pulse.usage = mr0.PulseUsage.EXCIT\n",
65
+ " rep.event_time[0] = t_rf\n",
66
+ " rep.event_time[1] = t_grad\n",
67
+ " rep.gradm[1, 2] = k\n",
68
+ "\n",
69
+ " rep = seq.new_rep(2)\n",
70
+ " rep.pulse.angle = pi\n",
71
+ " rep.pulse.usage = mr0.PulseUsage.REFOC\n",
72
+ " rep.event_time[0] = t_rf\n",
73
+ " rep.event_time[1] = t_grad\n",
74
+ " rep.gradm[1, 2] = k\n",
75
+ " rep.gradm[1, 0] = -96\n",
76
+ "\n",
77
+ " rep = seq.new_rep(2)\n",
78
+ " rep.pulse.angle = pi/2\n",
79
+ " rep.pulse.usage = mr0.PulseUsage.STORE\n",
80
+ " rep.event_time[0] = t_rf\n",
81
+ " rep.event_time[1] = 10e-3 # Could increase this for more T1 weighting\n",
82
+ "\n",
83
+ " # FLASH readout\n",
84
+ " for i in range(64 + dummies):\n",
85
+ " rep = seq.new_rep(2 + 64 + 1)\n",
86
+ " rep.pulse.usage = mr0.PulseUsage.EXCIT\n",
87
+ " rep.pulse.angle = 7 * pi/180\n",
88
+ " rep.pulse.phase = 0.5 * 117 * (i**2+i+2) * pi / 180\n",
89
+ "\n",
90
+ " rep.event_time[0] = 2e-3 # Pulse\n",
91
+ " rep.event_time[1] = 2e-3 # Rewinder\n",
92
+ " rep.event_time[2:-1] = 0.08e-3 # Readout\n",
93
+ " rep.event_time[-1] = 2e-3 # Spoiler\n",
94
+ "\n",
95
+ " rep.gradm[1, 0] = 96 - 33\n",
96
+ " rep.gradm[2:-1, 0] = 1\n",
97
+ " rep.gradm[-1, 0] = 96 - 31\n",
98
+ "\n",
99
+ " if i >= dummies:\n",
100
+ " # Linear reordered phase encoding\n",
101
+ " rep.gradm[1, 1] = i - 32\n",
102
+ " rep.gradm[-1, 1] = -rep.gradm[1, 1]\n",
103
+ "\n",
104
+ " rep.adc_usage[2:-1] = 1\n",
105
+ " rep.adc_phase[2:-1] = pi - rep.pulse.phase\n",
106
+ "\n",
107
+ " seq.normalized_grads = False\n",
108
+ " for rep in seq:\n",
109
+ " # Don't scale z-gradients used for diffusion\n",
110
+ " rep.gradm[:, :2] /= fov\n",
111
+ "\n",
112
+ " return seq"
113
+ ]
114
+ },
115
+ {
116
+ "cell_type": "code",
117
+ "execution_count": null,
118
+ "metadata": {
119
+ "tags": [
120
+ "hide-output"
121
+ ]
122
+ },
123
+ "outputs": [],
124
+ "source": [
125
+ "# Simulate\n",
126
+ "phantom = mr0.VoxelGridPhantom.brainweb(\"subject05.npz\")\n",
127
+ "phantom = phantom.interpolate(64, 64, 32).slices([16])\n",
128
+ "data = phantom.build()\n",
129
+ "\n",
130
+ "seq = build_seq(fov=data.size[0])\n",
131
+ "seq.plot_kspace_trajectory()\n",
132
+ "\n",
133
+ "graph = mr0.compute_graph(seq, data)\n",
134
+ "signal = mr0.execute_graph(graph, seq, data, print_progress=False)"
135
+ ]
136
+ },
137
+ {
138
+ "cell_type": "code",
139
+ "execution_count": null,
140
+ "metadata": {},
141
+ "outputs": [],
142
+ "source": [
143
+ "# Plot the result\n",
144
+ "kspace = signal.view(64, 64)\n",
145
+ "reco = torch.fft.fftshift(\n",
146
+ " torch.fft.fft2(torch.fft.fftshift(kspace), norm=\"forward\")\n",
147
+ ")\n",
148
+ "\n",
149
+ "plt.figure(figsize=(8, 7))\n",
150
+ "plt.subplot(221)\n",
151
+ "plt.title(\"abs(reco)\")\n",
152
+ "plt.imshow(reco.abs().cpu().flip(0), vmin=0)\n",
153
+ "plt.colorbar()\n",
154
+ "plt.axis(\"off\")\n",
155
+ "plt.subplot(222)\n",
156
+ "plt.title(\"angle(reco)\")\n",
157
+ "plt.imshow(reco.angle().cpu().flip(0), vmin=-np.pi, vmax=np.pi, cmap=\"twilight\")\n",
158
+ "plt.colorbar()\n",
159
+ "plt.axis(\"off\")\n",
160
+ "plt.subplot(223)\n",
161
+ "plt.title(\"log(abs(kspace))\")\n",
162
+ "plt.imshow(kspace.abs().log().cpu().flip(0))\n",
163
+ "plt.colorbar()\n",
164
+ "plt.axis(\"off\")\n",
165
+ "plt.subplot(224)\n",
166
+ "plt.title(\"Proton Density\")\n",
167
+ "plt.imshow(phantom.PD.cpu()[:, :, 0].T, origin='lower', vmin=0, vmax=1)\n",
168
+ "plt.colorbar()\n",
169
+ "plt.axis(\"off\")\n",
170
+ "plt.show()"
171
+ ]
172
+ }
173
+ ],
174
+ "metadata": {
175
+ "kernelspec": {
176
+ "display_name": "base",
177
+ "language": "python",
178
+ "name": "python3"
179
+ },
180
+ "language_info": {
181
+ "codemirror_mode": {
182
+ "name": "ipython",
183
+ "version": 3
184
+ },
185
+ "file_extension": ".py",
186
+ "mimetype": "text/x-python",
187
+ "name": "python",
188
+ "nbconvert_exporter": "python",
189
+ "pygments_lexer": "ipython3",
190
+ "version": "3.11.4"
191
+ },
192
+ "orig_nbformat": 4,
193
+ "vscode": {
194
+ "interpreter": {
195
+ "hash": "88279d2366fe020547cde40dd65aa0e3aa662a6ec1f3ca12d88834876c85e1a6"
196
+ }
197
+ }
198
+ },
199
+ "nbformat": 4,
200
+ "nbformat_minor": 2
201
+ }
@@ -1 +1 @@
1
- {"cells":[{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":401},"executionInfo":{"elapsed":52237,"status":"ok","timestamp":1698742386889,"user":{"displayName":"Moritz Zaiss","userId":"13462394581901772323"},"user_tz":-60},"id":"vTjDmgyofjbF","outputId":"47d34dc6-9bf1-489a-dbd9-0c0ffd9a5374","tags":["hide-cell"]},"outputs":[],"source":["# @title On Google Colab, you need to restart the runtime after executing this cell\n","!pip install numpy==1.24"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":401},"executionInfo":{"elapsed":52237,"status":"ok","timestamp":1698742386889,"user":{"displayName":"Moritz Zaiss","userId":"13462394581901772323"},"user_tz":-60},"id":"vTjDmgyofjbF","outputId":"47d34dc6-9bf1-489a-dbd9-0c0ffd9a5374","tags":["hide-cell"]},"outputs":[],"source":["!pip install pypulseq==1.3.1.post1 &> /dev/null\n","!pip install nevergrad &> /dev/null\n","!pip install MRzeroCore &> /dev/null\n","!wget https://github.com/MRsources/MRzero-Core/raw/main/documentation/playground_mr0/numerical_brain_cropped.mat &> /dev/null"]},{"cell_type":"markdown","metadata":{},"source":["(mr00_FLASH_2D_ernstAngle_opt)=\n","# MR00 2D FLASH Ernst angle optimization"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":400},"executionInfo":{"elapsed":6003,"status":"error","timestamp":1698682962173,"user":{"displayName":"Moritz Zaiss","userId":"13462394581901772323"},"user_tz":-60},"id":"O-_sr6lZjR_n","outputId":"bb12a7b4-904c-476a-db10-675bdc772583","scrolled":true},"outputs":[],"source":["#@title setup basic sequence params & phantom\n","# %% S0. SETUP env\n","import MRzeroCore as mr0\n","import numpy as np\n","\n","import pypulseq as pp\n","import torch\n","import matplotlib.pyplot as plt\n","\n","import nevergrad as ng\n","\n","from IPython.display import clear_output\n","\n","plt.rcParams['figure.figsize'] = [10, 5]\n","plt.rcParams['figure.dpi'] = 100 # 200 e.g. is really fine, but slower\n","\n","\n","\n","# %% S1. SETUP sys\n","\n","# choose the scanner limits\n","system = pp.Opts(max_grad=28,grad_unit='mT/m',max_slew=150,slew_unit='T/m/s',\n"," rf_ringdown_time=20e-6,rf_dead_time=100e-6,adc_dead_time=20e-6,grad_raster_time=50*10e-6)\n","\n","# Define FOV and resolution\n","fov = 220e-3\n","slice_thickness = 8e-3\n","sz = (32, 32) # spin system size / resolution\n","Nread = 64 # frequency encoding steps/samples\n","Nphase = 64 # phase encoding steps/samples\n","\n","# %% S4: SETUP SPIN SYSTEM/object on which we can run the MR sequence external.seq from above\n","\n","sz = [64, 64]\n","# (i) load a phantom object from file\n","obj_p = mr0.VoxelGridPhantom.load_mat('numerical_brain_cropped.mat')\n","obj_p = obj_p.interpolate(sz[0], sz[1], 1)\n","# Manipulate loaded data\n","obj_p.T2dash[:] = 30e-3\n","obj_p.D *= 0\n","obj_p.B0 *= 1 # alter the B0 inhomogeneity\n","# Store PD and B0 for comparison\n","PD = obj_p.PD\n","B0 = obj_p.B0\n","obj_p.plot()\n","# Convert Phantom into simulation data\n","obj_p = obj_p.build()"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"CUAKk_RaSXaQ"},"outputs":[],"source":["#@title set up functions for generating, simulating and reconstructing FLASH sequence\n","def ifft2d(x):\n"," x = torch.fft.fftshift(x)\n"," x = torch.fft.ifft2(x)\n"," x = torch.fft.ifftshift(x)\n"," return x\n","\n","def fft2d(x):\n"," x = torch.fft.ifftshift(x)\n"," x = torch.fft.fft2(x)\n"," x = torch.fft.fftshift(x)\n"," return x\n","\n","def generate_flash_seq(FA=10, fname='gre.seq', verbose=0):\n","\n"," # %% S2. DEFINE the sequence\n"," seq = pp.Sequence()\n","\n"," # Define rf events\n"," rf1, _, _ = pp.make_sinc_pulse(\n"," flip_angle= FA * np.pi / 180, duration=1e-3,\n"," slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4,\n"," system=system, return_gz=True\n"," )\n"," # rf1 = pp.make_block_pulse(flip_angle=90 * np.pi / 180, duration=1e-3, system=system)\n","\n"," # Define other gradients and ADC events\n"," gx = pp.make_trapezoid(channel='x', flat_area=Nread / fov, flat_time=10e-3, system=system)\n"," adc = pp.make_adc(num_samples=Nread, duration=10e-3, phase_offset=0 * np.pi/180, delay=gx.rise_time, system=system)\n"," gx_pre = pp.make_trapezoid(channel='x', area=-gx.area / 2, duration=5e-3, system=system)\n"," gx_spoil = pp.make_trapezoid(channel='x', area=1.5 * gx.area, duration=2e-3, system=system)\n","\n"," rf_phase = 0\n"," rf_inc = 0\n"," rf_spoiling_inc = 117\n","\n"," # ======\n"," # CONSTRUCT SEQUENCE\n"," # ======\n"," ##linear reordering\n"," phenc = np.arange(-Nphase // 2, Nphase // 2, 1) / fov\n"," permvec =np.arange(0, Nphase, 1)\n"," ## centric reordering\n"," #permvec = sorted(np.arange(len(phenc)), key=lambda x: abs(len(phenc) // 2 - x))\n"," ## random reordering\n"," #perm =np.arange(0, Nphase, 1); permvec = np.random.permutation(perm)\n","\n"," phenc_centr = phenc[permvec]\n","\n"," for ii in range(0, Nphase): # e.g. -64:63\n","\n"," rf1.phase_offset = rf_phase / 180 * np.pi # set current rf phase\n","\n"," adc.phase_offset = rf_phase / 180 * np.pi # follow with ADC\n"," rf_inc = divmod(rf_inc + rf_spoiling_inc, 360.0)[1] # increase increment\n"," # increment additional pahse\n"," rf_phase = divmod(rf_phase + rf_inc, 360.0)[1]\n","\n"," seq.add_block(rf1)\n"," seq.add_block(pp.make_delay(0.005))\n"," gp = pp.make_trapezoid(channel='y', area=phenc_centr[ii], duration=5e-3, system=system)\n"," seq.add_block(gx_pre, gp)\n"," seq.add_block(adc, gx)\n"," gp = pp.make_trapezoid(channel='y', area=-phenc_centr[ii], duration=5e-3, system=system)\n"," seq.add_block(gx_spoil, gp)\n"," if ii < Nphase - 1:\n"," seq.add_block(pp.make_delay(0.01))\n","\n","\n"," # %% S3. CHECK, PLOT and WRITE the sequence as .seq\n"," # Check whether the timing of the sequence is correct\n"," ok, error_report = seq.check_timing()\n"," if ok:\n"," if verbose > 0:\n"," print('Timing check passed successfully')\n"," else:\n"," print('Timing check failed. Error listing follows:')\n"," [print(e) for e in error_report]\n","\n"," # PLOT sequence\n"," if verbose > 0:\n"," sp_adc, t_adc = mr0.util.pulseq_plot(seq, clear=False, figid=(11,12))\n","\n"," # Prepare the sequence output for the scanner\n"," seq.set_definition('FOV', [fov, fov, slice_thickness])\n"," seq.set_definition('Name', 'gre')\n"," seq.write(fname)\n","\n"," reco_params = {'permvec': permvec}\n","\n"," return reco_params\n","\n","def simu_seq(fname, obj_p, reco_params, noiselevel=1e-4, verbose=0):\n","\n"," permvec = reco_params['permvec']\n","\n"," # %% S5:. SIMULATE the external.seq file and add acquired signal to ADC plot\n"," # Read in the sequence\n"," seq0 = mr0.Sequence.import_file(fname)\n"," if verbose > 0:\n"," seq0.plot_kspace_trajectory()\n"," # Simulate the sequence\n"," graph = mr0.compute_graph(seq0, obj_p, 200, 1e-3)\n"," signal = mr0.execute_graph(graph, seq0, obj_p, print_progress=False)\n","\n"," # PLOT sequence with signal in the ADC subplot\n"," if verbose > 0:\n"," plt.close(11);plt.close(12)\n"," sp_adc, t_adc = mr0.util.pulseq_plot(seq, clear=False, signal=signal.numpy())\n","\n"," # additional noise as simulation is perfect\n"," signal += noiselevel * np.random.randn(signal.shape[0], 2).view(np.complex128)\n","\n","\n"," # %% S6: MR IMAGE RECON of signal ::: #####################################\n"," if verbose > 0:\n"," fig = plt.figure() # fig.clf()\n"," plt.subplot(411)\n"," plt.title('ADC signal')\n"," plt.plot(torch.real(signal), label='real')\n"," plt.plot(torch.imag(signal), label='imag')\n"," # this adds ticks at the correct position szread\n"," major_ticks = np.arange(0, Nphase * Nread, Nread)\n"," ax = plt.gca()\n"," ax.set_xticks(major_ticks)\n"," ax.grid()\n","\n"," kspace = torch.reshape((signal), (Nphase, Nread)).clone().t()\n","\n"," ipermvec = np.argsort(permvec)\n","\n"," kspace=kspace[:,ipermvec]\n","\n"," img = fft2d(kspace)\n","\n"," return img\n"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"NwApBQJVSXaR"},"outputs":[],"source":["#@title nevergrad optimization\n","def calc_loss(FA): # loss function\n"," global iter\n"," reco_params = generate_flash_seq(FA=FA, fname='gre.seq')\n"," img = simu_seq('gre.seq', obj_p, reco_params, noiselevel=0*1e-3)\n"," mag = torch.sum(img.flatten().abs()**2)\n","\n"," clear_output(wait=True)\n"," plt.figure(figsize=(13,5))\n"," plt.subplot(1,2,1)\n"," plt.imshow(img.abs()), plt.colorbar()\n"," plt.title(f'iter {iter}: FA={FA[0]:.2f}, MAG={mag.item():.2f}')\n"," plt.subplot(1,2,2)\n"," plt.plot(values,'.-')\n"," plt.xlabel('iteration'), plt.ylabel('loss')\n"," plt.show()\n","\n"," iter += 1\n","\n"," return -mag.item()\n","\n","# def calc_loss(FA):\n","# E1 = np.exp(-20e-3/1)\n","# FArad = np.deg2rad(FA)\n","# S = np.sin(FArad) * (1-E1)/(1-np.cos(FArad)*E1)\n","# return -S\n","\n","def rescale_vars(x, a,b,c,d):\n"," # original range (a,b)\n"," # new range (c,d)\n"," return ((x-a) / (b-a)) * (d-c) + c\n","\n","def obj_fun_rescaled(x1): # rescaled loss functions, shuch that optimizer sees only normrange\n"," if type(x1) == list:\n"," x1 = np.array(x1)\n"," x1r = rescale_vars(x1, *normrange, *valrange)\n"," return calc_loss(x1r)\n","\n","def print_candidate_and_value(optimizer, candidate, value): # callback, print and save intermediate steps\n"," global cands, values, xx\n"," # print('iter', xx, 'cand:', candidate, 'val:', value)\n"," cands.append(candidate)\n"," values.append(value)\n","\n","iter = 0 # global iteration counter\n","\n","# number of cost function evaluations (\"budget\")\n","# Limited for building docs - should be increased\n","niter = 10\n","\n","# this is the range in which the optimizer operates, see https://cma-es.github.io/cmaes_sourcecode_page.html#practical\n","normrange = (-3,3)\n","\n","# boundaries in physical units\n","valrange = (1e-1,180)\n","\n","# initial value (not sure if it has any influence, probably depending on optimizer)\n","init = np.array([5]) # physical units\n","\n","# defining optimizable variables (\"instrumentation\")\n","instrum = ng.p.Instrumentation(\n"," ng.p.Array(init=rescale_vars(init,*valrange,*normrange)).set_bounds(*normrange),\n",")\n","\n","cands = [] # to save all candidates during opt\n","values = [] # to save loss values during opt\n","\n","optimizer = ng.optimizers.NGOpt(parametrization=instrum, budget=niter) # documentation says NGOpt is a good first choice, this is a \"meta-optimizer\" that chooses algorithm based on instrumentation\n","# optimizer = ng.optimizers.registry[\"PSO\"](parametrization=instrum, budget=niter) # particle swarm\n","# optimizer = ng.families.ParametrizedBO()(parametrization=instrum, budget=niter) # Bayesian optimization, this might be good for continuous in dim > 1, dim < ~100 (?)\n","# optimizer = ng.families.NonObjectOptimizer(method='Powell')(parametrization=instrum, budget=niter) # more traditional grad free things\n","# optimizer = ng.families.NonObjectOptimizer(method='NLOPT_GN_DIRECT')(parametrization=instrum, budget=niter) # more traditional grad free things\n","# optimizer = ng.families.ParametrizedCMA()(parametrization=instrum, budget=niter) # only in dim > 1\n","# optimizer = ng.families.RandomSearchMaker()(parametrization=instrum, budget=niter) # random search as baseline\n","\n","optimizer.register_callback(\"tell\", print_candidate_and_value) # set callback\n","\n","recommendation = optimizer.minimize(obj_fun_rescaled) # run opt\n","FAopt = rescale_vars(recommendation[0][0].value, *normrange, *valrange)\n","\n","print(\"final result:\", FAopt) # opt result\n","print(\"used optimizer\", optimizer._optim)\n"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"executionInfo":{"elapsed":542,"status":"ok","timestamp":1691486955197,"user":{"displayName":"Moritz Zaiss","userId":"13462394581901772323"},"user_tz":-120},"id":"u7jO7VTSSXaT","outputId":"c7a019de-02bd-4496-d552-ab15d3489a24"},"outputs":[],"source":["#@title some details on optimization history\n","x_explored = [cands[ii][0].value[0] for ii in range(len(cands))] # extract optimization history\n","FA_explored = np.array([rescale_vars(x, *normrange, *valrange) for x in x_explored]) # rescale back\n","\n","plt.figure(figsize=(7,12))\n","plt.subplot(4,1,1)\n","plt.plot(FA_explored,values,'.')\n","plt.xlabel('FA [deg]'), plt.ylabel('loss')\n","\n","plt.subplot(4,1,2)\n","plt.plot(FA_explored,'.-')\n","plt.ylabel('FA [deg]')\n","\n","plt.subplot(4,1,3)\n","plt.plot(values,'.-')\n","plt.ylabel('loss')\n","\n","plt.subplot(4,1,4)\n","plt.hist(FA_explored, bins=30)"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":944},"executionInfo":{"elapsed":98948,"status":"ok","timestamp":1691487054133,"user":{"displayName":"Moritz Zaiss","userId":"13462394581901772323"},"user_tz":-120},"id":"ujZRr2ppA9A7","outputId":"561e25e4-0a3f-4b93-a583-2f5fc28b113c"},"outputs":[],"source":["#@title manual line search\n","iter=0\n","FAs = np.linspace(0,90, 10) # Reduced precision for building docs\n","losses = np.zeros(FAs.shape)\n","for ii,FA in enumerate(FAs):\n"," losses[ii] = calc_loss(np.array([FA]))\n","\n","plt.figure()\n","plt.plot(FAs,losses,'.-')\n","plt.xlabel('FA'), plt.ylabel('loss')"]}],"metadata":{"colab":{"provenance":[{"file_id":"1jmZrTCl53SWE05bs-_Z0ge0tfBkgS6uo","timestamp":1691486549168}],"toc_visible":true},"kernelspec":{"display_name":"Python 3 (ipykernel)","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.3"}},"nbformat":4,"nbformat_minor":0}
1
+ {"cells":[{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":401},"executionInfo":{"elapsed":52237,"status":"ok","timestamp":1698742386889,"user":{"displayName":"Moritz Zaiss","userId":"13462394581901772323"},"user_tz":-60},"id":"vTjDmgyofjbF","outputId":"47d34dc6-9bf1-489a-dbd9-0c0ffd9a5374","tags":["hide-cell"]},"outputs":[],"source":["# @title On Google Colab, you need to restart the runtime after executing this cell\n","!pip install numpy==1.24"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":401},"executionInfo":{"elapsed":52237,"status":"ok","timestamp":1698742386889,"user":{"displayName":"Moritz Zaiss","userId":"13462394581901772323"},"user_tz":-60},"id":"vTjDmgyofjbF","outputId":"47d34dc6-9bf1-489a-dbd9-0c0ffd9a5374","tags":["hide-cell"]},"outputs":[],"source":["!pip install pypulseq==1.3.1.post1 &> /dev/null\n","!pip install nevergrad &> /dev/null\n","!pip install MRzeroCore &> /dev/null\n","!wget https://github.com/MRsources/MRzero-Core/raw/main/documentation/playground_mr0/numerical_brain_cropped.mat &> /dev/null"]},{"cell_type":"markdown","metadata":{},"source":["(mr00_FLASH_2D_ernstAngle_opt)=\n","# MR00 2D FLASH Ernst angle optimization"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":400},"executionInfo":{"elapsed":6003,"status":"error","timestamp":1698682962173,"user":{"displayName":"Moritz Zaiss","userId":"13462394581901772323"},"user_tz":-60},"id":"O-_sr6lZjR_n","outputId":"bb12a7b4-904c-476a-db10-675bdc772583","scrolled":true},"outputs":[],"source":["#@title setup basic sequence params & phantom\n","# %% S0. SETUP env\n","import MRzeroCore as mr0\n","import numpy as np\n","\n","import pypulseq as pp\n","import torch\n","import matplotlib.pyplot as plt\n","\n","import nevergrad as ng\n","\n","from IPython.display import clear_output\n","\n","plt.rcParams['figure.figsize'] = [10, 5]\n","plt.rcParams['figure.dpi'] = 100 # 200 e.g. is really fine, but slower\n","\n","\n","\n","# %% S1. SETUP sys\n","\n","# choose the scanner limits\n","system = pp.Opts(max_grad=28,grad_unit='mT/m',max_slew=150,slew_unit='T/m/s',\n"," rf_ringdown_time=20e-6,rf_dead_time=100e-6,adc_dead_time=20e-6,grad_raster_time=50*10e-6)\n","\n","# Define FOV and resolution\n","fov = 200e-3\n","slice_thickness = 8e-3\n","sz = (32, 32) # spin system size / resolution\n","Nread = 64 # frequency encoding steps/samples\n","Nphase = 64 # phase encoding steps/samples\n","\n","# %% S4: SETUP SPIN SYSTEM/object on which we can run the MR sequence external.seq from above\n","\n","sz = [64, 64]\n","# (i) load a phantom object from file\n","obj_p = mr0.VoxelGridPhantom.load_mat('numerical_brain_cropped.mat')\n","obj_p = obj_p.interpolate(sz[0], sz[1], 1)\n","# Manipulate loaded data\n","obj_p.T2dash[:] = 30e-3\n","obj_p.D *= 0\n","obj_p.B0 *= 1 # alter the B0 inhomogeneity\n","# Store PD and B0 for comparison\n","PD = obj_p.PD\n","B0 = obj_p.B0\n","obj_p.plot()\n","# Convert Phantom into simulation data\n","obj_p = obj_p.build()"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"CUAKk_RaSXaQ"},"outputs":[],"source":["#@title set up functions for generating, simulating and reconstructing FLASH sequence\n","def ifft2d(x):\n"," x = torch.fft.fftshift(x)\n"," x = torch.fft.ifft2(x)\n"," x = torch.fft.ifftshift(x)\n"," return x\n","\n","def fft2d(x):\n"," x = torch.fft.ifftshift(x)\n"," x = torch.fft.fft2(x)\n"," x = torch.fft.fftshift(x)\n"," return x\n","\n","def generate_flash_seq(FA=10, fname='gre.seq', verbose=0):\n","\n"," # %% S2. DEFINE the sequence\n"," seq = pp.Sequence()\n","\n"," # Define rf events\n"," rf1, _, _ = pp.make_sinc_pulse(\n"," flip_angle= FA * np.pi / 180, duration=1e-3,\n"," slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4,\n"," system=system, return_gz=True\n"," )\n"," # rf1 = pp.make_block_pulse(flip_angle=90 * np.pi / 180, duration=1e-3, system=system)\n","\n"," # Define other gradients and ADC events\n"," gx = pp.make_trapezoid(channel='x', flat_area=Nread / fov, flat_time=10e-3, system=system)\n"," adc = pp.make_adc(num_samples=Nread, duration=10e-3, phase_offset=0 * np.pi/180, delay=gx.rise_time, system=system)\n"," gx_pre = pp.make_trapezoid(channel='x', area=-gx.area / 2, duration=5e-3, system=system)\n"," gx_spoil = pp.make_trapezoid(channel='x', area=1.5 * gx.area, duration=2e-3, system=system)\n","\n"," rf_phase = 0\n"," rf_inc = 0\n"," rf_spoiling_inc = 117\n","\n"," # ======\n"," # CONSTRUCT SEQUENCE\n"," # ======\n"," ##linear reordering\n"," phenc = np.arange(-Nphase // 2, Nphase // 2, 1) / fov\n"," permvec =np.arange(0, Nphase, 1)\n"," ## centric reordering\n"," #permvec = sorted(np.arange(len(phenc)), key=lambda x: abs(len(phenc) // 2 - x))\n"," ## random reordering\n"," #perm =np.arange(0, Nphase, 1); permvec = np.random.permutation(perm)\n","\n"," phenc_centr = phenc[permvec]\n","\n"," for ii in range(0, Nphase): # e.g. -64:63\n","\n"," rf1.phase_offset = rf_phase / 180 * np.pi # set current rf phase\n","\n"," adc.phase_offset = rf_phase / 180 * np.pi # follow with ADC\n"," rf_inc = divmod(rf_inc + rf_spoiling_inc, 360.0)[1] # increase increment\n"," # increment additional pahse\n"," rf_phase = divmod(rf_phase + rf_inc, 360.0)[1]\n","\n"," seq.add_block(rf1)\n"," seq.add_block(pp.make_delay(0.005))\n"," gp = pp.make_trapezoid(channel='y', area=phenc_centr[ii], duration=5e-3, system=system)\n"," seq.add_block(gx_pre, gp)\n"," seq.add_block(adc, gx)\n"," gp = pp.make_trapezoid(channel='y', area=-phenc_centr[ii], duration=5e-3, system=system)\n"," seq.add_block(gx_spoil, gp)\n"," if ii < Nphase - 1:\n"," seq.add_block(pp.make_delay(0.01))\n","\n","\n"," # %% S3. CHECK, PLOT and WRITE the sequence as .seq\n"," # Check whether the timing of the sequence is correct\n"," ok, error_report = seq.check_timing()\n"," if ok:\n"," if verbose > 0:\n"," print('Timing check passed successfully')\n"," else:\n"," print('Timing check failed. Error listing follows:')\n"," [print(e) for e in error_report]\n","\n"," # PLOT sequence\n"," if verbose > 0:\n"," sp_adc, t_adc = mr0.util.pulseq_plot(seq, clear=False, figid=(11,12))\n","\n"," # Prepare the sequence output for the scanner\n"," seq.set_definition('FOV', [fov, fov, slice_thickness])\n"," seq.set_definition('Name', 'gre')\n"," seq.write(fname)\n","\n"," reco_params = {'permvec': permvec}\n","\n"," return reco_params\n","\n","def simu_seq(fname, obj_p, reco_params, noiselevel=1e-4, verbose=0):\n","\n"," permvec = reco_params['permvec']\n","\n"," # %% S5:. SIMULATE the external.seq file and add acquired signal to ADC plot\n"," # Read in the sequence\n"," seq0 = mr0.Sequence.import_file(fname)\n"," if verbose > 0:\n"," seq0.plot_kspace_trajectory()\n"," # Simulate the sequence\n"," graph = mr0.compute_graph(seq0, obj_p, 200, 1e-3)\n"," signal = mr0.execute_graph(graph, seq0, obj_p, print_progress=False)\n","\n"," # PLOT sequence with signal in the ADC subplot\n"," if verbose > 0:\n"," plt.close(11);plt.close(12)\n"," sp_adc, t_adc = mr0.util.pulseq_plot(seq, clear=False, signal=signal.numpy())\n","\n"," # additional noise as simulation is perfect\n"," signal += noiselevel * np.random.randn(signal.shape[0], 2).view(np.complex128)\n","\n","\n"," # %% S6: MR IMAGE RECON of signal ::: #####################################\n"," if verbose > 0:\n"," fig = plt.figure() # fig.clf()\n"," plt.subplot(411)\n"," plt.title('ADC signal')\n"," plt.plot(torch.real(signal), label='real')\n"," plt.plot(torch.imag(signal), label='imag')\n"," # this adds ticks at the correct position szread\n"," major_ticks = np.arange(0, Nphase * Nread, Nread)\n"," ax = plt.gca()\n"," ax.set_xticks(major_ticks)\n"," ax.grid()\n","\n"," kspace = torch.reshape((signal), (Nphase, Nread)).clone().t()\n","\n"," ipermvec = np.argsort(permvec)\n","\n"," kspace=kspace[:,ipermvec]\n","\n"," img = fft2d(kspace)\n","\n"," return img\n"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"NwApBQJVSXaR"},"outputs":[],"source":["#@title nevergrad optimization\n","def calc_loss(FA): # loss function\n"," global iter\n"," reco_params = generate_flash_seq(FA=FA, fname='gre.seq')\n"," img = simu_seq('gre.seq', obj_p, reco_params, noiselevel=0*1e-3)\n"," mag = torch.sum(img.flatten().abs()**2)\n","\n"," clear_output(wait=True)\n"," plt.figure(figsize=(13,5))\n"," plt.subplot(1,2,1)\n"," plt.imshow(img.abs()), plt.colorbar()\n"," plt.title(f'iter {iter}: FA={FA[0]:.2f}, MAG={mag.item():.2f}')\n"," plt.subplot(1,2,2)\n"," plt.plot(values,'.-')\n"," plt.xlabel('iteration'), plt.ylabel('loss')\n"," plt.show()\n","\n"," iter += 1\n","\n"," return -mag.item()\n","\n","# def calc_loss(FA):\n","# E1 = np.exp(-20e-3/1)\n","# FArad = np.deg2rad(FA)\n","# S = np.sin(FArad) * (1-E1)/(1-np.cos(FArad)*E1)\n","# return -S\n","\n","def rescale_vars(x, a,b,c,d):\n"," # original range (a,b)\n"," # new range (c,d)\n"," return ((x-a) / (b-a)) * (d-c) + c\n","\n","def obj_fun_rescaled(x1): # rescaled loss functions, shuch that optimizer sees only normrange\n"," if type(x1) == list:\n"," x1 = np.array(x1)\n"," x1r = rescale_vars(x1, *normrange, *valrange)\n"," return calc_loss(x1r)\n","\n","def print_candidate_and_value(optimizer, candidate, value): # callback, print and save intermediate steps\n"," global cands, values, xx\n"," # print('iter', xx, 'cand:', candidate, 'val:', value)\n"," cands.append(candidate)\n"," values.append(value)\n","\n","iter = 0 # global iteration counter\n","\n","# number of cost function evaluations (\"budget\")\n","# Limited for building docs - should be increased\n","niter = 10\n","\n","# this is the range in which the optimizer operates, see https://cma-es.github.io/cmaes_sourcecode_page.html#practical\n","normrange = (-3,3)\n","\n","# boundaries in physical units\n","valrange = (1e-1,180)\n","\n","# initial value (not sure if it has any influence, probably depending on optimizer)\n","init = np.array([5]) # physical units\n","\n","# defining optimizable variables (\"instrumentation\")\n","instrum = ng.p.Instrumentation(\n"," ng.p.Array(init=rescale_vars(init,*valrange,*normrange)).set_bounds(*normrange),\n",")\n","\n","cands = [] # to save all candidates during opt\n","values = [] # to save loss values during opt\n","\n","optimizer = ng.optimizers.NGOpt(parametrization=instrum, budget=niter) # documentation says NGOpt is a good first choice, this is a \"meta-optimizer\" that chooses algorithm based on instrumentation\n","# optimizer = ng.optimizers.registry[\"PSO\"](parametrization=instrum, budget=niter) # particle swarm\n","# optimizer = ng.families.ParametrizedBO()(parametrization=instrum, budget=niter) # Bayesian optimization, this might be good for continuous in dim > 1, dim < ~100 (?)\n","# optimizer = ng.families.NonObjectOptimizer(method='Powell')(parametrization=instrum, budget=niter) # more traditional grad free things\n","# optimizer = ng.families.NonObjectOptimizer(method='NLOPT_GN_DIRECT')(parametrization=instrum, budget=niter) # more traditional grad free things\n","# optimizer = ng.families.ParametrizedCMA()(parametrization=instrum, budget=niter) # only in dim > 1\n","# optimizer = ng.families.RandomSearchMaker()(parametrization=instrum, budget=niter) # random search as baseline\n","\n","optimizer.register_callback(\"tell\", print_candidate_and_value) # set callback\n","\n","recommendation = optimizer.minimize(obj_fun_rescaled) # run opt\n","FAopt = rescale_vars(recommendation[0][0].value, *normrange, *valrange)\n","\n","print(\"final result:\", FAopt) # opt result\n","print(\"used optimizer\", optimizer._optim)\n"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"executionInfo":{"elapsed":542,"status":"ok","timestamp":1691486955197,"user":{"displayName":"Moritz Zaiss","userId":"13462394581901772323"},"user_tz":-120},"id":"u7jO7VTSSXaT","outputId":"c7a019de-02bd-4496-d552-ab15d3489a24"},"outputs":[],"source":["#@title some details on optimization history\n","x_explored = [cands[ii][0].value[0] for ii in range(len(cands))] # extract optimization history\n","FA_explored = np.array([rescale_vars(x, *normrange, *valrange) for x in x_explored]) # rescale back\n","\n","plt.figure(figsize=(7,12))\n","plt.subplot(4,1,1)\n","plt.plot(FA_explored,values,'.')\n","plt.xlabel('FA [deg]'), plt.ylabel('loss')\n","\n","plt.subplot(4,1,2)\n","plt.plot(FA_explored,'.-')\n","plt.ylabel('FA [deg]')\n","\n","plt.subplot(4,1,3)\n","plt.plot(values,'.-')\n","plt.ylabel('loss')\n","\n","plt.subplot(4,1,4)\n","plt.hist(FA_explored, bins=30)"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":944},"executionInfo":{"elapsed":98948,"status":"ok","timestamp":1691487054133,"user":{"displayName":"Moritz Zaiss","userId":"13462394581901772323"},"user_tz":-120},"id":"ujZRr2ppA9A7","outputId":"561e25e4-0a3f-4b93-a583-2f5fc28b113c"},"outputs":[],"source":["#@title manual line search\n","iter=0\n","FAs = np.linspace(0,90, 10) # Reduced precision for building docs\n","losses = np.zeros(FAs.shape)\n","for ii,FA in enumerate(FAs):\n"," losses[ii] = calc_loss(np.array([FA]))\n","\n","plt.figure()\n","plt.plot(FAs,losses,'.-')\n","plt.xlabel('FA'), plt.ylabel('loss')"]}],"metadata":{"colab":{"provenance":[{"file_id":"1jmZrTCl53SWE05bs-_Z0ge0tfBkgS6uo","timestamp":1691486549168}],"toc_visible":true},"kernelspec":{"display_name":"Python 3 (ipykernel)","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.4"}},"nbformat":4,"nbformat_minor":0}
@@ -1 +1 @@
1
- {"cells":[{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":13863,"status":"ok","timestamp":1678740065139,"user":{"displayName":"Zhengguo Tan","userId":"10291917877743041231"},"user_tz":-60},"id":"vTjDmgyofjbF","outputId":"c702ebb6-274c-4e2e-fec0-c67810ceb02c","tags":["hide-cell"]},"outputs":[],"source":["!pip install pypulseq==1.3.1.post1 &> /dev/null\n","!pip install MRzeroCore &> /dev/null\n","!wget https://github.com/MRsources/MRzero-Core/raw/main/documentation/playground_mr0/numerical_brain_cropped.mat &> /dev/null"]},{"cell_type":"markdown","metadata":{},"source":["(mr0_CS_cartesian_seq)=\n","# Compressed Sensing - cartesian"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"executionInfo":{"elapsed":20513,"status":"ok","timestamp":1678741422978,"user":{"displayName":"Zhengguo Tan","userId":"10291917877743041231"},"user_tz":-60},"id":"O-_sr6lZjR_n","outputId":"d9cd1a67-4a63-44e6-df28-96198556f5d4"},"outputs":[],"source":["#@title generate\n","# %% S0. SETUP env\n","from skimage.restoration import denoise_tv_chambolle\n","import pywt\n","import MRzeroCore as mr0\n","import numpy as np\n","from matplotlib import pyplot as plt\n","plt.rcParams['figure.figsize'] = [10, 5]\n","plt.rcParams['figure.dpi'] = 100 # 200 e.g. is really fine, but slower\n","import pypulseq as pp\n","import torch\n","\n","experiment_id = 'exD01_bSSFP_2D'\n","\n","# %% S1. SETUP sys\n","\n","# choose the scanner limits\n","system = pp.Opts(\n"," max_grad=28,\n"," grad_unit='mT/m',\n"," max_slew=150,\n"," slew_unit='T/m/s',\n"," rf_ringdown_time=20e-6,\n"," rf_dead_time=100e-6,\n"," adc_dead_time=20e-6,\n"," grad_raster_time=50*10e-6\n",")\n","\n","# %% S2. DEFINE the sequence \n","seq = pp.Sequence()\n","\n","# Define FOV and resolution\n","fov = 220e-3 \n","slice_thickness=8e-3\n","sz=[128,128] # spin system size / resolution\n","Nread = sz[0] # frequency encoding steps/samples\n","Nphase = sz[1] # phase encoding steps/samples\n","\n","# Define rf events\n","rf1 = pp.make_sinc_pulse(flip_angle=5 * np.pi / 180, duration=1e-3,slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4, system=system)\n","# rf1, _= pp.make_block_pulse(flip_angle=90 * np.pi / 180, duration=1e-3, system=system)\n","\n","# Define other gradients and ADC events\n","gx = pp.make_trapezoid(channel='x', flat_area=Nread / fov, flat_time=10e-3, system=system)\n","adc = pp.make_adc(num_samples=Nread, duration=10e-3, phase_offset=0*np.pi/180,delay=gx.rise_time, system=system)\n","gx_pre = pp.make_trapezoid(channel='x', area=-gx.area / 2, duration=5e-3, system=system)\n","gx_spoil = pp.make_trapezoid(channel='x', area=1.5*gx.area, duration=2e-3, system=system)\n","\n","rf_phase = 0\n","rf_inc = 0\n","rf_spoiling_inc=117\n","\n","phase_enc__gradmoms = (torch.arange(0,Nphase,1)-Nphase//2) / fov\n","\n","# ======\n","# CONSTRUCT SEQUENCE\n","# ======\n","\n","idx = np.random.normal(loc=Nphase//2, scale=Nphase//4, size=(Nphase//2,)).astype(int)\n","idx[idx>=Nphase] = Nphase - 1\n","idx[idx<0] = 0\n","print('idx: ', idx)\n","\n","# idx = [1,5,15,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,45,50,55]\n","#idx = np.linspace(0,63,64)\n","#idx = [5,15,20,27,28,29,30,31,32,33,34,35,36,37,38,39,40,45,50,55]\n","# idx = np.random.poisson(Nread/2,Nread*8)\n","idx = np.unique(idx)\n","for ii in range(0, len(idx)): # e.g. -64:63\n","\n"," rf1.phase_offset = rf_phase / 180 * np.pi # set current rf phase\n"," \n"," adc.phase_offset = rf_phase / 180 * np.pi # follow with ADC\n"," rf_inc = divmod(rf_inc + rf_spoiling_inc, 360.0)[1] # increase increment\n"," rf_phase = divmod(rf_phase + rf_inc, 360.0)[1] # increment additional pahse\n","\n"," seq.add_block(rf1)\n"," gp= pp.make_trapezoid(channel='y', area=phase_enc__gradmoms[int(idx[ii])], duration=5e-3, system=system)\n"," seq.add_block(gx_pre,gp)\n"," seq.add_block(adc,gx)\n"," gp= pp.make_trapezoid(channel='y', area=-phase_enc__gradmoms[int(idx[ii])], duration=5e-3, system=system)\n"," seq.add_block(gx_spoil,gp)\n"," if ii<Nphase-1:\n"," seq.add_block(pp.make_delay(0.001))\n","# %% S3. CHECK, PLOT and WRITE the sequence as .seq\n","# Check whether the timing of the sequence is correct\n","ok, error_report = seq.check_timing()\n","if ok:\n"," print('Timing check passed successfully')\n","else:\n"," print('Timing check failed. Error listing follows:')\n"," [print(e) for e in error_report]\n","\n","# PLOT sequence\n","#sp_adc, t_adc = mr0.util.pulseq_plot(seq, clear=False)\n","\n","# Prepare the sequence output for the scanner\n","seq.set_definition('FOV', [fov, fov, slice_thickness])\n","seq.set_definition('Name', 'gre')\n","seq.write(experiment_id + '.seq')\n","# %% S4: SETUP SPIN SYSTEM/object on which we can run the MR sequence external.seq from above\n","\n","if 1:\n"," # (i) load a phantom object from file\n"," obj_p = mr0.VoxelGridPhantom.load_mat('numerical_brain_cropped.mat')\n"," obj_p = obj_p.interpolate(sz[0], sz[1], 1)\n"," # Manipulate loaded data\n"," obj_p.B0 *= 0\n"," obj_p.D *= 0\n","else:\n"," # or (ii) set phantom manually to a pixel phantom. Coordinate system is [-0.5, 0.5]^3\n"," obj_p = mr0.CustomVoxelPhantom(\n"," pos=[[-0.25, -0.25, 0]],\n"," PD=[1.0],\n"," T1=[3.0],\n"," T2=[0.5],\n"," T2dash=[30e-3],\n"," D=[0.0],\n"," B0 =0,\n"," voxel_size=0.1,\n"," voxel_shape=\"box\"\n"," )\n","\n","#obj_p.plot()\n","# Convert Phantom into simulation data\n","obj_p = obj_p.build()\n","\n","# %% S5:. SIMULATE the external.seq file and add acquired signal to ADC plot\n","\n","# Read in the sequence\n","seq0 = mr0.Sequence.import_file(experiment_id + '.seq')\n","#seq0.plot_kspace_trajectory()\n","# Simulate the sequence\n","graph = mr0.compute_graph(seq0, obj_p, 200, 1e-3)\n","signal = mr0.execute_graph(graph, seq0, obj_p, print_progress=False)\n","\n","# PLOT sequence with signal in the ADC subplot\n","#sp_adc, t_adc = mr0.util.pulseq_plot(seq, clear=True, signal=signal.numpy())\n","\n","kspace_adc=torch.reshape((signal),(len(idx),Nread)).clone().t()\n","\n","kspace = torch.zeros((Nread,Nread),dtype = torch.complex64)\n","kspace[:,idx] = kspace_adc\n","pattern = torch.zeros((Nread,Nread))\n","pattern[:,idx] = torch.ones(Nread,len(idx))\n","\n","# high frequencies centered as kspace and as FFT needs it\n","pattern = np.fft.fftshift(pattern.numpy())\n","kspace = np.fft.ifftshift(kspace.numpy())\n","\n","# kspace = kspace_full * pattern # apply the undersampling pattern\n","\n","# calculate the actually measured data in percent\n","actual_measured_percent = np.count_nonzero(pattern) / pattern.size * 100\n","\n","# Plotting\n","pattern_vis = np.fft.fftshift(pattern.copy())\n","kspace_vis = np.log(1+abs(np.fft.fftshift((kspace.copy()))))\n","fig = plt.figure(dpi=90)\n","plt.subplot(321)\n","plt.set_cmap(plt.gray())\n","# plt.imshow(abs(recon_nufft))\n","plt.ylabel('recon_full')\n","plt.subplot(322)\n","plt.set_cmap(plt.gray())\n","plt.imshow(abs(pattern_vis))\n","plt.ylabel(\"pattern_vis\")\n","plt.title(\"{:.1f} % sampled\".format(actual_measured_percent))\n","\n","plt.subplot(324)\n","plt.set_cmap(plt.gray())\n","plt.imshow(np.log(1+abs(kspace_adc.numpy().copy())))\n","plt.ylabel('kspace_adc')\n","plt.subplot(326)\n","plt.set_cmap(plt.gray())\n","plt.imshow(kspace_vis)\n","plt.ylabel('kspace*pattern')\n","plt.show()\n","\n","# %% ##########################################################################\n","# S6: compressed sensing MR reconstruction of undersampled signal\n","# S6.1: function definitions\n","\n","def shrink(coeff, epsilon):\n"," shrink_values = (abs(coeff) < epsilon)\n"," high_values = coeff >= epsilon\n"," low_values = coeff <= -epsilon\n"," coeff[shrink_values] = 0\n"," coeff[high_values] -= epsilon\n"," coeff[low_values] += epsilon\n","\n","\n","# help?\n","# https://www2.isye.gatech.edu/~brani/wp/kidsA.pdf\n","#for family in pywt.families():\n","# print(\"%s family: \" % family + ', '.join(pywt.wavelist(family)))\n","\n","\n","def waveletShrinkage(current, epsilon):\n"," # Compute Wavelet decomposition\n"," cA, (cH, cV, cD) = pywt.dwt2(current, 'haar')\n"," # Shrink\n"," shrink(cA, epsilon)\n"," shrink(cH, epsilon)\n"," shrink(cV, epsilon)\n"," shrink(cD, epsilon)\n"," wavelet = cA, (cH, cV, cD)\n"," # return inverse WT\n"," return pywt.idwt2(wavelet, 'haar')\n","\n","\n","def updateData(k_space, pattern, current, step, i):\n"," # go to k-space\n"," update = np.fft.ifft2(np.fft.fftshift(current))\n"," # compute difference\n"," update = k_space - (update * pattern)\n"," #print(\"i: {}, consistency RMSEpc: {:3.6f}\".format(i, np.abs(update[:]).sum() * 100))\n"," # return to image space\n"," update = np.fft.fftshift(np.fft.fft2(update))\n"," # improve current estimation by consitency\n"," update = current + (step * update)\n"," return update\n","\n","# %% S6.3 undersampling and undersampled reconstruction\n","# space= space/ np.linalg.norm(space[:]) # normalization of the data somethimes helps\n","\n","# parameters of iterative reconstructio using total variation denoising\n","denoising_strength = 5e-5\n","number_of_iterations = 4000\n","stepsz = 0.1\n","\n","# actual iterative reconstruction algorithm\n","current = np.zeros(kspace.shape)\n","first = updateData(kspace, pattern, current, 1, 0)\n","current_shrink = first\n","all_iter = np.zeros((kspace.shape[0], kspace.shape[1], number_of_iterations))\n","\n","for i in range(number_of_iterations):\n"," current = updateData(kspace, pattern, current_shrink,stepsz, i)\n","\n"," current_shrink = denoise_tv_chambolle(abs(current), denoising_strength)\n"," # current_shrink = waveletShrinkage(abs(current), denoising_strength)\n","\n"," all_iter[:, :, i] = current\n","\n","\n","plt.subplot(323)\n","plt.set_cmap(plt.gray())\n","plt.imshow(abs(first))\n","plt.ylabel('first iter (=NUFFT)')\n","plt.subplot(325)\n","plt.set_cmap(plt.gray())\n","plt.imshow(abs(current_shrink))\n","plt.ylabel('final recon')\n","\n","\n","# %% Plot all iter\n","# make 25 example iterations\n","idx = np.linspace(1, all_iter.shape[2], 25) - 1\n","# choose them from all iters\n","red_iter = all_iter[:, :, tuple(idx.astype(int))]\n","Tot = red_iter.shape[2]\n","Rows = Tot // 5\n","if Tot % 5 != 0:\n"," Rows += 1\n","Position = range(1, Tot + 1) # Position index\n","\n","fig = plt.figure()\n","for k in range(Tot):\n"," ax = fig.add_subplot(Rows, 5, Position[k])\n"," ax.imshow((abs((red_iter[:, :, k]))))\n"," plt.title('iter {}'.format(idx[k].astype(int)))\n"," print(k)\n","plt.show()"]}],"metadata":{"colab":{"provenance":[{"file_id":"1uTk3lc-O3xZS-rLDSkOLEYVV1rM0UTDJ","timestamp":1676904915190},{"file_id":"1lnFKubthQBxkz19cY7ScS-S0Hj9vHjEj","timestamp":1676708491940}]},"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.3"}},"nbformat":4,"nbformat_minor":0}
1
+ {"cells":[{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":13863,"status":"ok","timestamp":1678740065139,"user":{"displayName":"Zhengguo Tan","userId":"10291917877743041231"},"user_tz":-60},"id":"vTjDmgyofjbF","outputId":"c702ebb6-274c-4e2e-fec0-c67810ceb02c","tags":["hide-cell"]},"outputs":[],"source":["!pip install pypulseq==1.3.1.post1 &> /dev/null\n","!pip install MRzeroCore &> /dev/null\n","!wget https://github.com/MRsources/MRzero-Core/raw/main/documentation/playground_mr0/numerical_brain_cropped.mat &> /dev/null"]},{"cell_type":"markdown","metadata":{},"source":["(mr0_CS_cartesian_seq)=\n","# Compressed Sensing - cartesian"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"executionInfo":{"elapsed":20513,"status":"ok","timestamp":1678741422978,"user":{"displayName":"Zhengguo Tan","userId":"10291917877743041231"},"user_tz":-60},"id":"O-_sr6lZjR_n","outputId":"d9cd1a67-4a63-44e6-df28-96198556f5d4"},"outputs":[],"source":["#@title generate\n","# %% S0. SETUP env\n","from skimage.restoration import denoise_tv_chambolle\n","import pywt\n","import MRzeroCore as mr0\n","import numpy as np\n","from matplotlib import pyplot as plt\n","plt.rcParams['figure.figsize'] = [10, 5]\n","plt.rcParams['figure.dpi'] = 100 # 200 e.g. is really fine, but slower\n","import pypulseq as pp\n","import torch\n","\n","experiment_id = 'exD01_bSSFP_2D'\n","\n","# %% S1. SETUP sys\n","\n","# choose the scanner limits\n","system = pp.Opts(\n"," max_grad=28,\n"," grad_unit='mT/m',\n"," max_slew=150,\n"," slew_unit='T/m/s',\n"," rf_ringdown_time=20e-6,\n"," rf_dead_time=100e-6,\n"," adc_dead_time=20e-6,\n"," grad_raster_time=50*10e-6\n",")\n","\n","# %% S2. DEFINE the sequence \n","seq = pp.Sequence()\n","\n","# Define FOV and resolution\n","fov = 200e-3 \n","slice_thickness=8e-3\n","sz=[128,128] # spin system size / resolution\n","Nread = sz[0] # frequency encoding steps/samples\n","Nphase = sz[1] # phase encoding steps/samples\n","\n","# Define rf events\n","rf1 = pp.make_sinc_pulse(flip_angle=5 * np.pi / 180, duration=1e-3,slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4, system=system)\n","# rf1, _= pp.make_block_pulse(flip_angle=90 * np.pi / 180, duration=1e-3, system=system)\n","\n","# Define other gradients and ADC events\n","gx = pp.make_trapezoid(channel='x', flat_area=Nread / fov, flat_time=10e-3, system=system)\n","adc = pp.make_adc(num_samples=Nread, duration=10e-3, phase_offset=0*np.pi/180,delay=gx.rise_time, system=system)\n","gx_pre = pp.make_trapezoid(channel='x', area=-gx.area / 2, duration=5e-3, system=system)\n","gx_spoil = pp.make_trapezoid(channel='x', area=1.5*gx.area, duration=2e-3, system=system)\n","\n","rf_phase = 0\n","rf_inc = 0\n","rf_spoiling_inc=117\n","\n","phase_enc__gradmoms = (torch.arange(0,Nphase,1)-Nphase//2) / fov\n","\n","# ======\n","# CONSTRUCT SEQUENCE\n","# ======\n","\n","idx = np.random.normal(loc=Nphase//2, scale=Nphase//4, size=(Nphase//2,)).astype(int)\n","idx[idx>=Nphase] = Nphase - 1\n","idx[idx<0] = 0\n","print('idx: ', idx)\n","\n","# idx = [1,5,15,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,45,50,55]\n","#idx = np.linspace(0,63,64)\n","#idx = [5,15,20,27,28,29,30,31,32,33,34,35,36,37,38,39,40,45,50,55]\n","# idx = np.random.poisson(Nread/2,Nread*8)\n","idx = np.unique(idx)\n","for ii in range(0, len(idx)): # e.g. -64:63\n","\n"," rf1.phase_offset = rf_phase / 180 * np.pi # set current rf phase\n"," \n"," adc.phase_offset = rf_phase / 180 * np.pi # follow with ADC\n"," rf_inc = divmod(rf_inc + rf_spoiling_inc, 360.0)[1] # increase increment\n"," rf_phase = divmod(rf_phase + rf_inc, 360.0)[1] # increment additional pahse\n","\n"," seq.add_block(rf1)\n"," gp= pp.make_trapezoid(channel='y', area=phase_enc__gradmoms[int(idx[ii])], duration=5e-3, system=system)\n"," seq.add_block(gx_pre,gp)\n"," seq.add_block(adc,gx)\n"," gp= pp.make_trapezoid(channel='y', area=-phase_enc__gradmoms[int(idx[ii])], duration=5e-3, system=system)\n"," seq.add_block(gx_spoil,gp)\n"," if ii<Nphase-1:\n"," seq.add_block(pp.make_delay(0.001))\n","# %% S3. CHECK, PLOT and WRITE the sequence as .seq\n","# Check whether the timing of the sequence is correct\n","ok, error_report = seq.check_timing()\n","if ok:\n"," print('Timing check passed successfully')\n","else:\n"," print('Timing check failed. Error listing follows:')\n"," [print(e) for e in error_report]\n","\n","# PLOT sequence\n","#sp_adc, t_adc = mr0.util.pulseq_plot(seq, clear=False)\n","\n","# Prepare the sequence output for the scanner\n","seq.set_definition('FOV', [fov, fov, slice_thickness])\n","seq.set_definition('Name', 'gre')\n","seq.write(experiment_id + '.seq')\n","# %% S4: SETUP SPIN SYSTEM/object on which we can run the MR sequence external.seq from above\n","\n","if 1:\n"," # (i) load a phantom object from file\n"," obj_p = mr0.VoxelGridPhantom.load_mat('numerical_brain_cropped.mat')\n"," obj_p = obj_p.interpolate(sz[0], sz[1], 1)\n"," # Manipulate loaded data\n"," obj_p.B0 *= 0\n"," obj_p.D *= 0\n","else:\n"," # or (ii) set phantom manually to a pixel phantom. Coordinate system is [-0.5, 0.5]^3\n"," obj_p = mr0.CustomVoxelPhantom(\n"," pos=[[-0.25, -0.25, 0]],\n"," PD=[1.0],\n"," T1=[3.0],\n"," T2=[0.5],\n"," T2dash=[30e-3],\n"," D=[0.0],\n"," B0 =0,\n"," voxel_size=0.1,\n"," voxel_shape=\"box\"\n"," )\n","\n","#obj_p.plot()\n","# Convert Phantom into simulation data\n","obj_p = obj_p.build()\n","\n","# %% S5:. SIMULATE the external.seq file and add acquired signal to ADC plot\n","\n","# Read in the sequence\n","seq0 = mr0.Sequence.import_file(experiment_id + '.seq')\n","#seq0.plot_kspace_trajectory()\n","# Simulate the sequence\n","graph = mr0.compute_graph(seq0, obj_p, 200, 1e-3)\n","signal = mr0.execute_graph(graph, seq0, obj_p, print_progress=False)\n","\n","# PLOT sequence with signal in the ADC subplot\n","#sp_adc, t_adc = mr0.util.pulseq_plot(seq, clear=True, signal=signal.numpy())\n","\n","kspace_adc=torch.reshape((signal),(len(idx),Nread)).clone().t()\n","\n","kspace = torch.zeros((Nread,Nread),dtype = torch.complex64)\n","kspace[:,idx] = kspace_adc\n","pattern = torch.zeros((Nread,Nread))\n","pattern[:,idx] = torch.ones(Nread,len(idx))\n","\n","# high frequencies centered as kspace and as FFT needs it\n","pattern = np.fft.fftshift(pattern.numpy())\n","kspace = np.fft.ifftshift(kspace.numpy())\n","\n","# kspace = kspace_full * pattern # apply the undersampling pattern\n","\n","# calculate the actually measured data in percent\n","actual_measured_percent = np.count_nonzero(pattern) / pattern.size * 100\n","\n","# Plotting\n","pattern_vis = np.fft.fftshift(pattern.copy())\n","kspace_vis = np.log(1+abs(np.fft.fftshift((kspace.copy()))))\n","fig = plt.figure(dpi=90)\n","plt.subplot(321)\n","plt.set_cmap(plt.gray())\n","# plt.imshow(abs(recon_nufft))\n","plt.ylabel('recon_full')\n","plt.subplot(322)\n","plt.set_cmap(plt.gray())\n","plt.imshow(abs(pattern_vis))\n","plt.ylabel(\"pattern_vis\")\n","plt.title(\"{:.1f} % sampled\".format(actual_measured_percent))\n","\n","plt.subplot(324)\n","plt.set_cmap(plt.gray())\n","plt.imshow(np.log(1+abs(kspace_adc.numpy().copy())))\n","plt.ylabel('kspace_adc')\n","plt.subplot(326)\n","plt.set_cmap(plt.gray())\n","plt.imshow(kspace_vis)\n","plt.ylabel('kspace*pattern')\n","plt.show()\n","\n","# %% ##########################################################################\n","# S6: compressed sensing MR reconstruction of undersampled signal\n","# S6.1: function definitions\n","\n","def shrink(coeff, epsilon):\n"," shrink_values = (abs(coeff) < epsilon)\n"," high_values = coeff >= epsilon\n"," low_values = coeff <= -epsilon\n"," coeff[shrink_values] = 0\n"," coeff[high_values] -= epsilon\n"," coeff[low_values] += epsilon\n","\n","\n","# help?\n","# https://www2.isye.gatech.edu/~brani/wp/kidsA.pdf\n","#for family in pywt.families():\n","# print(\"%s family: \" % family + ', '.join(pywt.wavelist(family)))\n","\n","\n","def waveletShrinkage(current, epsilon):\n"," # Compute Wavelet decomposition\n"," cA, (cH, cV, cD) = pywt.dwt2(current, 'haar')\n"," # Shrink\n"," shrink(cA, epsilon)\n"," shrink(cH, epsilon)\n"," shrink(cV, epsilon)\n"," shrink(cD, epsilon)\n"," wavelet = cA, (cH, cV, cD)\n"," # return inverse WT\n"," return pywt.idwt2(wavelet, 'haar')\n","\n","\n","def updateData(k_space, pattern, current, step, i):\n"," # go to k-space\n"," update = np.fft.ifft2(np.fft.fftshift(current))\n"," # compute difference\n"," update = k_space - (update * pattern)\n"," #print(\"i: {}, consistency RMSEpc: {:3.6f}\".format(i, np.abs(update[:]).sum() * 100))\n"," # return to image space\n"," update = np.fft.fftshift(np.fft.fft2(update))\n"," # improve current estimation by consitency\n"," update = current + (step * update)\n"," return update\n","\n","# %% S6.3 undersampling and undersampled reconstruction\n","# space= space/ np.linalg.norm(space[:]) # normalization of the data somethimes helps\n","\n","# parameters of iterative reconstructio using total variation denoising\n","denoising_strength = 5e-5\n","number_of_iterations = 4000\n","stepsz = 0.1\n","\n","# actual iterative reconstruction algorithm\n","current = np.zeros(kspace.shape)\n","first = updateData(kspace, pattern, current, 1, 0)\n","current_shrink = first\n","all_iter = np.zeros((kspace.shape[0], kspace.shape[1], number_of_iterations))\n","\n","for i in range(number_of_iterations):\n"," current = updateData(kspace, pattern, current_shrink,stepsz, i)\n","\n"," current_shrink = denoise_tv_chambolle(abs(current), denoising_strength)\n"," # current_shrink = waveletShrinkage(abs(current), denoising_strength)\n","\n"," all_iter[:, :, i] = current\n","\n","\n","plt.subplot(323)\n","plt.set_cmap(plt.gray())\n","plt.imshow(abs(first))\n","plt.ylabel('first iter (=NUFFT)')\n","plt.subplot(325)\n","plt.set_cmap(plt.gray())\n","plt.imshow(abs(current_shrink))\n","plt.ylabel('final recon')\n","\n","\n","# %% Plot all iter\n","# make 25 example iterations\n","idx = np.linspace(1, all_iter.shape[2], 25) - 1\n","# choose them from all iters\n","red_iter = all_iter[:, :, tuple(idx.astype(int))]\n","Tot = red_iter.shape[2]\n","Rows = Tot // 5\n","if Tot % 5 != 0:\n"," Rows += 1\n","Position = range(1, Tot + 1) # Position index\n","\n","fig = plt.figure()\n","for k in range(Tot):\n"," ax = fig.add_subplot(Rows, 5, Position[k])\n"," ax.imshow((abs((red_iter[:, :, k]))))\n"," plt.title('iter {}'.format(idx[k].astype(int)))\n"," print(k)\n","plt.show()"]}],"metadata":{"colab":{"provenance":[{"file_id":"1uTk3lc-O3xZS-rLDSkOLEYVV1rM0UTDJ","timestamp":1676904915190},{"file_id":"1lnFKubthQBxkz19cY7ScS-S0Hj9vHjEj","timestamp":1676708491940}]},"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.3"}},"nbformat":4,"nbformat_minor":0}
@@ -0,0 +1 @@
1
+ {"cells":[{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/"},"executionInfo":{"elapsed":13556,"status":"ok","timestamp":1699010955218,"user":{"displayName":"Zhengguo Tan","userId":"10291917877743041231"},"user_tz":-60},"id":"vTjDmgyofjbF","outputId":"d7a4eee3-c327-4afe-8bc1-69fbb56be643","tags":["hide-cell"]},"outputs":[],"source":["!pip install pypulseq==1.3.1.post1 &> /dev/null\n","!pip install MRzeroCore &> /dev/null\n","!pip install torchkbnufft &> /dev/null\n","!wget https://github.com/MRsources/MRzero-Core/raw/main/documentation/playground_mr0/numerical_brain_cropped.mat &> /dev/null"]},{"cell_type":"markdown","metadata":{},"source":["(mr0_CS_radial_seq)=\n","# Compressed Sensing - radial"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":246},"executionInfo":{"elapsed":258,"status":"error","timestamp":1699010929473,"user":{"displayName":"Zhengguo Tan","userId":"10291917877743041231"},"user_tz":-60},"id":"O-_sr6lZjR_n","outputId":"a3e4234c-f31c-426c-d3f0-4d4e4fdcdbfc"},"outputs":[],"source":["# %% S0. SETUP env\n","from skimage.restoration import denoise_tv_chambolle\n","import pywt\n","import MRzeroCore as mr0\n","import pypulseq as pp\n","import numpy as np\n","import torch\n","from matplotlib import pyplot as plt\n","plt.rcParams['figure.figsize'] = [10, 5]\n","plt.rcParams['figure.dpi'] = 100 # 200 e.g. is really fine, but slower\n","\n","experiment_id = 'exF02_undersampled_radial'\n","\n","\n","# %% S1. SETUP sys\n","\n","# choose the scanner limits\n","system = pp.Opts(\n"," max_grad=28, grad_unit='mT/m', max_slew=150, slew_unit='T/m/s',\n"," rf_ringdown_time=20e-6, rf_dead_time=100e-6,\n"," adc_dead_time=20e-6, grad_raster_time=50 * 10e-6\n",")\n","\n","\n","# %% S2. DEFINE the sequence\n","seq = pp.Sequence()\n","\n","# Define FOV and resolution\n","fov = 220e-3\n","slice_thickness = 8e-3\n","sz = [128, 128] # spin system size / resolution\n","Nread = sz[0] # frequency encoding steps/samples\n","Nphase = 61 # phase encoding steps/samples, i.e. number of radial spokes\n","\n","# Define rf events\n","rf1, _, _ = pp.make_sinc_pulse(\n"," flip_angle=6 * np.pi / 180, duration=1e-3,\n"," slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4,\n"," system=system, return_gz=True\n",")\n","# rf1 = pp.make_block_pulse(flip_angle=90 * np.pi / 180, duration=1e-3, system=system)\n","\n","# Define other gradients and ADC events\n","gx = pp.make_trapezoid(channel='x', flat_area=Nread / fov, flat_time=5e-3, system=system)\n","gy = pp.make_trapezoid(channel='y', flat_area=Nread / fov, flat_time=5e-3, system=system)\n","\n","gx_pre = pp.make_trapezoid(channel='x', area=-gx.area / 2, duration=1e-3, system=system)\n","gy_pre = pp.make_trapezoid(channel='y', area=-gy.area / 2, duration=1e-3, system=system)\n","\n","os = 1\n","adc = pp.make_adc(num_samples=Nread * os, duration=5e-3, phase_offset=0 * np.pi / 180, delay=gx.rise_time, system=system)\n","\n","rf_phase = 180\n","rf_inc = 180\n","\n","# ======\n","# CONSTRUCT SEQUENCE\n","# ======\n","sdel = 1e-0\n","\n","rf0, _, _ = pp.make_sinc_pulse(\n"," flip_angle=6 / 2 * np.pi / 180, duration=1e-3,\n"," slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4,\n"," system=system, return_gz=True\n",")\n","seq.add_block(rf0)\n","seq.add_block(pp.make_delay(3e-3))\n","\n","for ii in range(-Nphase // 2, Nphase // 2): # e.g. -64:63\n"," rf1.phase_offset = rf_phase / 180 * np.pi # set current rf phase\n","\n"," adc.phase_offset = rf_phase / 180 * np.pi # follow with ADC\n"," # increment additional pahse\n"," rf_phase = divmod(rf_phase + rf_inc, 360.0)[1]\n","\n"," seq.add_block(rf1)\n","\n"," gx = pp.make_trapezoid(channel='x', flat_area=-Nread * np.sin(ii / Nphase * np.pi) / fov + 1e-9, flat_time=5e-3, system=system)\n"," gy = pp.make_trapezoid(channel='y', flat_area=Nread * np.cos(ii / Nphase * np.pi) / fov + 1e-9, flat_time=5e-3, system=system)\n","\n"," gx_pre = pp.make_trapezoid(channel='x', area=-gx.area / 2, duration=1e-3, system=system)\n"," gy_pre = pp.make_trapezoid(channel='y', area=-gy.area / 2, duration=1e-3, system=system)\n","\n"," seq.add_block(gx_pre, gy_pre)\n"," seq.add_block(adc, gx, gy)\n"," # seq.add_block(adc,gx,gy)\n"," seq.add_block(gx_pre, gy_pre)\n"," # seq.add_block(make_delay(10))\n","\n","\n","# %% S3. CHECK, PLOT and WRITE the sequence as .seq\n","# Check whether the timing of the sequence is correct\n","ok, error_report = seq.check_timing()\n","if ok:\n"," print('Timing check passed successfully')\n","else:\n"," print('Timing check failed. Error listing follows:')\n"," [print(e) for e in error_report]\n","\n","# PLOT sequence\n","sp_adc, t_adc = mr0.util.pulseq_plot(seq, clear=False)\n","\n","# Prepare the sequence output for the scanner\n","seq.set_definition('FOV', [fov, fov, slice_thickness])\n","seq.set_definition('Name', 'gre')\n","seq.write(experiment_id + '.seq')\n","\n","\n","# %% S4: SETUP SPIN SYSTEM/object on which we can run the MR sequence external.seq from above\n","\n","if 1:\n"," # (i) load a phantom object from file\n"," obj_p = mr0.VoxelGridPhantom.load_mat('numerical_brain_cropped.mat')\n"," obj_p = obj_p.interpolate(sz[0], sz[1], 1)\n"," # Manipulate loaded data\n"," obj_p.T2dash[:] = 30e-3\n"," obj_p.D *= 0\n"," obj_p.B0 *= 0\n","else:\n"," # or (ii) set phantom manually to a pixel phantom. Coordinate system is [-0.5, 0.5]^3\n"," obj_p = mr0.CustomVoxelPhantom(\n"," pos=[[-0.4, -0.4, 0], [-0.4, -0.2, 0], [-0.3, -0.2, 0], [-0.2, -0.2, 0], [-0.1, -0.2, 0]],\n"," PD=[1.0, 1.0, 0.5, 0.5, 0.5],\n"," T1=1.0,\n"," T2=0.1,\n"," T2dash=0.1,\n"," D=0.0,\n"," voxel_size=0.1,\n"," voxel_shape=\"box\"\n"," )\n","\n","obj_p.plot()\n","# Convert Phantom into simulation data\n","obj_p = obj_p.build()\n","\n","\n","# %% S5:. SIMULATE the external.seq file and add acquired signal to ADC plot\n","\n","# Read in the sequence\n","seq0 = mr0.Sequence.import_file(experiment_id + '.seq')\n","seq0.plot_kspace_trajectory()\n","kspace_loc = seq0.get_kspace()\n","# Simulate the sequence\n","graph = mr0.compute_graph(seq0, obj_p, 200, 1e-3)\n","signal = mr0.execute_graph(graph, seq0, obj_p, print_progress=False)\n","\n","# PLOT sequence with signal in the ADC subplot\n","sp_adc, t_adc = mr0.util.pulseq_plot(seq, clear=True, signal=signal.numpy())\n","\n","kspace_adc = torch.reshape((signal), (Nphase, Nread)).clone().t()"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"TDylBKLH6smB"},"outputs":[],"source":["print('kspace_loc shape: ', kspace_loc.shape)\n","print('kspace_adc shape: ', kspace_adc.shape)\n","\n","f, ax = plt.subplots(1, 1, figsize=(6, 6))\n","ax.imshow(np.log10(abs(kspace_adc)), cmap='gray')\n","ax.set_xlabel('radial spoke index')\n","ax.set_ylabel('readout')"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"0AXGa84z8ncu"},"outputs":[],"source":["traj = torch.reshape(kspace_loc, (Nphase, Nread, kspace_loc.shape[-1]))\n","traj = traj[..., :2]\n","\n","traj_scale = traj/Nread * 2\n","traj_convert = torch.reshape(traj_scale, (-1, 2)).transpose(1, 0)\n","\n","print('traj_scale shape: ', traj_scale.shape)\n","print('traj_convert shape: ', traj_convert.shape)\n","\n","kspace_res = torch.view_as_real(kspace_adc.transpose(1, 0))\n","kspace_res = torch.reshape(kspace_res, (1, 1, -1, 2))\n","\n","print('kspace_res shape: ', kspace_res.shape)"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"QfBq01JY6gM8"},"outputs":[],"source":["import scipy.interpolate\n","grid = kspace_loc[:, :2]\n","Nx = sz[0]\n","Ny = sz[1]\n","\n","X, Y = np.meshgrid(np.linspace(0, Nx - 1, Nx) - Nx / 2,\n"," np.linspace(0, Ny - 1, Ny) - Ny / 2)\n","grid = np.double(grid.numpy())\n","grid[np.abs(grid) < 1e-3] = 0\n","\n","# plot every 8 radial spokes\n","f, ax = plt.subplots(1, 1, figsize=(9, 9))\n","ax.plot(traj[::8, :Nx, 0].ravel(), traj[::8, :Nx, 1].ravel(), 'rx', markersize=3)\n","ax.plot(X, Y, 'k.', markersize=2)\n","plt.show()\n","\n","print(np.amin(grid[:, 0]), np.amax(grid[:, 0]))\n","print(np.amin(grid[:, 1]), np.amax(grid[:, 1]))"]},{"cell_type":"markdown","metadata":{"id":"AM6OLNjjtFpF"},"source":["NUFFT recon with density compensation\n","\n"," * Zhengguo Tan <zhengguo.tan@gmail.com>\n","\n"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"l7fEtzVbK0NO"},"outputs":[],"source":["import torchkbnufft as tkbn\n","\n","# compute density compensation function\n","dcf = (traj_convert[0, ...]**2 + traj_convert[1, ...]**2)**0.5\n","dcf = dcf.reshape(1, -1).repeat(2, 1).transpose(1, 0)\n","\n","print('dcf shape: ', dcf.shape)\n","\n","\n","img_shape = [Nread] * 2\n","\n","\n","# define nufft adjoint operator\n","nufft_adj = tkbn.KbNufftAdjoint(im_size=img_shape)\n","\n","img_dcf_tensor = nufft_adj(kspace_res * dcf, traj_convert)\n","img_tensor = nufft_adj(kspace_res, traj_convert)\n","\n","print('nufft_adj -> image shape: ', img_tensor.shape)\n","\n","\n","# define nufft forward operator\n","nufft_fwd = tkbn.KbNufft(im_size=img_shape)\n","\n","kspace_fwd = nufft_fwd(img_tensor, traj_convert)\n","\n","kspace_fwd_res = torch.reshape(kspace_fwd, (1, 1, Nphase, Nread, 2))\n","\n","ksp_cplx = torch.view_as_complex(kspace_fwd_res).transpose(-1, -2).cpu().detach().numpy()\n","\n","print('nufft_fwd -> kspace shape: ', kspace_fwd.shape)\n","\n","img = img_dcf_tensor.cpu().detach().numpy()\n","\n","img_cplx = img[..., 0] + 1j * img[..., 1]\n","img_cplx = np.flip(np.swapaxes(img_cplx, -1, -2), -2)\n","\n","f, ax = plt.subplots(1, 2, figsize=(12, 6))\n","ax[0].imshow(abs(np.squeeze(img_cplx)), cmap='gray')\n","ax[1].imshow(np.log10(abs(np.squeeze(ksp_cplx))), cmap='gray')\n","plt.show()"]},{"cell_type":"markdown","metadata":{"id":"A0WymLfYtJ8T"},"source":["Compressed sensing recon for radial sampling\n","\n"," * Zhengguo Tan <zhengguo.tan@gmail.com>\n"," "]},{"cell_type":"code","execution_count":null,"metadata":{"id":"iAyDfZWttJOY"},"outputs":[],"source":["def soft_thresh(input, lamda):\n","\n"," abs_input = abs(input)\n","\n"," sign = np.true_divide(input, abs_input,\n"," out=np.zeros_like(input), where=abs_input!=0)\n","\n"," magn = abs_input - lamda\n"," magn = (abs(magn) + magn) / 2\n","\n"," return magn * sign\n","\n","\n","def prox_wav(input, lamda):\n","\n"," if torch.is_tensor(input):\n"," input = input.cpu().detach().numpy()\n","\n"," # pywt outputs numpy arrays\n"," cA, (cH, cV, cD) = pywt.dwt2(input, 'db4')\n","\n"," cA_t = soft_thresh(cA, lamda)\n"," cH_t = soft_thresh(cH, lamda)\n"," cV_t = soft_thresh(cV, lamda)\n"," cD_t = soft_thresh(cD, lamda)\n","\n"," wav_coef = cA_t, (cH_t, cV_t, cD_t)\n","\n"," output = pywt.idwt2(wav_coef, 'db4')\n","\n"," return torch.tensor(output)\n","\n","\n","# compute maximal eigenvalue:\n","x = torch.randn(size=img_tensor.shape, dtype=img_tensor.dtype)\n","for n in range(30):\n"," y = nufft_adj(nufft_fwd(x, traj_convert), traj_convert)\n"," max_eig = torch.linalg.norm(y).ravel()\n"," x = y / max_eig\n","\n"," print(max_eig)\n","\n","\n","# Gradient method\n","Niter = 200\n","\n","alpha = (1 / max_eig).cpu().detach().numpy().item()\n","lamda = 0.001\n","\n","x = torch.zeros_like(img_tensor)\n","\n","for n in range(Niter):\n","\n"," x_old = x.clone()\n","\n"," r = nufft_fwd(x, traj_convert) - kspace_res\n"," g = nufft_adj(r, traj_convert)\n","\n"," x = prox_wav(x - alpha * g, alpha * lamda)\n","\n","\n"," resid = (torch.linalg.norm(x - x_old).ravel()).ravel()\n","\n"," print('> iter ' + str(n).zfill(4) + ' residuum ' + str(resid[0]))"]},{"cell_type":"markdown","metadata":{"id":"ISvqSJv1qNr1"},"source":["Plot results"]},{"cell_type":"code","execution_count":null,"metadata":{"id":"S7_P7m5CmUDk"},"outputs":[],"source":["recon = torch.view_as_complex(x).cpu().detach().numpy()\n","recon = np.flip(np.swapaxes(recon, -1, -2), -2)\n","\n","R1 = abs(np.squeeze(img_cplx))\n","R2 = abs(np.squeeze(recon))\n","\n","f, ax = plt.subplots(1, 2, figsize=(12, 6))\n","ax[0].imshow(R1, cmap='gray', vmin=0) # [32:96, 32:96]\n","ax[0].set_title('NUFFT')\n","\n","ax[1].imshow(R2, cmap='gray', vmin=0)\n","ax[1].set_title('Compressed Sensing')\n","plt.show()"]}],"metadata":{"colab":{"provenance":[{"file_id":"1uTk3lc-O3xZS-rLDSkOLEYVV1rM0UTDJ","timestamp":1676904915190},{"file_id":"1lnFKubthQBxkz19cY7ScS-S0Hj9vHjEj","timestamp":1676708491940}]},"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.3"}},"nbformat":4,"nbformat_minor":0}
@@ -87,7 +87,7 @@
87
87
  "seq = pp.Sequence()\n",
88
88
  "\n",
89
89
  "# Define FOV and resolution\n",
90
- "fov = 220e-3\n",
90
+ "fov = 200e-3\n",
91
91
  "slice_thickness=8e-3\n",
92
92
  "sz = (64, 64) # spin system size / resolution\n",
93
93
  "Nread = 64 # frequency encoding steps/samples\n",
@@ -87,7 +87,7 @@
87
87
  "seq = pp.Sequence()\n",
88
88
  "\n",
89
89
  "# Define FOV and resolution\n",
90
- "fov = 220e-3\n",
90
+ "fov = 200e-3\n",
91
91
  "slice_thickness=8e-3\n",
92
92
  "sz = (64, 64) # spin system size / resolution\n",
93
93
  "Nread = 64 # frequency encoding steps/samples\n",
@@ -1 +1 @@
1
- {"cells":[{"cell_type":"code","execution_count":null,"metadata":{"executionInfo":{"elapsed":40239,"status":"ok","timestamp":1691153098017,"user":{"displayName":"Moritz Zaiss","userId":"13462394581901772323"},"user_tz":-120},"id":"vTjDmgyofjbF","tags":["hide-cell"]},"outputs":[],"source":["!pip install pypulseq==1.3.1.post1 &> /dev/null\n","!pip install MRzeroCore &> /dev/null\n","!wget https://github.com/MRsources/MRzero-Core/raw/main/documentation/playground_mr0/subject05.npz &> /dev/null"]},{"cell_type":"markdown","metadata":{},"source":["(DWI_GRE_2D_seq)=\n","# Diffusion weighted GRE"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"executionInfo":{"elapsed":34603,"status":"ok","timestamp":1691153132617,"user":{"displayName":"Moritz Zaiss","userId":"13462394581901772323"},"user_tz":-120},"id":"O-_sr6lZjR_n","outputId":"20df9dff-4711-4688-f8a9-126130d8a7d7"},"outputs":[],"source":["import numpy as np\n","# newer numpy versions don't contain this, but pypulseq still relies on it\n","np.int = int\n","np.float = float\n","np.complex = complex\n","\n","import MRzeroCore as mr0\n","import pypulseq as pp\n","import torch\n","import matplotlib.pyplot as plt\n","\n","plt.rcParams['figure.figsize'] = [10, 5]\n","plt.rcParams['figure.dpi'] = 100 # 200 e.g. is really fine, but slower\n","\n","experiment_id = 'mr0_DWI_GRE_2D'\n","\n","# %% S1. SETUP sys\n","\n","# choose the scanner limits\n","system = pp.Opts(max_grad=28,grad_unit='mT/m',max_slew=150,slew_unit='T/m/s',\n"," rf_ringdown_time=20e-6,rf_dead_time=100e-6,adc_dead_time=20e-6,grad_raster_time=50*10e-6)\n","\n","# %% S2. DEFINE the sequence\n","seq = pp.Sequence()\n","\n","# Define FOV and resolution\n","fov = 220e-3\n","slice_thickness = 8e-3\n","Nread = 64 # frequency encoding steps/samples\n","Nphase = 64 # phase encoding steps/samples\n","\n","# Define rf events\n","rf1, _, _ = pp.make_sinc_pulse(\n"," flip_angle=90 * np.pi / 180, duration=1e-3,\n"," slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4,\n"," system=system, return_gz=True\n",")\n","# rf1 = pp.make_block_pulse(flip_angle=90 * np.pi / 180, duration=1e-3, system=system)\n","\n","# Define other gradients and ADC events\n","gx = pp.make_trapezoid(channel='x', flat_area=Nread / fov, flat_time=10e-3, system=system)\n","adc = pp.make_adc(num_samples=Nread, duration=10e-3, phase_offset=0 * np.pi/180, delay=gx.rise_time, system=system)\n","gx_pre = pp.make_trapezoid(channel='x', area=-gx.area / 2, duration=5e-3, system=system)\n","gx_spoil = pp.make_trapezoid(channel='x', area=1.5 * gx.area, duration=2e-3, system=system)\n","\n","# This DWI gradient will produce a b-value around ~700 s/mm^2\n","gx_diff = pp.make_trapezoid(channel='x', area=33 * gx.area, duration=10e-3, system=system)\n","gx_diff_ = pp.make_trapezoid(channel='x', area=-33 * gx.area, duration=10e-3, system=system)\n","\n","# ======\n","# CONSTRUCT SEQUENCE\n","# ======\n","##linear reordering\n","phenc = np.arange(-Nphase // 2, Nphase // 2, 1) / fov\n","permvec =np.arange(0, Nphase, 1)\n","## centric reordering\n","#permvec = sorted(np.arange(len(phenc)), key=lambda x: abs(len(phenc) // 2 - x))\n","## random reordering\n","#perm =np.arange(0, Nphase, 1); permvec = np.random.permutation(perm)\n","\n","phenc_centr = phenc[permvec]\n","\n","for ii in range(0, Nphase): # e.g. -64:63\n","\n"," seq.add_block(rf1)\n"," DWI=True #@param {type: \"boolean\"}\n"," if DWI:\n"," seq.add_block(gx_diff)\n"," seq.add_block(gx_diff_)\n"," else:\n"," seq.add_block(pp.make_delay(20e-3))\n","\n","\n"," gp = pp.make_trapezoid(channel='y', area=phenc_centr[ii], duration=5e-3, system=system)\n","\n"," seq.add_block(gx_pre, gp)\n"," seq.add_block(adc, gx)\n"," gp = pp.make_trapezoid(channel='y', area=-phenc_centr[ii], duration=5e-3, system=system)\n"," seq.add_block(gx_spoil, gp)\n"," if ii < Nphase - 1:\n"," seq.add_block(pp.make_delay(5))\n","\n","\n","# %% S3. CHECK, PLOT and WRITE the sequence as .seq\n","# Check whether the timing of the sequence is correct\n","ok, error_report = seq.check_timing()\n","if ok:\n"," print('Timing check passed successfully')\n","else:\n"," print('Timing check failed. Error listing follows:')\n"," [print(e) for e in error_report]\n","\n","# PLOT sequence\n","sp_adc, t_adc = mr0.util.pulseq_plot(seq, clear=False, figid=(11,12))\n","\n","# Prepare the sequence output for the scanner\n","seq.set_definition('FOV', [fov, fov, slice_thickness])\n","seq.set_definition('Name', 'gre')\n","seq.write(experiment_id + '.seq')\n","\n","\n","# %% S4: SETUP SPIN SYSTEM/object on which we can run the MR sequence external.seq from above\n","sz = [64, 64]\n","# (i) load a phantom object from file\n","obj_p = mr0.VoxelGridPhantom.brainweb('subject05.npz')\n","obj_p = obj_p.interpolate(sz[0], sz[1], 32).slices([15])\n","\n","# (ii) insert rectangular \"Tumor\"\n","# typical brain tumor ADC values are around ~1.5 * 10^-3 mm^2/s,\n","# which lies between GM/WM and CSF (https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3000221)\n","if 1:\n"," # construct tumor border region\n"," for ii in range(15, 25):\n"," for jj in range(15, 25):\n"," obj_p.D[ii, jj] = torch.tensor(2.5)\n","\n"," # construct tumor filling\n"," for ii in range(16, 24):\n"," for jj in range(16, 24):\n"," obj_p.D[ii, jj] = torch.tensor(1.5)\n","\n","\n","# Store PD and B0 for comparison\n","PD = obj_p.PD\n","B0 = obj_p.B0\n","obj_p.plot()\n","# Convert Phantom into simulation data\n","obj_p = obj_p.build()\n","\n","\n","# %% S5:. SIMULATE the external.seq file and add acquired signal to ADC plot\n","# Read in the sequence\n","seq0 = mr0.Sequence.import_file(experiment_id + '.seq')\n","seq0.plot_kspace_trajectory()\n","# Simulate the sequence\n","graph = mr0.compute_graph(seq0, obj_p, 200, 1e-3)\n","signal = mr0.execute_graph(graph, seq0, obj_p, print_progress=False)\n","\n","# PLOT sequence with signal in the ADC subplot\n","plt.close(11);plt.close(12)\n","sp_adc, t_adc = mr0.util.pulseq_plot(seq, clear=False, signal=signal.numpy())\n","\n","\n","# additional noise as simulation is perfect\n","signal += 1e-4 * np.random.randn(signal.shape[0], 2).view(np.complex128)\n","\n","\n","# %% S6: MR IMAGE RECON of signal ::: #####################################\n","fig = plt.figure() # fig.clf()\n","plt.subplot(411)\n","plt.title('ADC signal')\n","kspace = torch.reshape((signal), (Nphase, Nread)).clone().t()\n","plt.plot(torch.real(signal), label='real')\n","plt.plot(torch.imag(signal), label='imag')\n","\n","ipermvec = np.argsort(permvec)\n","\n","kspace=kspace[:,ipermvec]\n","# this adds ticks at the correct position szread\n","major_ticks = np.arange(0, Nphase * Nread, Nread)\n","ax = plt.gca()\n","ax.set_xticks(major_ticks)\n","ax.grid()\n","\n","# fftshift\n","spectrum = torch.fft.fftshift(kspace)\n","# FFT\n","space = torch.fft.fft2(spectrum)\n","# fftshift\n","space = torch.fft.ifftshift(space)\n","\n","\n","plt.subplot(345)\n","plt.title('k-space')\n","plt.imshow(np.abs(kspace.numpy()))\n","plt.subplot(349)\n","plt.title('log. k-space')\n","plt.imshow(np.log(np.abs(kspace.numpy())))\n","\n","plt.subplot(346)\n","plt.title('FFT-magnitude')\n","plt.imshow(np.abs(space.numpy()))\n","plt.colorbar()\n","plt.subplot(3, 4, 10)\n","plt.title('FFT-phase')\n","plt.imshow(np.angle(space.numpy()), vmin=-np.pi, vmax=np.pi)\n","plt.colorbar()\n","\n","# % compare with original phantom obj_p.PD\n","plt.subplot(348)\n","plt.title('phantom PD')\n","plt.imshow(PD)\n","plt.subplot(3, 4, 12)\n","plt.title('phantom B0')\n","plt.imshow(B0)"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":465},"executionInfo":{"elapsed":1130,"status":"ok","timestamp":1689757017587,"user":{"displayName":"Jonathan Endres","userId":"15767859839481375594"},"user_tz":-120},"id":"MNxkdhaMzLiM","outputId":"253e0812-04ed-4d60-850e-8b17707dd1bb"},"outputs":[],"source":["# Exemplary calculation of b-values and diffusion of simulated tissues for a single repetition\n","\n","traj = seq0[10].gradm.cumsum(0)[:-2]\n","dt = seq0[10].event_time[:-2]\n","time = dt.cumsum(0)\n","\n","k2 = traj[:, :3] * 2 * np.pi\n","k1 = torch.zeros_like(k2)\n","k1[1:, :] = k2[:-1, :]\n","# Integrate over each event to get b factor (lin. interp. grad)\n","b = 1/3 * dt * (k1**2 + k1*k2 + k2**2).sum(1) # s / m^2\n","\n","# typical ADC values: 10^-3 mm^2/s = 10^-9 m^2 / s\n","# typical DWI b-values: up to 4000 s / mm^2\n","\n","diffusion_gm = torch.exp(-1e-9 * 0.83 * torch.cumsum(b, 0)[:, None])\n","diffusion_wm = torch.exp(-1e-9 * 0.65 * torch.cumsum(b, 0)[:, None])\n","diffusion_csf = torch.exp(-1e-9 * 3.19 * torch.cumsum(b, 0)[:, None])\n","diffusion_tumor = torch.exp(-1e-9 * 1.5 * torch.cumsum(b, 0)[:, None])\n","\n","\n","plt.figure()\n","ax1 = plt.subplot(211)\n","plt.plot(time, b.cumsum(0) * 1e-6)\n","plt.ylabel(\"b-value [s / mm^2]\")\n","plt.grid()\n","plt.tick_params(\"x\", labelbottom=False)\n","plt.subplot(212, sharex=ax1)\n","plt.plot(time, diffusion_gm, label=\"gm\")\n","plt.plot(time, diffusion_wm, label=\"wm\")\n","plt.plot(time, diffusion_csf, label=\"csf\")\n","plt.plot(time, diffusion_tumor, label=\"tumor\")\n","plt.grid()\n","plt.legend()\n","plt.xlabel(\"Time [s]\")\n","plt.ylabel(\"Diffusion\")\n","plt.show()"]}],"metadata":{"colab":{"provenance":[{"file_id":"1uTk3lc-O3xZS-rLDSkOLEYVV1rM0UTDJ","timestamp":1676904915190},{"file_id":"1lnFKubthQBxkz19cY7ScS-S0Hj9vHjEj","timestamp":1676708491940}]},"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.3"}},"nbformat":4,"nbformat_minor":0}
1
+ {"cells":[{"cell_type":"code","execution_count":null,"metadata":{"executionInfo":{"elapsed":40239,"status":"ok","timestamp":1691153098017,"user":{"displayName":"Moritz Zaiss","userId":"13462394581901772323"},"user_tz":-120},"id":"vTjDmgyofjbF","tags":["hide-cell"]},"outputs":[],"source":["!pip install pypulseq==1.3.1.post1 &> /dev/null\n","!pip install MRzeroCore &> /dev/null\n","!wget https://github.com/MRsources/MRzero-Core/raw/main/documentation/playground_mr0/subject05.npz &> /dev/null"]},{"cell_type":"markdown","metadata":{},"source":["(DWI_GRE_2D_seq)=\n","# Diffusion weighted GRE"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":1000},"executionInfo":{"elapsed":34603,"status":"ok","timestamp":1691153132617,"user":{"displayName":"Moritz Zaiss","userId":"13462394581901772323"},"user_tz":-120},"id":"O-_sr6lZjR_n","outputId":"20df9dff-4711-4688-f8a9-126130d8a7d7"},"outputs":[],"source":["import numpy as np\n","# newer numpy versions don't contain this, but pypulseq still relies on it\n","np.int = int\n","np.float = float\n","np.complex = complex\n","\n","import MRzeroCore as mr0\n","import pypulseq as pp\n","import torch\n","import matplotlib.pyplot as plt\n","\n","plt.rcParams['figure.figsize'] = [10, 5]\n","plt.rcParams['figure.dpi'] = 100 # 200 e.g. is really fine, but slower\n","\n","experiment_id = 'mr0_DWI_GRE_2D'\n","\n","# %% S1. SETUP sys\n","\n","# choose the scanner limits\n","system = pp.Opts(max_grad=28,grad_unit='mT/m',max_slew=150,slew_unit='T/m/s',\n"," rf_ringdown_time=20e-6,rf_dead_time=100e-6,adc_dead_time=20e-6,grad_raster_time=50*10e-6)\n","\n","# %% S2. DEFINE the sequence\n","seq = pp.Sequence()\n","\n","# Define FOV and resolution\n","fov = 200e-3\n","slice_thickness = 8e-3\n","Nread = 64 # frequency encoding steps/samples\n","Nphase = 64 # phase encoding steps/samples\n","\n","# Define rf events\n","rf1, _, _ = pp.make_sinc_pulse(\n"," flip_angle=90 * np.pi / 180, duration=1e-3,\n"," slice_thickness=slice_thickness, apodization=0.5, time_bw_product=4,\n"," system=system, return_gz=True\n",")\n","# rf1 = pp.make_block_pulse(flip_angle=90 * np.pi / 180, duration=1e-3, system=system)\n","\n","# Define other gradients and ADC events\n","gx = pp.make_trapezoid(channel='x', flat_area=Nread / fov, flat_time=10e-3, system=system)\n","adc = pp.make_adc(num_samples=Nread, duration=10e-3, phase_offset=0 * np.pi/180, delay=gx.rise_time, system=system)\n","gx_pre = pp.make_trapezoid(channel='x', area=-gx.area / 2, duration=5e-3, system=system)\n","gx_spoil = pp.make_trapezoid(channel='x', area=1.5 * gx.area, duration=2e-3, system=system)\n","\n","# This DWI gradient will produce a b-value around ~700 s/mm^2\n","gx_diff = pp.make_trapezoid(channel='x', area=33 * gx.area, duration=10e-3, system=system)\n","gx_diff_ = pp.make_trapezoid(channel='x', area=-33 * gx.area, duration=10e-3, system=system)\n","\n","# ======\n","# CONSTRUCT SEQUENCE\n","# ======\n","##linear reordering\n","phenc = np.arange(-Nphase // 2, Nphase // 2, 1) / fov\n","permvec =np.arange(0, Nphase, 1)\n","## centric reordering\n","#permvec = sorted(np.arange(len(phenc)), key=lambda x: abs(len(phenc) // 2 - x))\n","## random reordering\n","#perm =np.arange(0, Nphase, 1); permvec = np.random.permutation(perm)\n","\n","phenc_centr = phenc[permvec]\n","\n","for ii in range(0, Nphase): # e.g. -64:63\n","\n"," seq.add_block(rf1)\n"," DWI=True #@param {type: \"boolean\"}\n"," if DWI:\n"," seq.add_block(gx_diff)\n"," seq.add_block(gx_diff_)\n"," else:\n"," seq.add_block(pp.make_delay(20e-3))\n","\n","\n"," gp = pp.make_trapezoid(channel='y', area=phenc_centr[ii], duration=5e-3, system=system)\n","\n"," seq.add_block(gx_pre, gp)\n"," seq.add_block(adc, gx)\n"," gp = pp.make_trapezoid(channel='y', area=-phenc_centr[ii], duration=5e-3, system=system)\n"," seq.add_block(gx_spoil, gp)\n"," if ii < Nphase - 1:\n"," seq.add_block(pp.make_delay(5))\n","\n","\n","# %% S3. CHECK, PLOT and WRITE the sequence as .seq\n","# Check whether the timing of the sequence is correct\n","ok, error_report = seq.check_timing()\n","if ok:\n"," print('Timing check passed successfully')\n","else:\n"," print('Timing check failed. Error listing follows:')\n"," [print(e) for e in error_report]\n","\n","# PLOT sequence\n","sp_adc, t_adc = mr0.util.pulseq_plot(seq, clear=False, figid=(11,12))\n","\n","# Prepare the sequence output for the scanner\n","seq.set_definition('FOV', [fov, fov, slice_thickness])\n","seq.set_definition('Name', 'gre')\n","seq.write(experiment_id + '.seq')\n","\n","\n","# %% S4: SETUP SPIN SYSTEM/object on which we can run the MR sequence external.seq from above\n","sz = [64, 64]\n","# (i) load a phantom object from file\n","obj_p = mr0.VoxelGridPhantom.brainweb('subject05.npz')\n","obj_p = obj_p.interpolate(sz[0], sz[1], 32).slices([15])\n","\n","# (ii) insert rectangular \"Tumor\"\n","# typical brain tumor ADC values are around ~1.5 * 10^-3 mm^2/s,\n","# which lies between GM/WM and CSF (https://www.ncbi.nlm.nih.gov/pmc/articles/PMC3000221)\n","if 1:\n"," # construct tumor border region\n"," for ii in range(15, 25):\n"," for jj in range(15, 25):\n"," obj_p.D[ii, jj] = torch.tensor(2.5)\n","\n"," # construct tumor filling\n"," for ii in range(16, 24):\n"," for jj in range(16, 24):\n"," obj_p.D[ii, jj] = torch.tensor(1.5)\n","\n","\n","# Store PD and B0 for comparison\n","PD = obj_p.PD\n","B0 = obj_p.B0\n","obj_p.plot()\n","# Convert Phantom into simulation data\n","obj_p = obj_p.build()\n","\n","\n","# %% S5:. SIMULATE the external.seq file and add acquired signal to ADC plot\n","# Read in the sequence\n","seq0 = mr0.Sequence.import_file(experiment_id + '.seq')\n","seq0.plot_kspace_trajectory()\n","# Simulate the sequence\n","graph = mr0.compute_graph(seq0, obj_p, 200, 1e-3)\n","signal = mr0.execute_graph(graph, seq0, obj_p, print_progress=False)\n","\n","# PLOT sequence with signal in the ADC subplot\n","plt.close(11);plt.close(12)\n","sp_adc, t_adc = mr0.util.pulseq_plot(seq, clear=False, signal=signal.numpy())\n","\n","\n","# additional noise as simulation is perfect\n","signal += 1e-4 * np.random.randn(signal.shape[0], 2).view(np.complex128)\n","\n","\n","# %% S6: MR IMAGE RECON of signal ::: #####################################\n","fig = plt.figure() # fig.clf()\n","plt.subplot(411)\n","plt.title('ADC signal')\n","kspace = torch.reshape((signal), (Nphase, Nread)).clone().t()\n","plt.plot(torch.real(signal), label='real')\n","plt.plot(torch.imag(signal), label='imag')\n","\n","ipermvec = np.argsort(permvec)\n","\n","kspace=kspace[:,ipermvec]\n","# this adds ticks at the correct position szread\n","major_ticks = np.arange(0, Nphase * Nread, Nread)\n","ax = plt.gca()\n","ax.set_xticks(major_ticks)\n","ax.grid()\n","\n","# fftshift\n","spectrum = torch.fft.fftshift(kspace)\n","# FFT\n","space = torch.fft.fft2(spectrum)\n","# fftshift\n","space = torch.fft.ifftshift(space)\n","\n","\n","plt.subplot(345)\n","plt.title('k-space')\n","plt.imshow(np.abs(kspace.numpy()))\n","plt.subplot(349)\n","plt.title('log. k-space')\n","plt.imshow(np.log(np.abs(kspace.numpy())))\n","\n","plt.subplot(346)\n","plt.title('FFT-magnitude')\n","plt.imshow(np.abs(space.numpy()))\n","plt.colorbar()\n","plt.subplot(3, 4, 10)\n","plt.title('FFT-phase')\n","plt.imshow(np.angle(space.numpy()), vmin=-np.pi, vmax=np.pi)\n","plt.colorbar()\n","\n","# % compare with original phantom obj_p.PD\n","plt.subplot(348)\n","plt.title('phantom PD')\n","plt.imshow(PD)\n","plt.subplot(3, 4, 12)\n","plt.title('phantom B0')\n","plt.imshow(B0)"]},{"cell_type":"code","execution_count":null,"metadata":{"colab":{"base_uri":"https://localhost:8080/","height":465},"executionInfo":{"elapsed":1130,"status":"ok","timestamp":1689757017587,"user":{"displayName":"Jonathan Endres","userId":"15767859839481375594"},"user_tz":-120},"id":"MNxkdhaMzLiM","outputId":"253e0812-04ed-4d60-850e-8b17707dd1bb"},"outputs":[],"source":["# Exemplary calculation of b-values and diffusion of simulated tissues for a single repetition\n","\n","traj = seq0[10].gradm.cumsum(0)[:-2]\n","dt = seq0[10].event_time[:-2]\n","time = dt.cumsum(0)\n","\n","k2 = traj[:, :3]\n","k1 = torch.zeros_like(k2)\n","k1[1:, :] = k2[:-1, :]\n","# Integrate over each event to get b factor (lin. interp. grad)\n","b = 1/3 * dt * (k1**2 + k1*k2 + k2**2).sum(1) # s / m^2\n","\n","# typical ADC values: 10^-3 mm^2/s = 10^-9 m^2 / s\n","# typical DWI b-values: up to 4000 s / mm^2\n","\n","diffusion_gm = torch.exp(-1e-9 * 0.83 * torch.cumsum(b, 0)[:, None])\n","diffusion_wm = torch.exp(-1e-9 * 0.65 * torch.cumsum(b, 0)[:, None])\n","diffusion_csf = torch.exp(-1e-9 * 3.19 * torch.cumsum(b, 0)[:, None])\n","diffusion_tumor = torch.exp(-1e-9 * 1.5 * torch.cumsum(b, 0)[:, None])\n","\n","\n","plt.figure()\n","ax1 = plt.subplot(211)\n","plt.plot(time, b.cumsum(0) * 1e-6)\n","plt.ylabel(\"b-value [s / mm^2]\")\n","plt.grid()\n","plt.tick_params(\"x\", labelbottom=False)\n","plt.subplot(212, sharex=ax1)\n","plt.plot(time, diffusion_gm, label=\"gm\")\n","plt.plot(time, diffusion_wm, label=\"wm\")\n","plt.plot(time, diffusion_csf, label=\"csf\")\n","plt.plot(time, diffusion_tumor, label=\"tumor\")\n","plt.grid()\n","plt.legend()\n","plt.xlabel(\"Time [s]\")\n","plt.ylabel(\"Diffusion\")\n","plt.show()"]}],"metadata":{"colab":{"provenance":[{"file_id":"1uTk3lc-O3xZS-rLDSkOLEYVV1rM0UTDJ","timestamp":1676904915190},{"file_id":"1lnFKubthQBxkz19cY7ScS-S0Hj9vHjEj","timestamp":1676708491940}]},"kernelspec":{"display_name":"Python 3","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.11.3"}},"nbformat":4,"nbformat_minor":0}