Skip to content

Commit 4afa7af

Browse files
committed
Merge pull request #1 from chrisfilo/enh
Fixes and cleanup -- Thanks Chris!
2 parents de0156a + f324065 commit 4afa7af

9 files changed

Lines changed: 70 additions & 66 deletions

File tree

File renamed without changes.
File renamed without changes.

load.py renamed to qap/load.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@ def load_mask(mask_file, ref_file=None):
5656
"""
5757
mask_img = nib.load(mask_file)
5858
mask_dat = mask_img.get_data()
59-
ref_img = nib.load(ref_file)
6059

6160
# Check that the specified mask is binary.
6261
mask_vals = np.unique(mask_dat)
@@ -65,6 +64,7 @@ def load_mask(mask_file, ref_file=None):
6564
raise Exception("")
6665

6766
if ref_file is not None:
67+
ref_img = nib.load(ref_file)
6868
# Verify that the mask and anatomical images have the same dimensions.
6969
if ref_img.shape != mask_img.shape:
7070
raise Exception("Error: Mask and anatomical image are different dimensions")

qc.py renamed to qap/qc.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
"""
44

55
from spatial_qc import summary_mask, snr, cnr, fber, efc, artifacts, fwhm, ghost_all
6-
from temporal_qc import mean_dvars_wrapper, mean_fd_wrapper, mean_outlier_timepoints, mean_quality_timepoints
6+
from temporal_qc import mean_dvars_wrapper, mean_outlier_timepoints, mean_quality_timepoints, summarize_fd
7+
from load import load_image, load_mask
78

89

