Source code for thesis.workflows.mrtrix3.nodes.preprocessing

"""DWI import and brain-mask preparation nodes."""

from pathlib import Path

from nipype import Node
from nipype.interfaces.mrtrix3 import MRConvert
from nipype.interfaces.utility import Function

from ..operations import generate_dwi2mask_task, prepare_brain_mask_task


[docs] def prepare_dwi_import( dwi_image: Path, bvec: Path, bval: Path, out_dir: Path, name: str = "dwi_import", ) -> Node: """Convert NIfTI DWI + FSL gradient table to ``dwi.mif``. The resulting ``.mif`` bundles the gradient table so downstream interfaces (``ResponseSD``, ``EstimateFOD``) do not need to be repeatedly told about ``bvecs``/``bvals``. Args: dwi_image: Path to the DWI NIfTI (typically ``data.nii.gz``). bvec: Path to the FSL bvecs file. bval: Path to the FSL bvals file. out_dir: Output directory; ``dwi.mif`` is written here. name: Nipype node name. Returns: Configured Nipype Node. """ out_dir.mkdir(parents=True, exist_ok=True) node = Node(MRConvert(), name=name) # ``MRConvert.in_file`` carries an ``exists=True`` trait, which would fail # build-time validation if the DWI hasn't been generated yet (e.g. when # this sub-workflow is embedded in ``full_pipeline`` (mrtrix3 backend) and consumes # preprocess output that doesn't exist at build time). When the static # paths are absent, leave the inputs Undefined; the parent meta-workflow # is expected to wire ``in_file`` and ``grad_fsl`` at runtime. if Path(dwi_image).exists(): node.inputs.in_file = str(dwi_image) node.inputs.grad_fsl = (str(bvec), str(bval)) node.inputs.out_file = str(out_dir / "dwi.mif") # Outputs land in the patient's shared dir, not the Nipype work dir, so # restarts must overwrite stale artefacts from a previous failed run. node.inputs.args = "-force" return node
[docs] def prepare_mask_node( mask_path: Path, out_dir: Path, dilate_voxels: int = 1, name: str = "mask_prep", ) -> Node: """Import the FSL brain mask and dilate it for MRtrix consumption. Wrapped as a Function node (rather than chaining ``MRConvert`` and ``MaskFilter``) so the dilation pass count is preserved as a single visible parameter and the output filename is deterministic. Args: mask_path: Source NIfTI mask (typically ``nodif_brain_mask.nii.gz``). out_dir: Output directory where ``mask.mif`` is written. dilate_voxels: Number of dilation passes (0 disables dilation). name: Nipype node name. Returns: Configured Nipype Node with ``out_file`` output. """ out_dir.mkdir(parents=True, exist_ok=True) node = Node( Function( input_names=["mask_path", "output_dir", "dilate_voxels"], output_names=["out_file"], function=prepare_brain_mask_task, ), name=name, ) node.inputs.mask_path = str(mask_path) node.inputs.output_dir = str(out_dir) node.inputs.dilate_voxels = int(dilate_voxels) return node
[docs] def prepare_dwi2mask_node( out_dir: Path, dilate_voxels: int = 1, algorithm: str = "legacy", name: str = "mask_prep", ) -> Node: """Generate the brain mask from the DWI via ``dwi2mask`` (mask_source='dwi2mask'). Drop-in alternative to :func:`prepare_mask_node`: produces the same ``mask.mif`` output field, but derives the mask from the imported DWI ``.mif`` instead of importing the FSL ``nodif_brain_mask``. The workflow wires ``dwi_file`` from the ``dwi_import`` node's ``out_file`` at build time. Args: out_dir: Output directory where ``mask.mif`` is written. dilate_voxels: Number of dilation passes (0 disables dilation). algorithm: ``dwi2mask`` algorithm selector (default ``"legacy"``). name: Nipype node name (default ``"mask_prep"`` so it is a true drop-in for :func:`prepare_mask_node`). Returns: Configured Nipype Node with ``out_file`` output and an unconnected ``dwi_file`` input (wired by the workflow). """ out_dir.mkdir(parents=True, exist_ok=True) node = Node( Function( input_names=["dwi_file", "output_dir", "dilate_voxels", "algorithm"], output_names=["out_file"], function=generate_dwi2mask_task, ), name=name, ) node.inputs.output_dir = str(out_dir) node.inputs.dilate_voxels = int(dilate_voxels) node.inputs.algorithm = str(algorithm) return node