Skip to content
This repository was archived by the owner on Dec 27, 2022. It is now read-only.

Commit 3fc3b0c

Browse files
author
Adam Richie-Halford
committed
Merge branch 'master' of github.com:akeshavan/preAFQ
2 parents 881d3a7 + 8711a79 commit 3fc3b0c

33 files changed

+16565
-50
lines changed

preafq/qc.py

Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import nibabel as nib
2+
import matplotlib
3+
matplotlib.use('agg')
4+
import matplotlib.pyplot as plt
5+
import numpy as np
6+
from nipype.utils.filemanip import fname_presuffix
7+
import os.path as op
8+
from dipy.segment.mask import median_otsu
9+
from io import BytesIO
10+
from nipype.utils.filemanip import save_json
11+
import base64
12+
13+
def reorient_array(data, aff):
14+
# rearrange the matrix to RAS orientation
15+
orientation = nib.orientations.io_orientation(aff)
16+
data_RAS = nib.orientations.apply_orientation(data, orientation)
17+
# In RAS
18+
return nib.orientations.apply_orientation(data_RAS,
19+
nib.orientations.axcodes2ornt("IPL"))
20+
21+
def mplfig(data, outfile=None, as_bytes=False):
22+
fig = plt.figure(frameon=False, dpi=data.shape[0])
23+
fig.set_size_inches(float(data.shape[1])/data.shape[0], 1)
24+
ax = plt.Axes(fig, [0., 0., 1., 1.])
25+
ax.set_axis_off()
26+
fig.add_axes(ax)
27+
ax.imshow(data, aspect=1, cmap=plt.cm.Greys_r) # used to be aspect="normal"
28+
if outfile:
29+
fig.savefig(outfile, dpi=data.shape[0], transparent=True)
30+
plt.close()
31+
return outfile
32+
if as_bytes:
33+
IObytes = BytesIO()
34+
plt.savefig(IObytes, format='png', dpi=data.shape[0], transparent=True)
35+
IObytes.seek(0)
36+
base64_jpgData = base64.b64encode(IObytes.read())
37+
return base64_jpgData.decode("ascii")
38+
39+
40+
def mplfigcontour(data, outfile=None, as_bytes=False):
41+
fig = plt.figure(frameon=False)
42+
fig.set_size_inches(float(data.shape[1])/data.shape[0], 1)
43+
ax = plt.Axes(fig, [0., 0., 1., 1.])
44+
ax.set_axis_off()
45+
fig.add_axes(ax)
46+
47+
bg = np.zeros(data.shape)
48+
bg[:] = np.nan
49+
ax.imshow(bg, aspect=1, cmap=plt.cm.Greys_r) # used to be aspect="normal"
50+
ax.contour(data, colors="red", linewidths=0.1)
51+
if outfile:
52+
fig.savefig(outfile, dpi=data.shape[0], transparent=True)
53+
plt.close()
54+
return outfile
55+
if as_bytes:
56+
IObytes = BytesIO()
57+
plt.savefig(IObytes, format='png', dpi=data.shape[0], transparent=True)
58+
IObytes.seek(0)
59+
base64_jpgData = base64.b64encode(IObytes.read())
60+
return base64_jpgData.decode("ascii")
61+
62+
def load_and_reorient(filename):
63+
img = nib.load(filename)
64+
data, aff = img.get_data(), img.affine
65+
data = reorient_array(data, aff)
66+
return data
67+
68+
def reshape3D(data, n=256):
69+
return np.pad(data, (((n-data.shape[0])//2, ((n-data.shape[0]) + (data.shape[0]%2 >0))//2),
70+
((n-data.shape[1])//2, ((n-data.shape[1]) + (data.shape[1]%2 >0))//2),
71+
(0,0)),
72+
"constant", constant_values = (0,0))
73+
74+
def reshape4D(data, n=256):
75+
return np.pad(data, (((n-data.shape[0])//2, ((n-data.shape[0]) + (data.shape[0]%2 >0))//2),
76+
((n-data.shape[1])//2, ((n-data.shape[1]) + (data.shape[1]%2 >0))//2),
77+
(0,0), (0,0)),
78+
"constant", constant_values = (0,0))
79+
80+
def get_middle_slices(data, slice_direction):
81+
slicer = {"ax": 0, "cor": 1, "sag": 2}
82+
all_data_slicer = [slice(None), slice(None), slice(None)]
83+
num_slices = data.shape[slicer[slice_direction]]
84+
slice_num = int(num_slices / 2)
85+
all_data_slicer[slicer[slice_direction]] = slice_num
86+
tile = data[tuple(all_data_slicer)]
87+
88+
# make it a square
89+
N = max(tile.shape[:2])
90+
tile = reshape3D(tile, N)
91+
92+
return tile
93+
94+
def nearest_square(limit):
95+
answer = 0
96+
while (answer+1)**2 < limit:
97+
answer += 1
98+
if (answer ** 2) == limit:
99+
return answer
100+
else:
101+
return answer + 1
102+
103+
def create_sprite_from_tiles(tile, out_file = None, as_bytes=False):
104+
num_slices = tile.shape[-1]
105+
N = nearest_square(num_slices)
106+
M = int(np.ceil(num_slices/N))
107+
# tile is square, so just make a big arr
108+
pix = tile.shape[0]
109+
110+
if len(tile.shape) == 3:
111+
mosaic = np.zeros((N*tile.shape[0], M*tile.shape[0]))
112+
else:
113+
mosaic = np.zeros((N*tile.shape[0], M*tile.shape[0], tile.shape[-2]))
114+
115+
mosaic[:] = np.nan
116+
helper = np.arange(N*M).reshape((N, M))
117+
118+
for t in range(num_slices):
119+
x, y = np.nonzero(helper == t)
120+
xmin = x[0] * pix
121+
xmax = (x[0] + 1) * pix
122+
ymin = y[0] * pix
123+
ymax = (y[0] + 1) * pix
124+
125+
if len(tile.shape) == 3:
126+
mosaic[xmin:xmax, ymin:ymax] = tile[:,:,t]
127+
else:
128+
mosaic[xmin:xmax, ymin:ymax, :] = tile[:,:,:,t]
129+
130+
if as_bytes:
131+
img = mplfig(mosaic, out_file, as_bytes=as_bytes)
132+
return dict(img=img, N=N, M=M, pix=pix, num_slices=num_slices)
133+
134+
if out_file:
135+
img = mplfig(mosaic, out_file), N, M, pix, num_slices
136+
137+
return dict(mosaic=mosaic, N=N, M=M, pix=pix, num_slices=num_slices)
138+
139+
140+
def createSprite4D(dwi_file):
141+
142+
# initialize output dict
143+
output = []
144+
145+
# load the file
146+
dwi = load_and_reorient(dwi_file)[:,:,:,1:]
147+
148+
# create tiles from center slice on each orientation
149+
for orient in ['sag', 'ax', 'cor']:
150+
tile = get_middle_slices(dwi, orient)
151+
152+
# create sprite images for each
153+
results = create_sprite_from_tiles(tile, as_bytes=True)
154+
results['img_type'] = '4dsprite'
155+
results['orientation'] = orient
156+
output.append(results)
157+
158+
return output
159+
160+
def createB0_ColorFA_Mask_Sprites(b0_file, colorFA_file, mask_file):
161+
colorfa = load_and_reorient(colorFA_file)
162+
b0 = load_and_reorient(b0_file)[:,:,:,0]
163+
anat_mask = load_and_reorient(mask_file)
164+
165+
166+
N = max(*b0.shape[:2])
167+
168+
# make a b0 sprite
169+
b0 = reshape3D(b0, N)
170+
_, mask = median_otsu(b0)
171+
outb0 = create_sprite_from_tiles(b0, as_bytes=True)
172+
outb0['img_type'] = 'brainsprite'
173+
174+
# make a colorFA sprite, masked by b0
175+
Q = reshape4D(colorfa, N)
176+
Q[mask == False] = np.nan
177+
Q = np.moveaxis(Q, -2, -1)
178+
outcolorFA = create_sprite_from_tiles(Q, as_bytes=True)
179+
outcolorFA['img_type'] = 'brainsprite'
180+
181+
# make an anat mask contour sprite
182+
outmask = create_sprite_from_tiles(reshape3D(anat_mask, N))
183+
img = mplfigcontour(outmask.pop("mosaic"), as_bytes=True)
184+
outmask['img'] = img
185+
186+
return outb0, outcolorFA, outmask
187+
188+
189+
def create_report_json(dwi_corrected_file, eddy_rms, eddy_report,
190+
color_fa_file, anat_mask_file,
191+
outpath=op.abspath('./report.json')):
192+
193+
report = {}
194+
report['dwi_corrected'] = createSprite4D(dwi_corrected_file)
195+
196+
b0, colorFA, mask = createB0_ColorFA_Mask_Sprites(dwi_corrected_file,
197+
color_fa_file,
198+
anat_mask_file)
199+
report['b0'] = b0
200+
report['colorFA'] = colorFA
201+
report['anat_mask'] = mask
202+
203+
with open(eddy_report, 'r') as f:
204+
report['eddy_report'] = f.readlines()
205+
206+
report['eddy_params'] = np.genfromtxt(eddy_rms).tolist()
207+
208+
save_json(outpath, report)
209+
return outpath

preafq/run_1.py

Lines changed: 24 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,6 @@
1212
from nipype.workflows.dmri.fsl.artifacts import all_fsl_pipeline
1313

1414

15-
def get_flirt_motion_parameters(eddy_params):
16-
import numpy as np
17-
import os.path as op
18-
data = np.genfromtxt(eddy_params)
19-
translations = data[:, :3]
20-
rotations = data[:, 3:6]
21-
rigid = np.hstack((rotations, translations))
22-
23-
motion_params = op.abspath('motion_parameters.par')
24-
np.savetxt(motion_params, rigid)
25-
26-
return motion_params
27-
28-
2915
def run_preAFQ(dwi_file, dwi_file_AP, dwi_file_PA, bvec_file, bval_file,
3016
subjects_dir, working_dir, out_dir):
3117
"""This is for HBN diffusion data
@@ -42,40 +28,18 @@ def run_preAFQ(dwi_file, dwi_file_AP, dwi_file_PA, bvec_file, bval_file,
4228
epi_AP = {'echospacing': 66.5e-3, 'enc_dir': 'y-'}
4329
epi_PA = {'echospacing': 66.5e-3, 'enc_dir': 'y'}
4430
prep = all_fsl_pipeline(epi_params=epi_AP, altepi_params=epi_PA)
45-
# prep_inputspec = prep.get_node('inputnode')
46-
# prep_outputspec = prep.get_node('outputnode')
47-
# print(prep_inputspec, prep_outputspec)
4831

4932
# initialize an overall workflow
5033
wf = pe.Workflow(name="preAFQ")
5134
wf.base_dir = op.abspath(working_dir)
5235

53-
# wf.connect([
54-
# (infosource, datasource, [('subject_id', 'subject_id')]),
55-
# (datasource, prep, [
56-
# ('dwi', 'inputnode.in_file'), ('dwi_rev', 'inputnode.alt_file'),
57-
# ('bvals', 'inputnode.in_bval'), ('bvecs', 'inputnode.in_bvec')
58-
# ]),
59-
# (prep, bias, [('outputnode.out_file', 'inputnode.in_file'),
60-
# ('outputnode.out_mask', 'inputnode.in_mask')]),
61-
# (datasource, bias, [('bvals', 'inputnode.in_bval')])
62-
# ])
63-
64-
# wf = create_dmri_preprocessing(name='preAFQ',
65-
# use_fieldmap=False,
66-
# fieldmap_registration=False)
67-
6836
prep.inputs.inputnode.in_file = dwi_file
6937
# prep.inputs.inputnode.alt_file = dwi_file_PA
7038
prep.inputs.inputnode.in_bvec = bvec_file
7139
prep.inputs.inputnode.in_bval = bval_file
7240
eddy = prep.get_node('fsl_eddy')
7341
eddy.inputs.repol = True
74-
eddy.inputs.niter = 1
75-
76-
77-
78-
# print(prep.inputs)
42+
eddy.inputs.niter = 1 # TODO: change back to 5 when running for real
7943

8044
merge = pe.Node(fsl.Merge(dimension='t'), name="mergeAPPA")
8145
merge.inputs.in_files = [dwi_file_AP, dwi_file_PA]
@@ -217,20 +181,31 @@ def get_orig(subjects_dir, sub):
217181
wf.connect(vt3, "transformed_file", convert1, "in_file")
218182
wf.connect(convert1, "out_file", datasink, "preafq.anat.@anat")
219183

220-
# wf.base_dir = working_dir
221-
# wf.write_graph()
184+
def reportNodeFunc(dwi_corrected_file, eddy_rms, eddy_report,
185+
color_fa_file, anat_mask_file):
186+
from preafq.qc import create_report_json
187+
188+
report = create_report_json(dwi_corrected_file, eddy_rms, eddy_report,
189+
color_fa_file, anat_mask_file)
190+
return report
191+
192+
reportNode = pe.Node(niu.Function(input_names=['dwi_corrected_file', 'eddy_rms',
193+
'eddy_report', 'color_fa_file',
194+
'anat_mask_file'],
195+
output_names=['report'],
196+
function=reportNodeFunc),
197+
name="reportJSON")
198+
199+
wf.connect(prep, "outputnode.out_file", reportNode, 'dwi_corrected_file')
200+
wf.connect(prep, "fsl_eddy.out_movement_rms", reportNode, 'eddy_rms')
201+
wf.connect(prep, "fsl_eddy.out_outlier_report", reportNode, 'eddy_report')
202+
wf.connect(threshold2, "binary_file", reportNode, 'anat_mask_file')
203+
wf.connect(get_tensor, "color_fa_file", reportNode, 'color_fa_file')
204+
205+
wf.connect(reportNode, 'report', datasink, 'preafq.report.@report')
206+
222207
wf.run()
223208

224209
copyfile(bval_file, op.join(
225210
out_dir, bids_sub_name, "preafq", "dwi", op.split(bval_file)[1]
226211
))
227-
228-
# dmri_corrected = glob(op.join(out_dir, '*/preafq/dwi', '*.nii.gz'))[0]
229-
# bvec_rotated = glob(op.join(out_dir, '*/preafq/dwi', '*.bvec'))[0]
230-
# bval_file = glob(op.join(out_dir, '*/preafq/dwi', '*.bval'))[0]
231-
# art_file = glob(op.join(out_dir, '*/preafq/art', '*.art.json'))[0]
232-
# motion_file = glob(op.join(out_dir, '*/preafq/art', '*.motion.txt'))[0]
233-
# outlier_file = glob(op.join(out_dir, '*/preafq/art',
234-
# '*.outliers.txt'))[0]
235-
236-
# return dmri_corrected, bvec_rotated, #art_file, motion_file, outlier_file

preafqViewer/.babelrc

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"presets": [
3+
["env", {
4+
"modules": false,
5+
"targets": {
6+
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
7+
}
8+
}],
9+
"stage-2"
10+
],
11+
"plugins": ["transform-vue-jsx", "transform-runtime"],
12+
"env": {
13+
"test": {
14+
"presets": ["env", "stage-2"],
15+
"plugins": ["transform-vue-jsx", "transform-es2015-modules-commonjs", "dynamic-import-node"]
16+
}
17+
}
18+
}

preafqViewer/.editorconfig

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
root = true
2+
3+
[*]
4+
charset = utf-8
5+
indent_style = space
6+
indent_size = 2
7+
end_of_line = lf
8+
insert_final_newline = true
9+
trim_trailing_whitespace = true

preafqViewer/.eslintignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
/build/
2+
/config/
3+
/dist/
4+
/*.js
5+
/test/unit/coverage/

0 commit comments

Comments
 (0)