Skip to content

Commit

Permalink
include number of distinct shells in reportlet
Browse files Browse the repository at this point in the history
  • Loading branch information
josephmje committed Feb 8, 2021
1 parent bb9104d commit 25652d6
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 8 deletions.
1 change: 1 addition & 0 deletions dmriprep/config/reports-spec.yml
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ sections:
- name: Diffusion
ordering: session,acquisition,run
reportlets:
- bids: {datatype: figures, desc: summary, suffix: dwi}
- bids: {datatype: figures, desc: validation, suffix: dwi}
- bids: {datatype: figures, desc: brain, suffix: mask}
caption: Average <em>b=0</em> that serves for reference in early preprocessing steps.
Expand Down
29 changes: 29 additions & 0 deletions dmriprep/interfaces/reports.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,16 @@
\t</ul>
"""

DIFFUSION_TEMPLATE = """\
\t\t<details open>
\t\t<summary>Summary</summary>
\t\t<ul class="elem-desc">
\t\t\t<li>Phase-encoding (PE) direction: {pe_direction}</li>
\t\t\t<li>Distinct shells: {shells_dist}</li>
\t\t</ul>
\t\t</details>
"""

ABOUT_TEMPLATE = """\t<ul>
\t\t<li>dMRIPrep version: {version}</li>
\t\t<li>dMRIPrep command: <code>{command}</code></li>
Expand Down Expand Up @@ -119,6 +129,25 @@ def _generate_segment(self):
)


class DiffusionSummaryInputSpec(BaseInterfaceInputSpec):
pe_direction = traits.Enum(
None, "i", "i-", "j", "j-", "k", "k-", desc="Phase-encoding direction detected"
)
shells_dist = traits.Dict(mandatory=True, desc="Number of distinct shells")


class DiffusionSummary(SummaryInterface):
input_spec = DiffusionSummaryInputSpec

def _generate_segment(self):
pe_direction = self.inputs.pe_direction
shells_dist = self.inputs.shells_dist

return DIFFUSION_TEMPLATE.format(
pe_direction=pe_direction, shells_dist=shells_dist
)


class AboutSummaryInputSpec(BaseInterfaceInputSpec):
version = Str(desc="dMRIPrep version")
command = Str(desc="dMRIPrep command")
Expand Down
12 changes: 12 additions & 0 deletions dmriprep/interfaces/vectors.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ class _CheckGradientTableOutputSpec(TraitedSpec):
out_bvec = File(exists=True)
full_sphere = traits.Bool()
pole = traits.Tuple(traits.Float, traits.Float, traits.Float)
num_shells = traits.Int
shells_dist = traits.Dict
b0_ixs = traits.List(traits.Int)


Expand All @@ -47,6 +49,10 @@ class CheckGradientTable(SimpleInterface):
(0.0, 0.0, 0.0)
>>> check.outputs.full_sphere
True
>>> check.outputs.num_shells
3
>>> check.outputs.shells_dist
{0.0: 12, 1200.0: 32, 2500.0: 61}
>>> check = CheckGradientTable(
... dwi_file=str(data_dir / 'dwi.nii.gz'),
Expand All @@ -56,6 +62,10 @@ class CheckGradientTable(SimpleInterface):
(0.0, 0.0, 0.0)
>>> check.outputs.full_sphere
True
>>> check.outputs.num_shells
3
>>> check.outputs.shells_dist
{0: 12, 1200: 32, 2500: 61}
>>> newrasb = np.loadtxt(check.outputs.out_rasb, skiprows=1)
>>> oldrasb = np.loadtxt(str(data_dir / 'dwi.tsv'), skiprows=1)
>>> np.allclose(newrasb, oldrasb, rtol=1.e-3)
Expand All @@ -81,6 +91,8 @@ def _run_interface(self, runtime):
pole = table.pole
self._results["pole"] = tuple(pole)
self._results["full_sphere"] = np.all(pole == 0.0)
self._results["num_shells"] = len(table.count_shells)
self._results["shells_dist"] = table.count_shells
self._results["b0_ixs"] = np.where(table.b0mask)[0].tolist()

cwd = Path(runtime.cwd).absolute()
Expand Down
8 changes: 7 additions & 1 deletion dmriprep/utils/vectors.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Utilities to operate on diffusion gradients."""
from .. import config
from collections import Counter
from pathlib import Path
from itertools import permutations
import nibabel as nb
Expand Down Expand Up @@ -174,6 +175,11 @@ def bvals(self, value):
raise ValueError("The number of b-vectors and b-values do not match")
self._bvals = np.array(value)

@property
def count_shells(self):
"""Count the number of volumes per b-value."""
return Counter(sorted(self._bvals))

@property
def b0mask(self):
"""Get a mask of low-b frames."""
Expand Down Expand Up @@ -291,7 +297,7 @@ def normalize_gradients(
Normalize b-vectors and b-values.
The resulting b-vectors will be of unit length for the non-zero b-values.
The resultinb b-values will be normalized by the square of the
The resulting b-values will be normalized by the square of the
corresponding vector amplitude.
Parameters
Expand Down
24 changes: 20 additions & 4 deletions dmriprep/workflows/dwi/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

from niworkflows.engine.workflows import LiterateWorkflow as Workflow
from ...interfaces import DerivativesDataSink
from ...interfaces.reports import DiffusionSummary


def init_dwi_preproc_wf(dwi_file, has_fieldmap=False):
Expand Down Expand Up @@ -41,6 +42,8 @@ def init_dwi_preproc_wf(dwi_file, has_fieldmap=False):
File path of the b-vectors
in_bval
File path of the b-values
metadata
dwi metadata
fmap
File path of the fieldmap
fmap_ref
Expand Down Expand Up @@ -78,6 +81,7 @@ def init_dwi_preproc_wf(dwi_file, has_fieldmap=False):
config.loggers.workflow.debug(
f"Creating DWI preprocessing workflow for <{dwi_file.name}>"
)
metadata = layout.get_metadata(str(dwi_file))

if has_fieldmap:
import re
Expand All @@ -103,6 +107,7 @@ def init_dwi_preproc_wf(dwi_file, has_fieldmap=False):
"dwi_file",
"in_bvec",
"in_bval",
"metadata",
# From SDCFlows
"fmap",
"fmap_ref",
Expand Down Expand Up @@ -130,12 +135,19 @@ def init_dwi_preproc_wf(dwi_file, has_fieldmap=False):
inputnode.inputs.dwi_file = str(dwi_file.absolute())
inputnode.inputs.in_bvec = str(layout.get_bvec(dwi_file))
inputnode.inputs.in_bval = str(layout.get_bval(dwi_file))
inputnode.metadata = metadata

outputnode = pe.Node(
niu.IdentityInterface(fields=["dwi_reference", "dwi_mask", "gradients_rasb"]),
name="outputnode",
)

summary = pe.Node(
DiffusionSummary(pe_direction=metadata.get("PhaseEncodingDirection")),
name="dwi_summary",
run_without_submitting=True,
)

gradient_table = pe.Node(CheckGradientTable(), name="gradient_table")

dwi_reference_wf = init_dwi_reference_wf(
Expand All @@ -149,6 +161,7 @@ def init_dwi_preproc_wf(dwi_file, has_fieldmap=False):
("in_bvec", "in_bvec"),
("in_bval", "in_bval")]),
(inputnode, dwi_reference_wf, [("dwi_file", "inputnode.dwi_file")]),
(gradient_table, summary, [("shells_dist", "shells_dist")]),
(gradient_table, dwi_reference_wf, [("b0_ixs", "inputnode.b0_ixs")]),
(gradient_table, outputnode, [("out_rasb", "gradients_rasb")]),
])
Expand Down Expand Up @@ -209,6 +222,7 @@ def _bold_reg_suffix(fallback):
# fmt: off
workflow.connect([
(inputnode, reportlets_wf, [("dwi_file", "inputnode.source_file")]),
(summary, reportlets_wf, [("out_report", "inputnode.summary_report")]),
(dwi_reference_wf, reportlets_wf, [
("outputnode.validation_report", "inputnode.validation_report"),
]),
Expand Down Expand Up @@ -239,10 +253,8 @@ def _bold_reg_suffix(fallback):
write_coeff=True,
)
unwarp_wf = init_unwarp_wf(
debug=config.execution.debug,
omp_nthreads=config.nipype.omp_nthreads
debug=config.execution.debug, omp_nthreads=config.nipype.omp_nthreads
)
unwarp_wf.inputs.inputnode.metadata = layout.get_metadata(str(dwi_file))

output_select = pe.Node(
KeySelect(fields=["fmap", "fmap_ref", "fmap_coeff", "fmap_mask"]),
Expand All @@ -257,7 +269,10 @@ def _bold_reg_suffix(fallback):
)

sdc_report = pe.Node(
SimpleBeforeAfter(before_label="Distorted", after_label="Corrected",),
SimpleBeforeAfter(
before_label="Distorted",
after_label="Corrected",
),
name="sdc_report",
mem_gb=0.1,
)
Expand All @@ -276,6 +291,7 @@ def _bold_reg_suffix(fallback):
(dwi_reference_wf, coeff2epi_wf, [
("outputnode.ref_image", "inputnode.target_ref"),
("outputnode.dwi_mask", "inputnode.target_mask")]),
(inputnode, unwarp_wf, [("metadata", "inputnode.metadata")]),
(dwi_reference_wf, unwarp_wf, [("outputnode.ref_image", "inputnode.distorted")]),
(coeff2epi_wf, unwarp_wf, [
("outputnode.fmap_coeff", "inputnode.fmap_coeff")]),
Expand Down
24 changes: 21 additions & 3 deletions dmriprep/workflows/dwi/outputs.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,26 @@ def init_reportlets_wf(output_dir, sdc_report=False, name="reportlets_wf"):

inputnode = pe.Node(
niu.IdentityInterface(
fields=["source_file", "dwi_ref", "dwi_mask", "validation_report", "sdc_report"]
fields=[
"source_file",
"summary_report",
"dwi_ref",
"dwi_mask",
"validation_report",
"sdc_report",
]
),
name="inputnode",
)

ds_report_summary = pe.Node(
DerivativesDataSink(
base_directory=output_dir, desc="summary", datatype="figures"
),
name="ds_report_summary",
run_without_submitting=True,
)

mask_reportlet = pe.Node(SimpleShowMaskRPT(), name="mask_reportlet")

ds_report_mask = pe.Node(
Expand All @@ -36,11 +52,13 @@ def init_reportlets_wf(output_dir, sdc_report=False, name="reportlets_wf"):

# fmt:off
workflow.connect([
(inputnode, ds_report_summary, [("source_file", "source_file"),
("summary_report", "in_file")]),
(inputnode, mask_reportlet, [("dwi_ref", "background_file"),
("dwi_mask", "mask_file")]),
(inputnode, ds_report_validation, [("source_file", "source_file")]),
(inputnode, ds_report_validation, [("source_file", "source_file"),
("validation_report", "in_file")]),
(inputnode, ds_report_mask, [("source_file", "source_file")]),
(inputnode, ds_report_validation, [("validation_report", "in_file")]),
(mask_reportlet, ds_report_mask, [("out_report", "in_file")]),
])
# fmt:on
Expand Down

0 comments on commit 25652d6

Please sign in to comment.