mrzerocore 0.3.1__tar.gz → 0.3.2__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 (96) hide show
  1. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/CHANGELOG.md +4 -0
  2. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/Cargo.lock +1 -1
  3. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/Cargo.toml +1 -1
  4. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/PKG-INFO +1 -1
  5. MRzeroCore-0.3.2/documentation/playground/templates/binary_masks.ipynb +85 -0
  6. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/phantom/sim_data.py +16 -4
  7. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/phantom/voxel_grid_phantom.py +97 -24
  8. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/util.py +71 -0
  9. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/.github/workflows/pypi_publish.yml +0 -0
  10. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/.gitignore +0 -0
  11. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/.readthedocs.yaml +0 -0
  12. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/EULA.txt +0 -0
  13. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/LICENSE +0 -0
  14. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/README.md +0 -0
  15. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/_config.yml +0 -0
  16. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/_toc.yml +0 -0
  17. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/api/phantom.md +0 -0
  18. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/api/reco.md +0 -0
  19. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/api/sequence.md +0 -0
  20. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/api/simulation/isochromat_sim.md +0 -0
  21. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/api/simulation/pdg_sim.md +0 -0
  22. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/api/simulation.md +0 -0
  23. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/api/util.md +0 -0
  24. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/api.md +0 -0
  25. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/intro.md +0 -0
  26. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/logo.png +0 -0
  27. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/phantom_generation.md +0 -0
  28. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground/generated/seqs/FLASH.ipynb +0 -0
  29. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground/quantified_brain.npz +0 -0
  30. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground/templates/generate.py +0 -0
  31. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground/templates/seqs.ipynb +0 -0
  32. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground/templates/template_A.ipynb +0 -0
  33. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/AdjDataUser2gB0_transversal_0.08moving_average.mat +0 -0
  34. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/flash.ipynb +0 -0
  35. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/flash_DWI.ipynb +0 -0
  36. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr00_FLASH_2D_ernstAngle_opt.ipynb +0 -0
  37. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_CS_cartesian_seq.ipynb +0 -0
  38. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_CS_radial_seq.ipynb +0 -0
  39. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_DREAM_STE_seq.ipynb +0 -0
  40. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_DREAM_STID_seq.ipynb +0 -0
  41. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_DWI_GRE_2D_seq.ipynb +0 -0
  42. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_EPI_2D_seq.ipynb +0 -0
  43. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_FID_seq.ipynb +0 -0
  44. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_FLASH_2D_seq.ipynb +0 -0
  45. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_GRE_to_FLASH.ipynb +0 -0
  46. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_RARE_2D_seq.ipynb +0 -0
  47. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_SE_CPMG_seq.ipynb +0 -0
  48. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_STE_3pulses_5echoes_seq.ipynb +0 -0
  49. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_bSSFP_2D_seq.ipynb +0 -0
  50. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_burst_TSE.ipynb +0 -0
  51. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_opt_FLASH_2D_IR_Fit_T1.ipynb +0 -0
  52. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_opt_FLASH_2D_IR_voxelNN_T1.ipynb +0 -0
  53. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_pypulseq_exmpls_seq.ipynb +0 -0
  54. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/mr0_upload_seq.ipynb +0 -0
  55. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/numerical_brain_cropped.mat +0 -0
  56. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/overview.md +0 -0
  57. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/ptx_phantom.p +0 -0
  58. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/pulseq_flash.ipynb +0 -0
  59. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/pulseq_rf_shim.ipynb +0 -0
  60. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/pulseq_sim_pTx.ipynb +0 -0
  61. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/seqs/flash pTx CP.seq +0 -0
  62. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/seqs/flash pTx EP.seq +0 -0
  63. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/seqs/flash pTx QM.seq +0 -0
  64. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/playground_mr0/subject05.npz +0 -0
  65. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/documentation/requirements.txt +0 -0
  66. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/pyproject.toml +0 -0
  67. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/__init__.py +0 -0
  68. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/phantom/brainweb/__init__.py +0 -0
  69. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/phantom/brainweb/brainweb_data.json +0 -0
  70. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/phantom/brainweb/brainweb_data_sources.txt +0 -0
  71. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/phantom/brainweb/output/.gitkeep +0 -0
  72. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/phantom/custom_voxel_phantom.py +0 -0
  73. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/pulseq/exporter.py +0 -0
  74. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/pulseq/exporter_v2.py +0 -0
  75. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/pulseq/helpers.py +0 -0
  76. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/pulseq/pulseq_exporter.py +0 -0
  77. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/pulseq/pulseq_loader/__init__.py +0 -0
  78. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/pulseq/pulseq_loader/adc.py +0 -0
  79. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/pulseq/pulseq_loader/helpers.py +0 -0
  80. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/pulseq/pulseq_loader/pulse.py +0 -0
  81. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/pulseq/pulseq_loader/pulseq_file/__init__.py +0 -0
  82. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/pulseq/pulseq_loader/pulseq_file/adc.py +0 -0
  83. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/pulseq/pulseq_loader/pulseq_file/block.py +0 -0
  84. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/pulseq/pulseq_loader/pulseq_file/definitons.py +0 -0
  85. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/pulseq/pulseq_loader/pulseq_file/gradient.py +0 -0
  86. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/pulseq/pulseq_loader/pulseq_file/helpers.py +0 -0
  87. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/pulseq/pulseq_loader/pulseq_file/rf.py +0 -0
  88. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/pulseq/pulseq_loader/pulseq_file/trap.py +0 -0
  89. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/pulseq/pulseq_loader/spoiler.py +0 -0
  90. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/reconstruction.py +0 -0
  91. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/sequence.py +0 -0
  92. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/simulation/isochromat_sim.py +0 -0
  93. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/simulation/main_pass.py +0 -0
  94. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/python/MRzeroCore/simulation/pre_pass.py +0 -0
  95. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/src/lib.rs +0 -0
  96. {MRzeroCore-0.3.1 → MRzeroCore-0.3.2}/src/pre_pass.rs +0 -0
