From 0a5aca52fbe16be1cfe6e92a3bb836dfd6359cfb Mon Sep 17 00:00:00 2001 From: oesteban Date: Thu, 2 May 2019 14:33:52 -0700 Subject: [PATCH 1/3] enh(reports): Miscellaneous improvements - [x] Resizing of reportlets: now reportlets can be either static or dynamic. Static reportlets are embedded in ```` tags, which are easier to resize (CSS works just fine). Conversely, dynamic reportlets are embedded in ```` tags, which are more finicky and require proper settings of ``height``, ``width``, ``viewBox`` and ``preserveAspectRatio``. Animations do not work within ```` tags. - [x] Better ordering of functional subreports. - [x] Improved subtitles and other minor amends. --- niworkflows/reports/core.py | 42 ++++++++++++------ niworkflows/reports/fmriprep.yml | 76 ++++++++++++++++++-------------- niworkflows/viz/utils.py | 18 +------- 3 files changed, 73 insertions(+), 63 deletions(-) diff --git a/niworkflows/reports/core.py b/niworkflows/reports/core.py index d4aa553274c..c9210bde0ab 100644 --- a/niworkflows/reports/core.py +++ b/niworkflows/reports/core.py @@ -1,11 +1,9 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- # emacs: -*- mode: python; py-indent-offset: 4; indent-tabs-mode: nil -*- # vi: set ft=python sts=4 ts=4 sw=4 et: """ -Reports builder for BIDS-Apps -^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +Reports builder for BIDS-Apps. +Generalizes report generation across BIDS-Apps """ from pathlib import Path @@ -20,7 +18,7 @@ PLURAL_SUFFIX = defaultdict(str('s').format, [('echo', 'es')]) -SVG_SNIPPET = """\ +SVG_SNIPPET = ["""\ Problem loading figure {0}. If the link below works, please try \ reloading the report in your browser. @@ -28,13 +26,17 @@
Get figure file: {0}
-""" +""", """\ + + +
+ Get figure file: {0} +
+"""] class Element(object): - """ - Just a basic component of a report - """ + """Just a basic component of a report""" def __init__(self, name, title=None): self.name = name @@ -145,7 +147,23 @@ def __init__(self, layout, out_dir, config=None): out_file = out_dir / linked_svg out_file.parent.mkdir(parents=True, exist_ok=True) copyfile(src, out_file, copy=True, use_hardlink=True) - contents = SVG_SNIPPET.format(linked_svg) + is_static = config.get('static', True) + contents = SVG_SNIPPET[is_static].format(linked_svg) + + # Our current implementations of dynamic reportlets do this themselves, + # however I'll leave the code here since this is potentially something we + # will want to transfer from every figure generator to this location. + # The following code misses setting preserveAspecRatio="xMidYMid meet" + # if not is_static: + # # Remove height and width attributes from initial tag + # svglines = out_file.read_text().splitlines() + # expr = re.compile(r' (height|width)=["\'][0-9]+(\.[0-9]*)?[a-z]*["\']') + # for l, line in enumerate(svglines[:6]): + # if line.strip().startswith('recon-all) @@ -29,7 +30,7 @@ sections: ordering: session reportlets: - bids: {datatype: fmap, desc: brain, suffix: mask} - caption: Brain extraction of the magnitude image from the fieldmap + caption: Brain extraction of the magnitude image from the fieldmap. subtitle: Skull stripped magnitude image - name: Functional ordering: session,task,run @@ -39,72 +40,72 @@ sections: - bids: {datatype: func, desc: magnitude, suffix: bold} caption: Results of affine coregistration between the magnitude image of the fieldmap and the reference EPI image + static: false subtitle: Fieldmap to EPI registration - bids: {datatype: func, desc: fieldmap, suffix: bold} - caption: Overlaid on the reference EPI image + caption: Overlaid on the reference EPI image. + static: false subtitle: Fieldmap - bids: {datatype: func, desc: sdc, suffix: bold} caption: Results of performing susceptibility distortion correction (SDC) on the EPI + static: false subtitle: Susceptibility distortion correction - bids: {datatype: func, desc: forcedsyn, suffix: bold} caption: The dataset contained some fieldmap information, but the argument --force-syn was used. The higher-priority SDC method was used. Here, we show the results of performing SyN-based SDC on the EPI for comparison. + static: false subtitle: Experimental fieldmap-less susceptibility distortion correction - - bids: {datatype: func, desc: rois, suffix: bold} - caption: Brain mask calculated on the BOLD signal (red contour), along with the - masks used for a/tCompCor.
The aCompCor mask (magenta contour) is a conservative - CSF and white-matter mask for extracting physiological and movement confounds. -
The fCompCor mask (blue contour) contains the top 5% most variable voxels - within a heavily-eroded brain-mask. - subtitle: ROIs in BOLD space - - bids: - datatype: func - desc: '[at]compcor' - extensions: [.html] - suffix: bold - - bids: {datatype: func, desc: '[at]compcorvar', suffix: bold} - caption: The cumulative variance explained by the first k components of the - t/aCompCor decomposition, plotted for all values of k. - The number of components that must be included in the model in order to - explain some fraction of variance in the decomposition mask can be used - as a feature selection criterion for confound regression. - - bids: {datatype: func, desc: 'confoundcorr', suffix: bold} - caption: | - Left: Heatmap summarizing the correlation structure among confound variables. - (Cosine bases and PCA-derived CompCor components are inherently orthogonal.) - Right: magnitude of the correlation between each confound time series and the - mean global signal. Strong correlations might be indicative of partial volume - effects and can inform decisions about feature orthogonalization prior to - confound regression. - subtitle: Correlations among nuisance regressors - bids: {datatype: func, desc: flirtnobbr, suffix: bold} caption: FSL flirt was used to generate transformations from EPI space to T1 Space - BBR refinement rejected. Note that Nearest Neighbor interpolation is used in the reportlets in order to highlight potential spin-history and other artifacts, whereas final images are resampled using Lanczos interpolation. - subtitle: EPI to T1 registration + static: false + subtitle: Alignment of functional and anatomical MRI data (volume based) - bids: {datatype: func, desc: coreg, suffix: bold} caption: mri_coreg (FreeSurfer) was used to generate transformations from EPI space to T1 Space - bbregister refinement rejected. Note that Nearest Neighbor interpolation is used in the reportlets in order to highlight potential spin-history and other artifacts, whereas final images are resampled using Lanczos interpolation. - subtitle: EPI to T1 registration + static: false + subtitle: Alignment of functional and anatomical MRI data (volume based) - bids: {datatype: func, desc: flirtbbr, suffix: bold} caption: FSL flirt was used to generate transformations from EPI-space to T1w-space - The white matter mask calculated with FSL fast (brain tissue segmentation) was used for BBR. Note that Nearest Neighbor interpolation is used in the reportlets in order to highlight potential spin-history and other artifacts, whereas final images are resampled using Lanczos interpolation. - subtitle: EPI to T1 registration + static: false + subtitle: Alignment of functional and anatomical MRI data (surface driven) - bids: {datatype: func, desc: bbregister, suffix: bold} caption: bbregister was used to generate transformations from EPI-space to T1w-space. Note that Nearest Neighbor interpolation is used in the reportlets in order to highlight potential spin-history and other artifacts, whereas final images are resampled using Lanczos interpolation. - subtitle: EPI to T1 registration + static: false + subtitle: Alignment of functional and anatomical MRI data (surface driven) + - bids: {datatype: func, desc: rois, suffix: bold} + caption: Brain mask calculated on the BOLD signal (red contour), along with the + masks used for a/tCompCor.
The aCompCor mask (magenta contour) is a conservative + CSF and white-matter mask for extracting physiological and movement confounds. +
The fCompCor mask (blue contour) contains the top 5% most variable voxels + within a heavily-eroded brain-mask. + subtitle: Brain mask and (temporal/anatomical) CompCor ROIs + - bids: + datatype: func + desc: '[at]compcor' + extensions: [.html] + suffix: bold + - bids: {datatype: func, desc: 'compcorvar', suffix: bold} + caption: The cumulative variance explained by the first k components of the + t/aCompCor decomposition, plotted for all values of k. + The number of components that must be included in the model in order to + explain some fraction of variance in the decomposition mask can be used + as a feature selection criterion for confound regression. + subtitle: Variance explained by t/aCompCor components - bids: {datatype: func, desc: carpetplot, suffix: bold} caption: Summary statistics are plotted, which may reveal trends or artifacts in the BOLD data. Global signals calculated within the whole-brain (GS), within @@ -116,6 +117,15 @@ sections: and white matter and CSF (red), indicated by the color map on the left-hand side. subtitle: BOLD Summary + - bids: {datatype: func, desc: 'confoundcorr', suffix: bold} + caption: | + Left: Heatmap summarizing the correlation structure among confound variables. + (Cosine bases and PCA-derived CompCor components are inherently orthogonal.) + Right: magnitude of the correlation between each confound time series and the + mean global signal. Strong correlations might be indicative of partial volume + effects and can inform decisions about feature orthogonalization prior to + confound regression. + subtitle: Correlations among nuisance regressors - bids: {datatype: func, desc: aroma, suffix: bold} caption: | Maps created with maximum intensity projection (glass brain) with a diff --git a/niworkflows/viz/utils.py b/niworkflows/viz/utils.py index f43ee8fce58..fbfd00b8d5e 100644 --- a/niworkflows/viz/utils.py +++ b/niworkflows/viz/utils.py @@ -6,7 +6,6 @@ import os import os.path as op -from pathlib import Path import subprocess import base64 import re @@ -560,8 +559,6 @@ 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() in_nii = nb.load(in_file) @@ -702,19 +699,6 @@ def plot_melodic_components(melodic_dir, in_file, tr=None, sns.despine(left=True, bottom=True) plt.subplots_adjust(hspace=0.5) - - image_buf = StringIO() - fig.savefig(image_buf, dpi=300, format='svg', transparent=True, + fig.savefig(out_file, dpi=300, format='svg', transparent=True, bbox_inches='tight', pad_inches=0.01) fig.clf() - image_svg = image_buf.getvalue() - - if compress is True or compress == 'auto': - image_svg = svg_compress(image_svg, compress) - image_svg = re.sub(' height="[0-9]+[a-z]*"', '', image_svg, count=1) - image_svg = re.sub(' width="[0-9]+[a-z]*"', '', image_svg, count=1) - image_svg = re.sub(' viewBox', - ' preseveAspectRation="xMidYMid meet" viewBox', - image_svg, count=1) - - Path(out_file).write_text(image_svg) From 06e96631282cb858daebe6967e2499d13664153f Mon Sep 17 00:00:00 2001 From: Oscar Esteban Date: Thu, 2 May 2019 22:21:41 -0700 Subject: [PATCH 2/3] Update core.py --- niworkflows/reports/core.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/niworkflows/reports/core.py b/niworkflows/reports/core.py index c9210bde0ab..0584ceb510f 100644 --- a/niworkflows/reports/core.py +++ b/niworkflows/reports/core.py @@ -211,7 +211,7 @@ class Report(object): >>> robj.generate_report() 0 >>> len((testdir / 'out' / 'fmriprep' / 'sub-01.html').read_text()) - 20862 + 19352 """ From 03851cc775031ebbbad730850bbe0cc9c4da79b5 Mon Sep 17 00:00:00 2001 From: oesteban Date: Thu, 2 May 2019 23:31:27 -0700 Subject: [PATCH 3/3] fix(tests): update doctests and add one more test --- niworkflows/reports/core.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/niworkflows/reports/core.py b/niworkflows/reports/core.py index 0584ceb510f..ea2d6e7d6ba 100644 --- a/niworkflows/reports/core.py +++ b/niworkflows/reports/core.py @@ -79,6 +79,15 @@ class Reportlet(Element): >>> r.name 'datatype-anat_desc-reconall' + >>> r.components[0][0].startswith('>> r = Reportlet(bl, out_figs, config={ + ... 'title': 'Some Title', 'bids': {'datatype': 'anat', 'desc': 'reconall'}, + ... 'description': 'Some description', 'static': False}) + >>> r.name + 'datatype-anat_desc-reconall' + >>> r.components[0][0].startswith('