Skip to content

Commit 25652d6

Browse files
committed
include number of distinct shells in reportlet
1 parent bb9104d commit 25652d6

File tree

6 files changed

+90
-8
lines changed

6 files changed

+90
-8
lines changed

dmriprep/config/reports-spec.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ sections:
6262
- name: Diffusion
6363
ordering: session,acquisition,run
6464
reportlets:
65+
- bids: {datatype: figures, desc: summary, suffix: dwi}
6566
- bids: {datatype: figures, desc: validation, suffix: dwi}
6667
- bids: {datatype: figures, desc: brain, suffix: mask}
6768
caption: Average <em>b=0</em> that serves for reference in early preprocessing steps.

dmriprep/interfaces/reports.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,16 @@
3030
\t</ul>
3131
"""
3232

33+
DIFFUSION_TEMPLATE = """\
34+
\t\t<details open>
35+
\t\t<summary>Summary</summary>
36+
\t\t<ul class="elem-desc">
37+
\t\t\t<li>Phase-encoding (PE) direction: {pe_direction}</li>
38+
\t\t\t<li>Distinct shells: {shells_dist}</li>
39+
\t\t</ul>
40+
\t\t</details>
41+
"""
42+
3343
ABOUT_TEMPLATE = """\t<ul>
3444
\t\t<li>dMRIPrep version: {version}</li>
3545
\t\t<li>dMRIPrep command: <code>{command}</code></li>
@@ -119,6 +129,25 @@ def _generate_segment(self):
119129
)
120130

121131

132+
class DiffusionSummaryInputSpec(BaseInterfaceInputSpec):
133+
pe_direction = traits.Enum(
134+
None, "i", "i-", "j", "j-", "k", "k-", desc="Phase-encoding direction detected"
135+
)
136+
shells_dist = traits.Dict(mandatory=True, desc="Number of distinct shells")
137+
138+
139+
class DiffusionSummary(SummaryInterface):
140+
input_spec = DiffusionSummaryInputSpec
141+
142+
def _generate_segment(self):
143+
pe_direction = self.inputs.pe_direction
144+
shells_dist = self.inputs.shells_dist
145+
146+
return DIFFUSION_TEMPLATE.format(
147+
pe_direction=pe_direction, shells_dist=shells_dist
148+
)
149+
150+
122151
class AboutSummaryInputSpec(BaseInterfaceInputSpec):
123152
version = Str(desc="dMRIPrep version")
124153
command = Str(desc="dMRIPrep command")

dmriprep/interfaces/vectors.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ class _CheckGradientTableOutputSpec(TraitedSpec):
2929
out_bvec = File(exists=True)
3030
full_sphere = traits.Bool()
3131
pole = traits.Tuple(traits.Float, traits.Float, traits.Float)
32+
num_shells = traits.Int
33+
shells_dist = traits.Dict
3234
b0_ixs = traits.List(traits.Int)
3335

3436

@@ -47,6 +49,10 @@ class CheckGradientTable(SimpleInterface):
4749
(0.0, 0.0, 0.0)
4850
>>> check.outputs.full_sphere
4951
True
52+
>>> check.outputs.num_shells
53+
3
54+
>>> check.outputs.shells_dist
55+
{0.0: 12, 1200.0: 32, 2500.0: 61}
5056
5157
>>> check = CheckGradientTable(
5258
... dwi_file=str(data_dir / 'dwi.nii.gz'),
@@ -56,6 +62,10 @@ class CheckGradientTable(SimpleInterface):
5662
(0.0, 0.0, 0.0)
5763
>>> check.outputs.full_sphere
5864
True
65+
>>> check.outputs.num_shells
66+
3
67+
>>> check.outputs.shells_dist
68+
{0: 12, 1200: 32, 2500: 61}
5969
>>> newrasb = np.loadtxt(check.outputs.out_rasb, skiprows=1)
6070
>>> oldrasb = np.loadtxt(str(data_dir / 'dwi.tsv'), skiprows=1)
6171
>>> np.allclose(newrasb, oldrasb, rtol=1.e-3)
@@ -81,6 +91,8 @@ def _run_interface(self, runtime):
8191
pole = table.pole
8292
self._results["pole"] = tuple(pole)
8393
self._results["full_sphere"] = np.all(pole == 0.0)
94+
self._results["num_shells"] = len(table.count_shells)
95+
self._results["shells_dist"] = table.count_shells
8496
self._results["b0_ixs"] = np.where(table.b0mask)[0].tolist()
8597

8698
cwd = Path(runtime.cwd).absolute()

dmriprep/utils/vectors.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Utilities to operate on diffusion gradients."""
22
from .. import config
3+
from collections import Counter
34
from pathlib import Path
45
from itertools import permutations
56
import nibabel as nb
@@ -174,6 +175,11 @@ def bvals(self, value):
174175
raise ValueError("The number of b-vectors and b-values do not match")
175176
self._bvals = np.array(value)
176177

178+
@property
179+
def count_shells(self):
180+
"""Count the number of volumes per b-value."""
181+
return Counter(sorted(self._bvals))
182+
177183
@property
178184
def b0mask(self):
179185
"""Get a mask of low-b frames."""
@@ -291,7 +297,7 @@ def normalize_gradients(
291297
Normalize b-vectors and b-values.
292298
293299
The resulting b-vectors will be of unit length for the non-zero b-values.
294-
The resultinb b-values will be normalized by the square of the
300+
The resulting b-values will be normalized by the square of the
295301
corresponding vector amplitude.
296302
297303
Parameters

dmriprep/workflows/dwi/base.py

Lines changed: 20 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from niworkflows.engine.workflows import LiterateWorkflow as Workflow
88
from ...interfaces import DerivativesDataSink
9+
from ...interfaces.reports import DiffusionSummary
910

1011

1112
def init_dwi_preproc_wf(dwi_file, has_fieldmap=False):
@@ -41,6 +42,8 @@ def init_dwi_preproc_wf(dwi_file, has_fieldmap=False):
4142
File path of the b-vectors
4243
in_bval
4344
File path of the b-values
45+
metadata
46+
dwi metadata
4447
fmap
4548
File path of the fieldmap
4649
fmap_ref
@@ -78,6 +81,7 @@ def init_dwi_preproc_wf(dwi_file, has_fieldmap=False):
7881
config.loggers.workflow.debug(
7982
f"Creating DWI preprocessing workflow for <{dwi_file.name}>"
8083
)
84+
metadata = layout.get_metadata(str(dwi_file))
8185

8286
if has_fieldmap:
8387
import re
@@ -103,6 +107,7 @@ def init_dwi_preproc_wf(dwi_file, has_fieldmap=False):
103107
"dwi_file",
104108
"in_bvec",
105109
"in_bval",
110+
"metadata",
106111
# From SDCFlows
107112
"fmap",
108113
"fmap_ref",
@@ -130,12 +135,19 @@ def init_dwi_preproc_wf(dwi_file, has_fieldmap=False):
130135
inputnode.inputs.dwi_file = str(dwi_file.absolute())
131136
inputnode.inputs.in_bvec = str(layout.get_bvec(dwi_file))
132137
inputnode.inputs.in_bval = str(layout.get_bval(dwi_file))
138+
inputnode.metadata = metadata
133139

134140
outputnode = pe.Node(
135141
niu.IdentityInterface(fields=["dwi_reference", "dwi_mask", "gradients_rasb"]),
136142
name="outputnode",
137143
)
138144

145+
summary = pe.Node(
146+
DiffusionSummary(pe_direction=metadata.get("PhaseEncodingDirection")),
147+
name="dwi_summary",
148+
run_without_submitting=True,
149+
)
150+
139151
gradient_table = pe.Node(CheckGradientTable(), name="gradient_table")
140152

141153
dwi_reference_wf = init_dwi_reference_wf(
@@ -149,6 +161,7 @@ def init_dwi_preproc_wf(dwi_file, has_fieldmap=False):
149161
("in_bvec", "in_bvec"),
150162
("in_bval", "in_bval")]),
151163
(inputnode, dwi_reference_wf, [("dwi_file", "inputnode.dwi_file")]),
164+
(gradient_table, summary, [("shells_dist", "shells_dist")]),
152165
(gradient_table, dwi_reference_wf, [("b0_ixs", "inputnode.b0_ixs")]),
153166
(gradient_table, outputnode, [("out_rasb", "gradients_rasb")]),
154167
])
@@ -209,6 +222,7 @@ def _bold_reg_suffix(fallback):
209222
# fmt: off
210223
workflow.connect([
211224
(inputnode, reportlets_wf, [("dwi_file", "inputnode.source_file")]),
225+
(summary, reportlets_wf, [("out_report", "inputnode.summary_report")]),
212226
(dwi_reference_wf, reportlets_wf, [
213227
("outputnode.validation_report", "inputnode.validation_report"),
214228
]),
@@ -239,10 +253,8 @@ def _bold_reg_suffix(fallback):
239253
write_coeff=True,
240254
)
241255
unwarp_wf = init_unwarp_wf(
242-
debug=config.execution.debug,
243-
omp_nthreads=config.nipype.omp_nthreads
256+
debug=config.execution.debug, omp_nthreads=config.nipype.omp_nthreads
244257
)
245-
unwarp_wf.inputs.inputnode.metadata = layout.get_metadata(str(dwi_file))
246258

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

259271
sdc_report = pe.Node(
260-
SimpleBeforeAfter(before_label="Distorted", after_label="Corrected",),
272+
SimpleBeforeAfter(
273+
before_label="Distorted",
274+
after_label="Corrected",
275+
),
261276
name="sdc_report",
262277
mem_gb=0.1,
263278
)
@@ -276,6 +291,7 @@ def _bold_reg_suffix(fallback):
276291
(dwi_reference_wf, coeff2epi_wf, [
277292
("outputnode.ref_image", "inputnode.target_ref"),
278293
("outputnode.dwi_mask", "inputnode.target_mask")]),
294+
(inputnode, unwarp_wf, [("metadata", "inputnode.metadata")]),
279295
(dwi_reference_wf, unwarp_wf, [("outputnode.ref_image", "inputnode.distorted")]),
280296
(coeff2epi_wf, unwarp_wf, [
281297
("outputnode.fmap_coeff", "inputnode.fmap_coeff")]),

dmriprep/workflows/dwi/outputs.py

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,26 @@ def init_reportlets_wf(output_dir, sdc_report=False, name="reportlets_wf"):
1313

1414
inputnode = pe.Node(
1515
niu.IdentityInterface(
16-
fields=["source_file", "dwi_ref", "dwi_mask", "validation_report", "sdc_report"]
16+
fields=[
17+
"source_file",
18+
"summary_report",
19+
"dwi_ref",
20+
"dwi_mask",
21+
"validation_report",
22+
"sdc_report",
23+
]
1724
),
1825
name="inputnode",
1926
)
27+
28+
ds_report_summary = pe.Node(
29+
DerivativesDataSink(
30+
base_directory=output_dir, desc="summary", datatype="figures"
31+
),
32+
name="ds_report_summary",
33+
run_without_submitting=True,
34+
)
35+
2036
mask_reportlet = pe.Node(SimpleShowMaskRPT(), name="mask_reportlet")
2137

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

3753
# fmt:off
3854
workflow.connect([
55+
(inputnode, ds_report_summary, [("source_file", "source_file"),
56+
("summary_report", "in_file")]),
3957
(inputnode, mask_reportlet, [("dwi_ref", "background_file"),
4058
("dwi_mask", "mask_file")]),
41-
(inputnode, ds_report_validation, [("source_file", "source_file")]),
59+
(inputnode, ds_report_validation, [("source_file", "source_file"),
60+
("validation_report", "in_file")]),
4261
(inputnode, ds_report_mask, [("source_file", "source_file")]),
43-
(inputnode, ds_report_validation, [("validation_report", "in_file")]),
4462
(mask_reportlet, ds_report_mask, [("out_report", "in_file")]),
4563
])
4664
# fmt:on

0 commit comments

Comments
 (0)