diff --git a/niworkflows/interfaces/segmentation.py b/niworkflows/interfaces/segmentation.py index 9e744cf657a..fd2522f52cb 100644 --- a/niworkflows/interfaces/segmentation.py +++ b/niworkflows/interfaces/segmentation.py @@ -115,3 +115,39 @@ def _post_run_hook(self, runtime): self._melodic_dir = outputs.out_dir NIWORKFLOWS_LOG.info('Generating report for MELODIC') + + +class ICA_AROMAInputSpecRPT(nrc.ReportCapableInputSpec, + fsl.aroma.ICA_AROMAInputSpec): + out_report = File( + 'ica_aroma_reportlet.svg', usedefault=True, desc='Filename for the visual' + ' report generated ' + 'by Nipype.') + report_mask = File(desc='Mask used to draw the outline on the reportlet. ' + 'If not set the mask will be derived from the data.') + +class ICA_AROMAOutputSpecRPT(nrc.ReportCapableOutputSpec, + fsl.aroma.ICA_AROMAOutputSpec): + pass + + +class ICA_AROMARPT(nrc.ReportCapableInterface, fsl.ICA_AROMA): + input_spec = ICA_AROMAInputSpecRPT + output_spec = ICA_AROMAOutputSpecRPT + + def _generate_report(self): + from niworkflows.viz.utils import plot_melodic_components + plot_melodic_components(melodic_dir=self.inputs.melodic_dir, + in_file=self.inputs.in_file, + out_file=self.inputs.out_report, + compress=self.inputs.compress_report, + report_mask=self.inputs.report_mask, + noise_components_file=self._noise_components_file + ) + + def _post_run_hook(self, runtime): + outputs = self.aggregate_outputs(runtime=runtime) + self._noise_components_file = os.path.join(outputs.out_dir, + "classified_motion_ICs.txt") + + NIWORKFLOWS_LOG.info('Generating report for ICA AROMA') diff --git a/niworkflows/viz/utils.py b/niworkflows/viz/utils.py index be6c9b8266d..39d94019354 100644 --- a/niworkflows/viz/utils.py +++ b/niworkflows/viz/utils.py @@ -448,18 +448,24 @@ def compose_view(bg_svgs, fg_svgs, ref=0, out_file='report.svg'): out_file = op.abspath(out_file) fig.save(out_file) + # Post processing + with open(out_file, 'r' if PY3 else 'rb') as f: + svg = f.read().split('\n') + + # Remove @keyframes flickerAnimation%s { 0%% {opacity: 1;} 100%% { opacity: 0; }} .foreground-svg { animation: 1s ease-in-out 0s alternate none infinite paused flickerAnimation%s;} .foreground-svg:hover { animation-play-state: running;} """ % tuple([uuid4()] * 2)) - with open(out_file, 'w' if PY3 else 'wb') as f: - f.write('\n'.join(svg)) + + with open(out_file, 'w' if PY3 else 'wb') as f: + f.write('\n'.join(svg)) return out_file @@ -488,7 +494,8 @@ def transform_to_2d(data, max_axis): def plot_melodic_components(melodic_dir, in_file, tr=None, out_file='melodic_reportlet.svg', - compress='auto', report_mask=None): + compress='auto', report_mask=None, + noise_components_file=None): from nilearn.plotting import plot_glass_brain from nilearn.image import index_img, iter_img import nibabel as nb @@ -497,6 +504,7 @@ def plot_melodic_components(melodic_dir, in_file, tr=None, import seaborn as sns from matplotlib.gridspec import GridSpec import os + import re from io import StringIO sns.set_style("white") current_palette = sns.color_palette() @@ -546,6 +554,10 @@ def plot_melodic_components(melodic_dir, in_file, tr=None, width_ratios=[1, 1, 1, 4, 0.001, 1, 1, 1, 4, ], height_ratios=[1.1, 1] * n_rows) + if noise_components_file: + with open(noise_components_file) as cf: + noise_components = [int(c) for c in cf.read().split(",")] + for i, img in enumerate( iter_img(os.path.join(melodic_dir, "melodic_IC.nii.gz"))): @@ -553,6 +565,16 @@ def plot_melodic_components(melodic_dir, in_file, tr=None, row = int(i / 2) l_row = row * 2 + if noise_components_file: + if (i + 1) in noise_components: + color_title = color_time = color_power = 'r' + else: + color_title = color_time = color_power = 'g' + else: + color_title = 'k' + color_time = current_palette[0] + color_power = current_palette[1] + data = img.get_data() for j in range(3): ax1 = fig.add_subplot(gs[l_row:l_row + 2, j + col * 5]) @@ -568,29 +590,33 @@ def plot_melodic_components(melodic_dir, in_file, tr=None, "C%d: Tot. var. expl. %.2g%%" % (i + 1, stats[i, 1]), x=0, y=1.18, fontsize=7, horizontalalignment='left', - verticalalignment='top') + verticalalignment='top', + color=color_title) ax2 = fig.add_subplot(gs[l_row, 3 + col * 5]) ax3 = fig.add_subplot(gs[l_row + 1, 3 + col * 5]) ax2.plot(np.arange(len(timeseries[:, i])) * tr, timeseries[:, i], - linewidth=min(200 / len(timeseries[:, i]), 1.0)) + linewidth=min(200 / len(timeseries[:, i]), 1.0), + color=color_time) ax2.set_xlim([0, len(timeseries[:, i]) * tr]) ax2.axes.get_yaxis().set_visible(False) ax2.autoscale_view('tight') ax2.tick_params(axis='both', which='major', pad=0) sns.despine(left=True, bottom=True) - zed = [tick.label.set_fontsize(6) for tick in - ax2.xaxis.get_major_ticks()] + for tick in ax2.xaxis.get_major_ticks(): + tick.label.set_fontsize(6) + tick.label.set_color(color_time) - ax3.plot(f[0:], power[0:, i], color=current_palette[1], + ax3.plot(f[0:], power[0:, i], color=color_power, linewidth=min(100 / len(power[0:, i]), 1.0)) ax3.set_xlim([f[0], f.max()]) ax3.axes.get_yaxis().set_visible(False) ax3.autoscale_view('tight') ax3.tick_params(axis='both', which='major', pad=0) - zed = [tick.label.set_fontsize(6) for tick in - ax3.xaxis.get_major_ticks()] + for tick in ax3.xaxis.get_major_ticks(): + tick.label.set_fontsize(6) + tick.label.set_color(color_power) sns.despine(left=True, bottom=True) plt.subplots_adjust(hspace=0.5)