Skip to content

Commit cd4edfd

Browse files
committed
ready for ismrm
1 parent fa8e28e commit cd4edfd

File tree

5 files changed

+113
-9
lines changed

5 files changed

+113
-9
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
"""SSFP MR Fingerprinting simulator"""
2+
3+
def mrf(flip, TR, T1, T2, diff=None, device="cpu"):
4+
"""
5+
Simulate an inversion-prepared SSFP sequence with variable flip angles.
6+
7+
Args:
8+
flip (array-like): Flip angle in [deg] of shape (npulses,).
9+
TR (float): Repetition time in [ms].
10+
T1 (float, array-like): Longitudinal relaxation time in [ms].
11+
T2 (float, array-like): Transverse relaxation time in [ms].
12+
diff (optional, str, tuple[str]): Arguments to get the signal derivative with respect to.
13+
Defaults to None (no differentation).
14+
device (optional, str): Computational device. Defaults to "cpu".
15+
"""
16+
# initialize simulator
17+
simulator = SSFPMRF(T1=T1, T2=T2, device=device, diff=diff)
18+
19+
# run simulator
20+
if diff:
21+
# actual simulation
22+
sig, dsig = simulator(flip=flip, TR=TR)
23+
return sig.detach().cpu().numpy(), dsig.detach().cpu().numpy()
24+
25+
else:
26+
# actual simulation
27+
sig = simulator(flip=flip, TR=TR)
28+
return sig.cpu().numpy()
29+
30+
# Simulator
31+
from epgtorchx import base
32+
from epgtorchx import ops
33+
34+
class SSFPMRF(base.BaseSimulator):
35+
"""Class to simulate inversion-prepared (variable flip angle) SSFP."""
36+
37+
@staticmethod
38+
def sequence(flip, TR, T1, T2, states, signal):
39+
40+
# get number of frames and echoes
41+
device = flip.device
42+
npulses = flip.shape[0]
43+
44+
# define operators
45+
# preparation
46+
InvPulse = ops.RFPulse(device, alpha=180.0)
47+
Crusher = ops.Spoil()
48+
Prep = ops.CompositeOperator(Crusher, InvPulse)
49+
50+
# readout
51+
RF = ops.RFPulse(device) # excitation
52+
E = ops.Relaxation(device, TR, T1, T2) # relaxation until TR
53+
S = ops.Shift() # gradient spoil
54+
55+
# actual sequence loop
56+
for n in range(npulses):
57+
# apply pulse
58+
states = RF(states, flip[n])
59+
60+
# record signal
61+
signal[n] = ops.observe(states)
62+
63+
# relax, recover and spoil
64+
states = E(states)
65+
states = S(states)
66+
67+
return signal

examples/scripts/02-derivatives.py

Lines changed: 43 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -104,18 +104,53 @@ def crlb_finitediff_cost(flip, ESP, T1, T2):
104104
tcost = t1 - t0
105105

106106
# plot derivative
107+
#%%
108+
fsz = 20
107109
plt.figure()
108-
plt.rcParams.update({'font.size': 22})
110+
plt.subplot(2,2,1)
111+
plt.rcParams.update({'font.size': 0.5 * fsz})
112+
plt.plot(angles, '.')
113+
plt.xlabel("Echo #", fontsize=fsz)
114+
plt.xlim([-1, 51])
115+
plt.ylabel("Flip Angle [deg]", fontsize=fsz)
116+
117+
plt.subplot(2,2,2)
118+
plt.rcParams.update({'font.size': 0.5 * fsz})
109119
plt.plot(abs(grad), '-k'), plt.plot(abs(grad0), '*r')
110-
plt.xlabel("Echo #", fontsize=40)
120+
plt.xlabel("Echo #", fontsize=fsz)
111121
plt.xlim([-1, 51])
112-
plt.ylabel(r"$\frac{\partial signal}{\partial T2}$ [a.u.]", fontsize=40)
122+
plt.ylabel(r"$\frac{\partial signal}{\partial T2}$ [a.u.]", fontsize=fsz)
113123
plt.legend(["Finite Diff", "Auto Diff"])
114124

115-
plt.figure()
116-
plt.rcParams.update({'font.size': 22})
125+
126+
plt.subplot(2,2,3)
127+
plt.rcParams.update({'font.size': 0.5 * fsz})
117128
plt.plot(abs(dcost), '-k'), plt.plot(abs(dcost0), '*r')
118-
plt.xlabel("Echo #", fontsize=40)
129+
plt.xlabel("Echo #", fontsize=fsz)
119130
plt.xlim([-1, 51])
120-
plt.ylabel(r"$\frac{\partial CRLB}{\partial FA}$ [a.u.]", fontsize=40)
121-
plt.legend(["Finite Diff", "Auto Diff"])
131+
plt.ylabel(r"$\frac{\partial CRLB}{\partial FA}$ [a.u.]", fontsize=fsz)
132+
plt.legend(["Finite Diff", "Auto Diff"])
133+
134+
plt.subplot(2,2,4)
135+
136+
# define labels
137+
# plot results
138+
labels = ['derivative of signal', 'CRLB objective gradient']
139+
time_finite = [round(tgrad0, 2), round(tcost0, 2)]
140+
time_auto = [round(tgrad, 2), round(tcost, 2)]
141+
142+
143+
x = np.arange(len(labels)) # the label locations
144+
width = 0.35 # the width of the bars
145+
rects1 = plt.bar(x + width/2, time_finite, width, label='Finite Diff')
146+
rects2 = plt.bar(x - width/2, time_auto, width, label='Auto Diff')
147+
148+
# Add some text for labels, title and custom x-axis tick labels, etc.
149+
plt.ylabel('Execution Time [s]', fontsize=fsz)
150+
plt.xticks(x, labels, fontsize=fsz)
151+
plt.ylim([0, 25])
152+
plt.legend()
153+
154+
plt.bar_label(rects1, padding=3, fontsize=fsz)
155+
plt.bar_label(rects2, padding=3, fontsize=fsz)
156+

src/epgtorchx/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
from . import regression
2424

2525
from .bloch import * # noqa
26+
from .bloch import base
27+
from .bloch import ops
2628
from .phantoms import * # noqa
2729

2830
__all__ = []

src/epgtorchx/bloch/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from . import model as _model
1111
from . import ops
1212
from . import blocks
13+
from .model import base
1314

1415
from .model import * # noqa
1516

src/epgtorchx/bloch/model/base.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -160,7 +160,6 @@ def simulate_ssfp(flip, TR, T1, T2, diff=None, device="cpu"):
160160
B1phase (optional, Union[float, npt.NDArray[float], torch.FloatTensor]): B1 relative phase in [deg]. (0.0 := nominal rf phase). Defaults to None.
161161
162162
"""
163-
164163
# main properties
165164
T1: Union[float, npt.NDArray[float], torch.FloatTensor] # ms
166165
T2: Union[float, npt.NDArray[float], torch.FloatTensor] # ms

0 commit comments

Comments
 (0)