@@ -1,3 +1,7 @@
1
+ - 0.3.2
2
+ - Added tissue maps to phantom and plotting (#68)
3
+ - Fixed `.plot()`ting of 3D VoxelGridPhantom, added slice parameter
4
+ - Added `simulate_2D` to util for easy simulation of pypulseq sequences
1
5
  - 0.3.1
2
6
  - WIP .dsv support with pydisseqt 0.1.5
3
7
  - changed type annotation in util.imshow for python 3.9 compatibility
@@ -59,7 +59,7 @@ dependencies = [
59
59
 
60
60
  [[package]]
61
61
  name = "mrzero_core"
62
- version = "0.3.1"
62
+ version = "0.3.2"
63
63
  dependencies = [
64
64
  "num-complex",
65
65
  "pyo3",
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "mrzero_core"
3
- version = "0.3.1"
3
+ version = "0.3.2"
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.3
2
2
  Name: MRzeroCore
3
- Version: 0.3.1
3
+ Version: 0.3.2
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -0,0 +1,85 @@
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": null,
6
+ "metadata": {},
7
+ "outputs": [],
8
+ "source": [
9
+ "import MRzeroCore as mr0\n",
10
+ "import matplotlib.pyplot as plt"
11
+ ]
12
+ },
13
+ {
14
+ "cell_type": "markdown",
15
+ "metadata": {},
16
+ "source": [
17
+ "# Generate phantoms with tissue masks"
18
+ ]
19
+ },
20
+ {
21
+ "cell_type": "code",
22
+ "execution_count": null,
23
+ "metadata": {},
24
+ "outputs": [],
25
+ "source": [
26
+ "\n",
27
+ "mr0.generate_brainweb_phantoms(\"bw_phantoms\" , \"3T\")"
28
+ ]
29
+ },
30
+ {
31
+ "cell_type": "code",
32
+ "execution_count": null,
33
+ "metadata": {},
34
+ "outputs": [],
35
+ "source": [
36
+ "\n",
37
+ "phantom = mr0.VoxelGridPhantom.brainweb(\"bw_phantoms/subject04_3T.npz\")\n",
38
+ "\n",
39
+ "obj = phantom.interpolate(256, 256, 32).slices([16])\n",
40
+ "obj.plot(plot_masks=True)\n",
41
+ "phantom = obj.build()"
42
+ ]
43
+ },
44
+ {
45
+ "cell_type": "code",
46
+ "execution_count": null,
47
+ "metadata": {},
48
+ "outputs": [],
49
+ "source": [
50
+ "\n",
51
+ "# get tissue masks from phantom\n",
52
+ "masks = phantom.tissue_masks\n",
53
+ "\n",
54
+ "#plot tissue masks\n",
55
+ "plt.figure()\n",
56
+ "for key, value in masks.items():\n",
57
+ " plt.imshow(value[:, :, 0])\n",
58
+ " plt.colorbar()\n",
59
+ " plt.title(key)\n",
60
+ " plt.show()"
61
+ ]
62
+ }
63
+ ],
64
+ "metadata": {
65
+ "kernelspec": {
66
+ "display_name": "mrzero_source",
67
+ "language": "python",
68
+ "name": "python3"
69
+ },
70
+ "language_info": {
71
+ "codemirror_mode": {
72
+ "name": "ipython",
73
+ "version": 3
74
+ },
75
+ "file_extension": ".py",
76
+ "mimetype": "text/x-python",
77
+ "name": "python",
78
+ "nbconvert_exporter": "python",
79
+ "pygments_lexer": "ipython3",
80
+ "version": "3.10.14"
81
+ }
82
+ },
83
+ "nbformat": 4,
84
+ "nbformat_minor": 2
85
+ }
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
- from typing import Callable, Any
2
+ from typing import Callable, Any, Optional, Dict
3
3
  import torch