910
###
@@ -94,7 +95,7 @@ def run_qc_temporal(func_path, mask_path,
9495

9596
# Mean FD (Jenkinson)
9697
if verbose: print "...mean FD"
97-
mean_fd = mean_fd_wrapper(motion_matrix)
98+
mean_fd = summarize_fd(motion_matrix)
9899

99100
# 3dTout
100101
if verbose: print "...3dTout"

spatial_qc.py renamed to qap/spatial_qc.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,12 @@ def artifacts(anat_data, fg_mask_data, calculate_qi2=False):
159159

160160
# divide by the standard deviation
161161
bgNoiseZ = bgNoise / bgNoise.std()
162-
bgChiParams = ss.chi.fit(bgNoiseZ)
162+
bgChiParams = stats.chi.fit(bgNoiseZ)
163163
#print bgChiParams
164164

165165
# now generate values that are consistent with the histogram
166166
yx = range(0,H.size)/bgNoise.std()
167-
rvs = ss.chi.pdf(yx,bgChiParams[0],loc=bgChiParams[1],scale=bgChiParams[2])
167+
rvs = stats.chi.pdf(yx,bgChiParams[0],loc=bgChiParams[1],scale=bgChiParams[2])
168168

169169
# now we can calculate the goodness of fit
170170
gof = np.average(np.absolute(H[halfMaxRightTail:]-rvs[halfMaxRightTail:]))
@@ -251,8 +251,8 @@ def ghost_mask(epi_data, mask_data, direction, ref_file=None, out_file=None):
251251

252252
# Save mask
253253
if ref_file is not None and out_file is not None:
254-
ref = nib.load(ref_file)
255-
out = nib.Nifti1Image(n2_mask_data, ref.get_affine(), ref.get_header())
254+
ref = nb.load(ref_file)
255+
out = nb.Nifti1Image(n2_mask_data, ref.get_affine(), ref.get_header())
256256
out.to_filename(out_file)
257257

258258
return(n2_mask_data)

temporal_qc.py renamed to qap/temporal_qc.py

Lines changed: 40 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
# DVARS
1515
from dvars import mean_dvars_wrapper
16+
from utils import remove_oblique_warning
1617

1718
# MeanFD
1819
## borrowed from C-PAC
@@ -31,83 +32,51 @@ def fd_jenkinson(in_file):
3132
Method to calculate Framewise Displacement (FD) calculations
3233
(Jenkinson et al., 2002)
3334
Parameters; in_file : string
34-
Returns; out_file : string
35+
Returns; FDs : numpy.array
3536
NOTE: infile should have one 3dvolreg affine matrix in one row - NOT the motion parameters
3637
'''
3738
import numpy as np
3839
import os
3940
import sys
4041
import math
4142

42-
out_file = os.path.join(os.getcwd(), 'FD_J.1D')
43-
44-
f = open(out_file, 'w')
45-
#print in_file
4643
pm_ = np.genfromtxt(in_file)
4744

4845
pm = np.zeros((pm_.shape[0],pm_.shape[1]+4))
4946
pm[:,:12]=pm_
5047
pm[:,12:]=[0.0, 0.0, 0.0, 1.0]
51-
52-
flag = 0
53-
54-
#The default radius (as in FSL) of a sphere represents the brain
55-
rmax = 80.0
5648

57-
#rigid body transformation matrix
58-
T_rb_prev = np.matrix(np.eye(4))
49+
# The default radius (as in FSL) of a sphere represents the brain
50+
rmax = 50.0
5951

60-
for i in range(0, pm.shape[0]):
52+
FDs = []
53+
T_rb_prev = np.matrix(pm[0].reshape(4,4))
54+
for i in range(1, pm.shape[0]):
6155
T_rb = np.matrix(pm[i].reshape(4,4)) # making use of the fact that the order of aff12 matrix is "row-by-row"
62-
63-
if flag == 0:
64-
flag = 1
65-
# first timepoint
66-
print >> f, 0
67-
else:
68-
M = np.dot(T_rb, T_rb_prev.I) - np.eye(4)
69-
A = M[0:3, 0:3]
70-
b = M[0:3, 3]
56+
M = np.dot(T_rb, T_rb_prev.I) - np.eye(4)
57+
A = M[0:3, 0:3]
58+
b = M[0:3, 3]
7159

72-
FD_J = math.sqrt((rmax*rmax/5)*np.trace(np.dot(A.T, A)) + np.dot(b.T, b))
73-
print >> f, '%.8f'%FD_J
74-
60+
FD_J = math.sqrt((rmax*rmax/5)*np.trace(np.dot(A.T, A)) + np.dot(b.T, b))
61+
FDs.append(FD_J)
7562
T_rb_prev = T_rb
76-
77-
f.close()
78-
79-
return out_file
63+
64+
return np.array(FDs)
8065

81-
def summarize_fd(in_file, fd_out_file=None, threshold=0.2):
66+
def summarize_fd(in_file, threshold=0.2):
8267
# Threshold is in terms of mm, i think?
8368

84-
# Run in temporary working directory
85-
tmpdir = mkdtemp()
86-
curdir = os.getcwd()
87-
os.chdir(tmpdir)
88-
8969
# Compute FD
90-
out_file = fd_jenkinson(in_file)
91-
fd = np.loadtxt(out_file)
70+
fd = fd_jenkinson(in_file)
9271

9372
# Calculate Mean
9473
mean_fd = fd.mean()
9574

9675
# Calculate Outliers
9776
## Number and Percent of frames (time points) where
9877
## movement (FD) exceeded threshold
99-
num_fd = np.float((fd>threshold).sum())
100-
percent_fd = (num_fd*100)/(len(fd)+1)
101-
102-
#data = np.loadtxt("/data/Projects/ABIDE_Initiative/CPAC/Output_2014-04-29_preproc/pipeline_abide_func_preproc/0050002_session_1/frame_wise_displacement/_scan_rest_1_rest/FD.1D")
103-
104-
# Clean-Up
105-
if fd_out_file:
106-
os.rename(out_file, fd_out_file)
107-
else:
108-
os.remove(out_file)
109-
os.chdir(curdir)
110-
os.rmdir(tmpdir)
78+
num_fd = (fd>threshold).sum()
79+
percent_fd = (num_fd*100.0)/(len(fd))
11180

11281
return (mean_fd, num_fd, percent_fd)
11382

@@ -147,11 +116,13 @@ def outlier_timepoints(func_file, mask_file, out_fraction=True):
147116
# or -legendre to remove any trend
148117
cmd = "3dToutcount %s" % str_opts
149118
out = commands.getoutput(cmd)
150-
119+
out = remove_oblique_warning(out)
120+
151121
# Extract time-series in output
152122
lines = out.splitlines()
153123
## remove general information
154124
lines = [ l for l in lines if l[:2] != "++" ]
125+
155126
## string => floats
156127
outliers= [ float(l.strip()) for l in lines ] # note: don't really need strip
157128

@@ -164,32 +135,35 @@ def mean_outlier_timepoints(*args, **kwrds):
164135

165136

166137
# 3dTqual
167-
def quality_timepoints(func_file, automask=True):
138+
def quality_timepoints(func_file, mask=None):
168139
"""
169140
Calculates a 'quality index' for each timepoint in the 4D functional dataset.
170141
Low values are good and indicate that the timepoint is not very different from the norm.
171142
"""
172143
import subprocess
173144

174145
opts = []
175-
if automask:
176-
opts.append("-automask")
146+
if mask:
147+
if mask=="auto":
148+
opts.append("-automask")
149+
else:
150+
opts.append("-mask %s"%mask)
177151
opts.append(func_file)
178152
str_opts= " ".join(opts)
179153

180154
cmd = "3dTqual %s" % str_opts
181155
p = subprocess.Popen(cmd.split(" "),
182156
stdout=subprocess.PIPE,
183157
stderr=subprocess.PIPE)
184-
out,err = p.communicate()
158+
out, _ = p.communicate()
185159

186160
#import code
187161
#code.interact(local=locals())
188162

189163
# Extract time-series in output
190164
lines = out.splitlines()
191165
## remove general information
192-
lines = [ l for l in lines if l[:2] != "++" ]
166+
lines = [ l for l in lines if l[:2] not in "++" ]
193167
## string => floats
194168
outliers= [ float(l.strip()) for l in lines ] # note: don't really need strip
195169

@@ -199,3 +173,14 @@ def mean_quality_timepoints(*args, **kwrds):
199173
qualities = quality_timepoints(*args, **kwrds)
200174
mean_qualities = np.mean(qualities)
201175
return mean_qualities
176+
177+
def median_tsnr(func_data):
178+
""" Calculates median of temporal Signal to Noise ratio within a mask"""
179+
180+
data_std = func_data.std(axis=0)
181+
# exclude voxels with no variance
182+
std_mask = (data_std !=0)
183+
tsnr = func_data.mean(axis=0)[std_mask]/data_std[std_mask]
184+
print tsnr.shape
185+
186+
return np.median(tsnr)

utils.py renamed to qap/utils.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,11 @@ def gen_file_map(sink_dir, resource_paths=None):
8686

8787
return analysis_map
8888

89+
def remove_oblique_warning(out_str):
90+
new_lines = []
91+
for i, line in enumerate(out_str.splitlines()):
92+
if line.startswith("*+ WARNING: If you are performing spatial transformations on an oblique dset,"):
93+
break
94+
new_lines.append(line)
95+
new_lines += out_str.splitlines()[i+7:]
96+
return "\n".join(new_lines)

requirements.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
nibabel
2+
nitime

usage.md

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ The spatial functional measures will make use of the mean functional image and i
118118
You should know the phase encoding direction to decide if you want to use `func_ghost_x` (RL/LR) or `func_ghost_y` (AP/PA).
119119

120120
from qap import ghost_all
121-
func_ghost_x,func_ghost_y = ghost_all(mean_func_data, func_mask_data)
121+
func_ghost_x, func_ghost_y, _ = ghost_all(mean_func_data, func_mask_data)
122122

123123

124124
## Temporal Functional
@@ -129,6 +129,7 @@ You should know the phase encoding direction to decide if you want to use `func_
129129
* **Mean Fractional Displacement - Jenkinson [func_mean_fd]:** A measure of subject head motion, which compares the motion between the current and previous volumes. This is calculated by summing the absolute value of displacement changes in the x, y and z directions and rotational changes about those three axes. The rotational changes are given distance values based on the changes across the surface of a 50mm radius sphere [^5][^8]. _Uses functional time-series._
130130
* **Number of volumes with FD greater than 0.2mm [func_num_fd]:** _Uses functional time-series._
131131
* **Percent of volumes with FD greater than 0.2mm [func_perc_fd]:** _Uses functional time-series._
132+
* **Median temporal Signal to Noise ratio [tsnr]:** _Uses functional time-series and a mask._
132133

133134

134135
### Standardized DVARS
@@ -147,17 +148,24 @@ The `out_fraction` option if `True` will return the mean _fraction_ of time-poin
147148

148149
### Median Distance Index
149150

150-
The `auto_mask` option if `True` will automatically compute the brain mask from the data otherwise the whole dataset will be used.
151+
The `mask` allows you to specify a mask file. If set to "auto" mask will be calculated from data. If not set the whole dataset will be used.
151152

152153
from qap import mean_quality_timepoints
153-
func_quality = mean_quality_timepoints(func_file, automask=True)
154+
func_quality = mean_quality_timepoints(func_file, mask="auto")
154155

155156
### FD
156157

157158
Here we describe computing `mean_fd`, `num_fd`, and `perc_fd`. This requires that you have the input coordinate transforms that is output by AFNI's `3dvolreg` during motion correction. The option `threshold` sets the threshold for determining the number and percent of volumes with FD greater than said threshold.
158159

159-
from qap import mean_fd_wrapper
160+
from qap import summarize_fd
160161
mean_fd, num_fd, perc_fd = summarize_fd(motion_matrix_file, threshold=0.2)
162+
163+
### tSNR
164+
165+
tSNR is a ratio of signal (mean across time) and noise (standard deviation around the mean across time) averaged over all voxels withing the specified mask
166+
167+
from qap import median_tsnr
168+
tsnr = median_tsnr(func_data, func_mask_data)
161169

162170

163171
## Determining Outliers

0 commit comments

Comments
 (0)