MRPhantom 2.2.0__tar.gz → 2.2.1__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 (27) hide show
  1. {mrphantom-2.2.0 → mrphantom-2.2.1/MRPhantom.egg-info}/PKG-INFO +1 -1
  2. {mrphantom-2.2.0 → mrphantom-2.2.1}/MRPhantom.egg-info/SOURCES.txt +5 -1
  3. {mrphantom-2.2.0/MRPhantom.egg-info → mrphantom-2.2.1}/PKG-INFO +1 -1
  4. mrphantom-2.2.1/mrphantom_src/Function.py +372 -0
  5. mrphantom-2.2.1/mrphantom_src/__init__.py +7 -0
  6. {mrphantom-2.2.0 → mrphantom-2.2.1}/mrphantom_src/ext/main.cpp +15 -13
  7. {mrphantom-2.2.0 → mrphantom-2.2.1}/mrphantom_src/ext/slime.cpp +31 -34
  8. {mrphantom-2.2.0 → mrphantom-2.2.1}/mrphantom_src/ext/slime.h +2 -2
  9. mrphantom-2.2.1/mrphantom_src/nmr_para.xml +42 -0
  10. mrphantom-2.2.1/mrphantom_src/utility.py +12 -0
  11. {mrphantom-2.2.0 → mrphantom-2.2.1}/pyproject.toml +1 -1
  12. mrphantom-2.2.1/test/test.py +15 -0
  13. mrphantom-2.2.1/test/test_Simple.py +22 -0
  14. mrphantom-2.2.0/mrphantom_src/Function.py +0 -436
  15. mrphantom-2.2.0/mrphantom_src/__init__.py +0 -5
  16. {mrphantom-2.2.0 → mrphantom-2.2.1}/LICENSE +0 -0
  17. {mrphantom-2.2.0 → mrphantom-2.2.1}/MANIFEST.in +0 -0
  18. {mrphantom-2.2.0 → mrphantom-2.2.1}/MRPhantom.egg-info/dependency_links.txt +0 -0
  19. {mrphantom-2.2.0 → mrphantom-2.2.1}/MRPhantom.egg-info/requires.txt +0 -0
  20. {mrphantom-2.2.0 → mrphantom-2.2.1}/MRPhantom.egg-info/top_level.txt +0 -0
  21. {mrphantom-2.2.0 → mrphantom-2.2.1}/README.md +0 -0
  22. {mrphantom-2.2.0 → mrphantom-2.2.1}/mrphantom_src/__pycache__/Function.cpython-312.pyc +0 -0
  23. {mrphantom-2.2.0 → mrphantom-2.2.1}/mrphantom_src/__pycache__/Type.cpython-312.pyc +0 -0
  24. {mrphantom-2.2.0 → mrphantom-2.2.1}/mrphantom_src/__pycache__/Utility.cpython-312.pyc +0 -0
  25. {mrphantom-2.2.0 → mrphantom-2.2.1}/mrphantom_src/__pycache__/__init__.cpython-312.pyc +0 -0
  26. {mrphantom-2.2.0 → mrphantom-2.2.1}/setup.cfg +0 -0
  27. {mrphantom-2.2.0 → mrphantom-2.2.1}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MRPhantom
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: Volumetric dynamic MRI Phantom of a Slime with respiratory and cardiac motion, and M0, phase, T1, T2, B0, coil sensitivity maps, boosted by a parallel C-API Backend.
5
5
  Author-email: Ryan <ryan_shanghaitech@proton.me>
6
6
  License-Expression: MIT
@@ -12,10 +12,14 @@ MRPhantom.egg-info/requires.txt
12
12
  MRPhantom.egg-info/top_level.txt
13
13
  mrphantom_src/Function.py
14
14
  mrphantom_src/__init__.py
15
+ mrphantom_src/nmr_para.xml
16
+ mrphantom_src/utility.py
15
17
  mrphantom_src/__pycache__/Function.cpython-312.pyc
16
18
  mrphantom_src/__pycache__/Type.cpython-312.pyc
17
19
  mrphantom_src/__pycache__/Utility.cpython-312.pyc
18
20
  mrphantom_src/__pycache__/__init__.cpython-312.pyc
19
21
  mrphantom_src/ext/main.cpp
20
22
  mrphantom_src/ext/slime.cpp
21
- mrphantom_src/ext/slime.h
23
+ mrphantom_src/ext/slime.h
24
+ test/test.py
25
+ test/test_Simple.py
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: MRPhantom
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: Volumetric dynamic MRI Phantom of a Slime with respiratory and cardiac motion, and M0, phase, T1, T2, B0, coil sensitivity maps, boosted by a parallel C-API Backend.
5
5
  Author-email: Ryan <ryan_shanghaitech@proton.me>
6
6
  License-Expression: MIT
@@ -0,0 +1,372 @@
1
+ from . import ext
2
+ from numpy import *
3
+ from numpy.fft import fftn, ifftn, fftshift, ifftshift
4
+ from numpy.typing import NDArray
5
+ from typing import *
6
+ from scipy.signal.windows import gaussian
7
+ from scipy.ndimage import gaussian_filter
8
+ from . import dictNmrPara, lstTissue
9
+
10
+ def genPhant(shape:Tuple, ampRes:float=0, ampCar:float=0) -> NDArray: # call C++ backend to generate a phantom
11
+ """
12
+ generate a phantom in Enum type
13
+
14
+ Args:
15
+ shape: shape of the phantom (independent from FOV)
16
+ ampRes: respiratory motion amplitude
17
+ ampCar: cardiac motion amplitude
18
+
19
+ Returns:
20
+ NDArray contains elements in `Tissue` enum type
21
+ """
22
+ nAx = len(shape)
23
+ shape = (1,)*(3-len(shape)) + shape
24
+ return ext.genPhant(nAx, *shape, ampRes, ampCar)
25
+
26
+ def LPF(arr:NDArray, std:float) -> NDArray:
27
+ lstWind = []
28
+ for l in arr.shape:
29
+ lstWind.append(gaussian(l, 1/(2*pi*std)))
30
+ wind = ones(arr.shape)
31
+ for a, w in enumerate(lstWind):
32
+ shape = [1] * arr.ndim
33
+ shape[a] = len(w)
34
+ wind *= w.reshape(shape)
35
+
36
+ arr = fftshift(fftn(ifftshift(arr)))
37
+ arr *= wind
38
+ arr = fftshift(ifftn(ifftshift(arr)))
39
+ return arr
40
+
41
+ def genPhMap(shape:Tuple, mean:int|float|None=None, std:int|float=pi/16) -> NDArray:
42
+ """
43
+ generate random phase map
44
+
45
+ Args:
46
+ shape: shape of the phantom (independent from FOV)
47
+ mean: mean of the noise
48
+ std: std of the noise
49
+
50
+ Returns:
51
+ smooth complex noise with unity magnitude
52
+ """
53
+ if mean is None: mean = random.uniform(-pi,pi)
54
+ mapPh = random.uniform(-pi, pi, shape)
55
+ mapPh = LPF(mapPh, 1/4).real
56
+ # normalize
57
+ mapPh -= mapPh.mean(); mapPh = asarray(mapPh)
58
+ mapPh /= mapPh.std()
59
+ mapPh *= std
60
+ mapPh += mean
61
+ # convert to rotation factor
62
+ mapPh = exp(1j*mapPh)
63
+ mapPh = mapPh/abs(mapPh)
64
+ return mapPh
65
+
66
+ def genB0Map(shape:Tuple, mean:int|float=0, std:int|float=1e-6*(2*pi*42.58e6*3)) -> NDArray:
67
+ """
68
+ generate random B0 map
69
+
70
+ Args:
71
+ shape: shape of the phantom (independent from FOV)
72
+ mean: mean of the noise
73
+ std: std of the noise
74
+
75
+ Returns:
76
+ smooth random noise in `rad/s`
77
+ """
78
+ mapB0 = random.uniform(-1, 1, shape)
79
+ mapB0 = LPF(mapB0, 1/4).real
80
+ # normalize
81
+ mapB0 -= mapB0.mean(); mapB0 = asarray(mapB0)
82
+ mapB0 /= mapB0.std()
83
+ mapB0 *= std
84
+ mapB0 += mean
85
+ return mapB0
86
+
87
+ def genCsm(shape:Tuple, nCh:int=12, mean:int|float|None=None, std:int|float=pi/16) -> NDArray:
88
+ """
89
+ generate random coil sensitivity map
90
+
91
+ Args:
92
+ shape: shape of the phantom (independent from FOV)
93
+ nCh: number of coils
94
+ mean: mean of the noise
95
+ std: std of the noise
96
+
97
+ Returns:
98
+ complex smooth and inhomogeneous map
99
+ """
100
+ if mean is None: mean = random.uniform(-pi,pi)
101
+ nAx = len(shape)
102
+ mapC = zeros([nCh,*shape], dtype=complex128)
103
+ arrCoor = meshgrid\
104
+ (
105
+ *(linspace(-0.5,0.5,s,0) for s in shape),
106
+ indexing="ij"
107
+ ); arrCoor = array(arrCoor).transpose(*arange(1,nAx+1), 0)
108
+ arrTht = linspace(0,2*pi,nCh,0)
109
+ arrCoorCoil = zeros([nCh,nAx], dtype=float64)
110
+ arrCoorCoil[:,-2:] = 1*array([sin(arrTht), cos(arrTht)]).T
111
+ if nAx == 3:
112
+ arrCoorCoil[0::2,0] = 0.2
113
+ arrCoorCoil[1::2,0] = -0.2
114
+ for iCh in range(nCh):
115
+ mapC[iCh] = genPhMap(shape, mean=mean, std=std)
116
+ dist = sqrt(sum((arrCoor - arrCoorCoil[iCh])**2, axis=-1))
117
+ mapC[iCh] *= exp(-dist)
118
+ return mapC
119
+
120
+ def genAmp(tScan:int|float, tRes:int|float, cyc:int|float, isRand:bool=True) -> NDArray:
121
+ """
122
+ generate amplitude curve
123
+
124
+ Args:
125
+ tScan: length of the signal in `s`
126
+ tRes: temporal resolution in `s`
127
+ cyc: period of the signal in `s`
128
+ isRand: make the signal have irregular period
129
+
130
+ Returns:
131
+ generated amplitude
132
+ """
133
+ nT = around(tScan/tRes).astype(int)
134
+
135
+ if isRand:
136
+ arrT = sort(random.rand(nT)*tScan)
137
+ arrAmp = sin(2*pi/cyc*arrT)
138
+
139
+ sigma = cyc/tRes/8
140
+ arrAmp = gaussian_filter(arrAmp, sigma)
141
+ else:
142
+ arrT = linspace(0, tScan, nT)
143
+ arrAmp = sin(2*pi/cyc*arrT)
144
+
145
+ return arrAmp
146
+
147
+ def genResAmp(tScan:int|float, tRes:int|float, cyc:int|float=pi/2) -> NDArray:
148
+ """
149
+ generate respiratory amplitude curve
150
+
151
+ Args:
152
+ tScan: length of the signal in `s`
153
+ tRes: temporal resolution in `s`
154
+ cyc: period of the signal in `s`
155
+
156
+ Returns:
157
+ generated amplitude, approx. -0.02~0.02
158
+ """
159
+ return 20e-3*genAmp(tScan, tRes, cyc, 1)
160
+
161
+ def genCarAmp(tScan:int|float, tRes:int|float, cyc:int|float=1) -> NDArray:
162
+ """
163
+ generate cardiac amplitude curve
164
+
165
+ Args:
166
+ tScan: length of the signal in `s`
167
+ tRes: temporal resolution in `s`
168
+ cyc: period of the signal in `s`
169
+
170
+ Returns:
171
+ generated amplitude, approx. -0.01~0.01
172
+ """
173
+ return 10e-3*genAmp(tScan, tRes, cyc, 0)
174
+
175
+ def fB02strB0(B0:int|float) -> str:
176
+ """
177
+ convert B0 data type from float/int to string.
178
+
179
+ Args:
180
+ B0: B0 in number format
181
+
182
+ Returns:
183
+ B0 in string format
184
+ """
185
+ if isclose(B0,0.55): return "B0_0T55"
186
+ if isclose(B0,1.5): return "B0_1T5"
187
+ if isclose(B0,3.0): return "B0_3T"
188
+ if isclose(B0,5.0): return "B0_5T0"
189
+ if isclose(B0,9.4): return "B0_9T4"
190
+ raise RuntimeError("unsupported B0")
191
+
192
+ def initSS_bSSFP(
193
+ B0: float,
194
+ TR: float = 5e-3,
195
+ FA_deg: float = 60.0,
196
+ ) -> None:
197
+ """
198
+ Precalculate and store the bSSFP steady-state signal Mss for every tissue.
199
+
200
+ Args:
201
+ B0: field strength
202
+ TR: repetition time
203
+ FA_deg: flip angle in degree
204
+ """
205
+ strB0 = fB02strB0(B0)
206
+ FA = deg2rad(FA_deg)
207
+
208
+ for strTissue in lstTissue:
209
+ dictTissue = dictNmrPara[strTissue]
210
+
211
+ PD = dictTissue[strB0]["PD"]
212
+ T1 = dictTissue[strB0]["T1"]
213
+ T2 = dictTissue[strB0]["T2"]
214
+
215
+ E1 = exp(-TR / T1)
216
+ E2 = exp(-TR / T2)
217
+
218
+ dictTissue["Mss"] = (
219
+ PD
220
+ * (1 - E1)
221
+ * sqrt(E2)
222
+ * sin(FA)
223
+ / (1 - (E1 - E2) * cos(FA) - E1 * E2)
224
+ )
225
+
226
+ def initSS_FLASH(
227
+ B0: float,
228
+ TE: float = 1e-3,
229
+ TR: float = 10e-3,
230
+ FA_deg: float = 10.0,
231
+ ) -> None:
232
+ """
233
+ Precalculate and store the FLASH steady-state signal Mss for every tissue.
234
+
235
+ Args:
236
+ B0: field strength
237
+ TE: echo time
238
+ TR: repetition time
239
+ FA_deg: flip angle in degree
240
+ """
241
+ strB0 = fB02strB0(B0)
242
+ FA = deg2rad(FA_deg)
243
+
244
+ for strTissue in lstTissue:
245
+ dictTissue = dictNmrPara[strTissue]
246
+
247
+ PD = dictTissue[strB0]["PD"]
248
+ T1 = dictTissue[strB0]["T1"]
249
+ T2s = dictTissue[strB0]["T2s"]
250
+
251
+ E1 = exp(-TR / T1)
252
+ E2 = exp(-TE / T2s)
253
+
254
+ dictTissue["Mss"] = (
255
+ PD
256
+ * sin(FA)
257
+ * (1 - E1)
258
+ / (1 - cos(FA) * E1)
259
+ * E2
260
+ )
261
+
262
+ def Enum2SS(arrPht:NDArray) -> NDArray:
263
+ """
264
+ get steady-state signal map of a phantom generated by `genPhant()`
265
+
266
+ Args:
267
+ arrPht: phantom
268
+
269
+ Returns:
270
+ steady-state signal map of the given phantom
271
+ """
272
+ mapSS = zeros_like(arrPht, dtype=float64)
273
+ for strTissue in lstTissue:
274
+ try: mapSS[arrPht==dictNmrPara[strTissue]["enum"]] = dictNmrPara[strTissue]["Mss"]
275
+ except KeyError: raise RuntimeError("Please call `initS_FLASH()` or `initS_bSSFP()` before `Enum2SS()`.")
276
+ return mapSS
277
+
278
+ def Enum2Para(arrPht:NDArray, B0:str|float="B0_1T5", strPara:str="PD") -> NDArray:
279
+ """
280
+ get PD map of a phantom generated by `genPhant()`
281
+
282
+ Args:
283
+ arrPht: phantom
284
+ B0: "B0_0T55" / "B0_1T5" / "B0_3T" / "B0_5T" / "B0_9T4" / 0.55 / 1.5 / 3.0 / 5.0 / 9.4
285
+ strPara: "PD" / "T1" / "T2" / "T2s" / "ADC" / "Om"
286
+
287
+ Returns:
288
+ Proton density map of the given phantom, relevant to water
289
+ """
290
+ mapPara = zeros_like(arrPht, dtype=float64)
291
+ if not isinstance(B0, str): B0 = fB02strB0(B0)
292
+ for strTissue in lstTissue:
293
+ mapPara[arrPht==dictNmrPara[strTissue]["enum"]] = dictNmrPara[strTissue][B0][strPara]
294
+ return mapPara
295
+
296
+ def Enum2PD(arrPht:NDArray, B0:str|int|float) -> NDArray:
297
+ """
298
+ get PD map of a phantom generated by `genPhant()`
299
+
300
+ Args:
301
+ arrPht: phantom
302
+ B0: field strength in Tesla
303
+
304
+ Returns:
305
+ Proton density map of the given phantom, relevant to water
306
+ """
307
+ return Enum2Para(arrPht, B0, "PD")
308
+
309
+ def Enum2T1(arrPht:NDArray, B0:str|int|float) -> NDArray:
310
+ """
311
+ get T1 map of a phantom generated by `genPhant()`
312
+
313
+ Args:
314
+ arrPht: phantom
315
+ B0: field strength in Tesla
316
+
317
+ Returns:
318
+ T1 map of the given phantom
319
+ """
320
+ return Enum2Para(arrPht, B0, "T1")
321
+
322
+ def Enum2T2(arrPht:NDArray, B0:str|int|float) -> NDArray:
323
+ """
324
+ get T2 map of a phantom generated by `genPhant()`
325
+
326
+ Args:
327
+ arrPht: phantom
328
+ B0: field strength in Tesla
329
+
330
+ Returns:
331
+ T2 map of the given phantom
332
+ """
333
+ return Enum2Para(arrPht, B0, "T2")
334
+
335
+ def Enum2T2s(arrPht:NDArray, B0:str|int|float) -> NDArray:
336
+ """
337
+ get T2* map of a phantom generated by `genPhant()`
338
+
339
+ Args:
340
+ arrPht: phantom
341
+ B0: field strength in Tesla
342
+
343
+ Returns:
344
+ T2* map of the given phantom
345
+ """
346
+ return Enum2Para(arrPht, B0, "T2s")
347
+
348
+ def Enum2Adc(arrPht:NDArray, B0:str|int|float) -> NDArray:
349
+ """
350
+ get Apparent Diffusion Coefficient (ADC) map (in `m^2/s`) of a phantom generated by `genPhant()`
351
+
352
+ Args:
353
+ arrPht: phantom
354
+ B0: field strength in Tesla
355
+
356
+ Returns:
357
+ ADC map of the given phantom
358
+ """
359
+ return Enum2Para(arrPht, B0, "ADC")
360
+
361
+ def Enum2Om(arrPht:NDArray, B0:str|int|float) -> NDArray:
362
+ """
363
+ get off-resonance map (in `rad/s`) of a phantom generated by `genPhant()`
364
+
365
+ Args:
366
+ arrPht: phantom
367
+ B0: field strength in Tesla
368
+
369
+ Returns:
370
+ off-resonance map of the given phantom
371
+ """
372
+ return Enum2Para(arrPht, B0, "Om")
@@ -0,0 +1,7 @@
1
+ from . import utility
2
+
3
+ with open("test/nmr_para.xml") as f:
4
+ dictNmrPara = utility.xml2dict(f)
5
+ lstTissue = list(dictNmrPara.keys())
6
+
7
+ from .Function import genPhant, genPhMap, genB0Map, genCsm, genAmp, genResAmp, genCarAmp, Enum2Para, Enum2PD, Enum2T1, Enum2T2, Enum2Adc, Enum2Om, initSS_bSSFP, initSS_FLASH, Enum2SS, Enum2T2s
@@ -6,11 +6,11 @@
6
6
  #include <cstring>
7
7
  #include <slime.h>
8
8
 
9
- bool inline checkNarg(int64_t lNarg, int64_t lNargExp)
9
+ bool inline checkNarg(int64_t nArg, int64_t nArgExp)
10
10
  {
11
- if (lNarg != lNargExp)
11
+ if (nArg != nArgExp)
12
12
  {
13
- printf("wrong num. of arg, narg=%ld, %ld expected\n", lNarg, lNargExp);
13
+ printf("wrong num. of arg, narg=%ld, %ld expected\n", nArg, nArgExp);
14
14
  abort();
15
15
  return false;
16
16
  }
@@ -19,34 +19,36 @@ bool inline checkNarg(int64_t lNarg, int64_t lNargExp)
19
19
 
20
20
  static PyObject* genPhant_py(PyObject* self, PyObject* const* args, Py_ssize_t nargs)
21
21
  {
22
- checkNarg(nargs,4);
22
+ checkNarg(nargs,6);
23
23
  int64_t nAx = PyLong_AsLongLong(args[0]);
24
- int64_t nPix = PyLong_AsLongLong(args[1]);
25
- double ampRes = PyFloat_AsDouble(args[2]);
26
- double ampCar = PyFloat_AsDouble(args[3]);
24
+ int64_t nZ = nAx==3 ? PyLong_AsLongLong(args[1]) : 1;
25
+ int64_t nY = PyLong_AsLongLong(args[2]);
26
+ int64_t nX = PyLong_AsLongLong(args[3]);
27
+ double ampRes = PyFloat_AsDouble(args[4]);
28
+ double ampCar = PyFloat_AsDouble(args[5]);
27
29
 
28
30
  // Generate into std::vector
29
- std::vector<uint8_t> vu8Phant;
30
- genPhant(nAx, nPix, ampRes, ampCar, &vu8Phant);
31
+ std::vector<uint8_t> vu8Phant(nZ*nY*nX, 0);
32
+ genPhant(nAx, nZ, nY, nX, ampRes, ampCar, &vu8Phant);
31
33
 
32
34
  // convert vector to numpy array
33
35
  PyObject* pPyObj_Arr;
34
36
  {
35
- npy_intp aDims[] = {nPix, nPix, nPix};
36
- pPyObj_Arr = PyArray_ZEROS(nAx, aDims, NPY_UINT8, 0);
37
+ npy_intp aDims[] = {nZ, nY, nX};
38
+ pPyObj_Arr = PyArray_ZEROS(nAx, aDims+3-nAx, NPY_UINT8, 0);
37
39
  }
38
40
 
39
41
  // fill the data in
40
42
  std::memcpy(PyArray_DATA((PyArrayObject*)pPyObj_Arr),
41
43
  vu8Phant.data(),
42
44
  vu8Phant.size() * sizeof(uint8_t));
43
-
45
+
44
46
  return pPyObj_Arr;
45
47
  }
46
48
 
47
49
  static PyMethodDef aMeth[] =
48
50
  {
49
- {"genPhant", (PyCFunction)genPhant_py, METH_FASTCALL, "genPhant(nAx, nPix, ampRes, ampCar) -> np.ndarray[uint8]"},
51
+ {"genPhant", (PyCFunction)genPhant_py, METH_FASTCALL, "genPhant(nAx, nZ, nY, nX, ampRes, ampCar) -> np.ndarray[uint8]"},
50
52
  {NULL, NULL, 0, NULL}
51
53
  };
52
54
 
@@ -1,3 +1,4 @@
1
+ #include <cstdio>
1
2
  #include <vector>
2
3
  #include <cstdint>
3
4
  #include <cmath>
@@ -12,8 +13,7 @@ enum Tissue : uint8_t
12
13
  Fat = 1,
13
14
  Myo = 2,
14
15
  Blood = 3,
15
- Fill = 4, // we fill the remaining with skeletal mussles
16
- Vessel = 5,
16
+ Liver = 4,
17
17
  };
18
18
 
19
19
  bool isInsideEllipsoid
@@ -39,48 +39,45 @@ bool isInsideEllipsoid
39
39
 
40
40
  bool genPhant
41
41
  (
42
- int64_t lNAx, int64_t lNPix,
43
- double dResAmp, double dCarAmp,
42
+ int64_t nAx, int64_t nZ, int64_t nY, int64_t nX,
43
+ double ampRes, double ampCar,
44
44
  std::vector<uint8_t>* voSlime
45
45
  )
46
46
  {
47
- const double dNPix = (double)lNPix;
48
- int64_t lNPix_Flat = 0;
49
- if (lNAx==2) lNPix_Flat = lNPix*lNPix;
50
- else if (lNAx==3)lNPix_Flat = lNPix*lNPix*lNPix;
51
- else throw std::runtime_error("lNAx != 2 && lNAx != 3");
52
-
53
- voSlime->assign((size_t)(lNPix_Flat), (uint8_t)(Tissue::Air));
47
+ size_t nPixSum = nZ*nY*nX;
48
+ voSlime->resize(nPixSum);
49
+ voSlime->assign(nPixSum, Tissue::Air);
54
50
 
55
51
  // shape parameter; r: radius, c: center
56
- const double dFatOt_rY = dNPix*400e-3 + dNPix*dResAmp;
57
- const double dFatOt_rX = dNPix*400e-3 - 5e-1*dNPix*dResAmp;
58
- const double dFatOt_rZ = dNPix*480e-3;
52
+ const double dFatOt_rY = nY*400e-3 + nY*ampRes;
53
+ const double dFatOt_rX = nX*400e-3 - 5e-1*nX*ampRes;
54
+ const double dFatOt_rZ = nZ*480e-3;
59
55
 
60
- const double dFatIn_rY = dNPix*380e-3 + dNPix*dResAmp;
61
- const double dFatIn_rX = dNPix*380e-3 - 5e-1*dNPix*dResAmp;
62
- const double dFatIn_rZ = dNPix*450e-3;
56
+ const double dFatIn_rY = nY*380e-3 + nY*ampRes;
57
+ const double dFatIn_rX = nX*380e-3 - 5e-1*nX*ampRes;
58
+ const double dFatIn_rZ = nZ*450e-3;
63
59
 
64
- const double dMyoOt_rY = dNPix*100e-3 + dNPix*dCarAmp;
65
- const double dMyoOt_rX = dNPix*120e-3 + dNPix*dCarAmp;
66
- const double dMyoOt_rZ = dMyoOt_rY;
60
+ const double dMyoOt_rY = nY*100e-3 + nY*ampCar;
61
+ const double dMyoOt_rX = nX*120e-3 + nX*ampCar;
62
+ const double dMyoOt_rZ = nZ*100e-3 + nZ*ampCar;;
67
63
 
68
- const double dMyoIn_rY = dNPix*60e-3 + dNPix*(2*dCarAmp);
69
- const double dMyoIn_rX = dNPix*60e-3 + dNPix*(2*dCarAmp);
70
- const double dMyoIn_rZ = dMyoIn_rY;
64
+ const double dMyoIn_rY = nY*60e-3 + nY*(2*ampCar);
65
+ const double dMyoIn_rX = nX*60e-3 + nX*(2*ampCar);
66
+ const double dMyoIn_rZ = nZ*60e-3 + nZ*(2*ampCar);;
71
67
 
72
68
  // Centers are at (0,0,0) in your centered coordinate system,
73
69
  const double dFat_cx = 0e0, dFat_cy = 0e0, dFat_cz = 0e0;
74
70
  const double dMyoOt_cx = 0e0, dMyoOt_cy = 0e0, dMyoOt_cz = 0e0;
75
- const double dMyoIn_cx = -dNPix*20e-3, dMyoIn_cy = 0e0, dMyoIn_cz = 0e0;
71
+ const double dMyoIn_cx = -nX*20e-3, dMyoIn_cy = 0e0, dMyoIn_cz = 0e0;
76
72
 
77
- /* generate phantom given by `dResAmp` and `dCarAmp` here */
73
+ /* generate phantom given by `ampRes` and `ampCar` here */
78
74
  #pragma omp parallel for schedule(static)
79
- for (int64_t i = 0; i < lNPix_Flat; ++i)
75
+ for (int64_t i = 0; i < nPixSum; ++i)
80
76
  {
81
- const int64_t x = i % lNPix - lNPix/2;
82
- const int64_t y = (i / lNPix) % lNPix - lNPix/2;
83
- const int64_t z = (lNAx == 3) ? (i / (lNPix*lNPix) - lNPix/2) : 0;
77
+ // derive coordinates
78
+ const int64_t x = i%nX - nX/2;
79
+ const int64_t y = (i/nX)%nY - nY/2;
80
+ const int64_t z = (nAx==3) ? (i/(nY*nX) - nZ/2) : 0;
84
81
 
85
82
  // decide what tissue current pixel is
86
83
  if (!isInsideEllipsoid(x,y,z, dFat_cx,dFat_cy,dFat_cz, dFatOt_rX,dFatOt_rY,dFatOt_rZ))
@@ -96,9 +93,9 @@ bool genPhant
96
93
  }
97
94
 
98
95
  // vessel balls
99
- #define V_HIT(cx,cy,cz,div) isInsideEllipsoid(x,y,z, ((cx)*dNPix), ((cy)*dNPix), ((cz)*dNPix), (dFatIn_rX/(div)), (dFatIn_rY/(div)), (dFatIn_rZ/(div)))
96
+ #define V_HIT(cx,cy,cz,div) isInsideEllipsoid(x,y,z, ((cx)*nX), ((cy)*nY), ((cz)*nZ), (dFatIn_rX/(div)), (dFatIn_rY/(div)), (dFatIn_rZ/(div)))
100
97
 
101
- // 48 “random” vessels (cx,cy,cz in dNPix fractions; div in ~[18..44])
98
+ // 48 “random” vessels (cx,cy,cz in nX,nY,nZ fractions; div in ~[18..44])
102
99
  #define VLIST \
103
100
  /* 5 on XY plane (z = 0) — slightly farther from heart */ \
104
101
  V_HIT( 0.18, 0.09, 0.00, 12) || \
@@ -128,16 +125,16 @@ bool genPhant
128
125
  V_HIT( 0.14, -0.14, -0.13, 16) || /* + - - */ \
129
126
  V_HIT(-0.15, -0.14, -0.13, 16) /* - - - */
130
127
 
131
- if ( VLIST )
128
+ if (VLIST)
132
129
  {
133
- (*voSlime)[(size_t)i] = (uint8_t)Tissue::Vessel;
130
+ (*voSlime)[(size_t)i] = (uint8_t)Tissue::Fat;
134
131
  continue;
135
132
  }
136
133
  // vessel balls (end)
137
134
 
138
135
  if (!isInsideEllipsoid(x,y,z, dMyoOt_cx,dMyoOt_cy,dMyoOt_cz, dMyoOt_rX,dMyoOt_rY,dMyoOt_rZ))
139
136
  {
140
- (*voSlime)[(size_t)i] = (uint8_t)Tissue::Fill;
137
+ (*voSlime)[(size_t)i] = (uint8_t)Tissue::Liver;
141
138
  continue;
142
139
  }
143
140
 
@@ -5,7 +5,7 @@
5
5
 
6
6
  extern bool genPhant
7
7
  (
8
- int64_t lNDim, int64_t lNPix,
9
- double dResAmp, double dCarAmp,
8
+ int64_t nAx, int64_t nZ, int64_t nY, int64_t nX,
9
+ double ampRes, double ampCar,
10
10
  std::vector<uint8_t>* voSlime
11
11
  );
@@ -0,0 +1,42 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <root>
3
+ <air enum="0">
4
+ <B0_0T55 PD="0.00" T1="inf" T2="1e-6" T2s="1e-6" ADC="0.0" Om="0e-6"/>
5
+ <B0_1T5 PD="0.00" T1="inf" T2="1e-6" T2s="1e-6" ADC="0.0" Om="0e-6"/>
6
+ <B0_3T PD="0.00" T1="inf" T2="1e-6" T2s="1e-6" ADC="0.0" Om="0e-6"/>
7
+ <B0_5T PD="0.00" T1="inf" T2="1e-6" T2s="1e-6" ADC="0.0" Om="0e-6"/>
8
+ <B0_9T4 PD="0.00" T1="inf" T2="1e-6" T2s="1e-6" ADC="0.0" Om="0e-6"/>
9
+ </air>
10
+
11
+ <fat enum="1">
12
+ <B0_0T55 PD="0.95" T1="280e-3" T2="100e-3" T2s="70e-3" ADC="0.15e-9" Om="-515.01"/>
13
+ <B0_1T5 PD="0.95" T1="343e-3" T2="58e-3" T2s="50e-3" ADC="0.15e-9" Om="-1404.57"/>
14
+ <B0_3T PD="0.95" T1="382e-3" T2="68e-3" T2s="35e-3" ADC="0.15e-9" Om="-2809.15"/>
15
+ <B0_5T PD="0.95" T1="450e-3" T2="60e-3" T2s="25e-3" ADC="0.15e-9" Om="-4681.92"/>
16
+ <B0_9T4 PD="0.95" T1="500e-3" T2="50e-3" T2s="15e-3" ADC="0.15e-9" Om="-8802.00"/>
17
+ </fat>
18
+
19
+ <myo enum="2">
20
+ <B0_0T55 PD="0.80" T1="700e-3" T2="60e-3" T2s="50e-3" ADC="1.55e-9" Om="0e-6"/>
21
+ <B0_1T5 PD="0.80" T1="1008e-3" T2="44e-3" T2s="33e-3" ADC="1.55e-9" Om="0e-6"/>
22
+ <B0_3T PD="0.80" T1="1220e-3" T2="47e-3" T2s="22e-3" ADC="1.55e-9" Om="0e-6"/>
23
+ <B0_5T PD="0.80" T1="1450e-3" T2="35e-3" T2s="15e-3" ADC="1.55e-9" Om="0e-6"/>
24
+ <B0_9T4 PD="0.80" T1="1800e-3" T2="20e-3" T2s="8e-3" ADC="1.55e-9" Om="0e-6"/>
25
+ </myo>
26
+
27
+ <blood enum="3">
28
+ <B0_0T55 PD="0.95" T1="1120e-3" T2="260e-3" T2s="80e-3" ADC="2.10e-9" Om="0e-6"/>
29
+ <B0_1T5 PD="0.95" T1="1441e-3" T2="290e-3" T2s="55e-3" ADC="2.10e-9" Om="0e-6"/>
30
+ <B0_3T PD="0.95" T1="1932e-3" T2="275e-3" T2s="30e-3" ADC="2.10e-9" Om="0e-6"/>
31
+ <B0_5T PD="0.95" T1="2100e-3" T2="90e-3" T2s="18e-3" ADC="2.10e-9" Om="0e-6"/>
32
+ <B0_9T4 PD="0.95" T1="2500e-3" T2="40e-3" T2s="10e-3" ADC="2.10e-9" Om="0e-6"/>
33
+ </blood>
34
+
35
+ <liver enum="4">
36
+ <B0_0T55 PD="0.90" T1="450e-3" T2="55e-3" T2s="45e-3" ADC="1.15e-9" Om="0e-6"/>
37
+ <B0_1T5 PD="0.90" T1="576e-3" T2="46e-3" T2s="30e-3" ADC="1.15e-9" Om="0e-6"/>
38
+ <B0_3T PD="0.90" T1="809e-3" T2="34e-3" T2s="18e-3" ADC="1.15e-9" Om="0e-6"/>
39
+ <B0_5T PD="0.90" T1="1000e-3" T2="34e-3" T2s="10e-3" ADC="1.15e-9" Om="0e-6"/>
40
+ <B0_9T4 PD="0.90" T1="1200e-3" T2="25e-3" T2s="5e-3" ADC="1.15e-9" Om="0e-6"/>
41
+ </liver>
42
+ </root>
@@ -0,0 +1,12 @@
1
+ import xml.etree.ElementTree as ET
2
+
3
+ def ele2dict(element):
4
+ result = {
5
+ key: int(value) if key=="enum" else float(value)
6
+ for key, value in element.attrib.items()
7
+ }
8
+ for child in element:
9
+ result[child.tag] = ele2dict(child)
10
+ return result
11
+
12
+ xml2dict = lambda fXml: ele2dict(ET.parse(fXml).getroot())
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "MRPhantom"
7
- version = "2.2.0"
7
+ version = "2.2.1"
8
8
  dependencies = ["numpy", "scipy"]
9
9
 
10
10
  description = "Volumetric dynamic MRI Phantom of a Slime with respiratory and cardiac motion, and M0, phase, T1, T2, B0, coil sensitivity maps, boosted by a parallel C-API Backend."
@@ -0,0 +1,15 @@
1
+ import xml.etree.ElementTree as ET
2
+
3
+ def ele2dict(element):
4
+ result = {
5
+ key: float(value)
6
+ for key, value in element.attrib.items()
7
+ }
8
+ for child in element:
9
+ result[child.tag] = ele2dict(child)
10
+ return result
11
+
12
+ xml2dict = lambda fXml: ele2dict(ET.parse(fXml).getroot())
13
+
14
+ with open("test/nmr_para.xml") as f:
15
+ dictNmrPara = xml2dict(f)
@@ -0,0 +1,22 @@
1
+ from numpy import *
2
+ from matplotlib.pyplot import *
3
+ import mrphantom as mpt
4
+ from scipy.ndimage import zoom
5
+
6
+ # 2D
7
+ nPix = 256
8
+ arrPhant = mpt.genPhant((nPix,nPix//4))
9
+ mpt.initSS_bSSFP(1.5)
10
+ arrM0 = mpt.Enum2SS(arrPhant)*mpt.genPhMap(arrPhant.shape)
11
+
12
+ arrM0 = zoom(arrM0, (1,4))
13
+
14
+ figure(figsize=(6,3), dpi=150)
15
+ subplot(121)
16
+ imshow(abs(arrM0), cmap="gray"); colorbar()
17
+ title("Magnitude")
18
+ subplot(122)
19
+ imshow(angle(arrM0), cmap="hsv", vmin=-pi, vmax=pi); colorbar()
20
+ title("Phase")
21
+ tight_layout()
22
+ show()
@@ -1,436 +0,0 @@
1
- from . import ext
2
- from numpy import *
3
- from numpy.typing import NDArray
4
- from enum import Enum
5
- from scipy.ndimage import gaussian_filter
6
-
7
- class Tissue(Enum):
8
- Air = 0
9
- Fat = 1
10
- Myo = 2 # Myocadium
11
- Blood = 3
12
- Liver = 4
13
- Vessel = 5
14
- NTissue = 6
15
-
16
- def genPhant(nAx:int=2, nPix:int=256, ampRes:float=0, ampCar:float=0) -> NDArray: # call C++ backend to generate a phantom
17
- """
18
- generate a phantom in Enum type
19
-
20
- Args:
21
- nAx: number of dimensions
22
- nPix: number of pixels
23
- ampRes: respiratory motion amplitude
24
- ampCar: cardiac motion amplitude
25
-
26
- Returns:
27
- NDArray contains elements in `Tissue` enum type
28
- """
29
- return ext.genPhant(nAx, nPix, ampRes, ampCar)
30
-
31
- def genPhMap(nAx:int=2, nPix:int=256, mean:int|float|None=None, std:int|float=pi/16) -> NDArray:
32
- """
33
- generate random phase map
34
-
35
- Args:
36
- nAx: number of dimensions
37
- nPix: number of pixels
38
- mean: mean of the noise
39
- std: std of the noise
40
-
41
- Returns:
42
- smooth complex noise with unity magnitude
43
- """
44
- if mean is None: mean = random.uniform(-pi,pi)
45
- mapPh = random.uniform(-pi, pi, [nPix for _ in range(nAx)])
46
- sigma = nPix/4
47
- mapPh = gaussian_filter(mapPh, sigma)
48
- # normalize
49
- mapPh -= mapPh.mean(); mapPh = asarray(mapPh)
50
- mapPh /= mapPh.std()
51
- mapPh *= std
52
- mapPh += mean
53
- # convert to rotation factor
54
- mapPh = exp(1j*mapPh)
55
- mapPh = mapPh/abs(mapPh)
56
- return mapPh
57
-
58
- def genB0Map(nAx:int=2, nPix:int=256, mean:int|float=0, std:int|float=1e-6*(2*pi*42.58e6*3)) -> NDArray:
59
- """
60
- generate random B0 map
61
-
62
- Args:
63
- nAx: number of dimensions
64
- nPix: number of pixels
65
- mean: mean of the noise
66
- std: std of the noise
67
-
68
- Returns:
69
- smooth random noise in `rad/s`
70
- """
71
- mapB0 = random.uniform(-1, 1, [nPix for _ in range(nAx)])
72
- sigma = nPix/4
73
- mapB0 = gaussian_filter(mapB0, sigma)
74
- # normalize
75
- mapB0 -= mapB0.mean(); mapB0 = asarray(mapB0)
76
- mapB0 /= mapB0.std()
77
- mapB0 *= std
78
- mapB0 += mean
79
- return mapB0
80
-
81
- def genCsm(nAx:int=2, nPix:int=256, nCh:int=12, mean:int|float|None=None, std:int|float=pi/16) -> NDArray:
82
- """
83
- generate random coil sensitivity map
84
-
85
- Args:
86
- nAx: number of dimensions
87
- nPix: number of pixels
88
- nCh: number of coils
89
- mean: mean of the noise
90
- std: std of the noise
91
-
92
- Returns:
93
- complex smooth and inhomogeneous map
94
- """
95
- if mean is None: mean = random.uniform(-pi,pi)
96
- mapC = zeros([nCh,*(nPix for _ in range(nAx))], dtype=complex128)
97
- arrCoor = meshgrid\
98
- (
99
- *(linspace(-0.5,0.5,nPix,0) for _ in range(nAx)),
100
- indexing="ij"
101
- ); arrCoor = array(arrCoor).transpose(*arange(1,nAx+1), 0)
102
- arrTht = linspace(0,2*pi,nCh,0)
103
- arrCoorCoil = zeros([nCh,nAx], dtype=float64)
104
- arrCoorCoil[:,-2:] = 1*array([sin(arrTht), cos(arrTht)]).T
105
- if nAx == 3:
106
- arrCoorCoil[0::2,0] = 0.2
107
- arrCoorCoil[1::2,0] = -0.2
108
- for iCh in range(nCh):
109
- mapC[iCh] = genPhMap(nAx=nAx, nPix=nPix, mean=mean, std=std)
110
- dist = sqrt(sum((arrCoor - arrCoorCoil[iCh])**2, axis=-1))
111
- mapC[iCh] *= exp(-dist)
112
- return mapC
113
-
114
- def genAmp(tScan:int|float, tRes:int|float, cyc:int|float, isRand:bool=True) -> NDArray:
115
- """
116
- generate amplitude curve
117
-
118
- Args:
119
- tScan: length of the signal in `s`
120
- tRes: temporal resolution in `s`
121
- cyc: period of the signal in `s`
122
- isRand: make the signal have irregular period
123
-
124
- Returns:
125
- generated amplitude
126
- """
127
- nT = around(tScan/tRes).astype(int)
128
-
129
- if isRand:
130
- arrT = sort(random.rand(nT)*tScan)
131
- arrAmp = sin(2*pi/cyc*arrT)
132
-
133
- sigma = cyc/tRes/8
134
- arrAmp = gaussian_filter(arrAmp, sigma)
135
- else:
136
- arrT = linspace(0, tScan, nT)
137
- arrAmp = sin(2*pi/cyc*arrT)
138
-
139
- return arrAmp
140
-
141
- def genResAmp(tScan:int|float, tRes:int|float, cyc:int|float=pi/2) -> NDArray:
142
- """
143
- generate respiratory amplitude curve
144
-
145
- Args:
146
- tScan: length of the signal in `s`
147
- tRes: temporal resolution in `s`
148
- cyc: period of the signal in `s`
149
-
150
- Returns:
151
- generated amplitude, approx. -0.02~0.02
152
- """
153
- return 20e-3*genAmp(tScan, tRes, cyc, 1)
154
-
155
- def genCarAmp(tScan:int|float, tRes:int|float, cyc:int|float=1) -> NDArray:
156
- """
157
- generate cardiac amplitude curve
158
-
159
- Args:
160
- tScan: length of the signal in `s`
161
- tRes: temporal resolution in `s`
162
- cyc: period of the signal in `s`
163
-
164
- Returns:
165
- generated amplitude, approx. -0.01~0.01
166
- """
167
- return 10e-3*genAmp(tScan, tRes, cyc, 0)
168
-
169
- def Enum2SS(arrPht:NDArray, ampCar:double=0e0, B0:double=5.0, TR:double=None, TE:double=None, FA_deg:double=None, bSSFP:bool=False, mapPh:NDArray=None) -> NDArray:
170
- """
171
- get steady-state signal map of a phantom generated by `genPhant()`
172
-
173
- Args:
174
- arrPht: phantom
175
- ampCar: cardiac motion amplitude
176
- TR: repetition time
177
- TE: echo time
178
- FA_deg: flip angle in degree
179
- bSSFP: True for bSSFP, False for FLASH
180
- mapPh: phase map array, can be generated using genPhMap() `NTissue` times
181
-
182
- Returns:
183
- steady state signal map of the given phantom
184
- """
185
- mapPD = Enum2PD(arrPht)
186
- mapT1 = Enum2T1(arrPht, B0)
187
- if bSSFP:
188
- if TR is None: TR = 5e-3
189
- if TE is None: TE = 1e-3
190
- if FA_deg is None: FA_deg = 60
191
- FA = FA_deg * pi/180
192
- mapT2 = Enum2T2(arrPht, B0)
193
- E1 = exp(-TR/mapT1)
194
- E2 = exp(-TR/mapT2)
195
- mapSS = (
196
- mapPD
197
- * (1-E1)
198
- * sqrt(E2)
199
- * sin(FA)
200
- / (1 - (E1-E2)*cos(FA) - E1*E2)
201
- ) + 0j
202
- else:
203
- if TR is None: TR = 10e-3
204
- if TE is None: TE = 1e-3
205
- if FA_deg is None: FA_deg = 10
206
- FA = FA_deg * pi/180
207
- mapT2s = Enum2T2s(arrPht, B0)
208
- E1 = exp(-TR/mapT1)
209
- E2 = exp(-TE/mapT2s)
210
- mapSS = (
211
- mapPD
212
- *sin(FA)
213
- *(1-E1)
214
- /(1-cos(FA)*E1)
215
- *E2
216
- ) + 0j
217
- kInFow = 1.5 + max(-ampCar,0)*20
218
- mapSS[arrPht==Tissue.Blood.value] *= kInFow
219
- mapSS[arrPht==Tissue.Vessel.value] *= kInFow
220
- if mapPh is not None:
221
- for iTissue in range(Tissue.NTissue.value):
222
- mapSS[arrPht==iTissue] *= mapPh[iTissue][arrPht==iTissue]
223
- return mapSS
224
-
225
- def Enum2PD(arrPht:NDArray) -> NDArray:
226
- """
227
- get PD map of a phantom generated by `genPhant()`
228
-
229
- Args:
230
- arrPht: phantom
231
-
232
- Returns:
233
- Proton density map of the given phantom, relevant to water
234
- """
235
- mapPD = zeros_like(arrPht, dtype=float64)
236
- mapPD[arrPht==Tissue.Air.value] = 0
237
- mapPD[arrPht==Tissue.Fat.value] = 0.95
238
- mapPD[arrPht==Tissue.Myo.value] = 0.80
239
- mapPD[arrPht==Tissue.Blood.value] = 0.95
240
- mapPD[arrPht==Tissue.Liver.value] = 0.90
241
- mapPD[arrPht==Tissue.Vessel.value] = 0.95
242
- return mapPD
243
-
244
- def Enum2T1(arrPht:NDArray, B0:int|float) -> NDArray:
245
- """
246
- get T1 map of a phantom generated by `genPhant()`
247
-
248
- Args:
249
- arrPht: phantom
250
-
251
- Returns:
252
- T1 map of the given phantom
253
- """
254
- mapT1 = zeros_like(arrPht, dtype=float64)
255
- if B0==0.55:
256
- mapT1[arrPht==Tissue.Air.value] = inf
257
- mapT1[arrPht==Tissue.Fat.value] = 280e-3
258
- mapT1[arrPht==Tissue.Myo.value] = 700e-3
259
- mapT1[arrPht==Tissue.Blood.value] = 1120e-3
260
- mapT1[arrPht==Tissue.Liver.value] = 450e-3
261
- mapT1[arrPht==Tissue.Vessel.value] = 1120e-3
262
- elif B0==1.5:
263
- mapT1[arrPht==Tissue.Air.value] = inf
264
- mapT1[arrPht==Tissue.Fat.value] = 350e-3
265
- mapT1[arrPht==Tissue.Myo.value] = 1030e-3
266
- mapT1[arrPht==Tissue.Blood.value] = 1450e-3
267
- mapT1[arrPht==Tissue.Liver.value] = 1580e-3
268
- mapT1[arrPht==Tissue.Vessel.value] = 1450e-3
269
- elif B0==3.0:
270
- mapT1[arrPht==Tissue.Air.value] = inf
271
- mapT1[arrPht==Tissue.Fat.value] = 400e-3
272
- mapT1[arrPht==Tissue.Myo.value] = 1200e-3
273
- mapT1[arrPht==Tissue.Blood.value] = 1800e-3
274
- mapT1[arrPht==Tissue.Liver.value] = 800e-3
275
- mapT1[arrPht==Tissue.Vessel.value] = 1800e-3
276
- elif B0==5.0:
277
- mapT1[arrPht==Tissue.Air.value] = inf
278
- mapT1[arrPht==Tissue.Fat.value] = 450e-3
279
- mapT1[arrPht==Tissue.Myo.value] = 1450e-3
280
- mapT1[arrPht==Tissue.Blood.value] = 2100e-3
281
- mapT1[arrPht==Tissue.Liver.value] = 1000e-3
282
- mapT1[arrPht==Tissue.Vessel.value] = 2100e-3
283
- elif B0==9.4:
284
- mapT1[arrPht==Tissue.Air.value] = inf
285
- mapT1[arrPht==Tissue.Fat.value] = 500e-3
286
- mapT1[arrPht==Tissue.Myo.value] = 1800e-3
287
- mapT1[arrPht==Tissue.Blood.value] = 2500e-3
288
- mapT1[arrPht==Tissue.Liver.value] = 1200e-3
289
- mapT1[arrPht==Tissue.Vessel.value] = 2500e-3
290
- else:
291
- raise RuntimeError(f"B0={B0:.2f} not available.")
292
- return mapT1
293
-
294
- def Enum2T2(arrPht:NDArray, B0:int|float) -> NDArray:
295
- """
296
- get T2 map of a phantom generated by `genPhant()`
297
-
298
- Args:
299
- arrPht: phantom
300
-
301
- Returns:
302
- T2 map of the given phantom
303
- """
304
- mapT2 = zeros_like(arrPht, dtype=float64)
305
- if B0==0.55:
306
- mapT2[arrPht==Tissue.Air.value] = 1e-6
307
- mapT2[arrPht==Tissue.Fat.value] = 100e-3
308
- mapT2[arrPht==Tissue.Myo.value] = 60e-3
309
- mapT2[arrPht==Tissue.Blood.value] = 260e-3
310
- mapT2[arrPht==Tissue.Liver.value] = 55e-3
311
- mapT2[arrPht==Tissue.Vessel.value] = 260e-3
312
- elif B0==1.5:
313
- mapT2[arrPht==Tissue.Air.value] = 1e-6
314
- mapT2[arrPht==Tissue.Fat.value] = 80e-3
315
- mapT2[arrPht==Tissue.Myo.value] = 45e-3
316
- mapT2[arrPht==Tissue.Blood.value] = 275e-3
317
- mapT2[arrPht==Tissue.Liver.value] = 46e-3
318
- mapT2[arrPht==Tissue.Vessel.value] = 275e-3
319
- elif B0==3.0:
320
- mapT2[arrPht==Tissue.Air.value] = 1e-6
321
- mapT2[arrPht==Tissue.Fat.value] = 70e-3
322
- mapT2[arrPht==Tissue.Myo.value] = 40e-3
323
- mapT2[arrPht==Tissue.Blood.value] = 120e-3
324
- mapT2[arrPht==Tissue.Liver.value] = 40e-3
325
- mapT2[arrPht==Tissue.Vessel.value] = 120e-3
326
- elif B0==5.0:
327
- mapT2[arrPht==Tissue.Air.value] = 1e-6
328
- mapT2[arrPht==Tissue.Fat.value] = 60e-3
329
- mapT2[arrPht==Tissue.Myo.value] = 35e-3
330
- mapT2[arrPht==Tissue.Blood.value] = 90e-3
331
- mapT2[arrPht==Tissue.Liver.value] = 34e-3
332
- mapT2[arrPht==Tissue.Vessel.value] = 90e-3
333
- elif B0==9.4:
334
- mapT2[arrPht==Tissue.Air.value] = 1e-6
335
- mapT2[arrPht==Tissue.Fat.value] = 50e-3
336
- mapT2[arrPht==Tissue.Myo.value] = 20e-3
337
- mapT2[arrPht==Tissue.Blood.value] = 40e-3
338
- mapT2[arrPht==Tissue.Liver.value] = 25e-3
339
- mapT2[arrPht==Tissue.Vessel.value] = 40e-3
340
- else:
341
- raise RuntimeError(f"B0={B0:.2f} not available.")
342
- return mapT2
343
-
344
- def Enum2T2s(arrPht:NDArray, B0:int|float) -> NDArray:
345
- """
346
- get T2* map of a phantom generated by `genPhant()`
347
-
348
- Args:
349
- arrPht: phantom
350
-
351
- Returns:
352
- T2* map of the given phantom
353
- """
354
- mapT2s = zeros_like(arrPht, dtype=float64)
355
- if B0==0.55:
356
- mapT2s[arrPht==Tissue.Air.value] = 1e-6
357
- mapT2s[arrPht==Tissue.Fat.value] = 70e-3
358
- mapT2s[arrPht==Tissue.Myo.value] = 50e-3
359
- mapT2s[arrPht==Tissue.Blood.value] = 80e-3
360
- mapT2s[arrPht==Tissue.Liver.value] = 45e-3
361
- mapT2s[arrPht==Tissue.Vessel.value] = 80e-3
362
- elif B0==1.5:
363
- mapT2s[arrPht==Tissue.Air.value] = 1e-6
364
- mapT2s[arrPht==Tissue.Fat.value] = 50e-3
365
- mapT2s[arrPht==Tissue.Myo.value] = 33e-3
366
- mapT2s[arrPht==Tissue.Blood.value] = 55e-3
367
- mapT2s[arrPht==Tissue.Liver.value] = 30e-3
368
- mapT2s[arrPht==Tissue.Vessel.value] = 55e-3
369
- elif B0==3.0:
370
- mapT2s[arrPht==Tissue.Air.value] = 1e-6
371
- mapT2s[arrPht==Tissue.Fat.value] = 35e-3
372
- mapT2s[arrPht==Tissue.Myo.value] = 22e-3
373
- mapT2s[arrPht==Tissue.Blood.value] = 30e-3
374
- mapT2s[arrPht==Tissue.Liver.value] = 18e-3
375
- mapT2s[arrPht==Tissue.Vessel.value] = 30e-3
376
- elif B0==5.0:
377
- mapT2s[arrPht==Tissue.Air.value] = 1e-6
378
- mapT2s[arrPht==Tissue.Fat.value] = 25e-3
379
- mapT2s[arrPht==Tissue.Myo.value] = 15e-3
380
- mapT2s[arrPht==Tissue.Blood.value] = 18e-3
381
- mapT2s[arrPht==Tissue.Liver.value] = 10e-3
382
- mapT2s[arrPht==Tissue.Vessel.value] = 18e-3
383
- elif B0==9.4:
384
- mapT2s[arrPht==Tissue.Air.value] = 1e-6
385
- mapT2s[arrPht==Tissue.Fat.value] = 15e-3
386
- mapT2s[arrPht==Tissue.Myo.value] = 8e-3
387
- mapT2s[arrPht==Tissue.Blood.value] = 10e-3
388
- mapT2s[arrPht==Tissue.Liver.value] = 5e-3
389
- mapT2s[arrPht==Tissue.Vessel.value] = 10e-3
390
- else:
391
- raise RuntimeError(f"B0={B0:.2f} not available.")
392
- return mapT2s
393
-
394
- def Enum2Adc(arrPht:NDArray, B0:int|float) -> NDArray:
395
- """
396
- get Apparent Diffusion Coefficient (ADC) map (in `m^2/s`) of a phantom generated by `genPhant()`
397
-
398
- Args:
399
- arrPht: phantom
400
- B0: field strength in Tesla
401
-
402
- Returns:
403
- ADC map of the given phantom
404
- """
405
- mapADC = zeros_like(arrPht, dtype=float64)
406
- if B0 in [0.55, 1.5, 3.0, 5.0, 9.4]:
407
- mapADC[arrPht==Tissue.Air.value] = 0.0
408
- mapADC[arrPht==Tissue.Fat.value] = 0.15e-9
409
- mapADC[arrPht==Tissue.Myo.value] = 1.55e-9
410
- mapADC[arrPht==Tissue.Blood.value] = 2.10e-9
411
- mapADC[arrPht==Tissue.Liver.value] = 1.15e-9
412
- mapADC[arrPht==Tissue.Vessel.value] = 2.10e-9
413
- else:
414
- raise RuntimeError(f"B0={B0:.2f} not available.")
415
- return mapADC
416
-
417
- def Enum2Om(arrPht:NDArray, B0:int|float) -> NDArray:
418
- """
419
- get off-resonance map (in `rad/s`) of a phantom generated by `genPhant()`
420
-
421
- Args:
422
- arrPht: phantom
423
- B0: field strength in Tesla
424
-
425
- Returns:
426
- off-resonance map of the given phantom
427
- """
428
- mapOm = zeros_like(arrPht, dtype=float64)
429
- ppm2om = 1e-6*(2*pi*42.58e6*B0)
430
- mapOm[arrPht==Tissue.Air.value] = 0
431
- mapOm[arrPht==Tissue.Fat.value] = 3.5*ppm2om
432
- mapOm[arrPht==Tissue.Myo.value] = 0
433
- mapOm[arrPht==Tissue.Blood.value] = 0
434
- mapOm[arrPht==Tissue.Liver.value] = 0
435
- mapOm[arrPht==Tissue.Vessel.value] = 0
436
- return mapOm
@@ -1,5 +0,0 @@
1
- # from .Type import Part
2
- # from .Function import genPhan
3
- # from .Utility import genAmp, genCsm
4
-
5
- from .Function import genPhant, genPhMap, genB0Map, genCsm, genAmp, genResAmp, genCarAmp, Enum2PD, Enum2T1, Enum2T2, Enum2Adc, Enum2Om, Enum2SS, Enum2T2s, Tissue
File without changes
File without changes
File without changes
File without changes
File without changes