4
4
  from numpy import pi
5
5
 
@@ -66,7 +66,8 @@ class SimData:
66
66
  dephasing_func: Callable[[torch.Tensor, torch.Tensor], torch.Tensor],
67
67
  recover_func: Callable[[SimData], Any] | None = None,
68
68
  phantom_motion=None,
69
- voxel_motion=None
69
+ voxel_motion=None,
70
+ tissue_masks: Optional[Dict[str,torch.Tensor]] = None,
70
71
  ) -> None:
71
72
  """Create a :class:`SimData` instance based on the given tensors.
72
73
 
@@ -94,6 +95,7 @@ class SimData:
94
95
  self.D = D.clamp(min=1e-6)
95
96
  self.B0 = B0.clone()
96
97
  self.B1 = B1.clone()
98
+ self.tissue_masks = tissue_masks
97
99
  self.coil_sens = coil_sens.clone()
98
100
  self.size = size.clone()
99
101
  self.voxel_pos = voxel_pos.clone()
@@ -126,7 +128,12 @@ class SimData:
126
128
  self.dephasing_func,
127
129
  self.recover_func,
128
130
  self.phantom_motion,
129
- self.voxel_motion
131
+ self.voxel_motion,
132
+ tissue_masks=(
133
+ {k: v.cuda() for k, v in self.tissue_masks.items()}
134
+ if self.tissue_masks is not None
135
+ else None
136
+ ),
130
137
  )
131
138
 
132
139
  def cpu(self) -> SimData:
@@ -150,7 +157,12 @@ class SimData:
150
157
  self.dephasing_func,
151
158
  self.recover_func,
152
159
  self.phantom_motion,
153
- self.voxel_motion
160
+ self.voxel_motion,
161
+ tissue_masks=(
162
+ {k: v.cpu() for k, v in self.tissue_masks.items()}
163
+ if self.tissue_masks is not None
164
+ else None
165
+ ),
154
166
  )
155
167
 
156
168
  @property
@@ -1,5 +1,5 @@
1
1
  from __future__ import annotations
2
- from typing import Literal
2
+ from typing import Literal, Optional, Dict
3
3
  from warnings import warn
4
4
  from scipy import io
5
5
  import numpy as np
@@ -84,6 +84,8 @@ class VoxelGridPhantom:
84
84
  (coil_count, sx, sy, sz) tensor of coil sensitivities
85
85
  size : torch.Tensor
86
86
  Size of the data, in meters.
87
+ tissue_masks : Dict[str, torch.Tensor]
88
+ Segmentation masks for different tissues. The keys are the tissue names
87
89
  """
88
90
 
89
91
  def __init__(
@@ -97,6 +99,7 @@ class VoxelGridPhantom:
97
99
  B1: torch.Tensor,
98
100
  coil_sens: torch.Tensor,
99
101
  size: torch.Tensor,
102
+ tissue_masks: Optional[Dict[str,torch.Tensor]] = None,
100
103
  ) -> None:
