Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Set up nipype2pydra converter templates #16

Merged
merged 16 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
297 changes: 193 additions & 104 deletions .github/workflows/ci-cd.yaml

Large diffs are not rendered by default.

32 changes: 0 additions & 32 deletions .github/workflows/pythonpackage.yml

This file was deleted.

7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,18 @@ dmypy.json
# Pycharm
.idea

# Vim
.*.sw[op]

# VS Code
.vscode

# Mac garbarge
.DS_store

# Generated files
/pydra/tasks/ants/_version.py
/related-packages/fileformats/fileformats/medimage_ants/_version.py
/related-packages/fileformats-extras/fileformats/extras/medimage_ants/_version.py
/pydra/tasks/ants/auto
/pydra/tasks/ants/_version.py
1 change: 0 additions & 1 deletion README.md

This file was deleted.

29 changes: 19 additions & 10 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
===============================
===========================
Pydra task package for ants
===============================
===========================

.. image:: https://github.com/nipype/pydra-ants/actions/workflows/pythonpackage.yaml/badge.svg
:target: https://github.com/nipype/pydra-ants/actions/workflows/pythonpackage.yaml
.. .. image:: https://codecov.io/gh/nipype/pydra-ants/branch/main/graph/badge.svg?token=UIS0OGPST7
.. :target: https://codecov.io/gh/nipype/pydra-ants
.. image:: https://github.com/nipype/pydra-ants/actions/workflows/ci-cd.yaml/badge.svg
:target: https://github.com/nipype/pydra-ants/actions/workflows/ci-cd.yaml
.. image:: https://codecov.io/gh/nipype/pydra-ants/branch/main/graph/badge.svg?token=UIS0OGPST7
:target: https://codecov.io/gh/nipype/pydra-ants
.. image:: https://img.shields.io/pypi/pyversions/pydra-ants.svg
:target: https://pypi.python.org/pypi/pydra-ants/
:alt: Supported Python versions
Expand All @@ -27,7 +27,7 @@ Automatically generated tasks can be found in the `pydra.tasks.ants.auto` packag
These packages should be treated with extreme caution as they likely do not pass testing.
Generated tasks that have been edited and pass testing are imported into one or more of the
`pydra.tasks.ants.v*` packages, corresponding to the version of the ants toolkit
they are designed for.
they are designed for.

Tests
-----
Expand Down Expand Up @@ -71,14 +71,22 @@ Contributing to this package
Developer installation
~~~~~~~~~~~~~~~~~~~~~~

Install the `fileformats <https://arcanaframework.github.io/fileformats/>`__ packages
corresponding to AFNI specific file formats


.. code-block::

$ pip install -e ./related-packages/fileformats[dev]
$ pip install -e ./related-packages/fileformats-extras[dev]

Install repo in developer mode from the source directory and install pre-commit to
ensure consistent code-style and quality.

.. code-block::

$ pip install -e .[test,dev]
$ pre-commit install
$ pre-commit install

Next install the requirements for running the auto-conversion script and generate the
Pydra task interfaces from their Nipype counterparts
Expand All @@ -93,7 +101,8 @@ The run the conversion script to convert Nipype interfaces to Pydra

$ nipype-auto-conv/generate

## Methodology
Methodology
~~~~~~~~~~~

The development of this package is expected to have two phases

Expand Down Expand Up @@ -149,6 +158,6 @@ in the ``inputs > types`` and ``outputs > types`` dicts of the YAML spec.

If the required file-type is not found implemented within fileformats, please see the `fileformats
docs <https://arcanaframework.github.io/fileformats/developer.html>`__ for instructions on how to define
new fileformat types, and see
new fileformat types, and see
`fileformats-medimage-extras <https://github.com/ArcanaFramework/fileformats-medimage-extras/blob/6c2dabe91e95687eebc2639bb6f034cf9595ecfc/fileformats/extras/medimage/nifti.py#L30-L48>`__
for an example on how to implement methods to generate sample data for them.
15 changes: 12 additions & 3 deletions nipype-auto-conv/generate
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@ from warnings import warn
from pathlib import Path
import shutil
from importlib import import_module
from tqdm import tqdm
import yaml
import nipype
import nipype2pydra.utils
from nipype2pydra.task import TaskConverter
from nipype2pydra.task import get_converter


SPECS_DIR = Path(__file__).parent / "specs"
Expand All @@ -35,7 +36,10 @@ auto_dir = PKG_ROOT / "pydra" / "tasks" / PKG_NAME / "auto"
if auto_dir.exists():
shutil.rmtree(auto_dir)

for fspath in sorted(SPECS_DIR.glob("**/*.yaml")):
all_interfaces = []
for fspath in tqdm(
sorted(SPECS_DIR.glob("**/*.yaml")), "converting interfaces from Nipype to Pydra"
):
with open(fspath) as f:
spec = yaml.load(f, Loader=yaml.SafeLoader)

Expand All @@ -49,13 +53,14 @@ for fspath in sorted(SPECS_DIR.glob("**/*.yaml")):

module_name = nipype2pydra.utils.to_snake_case(spec["task_name"])

converter = TaskConverter(
converter = get_converter(
output_module=f"pydra.tasks.{PKG_NAME}.auto.{module_name}",
callables_module=callables, # type: ignore
**spec,
)
converter.generate(PKG_ROOT)
auto_init += f"from .{module_name} import {converter.task_name}\n"
all_interfaces.append(converter.task_name)


with open(PKG_ROOT / "pydra" / "tasks" / PKG_NAME / "auto" / "_version.py", "w") as f:
Expand All @@ -68,5 +73,9 @@ post_release = (nipype_version + nipype2pydra_version).replace(".", "")
"""
)

auto_init += (
"\n\n__all__ = [\n" + "\n".join(f' "{i}",' for i in all_interfaces) + "\n]\n"
)

with open(PKG_ROOT / "pydra" / "tasks" / PKG_NAME / "auto" / "__init__.py", "w") as f:
f.write(auto_init)
3 changes: 2 additions & 1 deletion nipype-auto-conv/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ black
attrs>=22.1.0
nipype
pydra
tqdm
PyYAML>=6.0
fileformats >=0.8
fileformats-medimage >=0.4
fileformats-datascience >= 0.1
fileformats-medimage-ants
traits
nipype2pydra
nipype2pydra
31 changes: 17 additions & 14 deletions nipype-auto-conv/specs/affine_initializer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@
#
# Docs
# ----
#
#
# Initialize an affine transform (as in antsBrainExtraction.sh)
#
#
# >>> from nipype.interfaces.ants import AffineInitializer
# >>> init = AffineInitializer()
# >>> init.inputs.fixed_image = 'fixed1.nii'
# >>> init.inputs.moving_image = 'moving1.nii'
# >>> init.cmdline
# 'antsAffineInitializer 3 fixed1.nii moving1.nii transform.mat 15.000000 0.100000 0 10'
#
#
#
#
task_name: AffineInitializer
nipype_name: AffineInitializer
nipype_module: nipype.interfaces.ants.utils
Expand All @@ -34,9 +34,12 @@ inputs:
# type=file|default=<undefined>: reference image
moving_image: medimage/nifti1
# type=file|default=<undefined>: moving image
out_file: generic/file
out_file: Path
# type=file: output transform file
# type=file|default='transform.mat': output transform file
callable_defaults:
# dict[str, str] - names of methods/callable classes defined in the adjacent `*_callables.py`
# to set as the `default` method of input fields
metadata:
# dict[str, dict[str, any]] - additional metadata to set on any of the input fields (e.g. out_file: position: 1)
outputs:
Expand Down Expand Up @@ -88,15 +91,15 @@ tests:
environ:
# type=dict|default={}: Environment variables
imports:
# list[nipype2pydra.task.importstatement] - list import statements required by the test, with each list item
# list[nipype2pydra.task.base.importstatement] - list import statements required by the test, with each list item
# consisting of 'module', 'name', and optionally 'alias' keys
expected_outputs:
# dict[str, str] - expected values for selected outputs, noting that tests will typically
# be terminated before they complete for time-saving reasons, and therefore
# these values will be ignored, when running in CI
timeout: 10
# int - the value to set for the timeout in the generated test,
# after which the test will be considered to have been initialised
# int - the value to set for the timeout in the generated test,
# after which the test will be considered to have been initialised
# successfully. Set to 0 to disable the timeout (warning, this could
# lead to the unittests taking a very long time to complete)
xfail: true
Expand All @@ -110,15 +113,15 @@ tests:
moving_image:
# type=file|default=<undefined>: moving image
imports:
# list[nipype2pydra.task.importstatement] - list import statements required by the test, with each list item
# list[nipype2pydra.task.base.importstatement] - list import statements required by the test, with each list item
# consisting of 'module', 'name', and optionally 'alias' keys
expected_outputs:
# dict[str, str] - expected values for selected outputs, noting that tests will typically
# be terminated before they complete for time-saving reasons, and therefore
# these values will be ignored, when running in CI
timeout: 10
# int - the value to set for the timeout in the generated test,
# after which the test will be considered to have been initialised
# int - the value to set for the timeout in the generated test,
# after which the test will be considered to have been initialised
# successfully. Set to 0 to disable the timeout (warning, this could
# lead to the unittests taking a very long time to complete)
xfail: true
Expand All @@ -131,12 +134,12 @@ doctests:
# dict[str, str] - name-value pairs for inputs to be provided to the doctest.
# If the field is of file-format type and the value is None, then the
# '.mock()' method of the corresponding class is used instead.
fixed_image:
fixed_image: '"fixed1.nii"'
# type=file|default=<undefined>: reference image
moving_image:
moving_image: '"moving1.nii"'
# type=file|default=<undefined>: moving image
imports:
# list[nipype2pydra.task.importstatement] - list import statements required by the test, with each list item
# list[nipype2pydra.task.base.importstatement] - list import statements required by the test, with each list item
# consisting of 'module', 'name', and optionally 'alias' keys
directive:
# str - any doctest directive to place on the cmdline call, e.g. # doctest: +ELLIPSIS
21 changes: 20 additions & 1 deletion nipype-auto-conv/specs/affine_initializer_callables.py
Original file line number Diff line number Diff line change
@@ -1 +1,20 @@
"""Module to put any functions that are referred to in AffineInitializer.yaml"""
"""Module to put any functions that are referred to in the "callables" section of AffineInitializer.yaml"""

import os


def out_file_callable(output_dir, inputs, stdout, stderr):
outputs = _list_outputs(
output_dir=output_dir, inputs=inputs, stdout=stdout, stderr=stderr
)
return outputs["out_file"]


# Original source at L885 of <nipype-install>/interfaces/base/core.py
def _gen_filename(name, inputs=None, stdout=None, stderr=None, output_dir=None):
raise NotImplementedError


# Original source at L834 of <nipype-install>/interfaces/ants/utils.py
def _list_outputs(inputs=None, stdout=None, stderr=None, output_dir=None):
return {"out_file": os.path.abspath(inputs.out_file)}
25 changes: 14 additions & 11 deletions nipype-auto-conv/specs/ai.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@
#
# Docs
# ----
#
#
# Calculate the optimal linear transform parameters for aligning two images.
#
#
# Examples
# --------
# >>> AI(
Expand All @@ -17,16 +17,16 @@
# ... ).cmdline
# 'antsAI -c [10,1e-06,10] -d 3 -m Mattes[structural.nii,epi.nii,32,Regular,1]
# -o initialization.mat -p 0 -s [20,0.12] -t Affine[0.1] -v 0'
#
#
# >>> AI(fixed_image='structural.nii',
# ... moving_image='epi.nii',
# ... metric=('Mattes', 32, 'Regular', 1),
# ... search_grid=(12, (1, 1, 1)),
# ... ).cmdline
# 'antsAI -c [10,1e-06,10] -d 3 -m Mattes[structural.nii,epi.nii,32,Regular,1]
# -o initialization.mat -p 0 -s [20,0.12] -g [12.0,1x1x1] -t Affine[0.1] -v 0'
#
#
#
#
task_name: AI
nipype_name: AI
nipype_module: nipype.interfaces.ants.utils
Expand All @@ -43,15 +43,18 @@ inputs:
# passed to the field in the automatically generated unittests.
fixed_image: generic/file
# type=file|default=<undefined>: Image to which the moving_image should be transformed
moving_image: generic/file
# type=file|default=<undefined>: Image that will be transformed to fixed_image
fixed_image_mask: generic/file
# type=file|default=<undefined>: fixed mage mask
moving_image: generic/file
# type=file|default=<undefined>: Image that will be transformed to fixed_image
moving_image_mask: generic/file
# type=file|default=<undefined>: moving mage mask
output_transform: generic/file
output_transform: Path
# type=file: output file name
# type=file|default='initialization.mat': output file name
callable_defaults:
# dict[str, str] - names of methods/callable classes defined in the adjacent `*_callables.py`
# to set as the `default` method of input fields
metadata:
# dict[str, dict[str, any]] - additional metadata to set on any of the input fields (e.g. out_file: position: 1)
outputs:
Expand Down Expand Up @@ -113,15 +116,15 @@ tests:
environ:
# type=dict|default={}: Environment variables
imports:
# list[nipype2pydra.task.importstatement] - list import statements required by the test, with each list item
# list[nipype2pydra.task.base.importstatement] - list import statements required by the test, with each list item
# consisting of 'module', 'name', and optionally 'alias' keys
expected_outputs:
# dict[str, str] - expected values for selected outputs, noting that tests will typically
# be terminated before they complete for time-saving reasons, and therefore
# these values will be ignored, when running in CI
timeout: 10
# int - the value to set for the timeout in the generated test,
# after which the test will be considered to have been initialised
# int - the value to set for the timeout in the generated test,
# after which the test will be considered to have been initialised
# successfully. Set to 0 to disable the timeout (warning, this could
# lead to the unittests taking a very long time to complete)
xfail: true
Expand Down
19 changes: 18 additions & 1 deletion nipype-auto-conv/specs/ai_callables.py
Original file line number Diff line number Diff line change
@@ -1 +1,18 @@
"""Module to put any functions that are referred to in AI.yaml"""
"""Module to put any functions that are referred to in the "callables" section of AI.yaml"""


def output_transform_callable(output_dir, inputs, stdout, stderr):
outputs = _list_outputs(
output_dir=output_dir, inputs=inputs, stdout=stdout, stderr=stderr
)
return outputs["output_transform"]


# Original source at L885 of <nipype-install>/interfaces/base/core.py
def _gen_filename(name, inputs=None, stdout=None, stderr=None, output_dir=None):
raise NotImplementedError


# Original source at L539 of <nipype-install>/interfaces/ants/utils.py
def _list_outputs(inputs=None, stdout=None, stderr=None, output_dir=None):
return getattr(self, "_output")
Loading
Loading