Skip to content

Commit 971a814

Browse files
authored
Merge pull request #42 from gpilab/develop
Merge for version 2.3.0
2 parents 5a2c515 + 1c323c9 commit 971a814

File tree

7 files changed

+1517
-2
lines changed

7 files changed

+1517
-2
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
*.so
66
*.o
77
*.DS_Store
8+
*.zip
89
build/

gridding/GPI/Grid_GPI.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -98,7 +98,7 @@ def execType(self):
9898
def initUI(self):
9999

100100
# Widgets
101-
self.addWidget('Slider','Dims per Set',min=1)
101+
self.addWidget('Slider','Dims per Set',min=1,val=2)
102102
self.addWidget('SpinBox','Eff MTX XY', min=5, val=240)
103103
self.addWidget('SpinBox','Eff MTX Z', min=5, val=240)
104104
self.addWidget('DoubleSpinBox','dx (pixels)', val=0.0)

gridding/GPI/SDC_GPI.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def initUI(self):
6767

6868
# Widgets
6969
self.addWidget('PushButton','computenow',toggle=True)
70-
self.addWidget('Slider','Dims per Set',min=1)
70+
self.addWidget('Slider','Dims per Set',min=1,val=2)
7171
self.addWidget('SpinBox','Iterations',val=1, min=1)
7272
self.addWidget('DoubleSpinBox','Effective MTX XY',val=300.0, min=2.0)
7373
self.addWidget('DoubleSpinBox','Effective MTX Z',val=300.0, min=2.0)

spiral/GPI/SpiralGenCF_GPI.py

Lines changed: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
1+
# The GPI core node library is licensed under
2+
# either the BSD 3-clause or the LGPL v. 3.
3+
#
4+
# Under either license, the following additional term applies:
5+
#
6+
# NO CLINICAL USE. THE SOFTWARE IS NOT INTENDED FOR COMMERCIAL
7+
# PURPOSES AND SHOULD BE USED ONLY FOR NON-COMMERCIAL RESEARCH PURPOSES. THE
8+
# SOFTWARE MAY NOT IN ANY EVENT BE USED FOR ANY CLINICAL OR DIAGNOSTIC
9+
# PURPOSES. YOU ACKNOWLEDGE AND AGREE THAT THE SOFTWARE IS NOT INTENDED FOR
10+
# USE IN ANY HIGH RISK OR STRICT LIABILITY ACTIVITY, INCLUDING BUT NOT LIMITED
11+
# TO LIFE SUPPORT OR EMERGENCY MEDICAL OPERATIONS OR USES. LICENSOR MAKES NO
12+
# WARRANTY AND HAS NOR LIABILITY ARISING FROM ANY USE OF THE SOFTWARE IN ANY
13+
# HIGH RISK OR STRICT LIABILITY ACTIVITIES.
14+
#
15+
# If you elect to license the GPI core node library under the LGPL the
16+
# following applies:
17+
#
18+
# This file is part of the GPI core node library.
19+
#
20+
# The GPI core node library is free software: you can redistribute it
21+
# and/or modify it under the terms of the GNU Lesser General Public License as
22+
# published by the Free Software Foundation, either version 3 of the License,
23+
# or (at your option) any later version. GPI core node library is distributed
24+
# in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
25+
# the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
26+
# See the GNU Lesser General Public License for more details.
27+
#
28+
# You should have received a copy of the GNU Lesser General Public
29+
# License along with the GPI core node library. If not, see
30+
# <http://www.gnu.org/licenses/>.
31+
32+
33+
# Author: Jim Pipe
34+
# Date: 2020Oct
35+
36+
import gpi
37+
38+
class ExternalNode(gpi.NodeAPI):
39+
40+
"""Module to generate the gradient waveforms for a desired k-space
41+
waveform. Uses the core files spiralgencf_gen.c and spiralgencf_fill.cpp
42+
43+
INPUTS:
44+
GIRF_in - (optional) gradient impulse response function for gradient preconditioning
45+
46+
OUTPUTS:
47+
crds_out - output coordinates: the last dimension is 2 (kx/ky).
48+
grd_out - gradient waveforms used to produce crds_out. Dwell time is SPGRAST
49+
50+
WIDGETS: most widgets self-explanatory, here are a few clarifications:
51+
min undersample - R, i.e. the undersampling relative to (1/FOV) before
52+
kr reaches usamp st
53+
max undersample - R, i.e. the undersampling relative to (1/FOV) after
54+
kr exceeds usamp end
55+
usamp st (0-1) - the relative value of kr at which undersampling begins (0
56+
at the center, 1 at the edge of collected k-space) the samples are
57+
collected at the nyquist limit (1/FOV) prior to that
58+
usamp end (0-1) - the relative value of kr at which undersampling ends
59+
the samples are collected at the R times the nyquist limit (1/FOV)
60+
prior to that
61+
Max G Freq - limits the maximum frequency of the gradient waveform during
62+
the spiral readout. If set to 0, there is no limit (default minimum is 0.5 kHz)
63+
Start Window - time for rounding enforced when starting
64+
Corner Window - an angle determining the rounding enforced when
65+
transitioning between (freq, slew,grad) limits
66+
spinout - controls spiral in, out, etc.
67+
OUT - generate spiral-out waveform
68+
IN - generate spiral-in waveform
69+
OUT180 - generate spiral-out waveform and negate the waveform
70+
71+
****************************************
72+
*** The concept of "true resolution" ***
73+
k-space data are typically normalized, for the gridding process, to values
74+
between -0.5 and 0.5 For spiral data (which measure circular k-space) to
75+
have the same resolution as Cartesian (square k-space) they must measure a
76+
diameter of 2/sqrt(pi) ~ 1.13 larger than conventional k-space limits (so
77+
that the area of the circle equals the area of the square). k-space
78+
coordinates, therefore, are multiplied by 0.8, so that their range of
79+
0.8*2/sqrt(pi) = 0.903, or -0.451 to +0.451, fits within the gridded space.
80+
The resulting image, with no further zero-padding, will have pixels that
81+
are 0.8 times smaller than the requested resolution, with a matrix 25%
82+
larger in each dimension. This is a semi-complicated way of making sure
83+
that this works routinely, and is referred to by the authors as "true
84+
resolution".
85+
*****************************************
86+
"""
87+
88+
def execType(self):
89+
return gpi.GPI_PROCESS
90+
91+
def initUI(self):
92+
93+
import numpy as np
94+
95+
# Widgets
96+
self.addWidget('PushButton', 'compute', toggle=True)
97+
self.addWidget('TextBox', 'Info:')
98+
99+
self.addWidget('DoubleSpinBox', 'FOV (cm)',
100+
val=24.0, min=0.1, decimals=6)
101+
self.addWidget('DoubleSpinBox', 'Res (mm)',
102+
val=0.8, min=0.1, singlestep=0.1, decimals=5)
103+
self.addWidget('SpinBox', '# of Spiral Arms', val=16, min=1)
104+
105+
self.addWidget('ExclusivePushButtons', 'spinout',
106+
buttons=['OUT', 'IN', 'OUT180'],val=0)
107+
108+
self.addWidget('DoubleSpinBox', 'AD dwell time (us)',
109+
val=1.0, min=0.1, decimals=6)
110+
self.addWidget('DoubleSpinBox', 'MaxSlw (mT/m/ms)',
111+
val=150.0, min=0.01, decimals=6)
112+
self.addWidget('DoubleSpinBox', 'MaxGrd (mT/m)',
113+
val=40.0, min=0.01, decimals=6)
114+
115+
self.addWidget('DoubleSpinBox', 'min undersample',
116+
val=1.0, min=0.0, max=100.0)
117+
self.addWidget('DoubleSpinBox', 'max undersample',
118+
val=1.0, min=0.0, max=100.0)
119+
self.addWidget('DoubleSpinBox', 'usamp st (0 - 1)',
120+
val=0.0, min=0.0, max=1.0, singlestep=0.01)
121+
self.addWidget('DoubleSpinBox', 'usamp end (0 - 1)',
122+
val=1.0, min=0.0, max=1.0, singlestep=0.01)
123+
124+
self.addWidget('PushButton', 'Precompensate', toggle=True, val=1)
125+
self.addWidget('PushButton', 'Precondition', toggle=True, val=1)
126+
127+
self.addWidget('DoubleSpinBox', 'Max G Freq (kHz)', val=1.0, min=0.5, max=20.)
128+
self.addWidget('DoubleSpinBox', 'Start Window (us)', val=200.0, min=0.0)
129+
self.addWidget('DoubleSpinBox', 'End Window (us)', val=100.0, min=0.0)
130+
self.addWidget('DoubleSpinBox', 'Corner Window (cycles)', val=0.5, min=0.0)
131+
132+
self.addWidget('DoubleSpinBox', 'TrueRes Factor', val=1.0,
133+
min=0.0, max=1.0)
134+
135+
self.addWidget('DoubleSpinBox', 'x delay (us)',
136+
val=0.0, min=-100.0, max=100.0, visible = False)
137+
self.addWidget('DoubleSpinBox', 'y delay (us)',
138+
val=0.0, min=-100.0, max=100.0, visible = False)
139+
140+
self.addWidget('DoubleSpinBox', 'Gam (kHz/mT)',
141+
val=42.577, min=0.01, decimals=6, visible = False) # hide this until we need it
142+
143+
# IO Ports
144+
self.addInPort('GIRF_in', 'NPYarray',dtype=[np.float32,np.float64],ndim=1,obligation=gpi.OPTIONAL)
145+
self.addOutPort('crds_out', 'NPYarray')
146+
self.addOutPort('grd_out', 'NPYarray')
147+
self.addOutPort('gtf_out', 'NPYarray')
148+
149+
def compute(self):
150+
151+
import numpy as np
152+
153+
# convert units to ms, kHz, m, mT
154+
dwell = 0.001 * self.getVal('AD dwell time (us)')
155+
xdely = 0.001 * self.getVal('x delay (us)')
156+
ydely = 0.001 * self.getVal('y delay (us)')
157+
158+
mslew = self.getVal('MaxSlw (mT/m/ms)')
159+
mgrad = self.getVal('MaxGrd (mT/m)')
160+
gamma = self.getVal('Gam (kHz/mT)')
161+
162+
fov = 0.01 * self.getVal('FOV (cm)')
163+
164+
narms = float(self.getVal('# of Spiral Arms'))
165+
166+
trures_fac = self.getVal('TrueRes Factor')
167+
trures_acq = trures_fac * np.sqrt(np.pi)/2 + 1 - trures_fac
168+
169+
res = 0.001 * self.getVal('Res (mm)')
170+
171+
us_0 = self.getVal('usamp st (0 - 1)')
172+
us_1 = self.getVal('usamp end (0 - 1)')
173+
us_r0 = self.getVal('min undersample')
174+
us_r = self.getVal('max undersample')
175+
176+
precomp = self.getVal('Precompensate')
177+
precond = self.getVal('Precondition')
178+
179+
mgfrq = self.getVal('Max G Freq (kHz)')
180+
start_win = 0.001*self.getVal('Start Window (us)') # change to ms
181+
end_win = 0.001*self.getVal('End Window (us)') # change to ms
182+
corner_win = 2.*np.pi*self.getVal('Corner Window (cycles)') # change to radians
183+
184+
spinout = self.getVal('spinout')
185+
186+
if self.getVal('compute'):
187+
188+
girf = self.getData('GIRF_in')
189+
if girf is not None:
190+
# The first point should not be 0
191+
while girf[0] == 0:
192+
girf = girf[1:]
193+
# normalize to have unit area, so DC part of MTF is 1
194+
girf = np.float64(girf)/np.sum(np.float64(girf))
195+
else:
196+
# Make it a delta function
197+
girf = np.float64(np.array([1.,0.]))
198+
199+
gtf_res = 0.05 # spectral resolution in kHz
200+
spgrast = 0.005 # gradient raster in ms
201+
# gtf_len*spgrast = 1/gtf_res
202+
# force gtf_len to be even
203+
gtf_len = 2*int(0.5/(gtf_res*spgrast))
204+
205+
gtf = np.absolute(np.fft.fft(np.pad(girf,(0,gtf_len-girf.shape[0]))))
206+
207+
# import in thread to save namespace
208+
# spiralgencf corresponds to spiralgencf_PyMOD.cpp
209+
import gpi_core.spiral.spiralgencf as sp
210+
211+
print("end win",end_win)
212+
grd_out, crds_out = sp.coords(
213+
girf,gtf,dwell, xdely, ydely, mslew, mgrad, gamma,
214+
fov, res, narms,
215+
us_0, us_1, us_r0, us_r,
216+
mgfrq, precomp, precond, start_win, end_win, corner_win, spinout,
217+
trures_acq)
218+
219+
# Report Back to User
220+
nsamp = np.array(crds_out.shape)[-2]
221+
222+
spgrast = 0.005 # Gradient raster time in ms
223+
224+
smp = "Samples: " + str(nsamp) + "\n"
225+
tau = "Tau (ms): " + str(dwell * nsamp) + "\n"
226+
tgrad = spgrast * np.array(grd_out.shape)[1]
227+
tgr = "TGrad (ms): " + str(tgrad) + "\n"
228+
info = smp + tau + tgr
229+
self.setAttr('Info:', val=info)
230+
231+
grd_out = grd_out[..., 0:2]
232+
233+
self.setData('crds_out', crds_out)
234+
self.setData('grd_out', grd_out)
235+
self.setData('gtf_out', gtf)
236+
237+
return(0)

0 commit comments

Comments
 (0)