101
104
  """Set the phantom attributes to the provided parameters.
102
105
 
@@ -110,6 +113,7 @@ class VoxelGridPhantom:
110
113
  self.D = torch.as_tensor(D, dtype=torch.float32)
111
114
  self.B0 = torch.as_tensor(B0, dtype=torch.float32)
112
115
  self.B1 = torch.as_tensor(B1, dtype=torch.float32)
116
+ self.tissue_masks = tissue_masks
113
117
  self.coil_sens = torch.as_tensor(coil_sens, dtype=torch.float32)
114
118
  self.size = torch.as_tensor(size, dtype=torch.float32)
115
119
 
@@ -167,7 +171,8 @@ class VoxelGridPhantom:
167
171
  voxel_pos,
168
172
  torch.as_tensor(shape, device=self.PD.device) / 2 / self.size,
169
173
  dephasing_func,
170
- recover_func=lambda data: recover(mask, data)
174
+ recover_func=lambda data: recover(mask, data),
175
+ tissue_masks=self.tissue_masks
171
176
  )
172
177
 
173
178
  @classmethod
@@ -193,7 +198,12 @@ class VoxelGridPhantom:
193
198
  size = torch.tensor(data['FOV'], dtype=torch.float)
194
199
  except KeyError:
195
200
  size = torch.tensor([0.192, 0.192, 0.192])
196
-
201
+
202
+ tissue_masks = {
203
+ key: torch.tensor(mask)
204
+ for key, mask in data.items()
205
+ if key.startswith("tissue_")
206
+ }
197
207
  if B1.ndim == 3:
198
208
  # Add coil-dimension
199
209
  B1 = B1[None, ...]
@@ -201,6 +211,7 @@ class VoxelGridPhantom:
201
211
  return cls(
202
212
  PD, T1, T2, T2dash, D, B0, B1,
203
213
  torch.ones(1, *PD.shape), size,
214
+ tissue_masks=tissue_masks
204
215
  )
205
216
 
206
217
  @classmethod
@@ -303,6 +314,9 @@ class VoxelGridPhantom:
303
314
  select(self.B1).unsqueeze(0),
304
315
  select(self.coil_sens).unsqueeze(0),
305
316
  self.size.clone(),
317
+ tissue_masks={
318
+ key: mask[..., slices] for key, mask in self.tissue_masks.items()
319
+ },
306
320
  )
307
321
 
308
322
  def scale_fft(self, x: int, y: int, z: int) -> VoxelGridPhantom:
@@ -342,6 +356,7 @@ class VoxelGridPhantom:
342
356
  scale(self.B1.squeeze()).unsqueeze(0),
343
357
  scale(self.coil_sens.squeeze()).unsqueeze(0),
344
358
  self.size.clone(),
359
+ tissue_masks= {key: scale(mask) for key, mask in self.tissue_masks.items()}
345
360
  )
346
361
 
347
362
  def interpolate(self, x: int, y: int, z: int) -> VoxelGridPhantom:
@@ -379,6 +394,18 @@ class VoxelGridPhantom:
379
394
  output[i, ...] = resample(tensor[i, ...])
380
395
  return output
381
396
 
397
+ def resample_masks(tensors: Dict) -> Dict:
398
+ output = {}
399
+ for key, mask in tensors.items():
400
+ # Interpolate the mask
401
+ interpolated_mask = torch.nn.functional.interpolate(
402
+ mask[None, None, ...].float(), size=(x, y, z), mode='area'
403
+ )[0, 0, ...]
404
+ # Store the result
405
+ output[key] = interpolated_mask
406
+
407
+ return output
408
+
382
409
  return VoxelGridPhantom(
383
410
  resample(self.PD),
384
411
  resample(self.T1),
@@ -389,14 +416,32 @@ class VoxelGridPhantom:
389
416
  resample_multicoil(self.B1),
390
417
  resample_multicoil(self.coil_sens),
391
418
  self.size.clone(),
419
+ tissue_masks=resample_masks(self.tissue_masks)
392
420
  )
393
421
 
394
- def plot(self) -> None:
395
- """Print and plot all data stored in this phantom."""
422
+ def plot(self, plot_masks=False, plot_slice="center") -> None:
423
+ """
424
+ Print and plot all data stored in this phantom.
425
+
426
+ Parameters
427
+ ----------
428
+ plot_masks : bool
429
+ Plot tissue masks stored in this phantom (assumes they exist)
430
+ slice : str | int
431
+ If int, the specified slice is plotted. "center" plots the center
432
+ slice and "all" plots all slices as a grid.
433
+ """
396
434
  print("VoxelGridPhantom")
397
435
  print(f"size = {self.size}")
398
436
  # Center slice
399
- s = self.PD.shape[2] // 2
437
+ if plot_slice == "center":
438
+ s = self.PD.shape[2] // 2
439
+ elif plot_slice == "all":
440
+ s = slice(None)
441
+ elif plot_slice is int:
442
+ s = plot_slice
443
+ else:
444
+ raise ValueError("expected plot_slice to be 'all', 'center' or an integer")
400
445
  # Warn if we only print a part of all data
401
446
  if self.coil_sens.shape[0] > 1:
402
447
  print(f"Plotting 1st of {self.coil_sens.shape[0]} coil sens maps")
@@ -405,40 +450,68 @@ class VoxelGridPhantom:
405
450
  if self.PD.shape[2] > 1:
406
451
  print(f"Plotting slice {s} / {self.PD.shape[2]}")
407
452
 
408
- plt.figure(figsize=(12, 10))
409
- plt.subplot(331)
410
- plt.title("PD")
453
+ # Determine the number of subplots needed
454
+ num_plots = 9 # Base number of plots without masks
455
+ if plot_masks:
456
+ num_masks = len(self.tissue_masks)
457
+ num_plots += num_masks
458
+
459
+ # Calculate the grid size based on the number of plots
460
+ cols = 3
461
+ rows = int(np.ceil(num_plots / cols))
411
462
 
412
- imshow(self.PD, vmin=0)
463
+ plt.figure(figsize=(12, rows * 3))
464
+
465
+ # Plot the basic maps
466
+ plt.subplot(rows, cols, 1)
467
+ plt.title("PD")
468
+ imshow(self.PD[:, :, s], vmin=0)
413
469
  plt.colorbar()
414
- plt.subplot(332)
470
+
471
+ plt.subplot(rows, cols, 2)
415
472
  plt.title("T1")
416
- imshow(self.T1, vmin=0)
473
+ imshow(self.T1[:, :, s], vmin=0)
417
474
  plt.colorbar()
418
- plt.subplot(333)
475
+
476
+ plt.subplot(rows, cols, 3)
419
477
  plt.title("T2")
420
- imshow(self.T2, vmin=0)
478
+ imshow(self.T2[:, :, s], vmin=0)
421
479
  plt.colorbar()
422
- plt.subplot(334)
480
+
481
+ plt.subplot(rows, cols, 4)
423
482
  plt.title("T2'")
424
- imshow(self.T2dash, vmin=0)
483
+ imshow(self.T2dash[:, :, s], vmin=0)
425
484
  plt.colorbar()
426
- plt.subplot(335)
485
+
486
+ plt.subplot(rows, cols, 5)
427
487
  plt.title("D")
428
- imshow(self.D, vmin=0)
488
+ imshow(self.D[:, :, s], vmin=0)
429
489
  plt.colorbar()
430
- plt.subplot(337)
490
+
491
+ plt.subplot(rows, cols, 7)
431
492
  plt.title("B0")
432
- imshow(self.B0)
493
+ imshow(self.B0[:, :, s])
433
494
  plt.colorbar()
434
- plt.subplot(338)
495
+
496
+ plt.subplot(rows, cols, 8)
435
497
  plt.title("B1")
436
- imshow(self.B1[0, ...])
498
+ imshow(self.B1[0, :, :, s])
437
499
  plt.colorbar()
438
- plt.subplot(339)
500
+
501
+ plt.subplot(rows, cols, 9)
439
502
  plt.title("coil sens")
440
- imshow(self.coil_sens[0, ...], vmin=0)
503
+ imshow(self.coil_sens[0, :, :, s], vmin=0)
441
504
  plt.colorbar()
505
+
506
+ # Conditionally plot masks if plot_masks is True
507
+ if plot_masks:
508
+ for i, (key, mask) in enumerate(self.tissue_masks.items()):
509
+ plt.subplot(rows, cols, 10 + i)
510
+ plt.title(key)
511
+ imshow(mask)
512
+ plt.colorbar()
513
+
514
+ plt.tight_layout()
442
515
  plt.show()
443
516
 
444
517
  def plot3D(self, data2print: int = 0) -> None:
@@ -342,3 +342,74 @@ def imshow(data: Union[np.ndarray, torch.Tensor], *args, **kwargs):
342
342
  data[x:x+tmp.shape[0], y:y+tmp.shape[1]] = tmp[:, :, i]
343
343
 
344
344
  plt.imshow(data.T, *args, origin="lower", **kwargs)
345
+
346
+
347
+ def simulate_2d(seq, sim_size=None, noise_level=0, dB0=0, B0_scale=1, B0_polynomial=None):
348
+ # Copied and modified from https://github.com/pulseq/MR-Physics-with-Pulseq
349
+ import urllib
350
+ import MRzeroCore as mr0
351
+ # Download .mat file for phantom if necessary
352
+ sim_url = 'https://github.com/mzaiss/MRTwin_pulseq/raw/mr0-core/data/numerical_brain_cropped.mat'
353
+ sim_filename = os.path.basename(sim_url)
354
+ if not os.path.exists(sim_filename):
355
+ print(f'Downloading {sim_url}...')
356
+ urllib.request.urlretrieve(sim_url, sim_filename)
357
+
358
+ # If seq is not a str, assume it is a sequence object and write to a temporary sequence file
359
+ if not isinstance(seq, str):
360
+ seq.write('tmp.seq')
361
+ seq_filename = 'tmp.seq'
362
+ adc_samples = int(seq.adc_library.data[1][0])
363
+ else:
364
+ seq_filename = seq
365
+ tmp_seq = pp.Sequence()
366
+ tmp_seq.read(seq_filename)
367
+ adc_samples = int(tmp_seq.adc_library.data[1][0])
368
+
369
+ # Create phantom from .mat file
370
+ obj_p = mr0.VoxelGridPhantom.load_mat(sim_filename)
371
+ if sim_size is not None:
372
+ obj_p = obj_p.interpolate(sim_size[0], sim_size[1], 1)
373
+
374
+ # Manipulate loaded data
375
+ obj_p.B0 *= B0_scale
376
+ obj_p.B0 += dB0
377
+
378
+ if B0_polynomial is not None:
379
+ x,y = torch.meshgrid(torch.linspace(-1,1,obj_p.PD.shape[0]),torch.linspace(-1,1,obj_p.PD.shape[1]))
380
+
381
+ obj_p.B0 = B0_polynomial[0]
382
+ if len(B0_polynomial) > 1:
383
+ obj_p.B0 += x * B0_polynomial[1]
384
+ if len(B0_polynomial) > 2:
385
+ obj_p.B0 += y * B0_polynomial[2]
386
+ if len(B0_polynomial) > 3:
387
+ obj_p.B0 += x*x * B0_polynomial[3]
388
+ if len(B0_polynomial) > 4:
389
+ obj_p.B0 += y*y * B0_polynomial[4]
390
+ if len(B0_polynomial) > 5:
391
+ obj_p.B0 += x*y * B0_polynomial[5]
392
+
393
+ obj_p.B0 = obj_p.B0[:,:,None]
394
+
395
+ obj_p.D *= 0
396
+
397
+ # Convert Phantom into simulation data
398
+ obj_p = obj_p.build()
399
+
400
+ # MR zero simulation
401
+ seq0 = mr0.Sequence.import_file(seq_filename)
402
+
403
+ # Remove temporary sequence file
404
+ if not isinstance(seq, str):
405
+ os.unlink('tmp.seq')
406
+
407
+ # Simulate the sequence
408
+ graph = mr0.compute_graph(seq0, obj_p, 200, 1e-5)
409
+ signal = mr0.execute_graph(graph, seq0, obj_p)
410
+
411
+ # Add noise to the simulated data
412
+ if noise_level > 0:
413
+ signal += noise_level * torch.randn(*signal.shape, dtype=signal.dtype)
414
+
415
+ return signal
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes