MRPhantom 0.0.0__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.
@@ -0,0 +1,7 @@
1
+ Copyright 2025 Rui Luo
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1 @@
1
+ global-include *.py *.c *.cpp *.h *.hpp
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: MRPhantom
3
+ Version: 0.0.0
4
+ Summary: MRI Phantom of a Slime with Repiratory and Cardiac Motion, and M0, Phase, T1, T2, B0 maps, Boosted by a C-API Backend.
5
+ Author-email: Ryan <ryan_shanghaitech@proton.me>
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/RyanShanghaitech/Slime
8
+ Project-URL: Examples, https://github.com/RyanShanghaitech/Slime/tree/main/example
9
+ Keywords: MRI,Phantom,Motion,Quantitative
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: numpy
13
+ Dynamic: license-file
14
+
15
+ # MRI Digital Phantom
16
+ A quantitative phantom of a slime with cardiac and respiratory motion.
@@ -0,0 +1,21 @@
1
+ LICENSE
2
+ MANIFEST.in
3
+ README.md
4
+ pyproject.toml
5
+ setup.py
6
+ ./slime_src/ext/main.cpp
7
+ ./slime_src/ext/slime.cpp
8
+ MRPhantom.egg-info/PKG-INFO
9
+ MRPhantom.egg-info/SOURCES.txt
10
+ MRPhantom.egg-info/dependency_links.txt
11
+ MRPhantom.egg-info/requires.txt
12
+ MRPhantom.egg-info/top_level.txt
13
+ example/Example_2D.py
14
+ example/Example_3D.py
15
+ example/Example_Simple.py
16
+ example/Example_Utility.py
17
+ slime_src/Function.py
18
+ slime_src/__init__.py
19
+ slime_src/ext/main.cpp
20
+ slime_src/ext/slime.cpp
21
+ slime_src/ext/slime.h
@@ -0,0 +1 @@
1
+ numpy
@@ -0,0 +1 @@
1
+ slime
@@ -0,0 +1,16 @@
1
+ Metadata-Version: 2.4
2
+ Name: MRPhantom
3
+ Version: 0.0.0
4
+ Summary: MRI Phantom of a Slime with Repiratory and Cardiac Motion, and M0, Phase, T1, T2, B0 maps, Boosted by a C-API Backend.
5
+ Author-email: Ryan <ryan_shanghaitech@proton.me>
6
+ License-Expression: MIT
7
+ Project-URL: Repository, https://github.com/RyanShanghaitech/Slime
8
+ Project-URL: Examples, https://github.com/RyanShanghaitech/Slime/tree/main/example
9
+ Keywords: MRI,Phantom,Motion,Quantitative
10
+ Description-Content-Type: text/markdown
11
+ License-File: LICENSE
12
+ Requires-Dist: numpy
13
+ Dynamic: license-file
14
+
15
+ # MRI Digital Phantom
16
+ A quantitative phantom of a slime with cardiac and respiratory motion.
@@ -0,0 +1,2 @@
1
+ # MRI Digital Phantom
2
+ A quantitative phantom of a slime with cardiac and respiratory motion.
@@ -0,0 +1,37 @@
1
+ from numpy import *
2
+ from matplotlib.pyplot import *
3
+ import slime
4
+ from time import time
5
+
6
+ tScan = 10
7
+ tRes = 10e-3
8
+ nT = int(tScan/tRes)
9
+ nPix = 256
10
+
11
+ cycRes = pi/2
12
+ cycCar = 1
13
+ arrAmpRes = 20e-3*slime.genAmp(tScan, tRes, cycRes, 1)
14
+ arrAmpCar = 10e-3*slime.genAmp(tScan, tRes, cycCar, 0)
15
+
16
+ fig = figure()
17
+ ax = fig.add_subplot(211)
18
+ ax.plot(arrAmpRes, ".-")
19
+ ax.set_title("Respiratory")
20
+ ax = fig.add_subplot(212)
21
+ ax.plot(arrAmpCar, ".-")
22
+ ax.set_title("Cardiac")
23
+
24
+ fig = figure(figsize=(6,6), dpi=120)
25
+ ax = fig.add_subplot(111)
26
+ axim = ax.imshow(zeros([nPix,nPix]), cmap='gray', vmin=0, vmax=1)
27
+
28
+ while 1:
29
+ for iT in range(nT):
30
+ arrPhant = slime.genPhant(2, nPix, arrAmpRes[iT], arrAmpCar[iT])
31
+ arrM0 = slime.Enum2M0(arrPhant, arrAmpCar[iT])
32
+
33
+ axim.set_data(arrM0)
34
+ ax.set_title(f"time: {iT*tRes:.2f}s")
35
+ draw()
36
+ pause(tRes/10)
37
+ # pause(1e-2)
@@ -0,0 +1,42 @@
1
+ from numpy import *
2
+ from matplotlib.pyplot import *
3
+ import slime
4
+ from time import time
5
+
6
+ tScan = 10
7
+ tRes = 10e-3
8
+ nT = int(tScan/tRes)
9
+ nPix = 256
10
+
11
+ cycRes = pi/2
12
+ cycCar = 1
13
+ arrAmpRes = 20e-3*slime.genAmp(tScan, tRes, cycRes, 1)
14
+ arrAmpCar = 10e-3*slime.genAmp(tScan, tRes, cycCar, 0)
15
+
16
+ fig = figure()
17
+ ax = fig.add_subplot(211)
18
+ ax.plot(arrAmpRes, ".-")
19
+ ax.set_title("Respiratory")
20
+ ax = fig.add_subplot(212)
21
+ ax.plot(arrAmpCar, ".-")
22
+ ax.set_title("Cardiac")
23
+
24
+ fig = figure(figsize=(9,3), dpi=120)
25
+ ax1 = fig.add_subplot(131)
26
+ axim1 = ax1.imshow(zeros([nPix,nPix]), cmap='gray', vmin=0, vmax=1)
27
+ ax2 = fig.add_subplot(132)
28
+ axim2 = ax2.imshow(zeros([nPix,nPix]), cmap='gray', vmin=0, vmax=1)
29
+ ax3 = fig.add_subplot(133)
30
+ axim3 = ax3.imshow(zeros([nPix,nPix]), cmap='gray', vmin=0, vmax=1)
31
+
32
+ while 1:
33
+ for iT in range(0,nT,10):
34
+ arrPhant = slime.genPhant(3, nPix, arrAmpRes[iT], arrAmpCar[iT])
35
+ arrM0 = slime.Enum2M0(arrPhant, arrAmpCar[iT])
36
+
37
+ axim1.set_data(arrM0[nPix//2,:,:])
38
+ axim2.set_data(arrM0[:,nPix//2,:])
39
+ axim3.set_data(arrM0[:,:,nPix//2])
40
+ ax2.set_title(f"time: {iT*tRes:.2f}s")
41
+ draw()
42
+ pause(tRes/10)
@@ -0,0 +1,63 @@
1
+ from numpy import *
2
+ from matplotlib.pyplot import *
3
+ import slime
4
+
5
+ # 2D
6
+ nPix = 4096
7
+ arrPhant = slime.genPhant(nAx=2, nPix=nPix)
8
+ arrM0 = slime.Enum2M0(arrPhant)
9
+
10
+ arrM0Abs = abs(arrM0)
11
+ figure(figsize=(3,3), dpi=120)
12
+ imshow(arrM0Abs, cmap="gray"); colorbar()
13
+ draw(); pause(0.5)
14
+
15
+ # 3D
16
+ nPix = 256
17
+ arrPhant = slime.genPhant(nAx=3, nPix=nPix)
18
+ arrM0 = slime.Enum2M0(arrPhant)
19
+
20
+ arrM0Abs = abs(arrM0)
21
+ figure(figsize=(9,3), dpi=120)
22
+ subplot(131)
23
+ imshow(arrM0Abs[nPix//2,:,:], cmap="gray"); colorbar()
24
+ subplot(132)
25
+ imshow(arrM0Abs[:,nPix//2,:], cmap="gray"); colorbar()
26
+ subplot(133)
27
+ imshow(arrM0Abs[:,:,nPix//2], cmap="gray"); colorbar()
28
+ draw(); pause(0.5)
29
+
30
+ # --- NEW FIGURE: animated 3D slice viewer (does not modify the static figure) ---
31
+ V = arrM0Abs # already computed above (shape: [nPix,nPix,nPix])
32
+
33
+ vmin = V.min()
34
+ vmax = V.max()
35
+
36
+ figA = figure(figsize=(9, 3), dpi=120)
37
+ ax1 = figA.add_subplot(131)
38
+ ax2 = figA.add_subplot(132)
39
+ ax3 = figA.add_subplot(133)
40
+
41
+ k0 = nPix // 2
42
+ im1 = ax1.imshow(V[k0, :, :], cmap="gray", vmin=vmin, vmax=vmax, origin="lower")
43
+ im2 = ax2.imshow(V[:, k0, :], cmap="gray", vmin=vmin, vmax=vmax, origin="lower")
44
+ im3 = ax3.imshow(V[:, :, k0], cmap="gray", vmin=vmin, vmax=vmax, origin="lower")
45
+
46
+ ax1.set_title(f"axial z={k0}")
47
+ ax2.set_title(f"coronal y={k0}")
48
+ ax3.set_title(f"sagittal x={k0}")
49
+
50
+ tight_layout()
51
+ draw()
52
+ pause(0.25)
53
+
54
+ pause_dt = 1e-6 # seconds; increase if too fast
55
+ for k in list(range(nPix)) + list(range(nPix-2, 0, -1)):
56
+ im1.set_data(V[k, :, :]); ax1.set_title(f"axial z={k}")
57
+ im2.set_data(V[:, k, :]); ax2.set_title(f"coronal y={k}")
58
+ im3.set_data(V[:, :, k]); ax3.set_title(f"sagittal x={k}")
59
+ figA.canvas.draw_idle()
60
+ pause(pause_dt)
61
+ # --- end block ---
62
+
63
+ show()
@@ -0,0 +1,61 @@
1
+ import slime
2
+ from numpy import *
3
+ from matplotlib.pyplot import *
4
+ from matplotlib.colors import ListedColormap
5
+
6
+ nDim = 3
7
+ nPix = 128
8
+
9
+ mapPh = slime.genPhMap(nDim, nPix, std=pi/16)
10
+ mapB0 = slime.genB0Map(nDim, nPix, std=1) # unit: ppm
11
+
12
+ arrPhant = slime.genPhant(nDim, nPix)
13
+ mapM0 = slime.Enum2M0(arrPhant)
14
+ mapT1 = slime.Enum2T1(arrPhant)
15
+ mapT2 = slime.Enum2T2(arrPhant)
16
+ mapOm = slime.Enum2Om(arrPhant)
17
+ mapC = slime.genCsm(nDim, nPix, mean=0, std=pi/16)
18
+
19
+ # plot
20
+ cmT1 = ListedColormap(loadtxt("./Resource/lipari.csv"), name="T1")
21
+ cmT2 = ListedColormap(loadtxt("./Resource/navia.csv"), name="T2")
22
+
23
+ figure(figsize=(9,5), dpi=120)
24
+ subplot(121)
25
+ imshow(angle(mapPh[:,nPix//2,:]), cmap="hsv", vmin=-pi, vmax=pi)
26
+ colorbar()
27
+ title("phase map")
28
+ subplot(122)
29
+ imshow(mapB0[:,nPix//2,:], vmin=-3, vmax=3)
30
+ colorbar().set_label("ppm")
31
+ title("B0 map")
32
+
33
+ mapM0Abs = abs(mapM0)
34
+ figure(figsize=(9,9), dpi=120)
35
+ subplot(221)
36
+ imshow(mapM0Abs[:,nPix//2,:], cmap="gray"); colorbar(); title("M0 map")
37
+ subplot(222)
38
+ imshow(mapT1[:,nPix//2,:]*1000, cmap=cmT1); colorbar().set_label("ms"); title("T1 map")
39
+ subplot(223)
40
+ imshow(mapT2[:,nPix//2,:]*1000, cmap=cmT2); colorbar().set_label("ms"); title("T2 map")
41
+ subplot(224)
42
+ imshow(mapOm[:,nPix//2,:]); colorbar(); title("Om map")
43
+
44
+ mapCsmAbs = abs(mapC)
45
+ mapCsmAng = angle(mapC)
46
+ fig = figure(figsize=(9,9), dpi=120)
47
+ gs = fig.add_gridspec(2,1)
48
+
49
+ subfig = fig.add_subfigure(gs[0])
50
+ for iFig in range(3*4):
51
+ ax = subfig.add_subplot(3,4,iFig+1)
52
+ axim = ax.imshow(mapCsmAbs[iFig,nPix-2,:,:], cmap="gray", vmin=0, vmax=1)
53
+ subfig.colorbar(axim)
54
+
55
+ subfig = fig.add_subfigure(gs[1])
56
+ for iFig in range(3*4):
57
+ ax = subfig.add_subplot(3,4,iFig+1)
58
+ axim = ax.imshow(mapCsmAng[iFig,nPix-2,:,:], cmap="hsv", vmin=-pi, vmax=pi)
59
+ subfig.colorbar(axim)
60
+
61
+ show()
@@ -0,0 +1,20 @@
1
+ [build-system]
2
+ requires = ["setuptools", "numpy"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "MRPhantom"
7
+ version = "0.0.0"
8
+ dependencies = ["numpy"]
9
+
10
+ description = "MRI Phantom of a Slime with Repiratory and Cardiac Motion, and M0, Phase, T1, T2, B0 maps, Boosted by a C-API Backend."
11
+ keywords = ["MRI", "Phantom", "Motion", "Quantitative"]
12
+ authors = [{name = "Ryan", email = "ryan_shanghaitech@proton.me"}]
13
+
14
+ license = "MIT"
15
+ license-files = ["LICENSE"]
16
+ readme = "README.md"
17
+
18
+ [project.urls]
19
+ Repository = "https://github.com/RyanShanghaitech/Slime"
20
+ Examples = "https://github.com/RyanShanghaitech/Slime/tree/main/example"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,39 @@
1
+ from setuptools import setup, Extension
2
+ import numpy
3
+
4
+ _sources = \
5
+ [
6
+ './slime_src/ext/main.cpp',
7
+ './slime_src/ext/slime.cpp',
8
+ ]
9
+
10
+ modExt = Extension\
11
+ (
12
+ "slime.ext",
13
+ sources = _sources,
14
+ include_dirs = ["./slime_src/ext/", numpy.get_include()],
15
+ language = 'c++',
16
+ extra_compile_args = ["-O3", "-fopenmp"],
17
+ extra_link_args = ["-fopenmp"],
18
+ )
19
+
20
+ _packages = \
21
+ [
22
+ "slime",
23
+ "slime.ext",
24
+ ]
25
+
26
+ _package_dir = \
27
+ {
28
+ "slime":"./slime_src/",
29
+ "slime.ext":"./slime_src/ext/",
30
+ }
31
+
32
+ setup\
33
+ (
34
+ name = 'slime',
35
+ ext_modules = [modExt],
36
+ packages = _packages,
37
+ package_dir = _package_dir,
38
+ include_package_data = True
39
+ )
@@ -0,0 +1,144 @@
1
+ import slime.ext as ext
2
+ from numpy import *
3
+ from enum import Enum
4
+ from scipy.ndimage import gaussian_filter
5
+
6
+ class Tissue(Enum):
7
+ Air = 0
8
+ Fat = 1
9
+ Myo = 2
10
+ Blood = 3
11
+ Fill = 4
12
+ Vessel = 5
13
+
14
+ def genPhant(nAx:int=2, nPix:int=256, ampRes:float=0, ampCar:float=0) -> ndarray: # call C++ backend to generate a phantom
15
+ '''
16
+ nAx: number of dimensions
17
+ nPix: number of pixels
18
+ ampRes: respiratory motion amplitude
19
+ ampCar: cardiac motion amplitude
20
+ '''
21
+ return ext.genPhant(nAx, nPix, ampRes, ampCar)
22
+
23
+ def genPhMap(nAx:int=2, nPix:int=256, mean:int|float|None=None, std:int|float=pi/16) -> ndarray:
24
+ '''
25
+ # return
26
+ smooth complex rotation factor
27
+ '''
28
+ if mean == None: mean = random.uniform(-pi,pi)
29
+ mapPh = random.uniform(-pi, pi, [nPix for _ in range(nAx)])
30
+ sigma = nPix/4
31
+ mapPh = gaussian_filter(mapPh, sigma)
32
+ # normalize
33
+ mapPh -= mapPh.mean(); mapPh = asarray(mapPh)
34
+ mapPh /= mapPh.std()
35
+ mapPh *= std
36
+ mapPh += mean
37
+ # convert to rotation factor
38
+ mapPh = exp(1j*mapPh)
39
+ mapPh = mapPh/abs(mapPh)
40
+ return mapPh
41
+
42
+ def genB0Map(nAx:int=2, nPix:int=256, mean:int|float=0, std:int|float=1e-6*(2*pi*42.58e6*3)) -> ndarray:
43
+ '''
44
+ # return
45
+ smooth random number
46
+ '''
47
+ mapB0 = random.uniform(-1, 1, [nPix for _ in range(nAx)])
48
+ sigma = nPix/4
49
+ mapB0 = gaussian_filter(mapB0, sigma)
50
+ # normalize
51
+ mapB0 -= mapB0.mean(); mapB0 = asarray(mapB0)
52
+ mapB0 /= mapB0.std()
53
+ mapB0 *= std
54
+ mapB0 += mean
55
+ return mapB0
56
+
57
+ def genCsm(nAx:int=2, nPix:int=256, nCh:int=12, mean:int|float|None=None, std:int|float=pi/16) -> ndarray:
58
+ if mean == None: mean = random.uniform(-pi,pi)
59
+ mapC = zeros([nCh,*(nPix for _ in range(nAx))], dtype=complex128)
60
+ arrCoor = meshgrid\
61
+ (
62
+ *(linspace(-0.5,0.5,nPix,0) for _ in range(nAx)),
63
+ indexing="ij"
64
+ ); arrCoor = array(arrCoor).transpose(*arange(1,nAx+1), 0)
65
+ arrTht = linspace(0,2*pi,nCh,0)
66
+ arrCoorCoil = zeros([nCh,nAx], dtype=float64)
67
+ arrCoorCoil[:,-2:] = 1*array([sin(arrTht), cos(arrTht)]).T
68
+ if nAx == 3:
69
+ arrCoorCoil[0::2,0] = 0.2
70
+ arrCoorCoil[1::2,0] = -0.2
71
+ for iCh in range(nCh):
72
+ mapC[iCh] = genPhMap(nAx=nAx, nPix=nPix, mean=mean, std=std)
73
+ dist = sqrt(sum((arrCoor - arrCoorCoil[iCh])**2, axis=-1))
74
+ mapC[iCh] *= exp(-dist)
75
+ return mapC
76
+
77
+ def genAmp(tScan:int|float, tRes:int|float, cyc:int|float, isRand:bool=True) -> ndarray:
78
+ '''
79
+ # parameter
80
+ `tScan`: how many seconds this waveform contains
81
+ `tRes`: how many seconds per frame
82
+ `cyc`: cycle of desired signal in second
83
+ `isRand`: whether to randomize the waveform
84
+ '''
85
+ nT = int(tScan/tRes)
86
+
87
+ if isRand:
88
+ arrT = sort(random.rand(nT)*tScan)
89
+ arrAmp = sin(2*pi/cyc*arrT)
90
+
91
+ sigma = cyc/tRes/8
92
+ arrAmp = gaussian_filter(arrAmp, sigma)
93
+ else:
94
+ arrT = linspace(0, tScan, nT)
95
+ arrAmp = sin(2*pi/cyc*arrT)
96
+
97
+ return arrAmp
98
+
99
+ def genResAmp(tScan:int|float, tRes:int|float, cyc:int|float=pi/2) -> ndarray:
100
+ return 20e-3*genAmp(tScan, tRes, cyc, 1)
101
+
102
+ def genCarAmp(tScan:int|float, tRes:int|float, cyc:int|float=1) -> ndarray:
103
+ return 10e-3*genAmp(tScan, tRes, cyc, 0)
104
+
105
+ def Enum2M0(arrPhan:ndarray, ampCar:double=0e0) -> ndarray:
106
+ mapM0 = zeros_like(arrPhan, dtype=float64)
107
+ mapM0[arrPhan==Tissue.Air.value] = 0 # random.randn(sum(arrPhan==Part.Air.value))*1e-3
108
+ mapM0[arrPhan==Tissue.Fat.value] = 1.0
109
+ mapM0[arrPhan==Tissue.Myo.value] = 0.2
110
+ mapM0[arrPhan==Tissue.Blood.value] = 0.8
111
+ mapM0[arrPhan==Tissue.Fill.value] = 0.5
112
+ mapM0[arrPhan==Tissue.Vessel.value] = 0.8 - ampCar*10
113
+ return mapM0
114
+
115
+ def Enum2T1(arrPhan:ndarray) -> ndarray:
116
+ mapT1 = zeros_like(arrPhan, dtype=float64)
117
+ mapT1[arrPhan==Tissue.Air.value] = inf # 10000e-3 #random.uniform(1e-3, 10000e-3, sum(arrPhan==Part.Air.value))
118
+ mapT1[arrPhan==Tissue.Fat.value] = 350e-3
119
+ mapT1[arrPhan==Tissue.Myo.value] = 1300e-3
120
+ mapT1[arrPhan==Tissue.Blood.value] = 1500e-3
121
+ mapT1[arrPhan==Tissue.Fill.value] = 1600e-3
122
+ mapT1[arrPhan==Tissue.Vessel.value] = 1500e-3
123
+ return mapT1
124
+
125
+ def Enum2T2(arrPhan:ndarray) -> ndarray:
126
+ mapT2 = zeros_like(arrPhan, dtype=float64)
127
+ mapT2[arrPhan==Tissue.Air.value] = 1e-6 # random.uniform(1e-3, 1000e-3, sum(arrPhan==Part.Air.value))
128
+ mapT2[arrPhan==Tissue.Fat.value] = 75e-3
129
+ mapT2[arrPhan==Tissue.Myo.value] = 50e-3
130
+ mapT2[arrPhan==Tissue.Blood.value] = 225e-3
131
+ mapT2[arrPhan==Tissue.Fill.value] = 40e-3
132
+ mapT2[arrPhan==Tissue.Vessel.value] = 225e-3
133
+ return mapT2
134
+
135
+ def Enum2Om(arrPhan:ndarray, B0:int|float=3) -> ndarray:
136
+ mapOm = zeros_like(arrPhan, dtype=float64)
137
+ ppm2om = 1e-6*(2*pi*42.58e6*B0)
138
+ mapOm[arrPhan==Tissue.Air.value] = 0 # random.uniform(1e-3, 10e-3, sum(arrPhan==Part.Air.value))*ppm2om
139
+ mapOm[arrPhan==Tissue.Fat.value] = 3.5*ppm2om
140
+ mapOm[arrPhan==Tissue.Myo.value] = 0
141
+ mapOm[arrPhan==Tissue.Blood.value] = 0
142
+ mapOm[arrPhan==Tissue.Fill.value] = 0
143
+ mapOm[arrPhan==Tissue.Vessel.value] = 0
144
+ return mapOm
@@ -0,0 +1,5 @@
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, Enum2M0, Enum2T1, Enum2T2, Enum2Om
@@ -0,0 +1,66 @@
1
+ #define PY_SSIZE_T_CLEAN
2
+ #include <Python.h>
3
+ #include <numpy/arrayobject.h>
4
+
5
+ #include <vector>
6
+ #include <cstring>
7
+ #include <slime.h>
8
+
9
+ bool inline checkNarg(int64_t lNarg, int64_t lNargExp)
10
+ {
11
+ if (lNarg != lNargExp)
12
+ {
13
+ printf("wrong num. of arg, narg=%ld, %ld expected\n", lNarg, lNargExp);
14
+ abort();
15
+ return false;
16
+ }
17
+ return true;
18
+ }
19
+
20
+ static PyObject* genPhant_py(PyObject* self, PyObject* const* args, Py_ssize_t nargs)
21
+ {
22
+ checkNarg(nargs,4);
23
+ int64_t lNAx = PyLong_AsLongLong(args[0]);
24
+ int64_t lNPix = PyLong_AsLongLong(args[1]);
25
+ double dResAmp = PyFloat_AsDouble(args[2]);
26
+ double dCarAmp = PyFloat_AsDouble(args[3]);
27
+
28
+ // Generate into std::vector
29
+ std::vector<uint8_t> vu8Phant;
30
+ genPhant(lNAx, lNPix, dResAmp, dCarAmp, &vu8Phant);
31
+
32
+ // convert vector to numpy array
33
+ PyObject* ppyoNpa;
34
+ {
35
+ npy_intp aDims[] = {lNPix, lNPix, lNPix};
36
+ ppyoNpa = PyArray_ZEROS(lNAx, aDims, NPY_UINT8, 0);
37
+ }
38
+
39
+ // fill the data in
40
+ std::memcpy(PyArray_DATA((PyArrayObject*)ppyoNpa),
41
+ vu8Phant.data(),
42
+ vu8Phant.size() * sizeof(uint8_t));
43
+
44
+ return ppyoNpa;
45
+ }
46
+
47
+ static PyMethodDef aMeth[] =
48
+ {
49
+ {"genPhant", (PyCFunction)genPhant_py, METH_FASTCALL, "genPhant(lNAx, lNPix, dResAmp, dCarAmp) -> np.ndarray[uint8]"},
50
+ {NULL, NULL, 0, NULL}
51
+ };
52
+
53
+ static struct PyModuleDef sMod =
54
+ {
55
+ PyModuleDef_HEAD_INIT,
56
+ "ext",
57
+ NULL,
58
+ -1,
59
+ aMeth
60
+ };
61
+
62
+ PyMODINIT_FUNC PyInit_ext(void)
63
+ {
64
+ import_array(); // required for NumPy C-API
65
+ return PyModule_Create(&sMod);
66
+ }
@@ -0,0 +1,163 @@
1
+ #include <vector>
2
+ #include <cstdint>
3
+ #include <cmath>
4
+ #include <stdexcept>
5
+
6
+ #include <omp.h>
7
+
8
+ // Tissue
9
+ enum Tissue : uint8_t
10
+ {
11
+ Air = 0,
12
+ Fat = 1,
13
+ Myo = 2,
14
+ Blood = 3,
15
+ Fill = 4, // we fill the remaining with skeletal mussles
16
+ Vessel = 5,
17
+ };
18
+
19
+ bool isInsideEllipsoid
20
+ (
21
+ double x, double y, double z,
22
+ double cx, double cy, double cz,
23
+ double rX, double rY, double rZ
24
+ )
25
+ {
26
+ if (rX <= 0e0 || rY <= 0e0 || rZ <= 0e0) return false;
27
+
28
+ const double dx = x - cx;
29
+ const double dy = y - cy;
30
+ const double dz = z - cz;
31
+
32
+ const double v =
33
+ (dx*dx) / (rX*rX) +
34
+ (dy*dy) / (rY*rY) +
35
+ (dz*dz) / (rZ*rZ);
36
+
37
+ return v <= 1e0;
38
+ }
39
+
40
+ bool genPhant
41
+ (
42
+ int64_t lNAx, int64_t lNPix,
43
+ double dResAmp, double dCarAmp,
44
+ std::vector<uint8_t>* voSlime
45
+ )
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));
54
+
55
+ // 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;
59
+
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;
63
+
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;
67
+
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;
71
+
72
+ // Centers are at (0,0,0) in your centered coordinate system,
73
+ const double dFat_cx = 0e0, dFat_cy = 0e0, dFat_cz = 0e0;
74
+ 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;
76
+
77
+ /* generate phantom given by `dResAmp` and `dCarAmp` here */
78
+ #pragma omp parallel for schedule(static)
79
+ for (int64_t i = 0; i < lNPix_Flat; ++i)
80
+ {
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;
84
+
85
+ // decide what tissue current pixel is
86
+ if (!isInsideEllipsoid(x,y,z, dFat_cx,dFat_cy,dFat_cz, dFatOt_rX,dFatOt_rY,dFatOt_rZ))
87
+ {
88
+ (*voSlime)[(size_t)i] = (uint8_t)Tissue::Air;
89
+ continue;
90
+ }
91
+
92
+ if (!isInsideEllipsoid(x,y,z, dFat_cx,dFat_cy,dFat_cz, dFatIn_rX,dFatIn_rY,dFatIn_rZ))
93
+ {
94
+ (*voSlime)[(size_t)i] = (uint8_t)Tissue::Fat;
95
+ continue;
96
+ }
97
+
98
+ // vessel balls (move with FatIn distortion; positioned away from heart)
99
+ const double dFatIn0_rX = dNPix*380e-3;
100
+ const double dFatIn0_rY = dNPix*380e-3;
101
+ const double dFatIn0_rZ = dNPix*450e-3;
102
+
103
+ const double sX = dFatIn_rX / dFatIn0_rX;
104
+ const double sY = dFatIn_rY / dFatIn0_rY;
105
+ const double sZ = dFatIn_rZ / dFatIn0_rZ;
106
+
107
+ // vessel balls
108
+ #define V_HIT(cx,cy,cz,div) isInsideEllipsoid(x,y,z, ((cx)*dNPix*sX), ((cy)*dNPix*sY), ((cz)*dNPix*sZ), (dFatIn_rX/(div)), (dFatIn_rY/(div)), (dFatIn_rZ/(div)))
109
+
110
+ // 48 “random” vessels (cx,cy,cz in dNPix fractions; div in ~[18..44])
111
+ #define VLIST \
112
+ /* 5 on XY plane (z = 0) — slightly farther from heart */ \
113
+ V_HIT( 0.18, 0.09, 0.00, 12) || \
114
+ V_HIT(-0.19, 0.07, 0.00, 16) || \
115
+ V_HIT( 0.11, -0.17, 0.00, 19) || \
116
+ V_HIT(-0.13, -0.16, 0.00, 21) || \
117
+ V_HIT( 0.04, 0.20, 0.00, 22) || \
118
+ /* 5 on YZ plane (x = 0) — adjusted to avoid closeness with XZ plane (y=0) */ \
119
+ V_HIT( 0.00, 0.14, 0.13, 12) || \
120
+ V_HIT( 0.00, -0.15, 0.12, 16) || \
121
+ V_HIT( 0.00, 0.09, -0.17, 19) || \
122
+ V_HIT( 0.00, -0.12, -0.16, 21) || \
123
+ V_HIT( 0.00, 0.23, 0.06, 22) || \
124
+ /* 5 on XZ plane (y = 0) — adjusted corresponding “near” one */ \
125
+ V_HIT( 0.16, 0.00, 0.12, 12) || \
126
+ V_HIT(-0.17, 0.00, 0.11, 16) || \
127
+ V_HIT( 0.13, 0.00, -0.16, 19) || \
128
+ V_HIT(-0.12, 0.00, -0.17, 21) || \
129
+ V_HIT( 0.23, 0.00, -0.06, 22) || \
130
+ /* 8 octants (near heart, slightly pushed outward) */ \
131
+ V_HIT( 0.14, 0.13, 0.12, 16) || /* + + + */ \
132
+ V_HIT(-0.15, 0.13, 0.12, 16) || /* - + + */ \
133
+ V_HIT( 0.14, -0.14, 0.12, 16) || /* + - + */ \
134
+ V_HIT(-0.15, -0.14, 0.12, 16) || /* - - + */ \
135
+ V_HIT( 0.14, 0.13, -0.13, 16) || /* + + - */ \
136
+ V_HIT(-0.15, 0.13, -0.13, 16) || /* - + - */ \
137
+ V_HIT( 0.14, -0.14, -0.13, 16) || /* + - - */ \
138
+ V_HIT(-0.15, -0.14, -0.13, 16) /* - - - */
139
+
140
+ if ( VLIST )
141
+ {
142
+ (*voSlime)[(size_t)i] = (uint8_t)Tissue::Vessel;
143
+ continue;
144
+ }
145
+ // vessel balls (end)
146
+
147
+ if (!isInsideEllipsoid(x,y,z, dMyoOt_cx,dMyoOt_cy,dMyoOt_cz, dMyoOt_rX,dMyoOt_rY,dMyoOt_rZ))
148
+ {
149
+ (*voSlime)[(size_t)i] = (uint8_t)Tissue::Fill;
150
+ continue;
151
+ }
152
+
153
+ if (!isInsideEllipsoid(x,y,z, dMyoIn_cx,dMyoIn_cy,dMyoIn_cz, dMyoIn_rX,dMyoIn_rY,dMyoIn_rZ))
154
+ {
155
+ (*voSlime)[(size_t)i] = (uint8_t)Tissue::Myo;
156
+ continue;
157
+ }
158
+
159
+ (*voSlime)[(size_t)i] = (uint8_t)Tissue::Blood;
160
+ }
161
+
162
+ return true;
163
+ }
@@ -0,0 +1,11 @@
1
+ #pragma once
2
+
3
+ #include <vector>
4
+ #include <cstdint>
5
+
6
+ extern bool genPhant
7
+ (
8
+ int64_t lNDim, int64_t lNPix,
9
+ double dResAmp, double dCarAmp,
10
+ std::vector<uint8_t>* voSlime
11
+ );