Source code for thesis.workflows.mrtrix3.operations.mask_prep

"""Brain-mask preparation for the MRtrix3 workflow.

Imports the FSL ``nodif_brain_mask.nii.gz`` to MRtrix's native ``.mif``
format and optionally dilates it. The dilation step prevents the white-
matter gaps that ``dwi2mask legacy`` produces in the cerebral peduncle and
other deep WM (a verified failure mode that killed tractography during
the manual MRtrix3 validation run).

Used as the body of a Nipype Function node so the workflow graph captures
the dilation parameter and output path explicitly.
"""

# NOTE: Nipype Function nodes execute in subprocesses and cannot pickle the
# project's loguru logger. Use ``print`` for diagnostics inside this body.


[docs] def prepare_brain_mask_task( mask_path: str, output_dir: str, dilate_voxels: int = 1, ) -> str: """Import the FSL brain mask into ``mask.mif`` with optional dilation. Args: mask_path: Absolute path to the source NIfTI brain mask. output_dir: Directory where ``mask.mif`` is written. dilate_voxels: Number of dilation passes (0 disables dilation). Returns: Absolute path to the produced ``.mif`` mask. """ import shutil import subprocess from pathlib import Path out_dir = Path(output_dir) out_dir.mkdir(parents=True, exist_ok=True) src = Path(mask_path) if not src.exists(): raise FileNotFoundError(f"Brain mask not found: {src}") converted = out_dir / "mask_raw.mif" final = out_dir / "mask.mif" if shutil.which("mrconvert") is None: raise RuntimeError("mrconvert not found on PATH; ensure MRtrix3 is installed.") convert_cmd = ["mrconvert", str(src), str(converted), "-force"] print(f"[mrtrix3] {' '.join(convert_cmd)}") subprocess.run(convert_cmd, check=True) if dilate_voxels and dilate_voxels > 0: if shutil.which("maskfilter") is None: raise RuntimeError("maskfilter not found on PATH; ensure MRtrix3 is installed.") dilate_cmd = [ "maskfilter", str(converted), "dilate", str(final), "-npass", str(int(dilate_voxels)), "-force", ] print(f"[mrtrix3] {' '.join(dilate_cmd)}") subprocess.run(dilate_cmd, check=True) try: converted.unlink() except OSError: pass else: converted.replace(final) return str(final)
[docs] def generate_dwi2mask_task( dwi_file: str, output_dir: str, dilate_voxels: int = 1, algorithm: str = "legacy", ) -> str: """Generate a brain mask from the DWI via MRtrix3 ``dwi2mask``. Alternative to :func:`prepare_brain_mask_task`: instead of importing and dilating the FSL ``nodif_brain_mask``, this derives the mask directly from the (already ``.mif``) DWI series with ``dwi2mask``. An optional dilation pass widens the mask the same way :func:`prepare_brain_mask_task` does, so downstream ACT does not lose deep white matter. Args: dwi_file: Absolute path to the DWI ``.mif`` (carries its gradient table). output_dir: Directory where ``mask.mif`` is written. dilate_voxels: Number of dilation passes (0 disables dilation). algorithm: ``dwi2mask`` algorithm selector (default ``"legacy"``). Returns: Absolute path to the produced ``.mif`` mask. """ import shutil import subprocess from pathlib import Path out_dir = Path(output_dir) out_dir.mkdir(parents=True, exist_ok=True) src = Path(dwi_file) if not src.exists(): raise FileNotFoundError(f"DWI not found: {src}") raw = out_dir / "mask_raw.mif" final = out_dir / "mask.mif" if shutil.which("dwi2mask") is None: raise RuntimeError("dwi2mask not found on PATH; ensure MRtrix3 is installed.") mask_cmd = ["dwi2mask", str(algorithm), str(src), str(raw), "-force"] print(f"[mrtrix3] {' '.join(mask_cmd)}") subprocess.run(mask_cmd, check=True) if dilate_voxels and dilate_voxels > 0: if shutil.which("maskfilter") is None: raise RuntimeError("maskfilter not found on PATH; ensure MRtrix3 is installed.") dilate_cmd = [ "maskfilter", str(raw), "dilate", str(final), "-npass", str(int(dilate_voxels)), "-force", ] print(f"[mrtrix3] {' '.join(dilate_cmd)}") subprocess.run(dilate_cmd, check=True) try: raw.unlink() except OSError: pass else: raw.replace(final) return str(final)