Repository: nipy/nipype

Issue:
Compatibility with `nibabel` 4.0.0
### Actual behavior

`nipype` uses the [now removed](https://nipy.org/nibabel/changelog.html#api-changes-and-deprecations) `nibabel.trackvis` interface, which leads to a `ModuleNotFoundError`. 

https://github.com/nipy/nipype/blob/f4343d6ddaee814aa16b197cc729a10d437990bf/nipype/interfaces/mrtrix/convert.py#L8
```
│     ---- (full traceback above) ----
│     File "/usr/local/miniconda/lib/python3.10/site-packages/fmriprep/cli/workflow.py", line 82, in build_workflow
│         retval["workflow"] = init_fmriprep_wf()
│     File "/usr/local/miniconda/lib/python3.10/site-packages/fmriprep/workflows/base.py", line 64, in init_fmriprep_wf
│         single_subject_wf = init_single_subject_wf(subject_id)
│     File "/usr/local/miniconda/lib/python3.10/site-packages/fmriprep/workflows/base.py", line 304, in init_single_subject_wf
│         func_preproc_wf = init_func_preproc_wf(bold_file)
│     File "/usr/local/miniconda/lib/python3.10/site-packages/fmriprep/workflows/bold/base.py", line 419, in init_func_preproc_wf
│         bold_sdc_wf = init_sdc_estimate_wf(fmaps, metadata,
│     File "/usr/local/miniconda/lib/python3.10/site-packages/sdcflows/workflows/base.py", line 154, in init_sdc_estimate_wf
│         from .fmap import init_fmap2field_wf
│     File "/usr/local/miniconda/lib/python3.10/site-packages/sdcflows/workflows/fmap.py", line 29, in <module>
│         from .gre import init_fmap_postproc_wf, init_magnitude_wf
│     File "/usr/local/miniconda/lib/python3.10/site-packages/sdcflows/workflows/gre.py", line 17, in <module>
│         from niflow.nipype1.workflows.dmri.fsl.utils import cleanup_edge_pipeline
│     File "/usr/local/miniconda/lib/python3.10/site-packages/niflow/nipype1/workflows/dmri/__init__.py", line 4, in <module>
│         from . import camino, mrtrix, fsl, dipy
│     File "/usr/local/miniconda/lib/python3.10/site-packages/niflow/nipype1/workflows/dmri/mrtrix/__init__.py", line 3, in <module>
│         from .diffusion import create_mrtrix_dti_pipeline
│     File "/usr/local/miniconda/lib/python3.10/site-packages/niflow/nipype1/workflows/dmri/mrtrix/diffusion.py", line 5, in <module>
│         from nipype.interfaces import mrtrix as mrtrix
│     File "/usr/local/miniconda/lib/python3.10/site-packages/nipype/interfaces/mrtrix/__init__.py", line 36, in <module>
│         from .convert import MRTrix2TrackVis
│     File "/usr/local/miniconda/lib/python3.10/site-packages/nipype/interfaces/mrtrix/convert.py", line 6, in <module>
│         import nibabel.trackvis as trk
└─    ModuleNotFoundError: No module named 'nibabel.trackvis'
```

### Expected behavior

There should not be an error.

### My suggestions

- [ ] Update the requirements.txt to indicate a lack of compatibility with `nibabel` 4.0.0,

https://github.com/nipy/nipype/blob/f4343d6ddaee814aa16b197cc729a10d437990bf/requirements.txt#L4

- [ ] Find out where the removed interfaces were used, and replace them.

### How to replicate the behavior

I'm skipping this for now, because I think the issue is obvious from the code. If I'm mistaken, please let me know and I will edit to add some steps.

### Script/Workflow details

This happens when using `fMRIPrep` LTS with [`HALFpipe`](https://github.com/HALFpipe/HALFpipe).

### Platform details:

<!-- Please run the following code from your shell and place the output between the triple ticks, below.
python -c "import nipype; from pprint import pprint; pprint(nipype.get_info())"
-->
I am skipping this for now, since I believe the issue is unrelated to my platform. Please let me know if you require this information.

### Execution environment

Container downloaded from <https://download.fmri.science/singularity/halfpipe-halfpipe-latest.sif>


Discussion:
I think it makes more sense to make it a runtime error instead of import time. Most people will not be using this interface. So we move the `import` into the method that calls it, and wrap it with a `try/except ImportError` to give a more helpful error message.

Resolving patch:
diff --git a/nipype/interfaces/mrtrix/convert.py b/nipype/interfaces/mrtrix/convert.py
--- a/nipype/interfaces/mrtrix/convert.py
+++ b/nipype/interfaces/mrtrix/convert.py
@@ -3,9 +3,7 @@
 # vi: set ft=python sts=4 ts=4 sw=4 et:
 import os.path as op
 import nibabel as nb
-import nibabel.trackvis as trk
 import numpy as np
-from nibabel.trackvis import HeaderError
 from nibabel.volumeutils import native_code
 from nibabel.orientations import aff2axcodes
 
@@ -120,7 +118,7 @@ def track_gen(track_points):
             nan_str = fileobj.read(bytesize)
             if len(pts_str) < (n_pts * bytesize):
                 if not n_streams == stream_count:
-                    raise HeaderError(
+                    raise nb.trackvis.HeaderError(
                         "Expecting %s points, found only %s" % (stream_count, n_streams)
                     )
                     iflogger.error(
@@ -255,7 +253,7 @@ def _run_interface(self, runtime):
 
             final_streamlines = transform_tracking_output(transformed_streamlines, aff)
             trk_tracks = ((ii, None, None) for ii in final_streamlines)
-            trk.write(out_filename, trk_tracks, trk_header)
+            nb.trackvis.write(out_filename, trk_tracks, trk_header)
             iflogger.info("Saving transformed Trackvis file as %s", out_filename)
             iflogger.info("New TrackVis Header:")
             iflogger.info(trk_header)
@@ -271,7 +269,7 @@ def _run_interface(self, runtime):
                 streamlines, trk_header, affine
             )
             trk_tracks = ((ii, None, None) for ii in transformed_streamlines)
-            trk.write(out_filename, trk_tracks, trk_header)
+            nb.trackvis.write(out_filename, trk_tracks, trk_header)
             iflogger.info("Saving Trackvis file as %s", out_filename)
             iflogger.info("TrackVis Header:")
             iflogger.info(trk_header)


Test patch: