[ENH]Add interfaces for T1Prep with branch Enh/t1prep#3753
[ENH]Add interfaces for T1Prep with branch Enh/t1prep#3753ChristianGaser wants to merge 15 commits into
Conversation
Introduce a new nipype.interfaces.t1prep package: add __init__.py, cat_surf.py (wrapping the t1prep/cat_surf API as individual Nipype interfaces), preprocess.py, surface.py, and a test suite for many cat_surf operations. Update doc/interfaces.rst to list T1Prep among third-party tools.
Introduce a common base for T1Prep interfaces (Info, T1PrepCommand) and an import helper for the cat_surf API. Add a new longitudinal interface (T1PrepRealignLongitudinal) and refactor existing preprocess and surface interfaces to use the shared T1PrepCommand and import helper; normalize input names (e.g. Segment 'input' -> 'in_file'). Add comprehensive unit tests for the new and refactored interfaces, and document the new T1Prep interface in the changelog; update .zenodo.json with an additional contributor entry.
Add '# doctest: +SKIP' markers to doctest example lines across nipype/interfaces/t1prep modules (cat_surf.py, longitudinal.py, preprocess.py, surface.py). This prevents doctest from attempting to run examples that reference local/nonexistent files during test runs or CI; no runtime behavior changes.
Remove redundant "Surf" from several CatSurf interface class names and their specs. Updated exports in nipype/interfaces/t1prep/__init__.py and documentation/listings in cat_surf.py.
Remove the cat_surf-based T1PrepCatSurf surface post-processing interface and its tests. Deleted the T1PrepCatSurf class and its input/output specs from nipype/interfaces/t1prep/surface.py, removed its export and references from nipype/interfaces/t1prep/__init__.py, and removed autogenerated and unit tests (tests/test_auto_T1PrepCatSurf.py and related tests in tests/test_surface.py).
Replace underscore-style CLI options (--label_dir, --mri_dir, --report_dir) with hyphenated forms (--label-dir, --mri-dir, --report-dir) in the T1PrepSegment interface.
Update argstr values in nipype/interfaces/t1prep/preprocess.py to use hyphenated flags for directory inputs.
There was a problem hiding this comment.
Pull request overview
This PR adds a new nipype.interfaces.t1prep interface family to wrap the T1Prep T1-weighted MRI preprocessing pipeline (full pipeline, segmentation, surface estimation, longitudinal realignment) and exposes the cat_surf Python API as individual Nipype BaseInterface nodes, along with tests and documentation updates.
Changes:
- Added new T1Prep command-line interfaces (
T1Prep,T1PrepSegment,T1PrepSurfaceEstimation,T1PrepRealignLongitudinal) plus shared base helpers. - Added extensive
cat_surffunction wrappers as Nipype interfaces, enabling node-level use in workflows. - Added unit/spec tests and updated documentation/changelog (plus Zenodo metadata update).
Reviewed changes
Copilot reviewed 55 out of 56 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
nipype/interfaces/t1prep/__init__.py |
Exports the T1Prep and cat_surf interfaces from a single entrypoint. |
nipype/interfaces/t1prep/base.py |
Adds T1Prep version detection and a CommandLine base that uses the current interpreter. |
nipype/interfaces/t1prep/preprocess.py |
Implements the full pipeline and segmentation-stage command-line interfaces. |
nipype/interfaces/t1prep/surface.py |
Implements the surface-estimation command-line interface and output mapping. |
nipype/interfaces/t1prep/longitudinal.py |
Implements the longitudinal realignment command-line interface. |
nipype/interfaces/t1prep/cat_surf.py |
Provides BaseInterface wrappers for the cat_surf Python API. |
nipype/interfaces/t1prep/tests/* |
Adds both hand-written behavioral tests and auto-generated spec tests for the new interfaces. |
doc/interfaces.rst |
Adds T1Prep to the documented list of third-party tool interfaces. |
doc/changelog/1.X.X-changelog.rst |
Adds an “Upcoming release” changelog entry for the new interfaces. |
.zenodo.json |
Adds a new creator entry (currently introduces invalid JSON). |
Comments suppressed due to low confidence (3)
nipype/interfaces/t1prep/cat_surf.py:1262
CatSurfSmoothMeshdefines an input traitlambda_, but_run_interface()never passes it tocat_surf.smooth_mesh(...). Either thread this parameter through (if supported by the underlying API) or remove the trait to avoid a misleading/ineffective input.
class input_spec(BaseInterfaceInputSpec):
vertices = traits.Any(mandatory=True, desc="Input vertex array (N, 3).")
faces = traits.Any(mandatory=True, desc="Input face array (M, 3).")
iterations = traits.Int(10, usedefault=True, desc="Number of smoothing iterations.")
lambda_ = traits.Float(0.5, usedefault=True, desc="Laplacian smoothing weight (lambda).")
class output_spec(TraitedSpec):
vertices = traits.Any(desc="Smoothed vertex array.")
faces = traits.Any(desc="Face array (unchanged).")
def _run_interface(self, runtime):
cs = _import_cat_surf()
self._v, self._fcs = cs.smooth_mesh(
self.inputs.vertices, self.inputs.faces,
iterations=self.inputs.iterations,
)
nipype/interfaces/t1prep/cat_surf.py:972
- These file inputs are required to exist at runtime (they are read by
cat_surf.surf_warp), but theFile(...)traits don't setexists=True. Addingexists=Truewill provide earlier, clearer validation errors when a path is wrong.
class CatSurfWarpInputSpec(BaseInterfaceInputSpec):
source_file = File(mandatory=True, desc="Source surface file (central surface).")
source_sphere_file = File(mandatory=True, desc="Source sphere file.")
target_file = File(mandatory=True, desc="Target (average) surface file used as registration template.")
target_sphere_file = File(mandatory=True, desc="Target (average) sphere file.")
output_sphere_file = File(mandatory=True, desc="Output registered sphere file path.")
nipype/interfaces/t1prep/cat_surf.py:1523
volume_fileandlabel_fileare required inputs that will be read from disk, but theFile(...)traits don't specifyexists=True. Settingexists=Truewould align with typical Nipype interface validation and fail fast on missing inputs.
class input_spec(BaseInterfaceInputSpec):
volume_file = File(
mandatory=True,
desc="NIfTI volume from which to extract the surface (e.g. PPM probability map).",
)
label_file = File(
mandatory=True,
desc="NIfTI label volume used as a mask (e.g. hemisphere partition map).",
)
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #3753 +/- ##
==========================================
+ Coverage 72.89% 73.67% +0.77%
==========================================
Files 1279 1332 +53
Lines 59366 61275 +1909
==========================================
+ Hits 43277 45143 +1866
- Misses 16089 16132 +43 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
Apply consistent multiline formatting and wrapping across nipype/interfaces/t1prep files.
Adds unit tests for nipype.interfaces.t1prep: new test_base.py covering Info.version caching/import behavior, version parsing, import_cat_surf preference, and T1PrepCommand behavior. Extends test_cat_surf.py with a fake cat_surf C-extension.
Collapse the pytest.mark.parametrize decorator to a single line and add explicit test setup in test_catsurf_dispatch_with_fake_module.
Clean up test_catsurf_dispatch_with_fake_module in nipype/interfaces/t1prep/tests/test_cat_surf.py by removing an unnecessary instantiation of iface() and the call to _set_mandatory_inputs.
Add exists=True validation to many File traits across t1prep CatSurf interfaces to ensure input paths exist. Replace the single lambda_ smoothing parameter in CatSurfSmoothMesh with two explicit weights (alpha=0.1, beta=0.5).
Add '# doctest: +SKIP' to multiple example lines in nipype/interfaces/t1prep/cat_surf.py to prevent running those doctests during test execution. Also reflow the source_sphere_file File(...) declaration to a single line for minor style cleanup.
Reformat source_surface_file and source_sphere_file in CatSurfResampleToSphereInputSpec to single-line File(...) declarations for style/consistency.
There was a problem hiding this comment.
Have you written a workflow with these interfaces, where you read the values in one node, use them in another, and write them in yet another? I'm a bit skeptical of how well that will work with nipype's data transfer model, but it's possible it would work. It would ultimately be using Python's pickling as its I/O interface, and seems unlikely to be more efficient than just loading files as needed. So a more typical CatSurfGetArea would be:
class CatSurfGetAreaInputSpec(TraitedSpec):
in_file = File(
exists=True,
mandatory=True,
desc="Surface file to read (.gii, FreeSurfer, or .obj).",
)
out_file = File(desc="Output per-vertex area file path.")
class CatSurfGetAreaOutputSpec(TraitedSpec):
out_file = File(desc="Output per-vertex area file path.")
total_area = traits.Float(desc="Total surface area (mm²).")
class CatSurfGetArea(SimpleInterface):
"""Compute per-vertex surface area.
Wraps ``cat_surf.get_area(vertices, faces) → (area, total_area)``.
Each vertex receives the sum of one-third of the areas of its adjacent
triangles (the standard Voronoi tessellation-based area).
Examples
--------
>>> node = CatSurfGetArea()
>>> node.inputs.in_file = 'lh.pial'
>>> res = node.run() # doctest: +SKIP
>>> per_vertex_area = res.outputs.area # doctest: +SKIP
"""
input_spec = CatSurfGetAreaInputSpec
output_spec = CatSurfGetAreaOutputSpec
def _run_interface(self, runtime):
# Set default output name
if not (out_file := self.inputs.out_file):
out_file = fname_presuffix(self.inputs.in_file, suffix='_area', newpath=runtime.cwd)
# Ensure absolute path, but respect a user-entered absolute path
out_file = Path(runtime.cwd) / self.inputs.out_file
cs = _import_cat_surf()
v, fcs = cs.read_surface(self.inputs.in_file)
area, total = cs.get_area(v, fcs)
cs.write_values(out_file, area)
self._results.update(total_area=float(total), out_file=out_file)
return runtimeAdditional notes:
- There is no longer any difference between
TraitedSpecandBaseInterfaceInputSpec, so just useTraitedSpec. nipype.interfaces.base.SimpleInterfaceallows you to set the outputs in theself._resultsdict in_run_interface, and avoid writing a custom_list_outputsmethod.- Given that these interfaces will be imported from
nipype.interfaces.t1prep.cat_surf, it's not strictly necessary to prefix them all withCatSurf. But interface names are up to the contributor, so whatever works for you.
Summary
Add interfaces for T1Prep – T1-weighted MRI preprocessing pipeline (PyCAT). T1Prep performs skull-stripping, tissue segmentation, and cortical surface reconstruction using DeepMriPrep and the CAT-Surface library (via cat-surf).
List of changes proposed in this PR (pull-request)
Sub-module interfaces
:class:
T1PrepFull pipeline: skull-strip → segment → surface estimate. Wraps
python -m t1prep.t1prep.:class:
T1PrepSegmentSegmentation stage only. Wraps
python -m t1prep.segment.:class:
T1PrepSurfaceEstimationSurface-estimation stage for one hemisphere. Wraps
python -m t1prep.surface_estimation.:class:
T1PrepRealignLongitudinalRigid realignment of longitudinal T1w time-points. Wraps
python -m t1prep.realign_longitudinal.Base classes
:class:
InfoT1Prep package version detection.
:class:
T1PrepCommandBase
CommandLineforpython -m t1prep.<module>invocations.Interfaces for the full
cat_surf/t1prep.cat_surfPython API.Every public function in
cat_surfthat T1Prep exposes is wrapped as a separate :class:~nipype.interfaces.base.BaseInterfacesub-class so that each operation can be used as an independent node inside a Nipype workflow.References