Skip to content

[ENH]Add interfaces for T1Prep with branch Enh/t1prep#3753

Open
ChristianGaser wants to merge 15 commits into
nipy:masterfrom
ChristianGaser:enh/t1prep
Open

[ENH]Add interfaces for T1Prep with branch Enh/t1prep#3753
ChristianGaser wants to merge 15 commits into
nipy:masterfrom
ChristianGaser:enh/t1prep

Conversation

@ChristianGaser

Copy link
Copy Markdown
Contributor

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)

  • Add new interface at nipype/interfaces/t1prep
  • Add test functions at nipype/interfaces/t1prep/tests
  • Updates doc/interfaces.rst

Sub-module interfaces

:class:T1Prep
Full pipeline: skull-strip → segment → surface estimate. Wraps python -m t1prep.t1prep.

:class:T1PrepSegment
Segmentation stage only. Wraps python -m t1prep.segment.

:class:T1PrepSurfaceEstimation
Surface-estimation stage for one hemisphere. Wraps python -m t1prep.surface_estimation.

:class:T1PrepRealignLongitudinal
Rigid realignment of longitudinal T1w time-points. Wraps python -m t1prep.realign_longitudinal.

Base classes

:class:Info
T1Prep package version detection.

:class:T1PrepCommand
Base CommandLine for python -m t1prep.<module> invocations.

Interfaces for the full cat_surf / t1prep.cat_surf Python API.

Every public function in cat_surf that T1Prep exposes is wrapped as a separate :class:~nipype.interfaces.base.BaseInterface sub-class so that each operation can be used as an independent node inside a Nipype workflow.

References

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.
Copilot AI review requested due to automatic review settings May 18, 2026 21:22
@ChristianGaser ChristianGaser changed the title Enh/t1prep [ENH]Add interfaces for T1Prep with branch Enh/t1prep May 18, 2026

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_surf function 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

  • CatSurfSmoothMesh defines an input trait lambda_, but _run_interface() never passes it to cat_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 the File(...) traits don't set exists=True. Adding exists=True will 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_file and label_file are required inputs that will be read from disk, but the File(...) traits don't specify exists=True. Setting exists=True would 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.

Comment thread .zenodo.json Outdated
Comment thread nipype/interfaces/t1prep/cat_surf.py
Comment thread nipype/interfaces/t1prep/surface.py
Comment thread nipype/interfaces/t1prep/tests/test_cat_surf.py
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@codecov

codecov Bot commented May 20, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.73565% with 43 lines in your changes missing coverage. Please review.
✅ Project coverage is 73.67%. Comparing base (a8d7130) to head (e3ae6f2).
⚠️ Report is 4 commits behind head on master.

Files with missing lines Patch % Lines
nipype/interfaces/t1prep/tests/test_cat_surf.py 93.95% 11 Missing ⚠️
...prep/tests/test_auto_CatSurfBbregDetectContrast.py 92.30% 1 Missing ⚠️
.../tests/test_auto_CatSurfCorrectThicknessFolding.py 92.30% 1 Missing ⚠️
...1prep/tests/test_auto_CatSurfCountIntersections.py 92.30% 1 Missing ⚠️
...erfaces/t1prep/tests/test_auto_CatSurfCurvature.py 92.30% 1 Missing ⚠️
...interfaces/t1prep/tests/test_auto_CatSurfDeform.py 92.30% 1 Missing ⚠️
...prep/tests/test_auto_CatSurfEulerCharacteristic.py 92.30% 1 Missing ⚠️
...nterfaces/t1prep/tests/test_auto_CatSurfGetArea.py 92.30% 1 Missing ⚠️
...t1prep/tests/test_auto_CatSurfGetAreaNormalized.py 92.30% 1 Missing ⚠️
...t1prep/tests/test_auto_CatSurfHausdorffDistance.py 92.30% 1 Missing ⚠️
... and 23 more
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 runtime

Additional notes:

  1. There is no longer any difference between TraitedSpec and BaseInterfaceInputSpec, so just use TraitedSpec.
  2. nipype.interfaces.base.SimpleInterface allows you to set the outputs in the self._results dict in _run_interface, and avoid writing a custom _list_outputs method.
  3. Given that these interfaces will be imported from nipype.interfaces.t1prep.cat_surf, it's not strictly necessary to prefix them all with CatSurf. But interface names are up to the contributor, so whatever works for you.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants