Skip to content

ENH: add rodent-specific adaption for B-spline #630

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

24 changes: 22 additions & 2 deletions niworkflows/anat/ants.py
Original file line number Diff line number Diff line change
Expand Up @@ -507,6 +507,8 @@ def init_atropos_wf(
padding=10,
in_segmentation_model=tuple(ATROPOS_MODELS["T1w"].values()),
bspline_fitting_distance=200,
adaptive_bspline_grid=False,
n4_iter=5,
wm_prior=False,
):
"""
Expand Down Expand Up @@ -556,6 +558,12 @@ def init_atropos_wf(
``(4,4,2,3)`` uses K=4, CSF=4, GM=2, WM=3.
bspline_fitting_distance : float
The size of the b-spline mesh grid elements, in mm (default: 200)
adaptive_bspline_grid : :obj:`bool`
If true, defines the number of B-Spline mesh grid elements in each dimension rather
than using the isotropic distance given in ``bspline_fitting_distance``.
n4_iter : :obj:`int`
The number of B-Spline fitting iterations (default: 5). Fewer (e.g. 4) are recommended
for rodents and other non-human/non-adult cases.
wm_prior : :obj:`bool`
Whether the WM posterior obtained with ATROPOS should be regularized with a prior
map (typically, mapped from the template). When ``wm_prior`` is ``True`` the input
Expand Down Expand Up @@ -755,7 +763,7 @@ def init_atropos_wf(
dimension=3,
save_bias=True,
copy_header=True,
n_iterations=[50] * 5,
n_iterations=[50]*n4_iter,
convergence_threshold=1e-7,
shrink_factor=4,
bspline_fitting_distance=bspline_fitting_distance,
Expand Down Expand Up @@ -875,6 +883,19 @@ def _argmax(in_dice):
(apply_wm_prior, inu_n4_final, [("out", "weight_image")]),
])
# fmt: on

if adaptive_bspline_grid:
# set INU bspline grid based on image shape
from ..utils.images import _bspline_grid
bspline_grid = pe.Node(niu.Function(function=_bspline_grid), name="bspline_grid")

# fmt:off
wf.connect([
(inputnode, bspline_grid, [(("in_files", _pop), "in_file")]),
(bspline_grid, inu_n4_final, [("out", "args")])
])
# fmt:on

return wf


Expand Down Expand Up @@ -1045,7 +1066,6 @@ def init_n4_only_wf(
]),
])
# fmt: on

return wf


Expand Down
22 changes: 22 additions & 0 deletions niworkflows/utils/images.py
Original file line number Diff line number Diff line change
Expand Up @@ -324,3 +324,25 @@ def nii_ones_like(in_file, value, dtype, newpath=None):
nii.to_filename(out_file)

return out_file


def _bspline_grid(in_file):
"""
Estimate B-Spline fitting distance grid using the number of slices of ``in_file``.

Using slice number to determine grid sparsity is inspired by the conversation found at
https://itk.org/pipermail/community/2014-February/005036.html

"""
import nibabel as nb
import numpy as np
import math

img = nb.load(in_file)
zooms = img.header.get_zooms()[:3]
# find extent of each dimension wrt voxel sizes
extent = np.array(img.shape[:3]) * zooms
# convert ratio to integers
retval = [f"{math.ceil(i / extent[np.argmin(extent)])}" for i in extent]
# return string for command line call
return f"-b [{'x'.join(retval)}]"
25 changes: 24 additions & 1 deletion niworkflows/workflows/epi/refmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,8 @@
def init_epi_reference_wf(
omp_nthreads,
auto_bold_nss=False,
adaptive_bspline_grid=False,
n4_iter=5,
name="epi_reference_wf",
):
"""
Expand Down Expand Up @@ -84,6 +86,12 @@ def init_epi_reference_wf(
If ``True``, determines nonsteady states in the beginning of the timeseries
and selects them for the averaging of each run.
IMPORTANT: this option applies only to BOLD EPIs.
adaptive_bspline_grid : :obj:`bool`
If ``True``, determines the number of B-Spline grid elements from data shape
and feeds them into N4BiasFieldCorrection, rather than setting an isotropic distance.
n4_iter : :obj:`int`
The number of B-Spline fitting iterations (default: 5). Fewer (e.g. 4) are recommended
for rodents and other non-human/non-adult cases.

Inputs
------
Expand Down Expand Up @@ -158,14 +166,15 @@ def init_epi_reference_wf(
N4BiasFieldCorrection(
dimension=3,
copy_header=True,
n_iterations=[50] * 5,
n_iterations=[50]*n4_iter,
convergence_threshold=1e-7,
shrink_factor=4,
),
n_procs=omp_nthreads,
name="n4_avgs",
iterfield=["input_image"],
)

clip_bg_noise = pe.MapNode(
IntensityClip(p_min=2.0, p_max=100.0),
name="clip_bg_noise",
Expand Down Expand Up @@ -225,6 +234,20 @@ def _set_threads(in_list, maximum):
else:
wf.connect(inputnode, "t_masks", per_run_avgs, "t_mask")

# rodent-specific N4 settings
if adaptive_bspline_grid:
from ...utils.images import _bspline_grid
from ...utils.connections import pop_file as _pop
# set INU bspline grid based on voxel size
bspline_grid = pe.Node(niu.Function(function=_bspline_grid), name="bspline_grid")

# fmt:off
wf.connect([
(clip_avgs, bspline_grid, [(("out_file", _pop), "in_file")]),
(bspline_grid, n4_avgs, [("out", "args")])
])
# fmt:on

return wf


Expand Down
2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ install_requires =
matplotlib >= 2.2.0
nibabel >= 3.0.1
nilearn >= 0.2.6, != 0.5.0, != 0.5.1
nipype >= 1.5.1
nipype @ git+https://github.com/nipy/nipype.git@master
nitransforms >= 20.0.0rc3,<20.2
numpy
packaging
Expand Down