|
38 | 38 | iflogger = logging.getLogger('interface')
|
39 | 39 |
|
40 | 40 |
|
| 41 | +class FramewiseDisplacementInputSpec(BaseInterfaceInputSpec): |
| 42 | + in_plots = File(exists=True, desc='motion parameters as written by FSL MCFLIRT') |
| 43 | + radius = traits.Float(50, usedefault=True, |
| 44 | + desc='radius in mm to calculate angular FDs, 50mm is the ' |
| 45 | + 'default since it is used in Power et al. 2012') |
| 46 | + out_file = File('fd_power_2012.txt', usedefault=True, desc='output file name') |
| 47 | + out_figure = File('fd_power_2012.pdf', usedefault=True, desc='output figure name') |
| 48 | + series_tr = traits.Float(desc='repetition time in sec.') |
| 49 | + save_plot = traits.Bool(False, usedefault=True, desc='write FD plot') |
| 50 | + normalize = traits.Bool(False, usedefault=True, desc='calculate FD in mm/s', |
| 51 | + requires=['series_tr']) |
| 52 | + figdpi = traits.Int(100, usedefault=True, desc='output dpi for the FD plot') |
| 53 | + figsize = traits.Tuple(traits.Float(11.7), traits.Float(2.3), usedefault=True, |
| 54 | + desc='output figure size') |
| 55 | + |
| 56 | +class FramewiseDisplacementOutputSpec(TraitedSpec): |
| 57 | + out_file = File(desc='calculated FD per timestep') |
| 58 | + out_figure = File(desc='output image file') |
| 59 | + fd_average = traits.Float(desc='average FD') |
| 60 | + |
| 61 | +class FramewiseDisplacement(BaseInterface): |
| 62 | + """ |
| 63 | + Calculate the :abbr:`FD (framewise displacement)` as in [Power2012]_. |
| 64 | + This implementation reproduces the calculation in fsl_motion_outliers |
| 65 | +
|
| 66 | + .. [Power2012] Power et al., Spurious but systematic correlations in functional |
| 67 | + connectivity MRI networks arise from subject motion, NeuroImage 59(3), |
| 68 | + 2012. doi:`10.1016/j.neuroimage.2011.10.018 |
| 69 | + <http://dx.doi.org/10.1016/j.neuroimage.2011.10.018>`_. |
| 70 | +
|
| 71 | +
|
| 72 | + """ |
| 73 | + |
| 74 | + input_spec = FramewiseDisplacementInputSpec |
| 75 | + output_spec = FramewiseDisplacementOutputSpec |
| 76 | + |
| 77 | + def _run_interface(self, runtime): |
| 78 | + mpars = np.loadtxt(self.inputs.in_plots) # mpars is N_t x 6 |
| 79 | + diff = mpars[:-1, :] - mpars[1:, :] |
| 80 | + diff[:, :3] *= self.inputs.radius |
| 81 | + fd_res = np.abs(diff).sum(axis=1) |
| 82 | + |
| 83 | + self._results = { |
| 84 | + 'out_file': op.abspath(self.inputs.out_file), |
| 85 | + 'fd_average': float(fd_res.mean()) |
| 86 | + } |
| 87 | + np.savetxt(self.inputs.out_file, fd_res) |
| 88 | + |
| 89 | + if self.inputs.save_plot: |
| 90 | + self._results['out_figure'] = op.abspath(self.inputs.out_figure) |
| 91 | + tr = None |
| 92 | + if self.inputs.normalize: |
| 93 | + tr = self.inputs.series_tr |
| 94 | + fig = plot_fd(fd_res, self.inputs.figsize, series_tr=tr) |
| 95 | + fig.savefig(self._results['out_figure'], dpi=float(self.inputs.figdpi), |
| 96 | + format=self.inputs.out_figure[-3:], |
| 97 | + bbox_inches='tight') |
| 98 | + return runtime |
| 99 | + |
| 100 | + def _list_outputs(self): |
| 101 | + return self._results |
| 102 | + |
| 103 | + |
41 | 104 | class PickAtlasInputSpec(BaseInterfaceInputSpec):
|
42 | 105 | atlas = File(exists=True, desc="Location of the atlas that will be used.",
|
43 | 106 | mandatory=True)
|
@@ -1460,6 +1523,40 @@ def merge_rois(in_files, in_idxs, in_ref,
|
1460 | 1523 | return out_file
|
1461 | 1524 |
|
1462 | 1525 |
|
| 1526 | +def plot_fd(fd_values, figsize, series_tr=None): |
| 1527 | + """ |
| 1528 | + A helper function to plot the framewise displacement |
| 1529 | + """ |
| 1530 | + import matplotlib |
| 1531 | + matplotlib.use('Agg') |
| 1532 | + import matplotlib.pyplot as plt |
| 1533 | + from matplotlib.gridspec import GridSpec |
| 1534 | + from matplotlib.backends.backend_pdf import FigureCanvasPdf as FigureCanvas |
| 1535 | + import seaborn as sns |
| 1536 | + |
| 1537 | + fig = plt.Figure(figsize=figsize) |
| 1538 | + FigureCanvas(fig) |
| 1539 | + grid = GridSpec(1, 2, width_ratios=[3, 1], wspace=0.025) |
| 1540 | + grid.update(hspace=1.0, right=0.95, left=0.1, bottom=0.2) |
| 1541 | + |
| 1542 | + ax = fig.add_subplot(grid[0, :-1]) |
| 1543 | + ax.plot(fd_values) |
| 1544 | + ax.set_xlim((0, len(fd_values))) |
| 1545 | + ax.set_ylabel('FD [{}]'.format('mm/s' if series_tr is not None else 'mm')) |
| 1546 | + |
| 1547 | + xlabel = 'Frame #' |
| 1548 | + if series_tr is not None: |
| 1549 | + xlabel = 'Frame # ({} sec TR)'.format(series_tr) |
| 1550 | + ax.set_xlabel(xlabel) |
| 1551 | + ylim = ax.get_ylim() |
| 1552 | + |
| 1553 | + ax = fig.add_subplot(grid[0, -1]) |
| 1554 | + sns.distplot(fd_values, vertical=True, ax=ax) |
| 1555 | + ax.set_xlabel('Frames') |
| 1556 | + ax.set_ylim(ylim) |
| 1557 | + ax.set_yticklabels([]) |
| 1558 | + return fig |
| 1559 | + |
1463 | 1560 | # Deprecated interfaces ------------------------------------------------------
|
1464 | 1561 |
|
1465 | 1562 | class Distance(nam.Distance):
|
|
0 commit comments