From 6f2047c9d581ce22dee4d68b7c2917685c50e171 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Boris=20Cl=C3=A9net?= Date: Thu, 25 Jan 2024 13:50:34 +0100 Subject: [PATCH] [ENH][DOC] node generators in core module --- docs/core.md | 43 ++++++++++++++++++++++++++++++++++++++++ narps_open/core/nodes.py | 9 ++++++--- tests/core/test_nodes.py | 23 +++++---------------- 3 files changed, 54 insertions(+), 21 deletions(-) diff --git a/docs/core.md b/docs/core.md index 311faea4..2097c998 100644 --- a/docs/core.md +++ b/docs/core.md @@ -124,3 +124,46 @@ This module contains a set of functions dedicated to computations on images. # Get dimensions of voxels along x, y, and z in mm (returns e.g.: [1.0, 1.0, 1.0]). get_voxel_dimensions('/path/to/the/image.nii.gz') ``` + +## narps_open.core.nodes + +This module contains a set of node creators inheriting form the `narps_open.core.nodes.NodeCreator` abstract class. +These are responsible for creating nipype `Node` objects (for now, only based on the `Function` interface, with functions defined in the `narps_open.core.common` module) to be used inside pipeline code. This allows to factorize code, hence making code simpler to read inside pipeline definition. + +Here is an example how to use the node creators : + +```python +from narps_open.core.nodes import RemoveDirectoryNodeCreator, RemoveFileNodeCreator + +# Create a Node to remove a directory +remove_smoothed = RemoveDirectoryNodeCreator.create_node('remove_smoothed') +remove_smoothed.inputs.directory_name = 'my_directory' + +# Create a Node to remove a file +remove_gunzip = RemoveFileNodeCreator.create_node('remove_gunzip') +remove_gunzip.inputs.file_name = 'my_file' +``` + +For your information, this is how an equivalent code would look like without node creators. + +```python +from nipype import Node +from nipype.interfaces.utility import Function +from narps_open.core.common import remove_directory, remove_file + +# Create a Node to remove a directory +remove_smoothed = Node(Function( + function = remove_directory, + input_names = ['_', 'directory_name'], + output_names = [] + ), name = 'remove_smoothed') +remove_smoothed.inputs.directory_name = 'my_directory' + +# Create a Node to remove a file +remove_gunzip = Node(Function( + function = remove_file, + input_names = ['_', 'file_name'], + output_names = [] + ), name = 'remove_gunzip') +remove_gunzip.inputs.file_name = 'my_file' +``` diff --git a/narps_open/core/nodes.py b/narps_open/core/nodes.py index 58438015..c28480fd 100644 --- a/narps_open/core/nodes.py +++ b/narps_open/core/nodes.py @@ -13,8 +13,9 @@ class NodeCreator(ABC): """ An abstract class to shape what node creators must provide """ + @staticmethod @abstractmethod - def create_node(self, name: str) -> Node: + def create_node(name: str) -> Node: """ Return a new Node (the interface of the Node is defined by specialized classes) Arguments: name, str : the name of the node @@ -23,7 +24,8 @@ def create_node(self, name: str) -> Node: class RemoveDirectoryNodeCreator(NodeCreator): """ A node creator that provides an interface allowing to remove a directory """ - def create_node(self, name: str) -> Node: + @staticmethod + def create_node(name: str) -> Node: return Node(Function( function = remove_directory, input_names = ['_', 'directory_name'], @@ -33,7 +35,8 @@ def create_node(self, name: str) -> Node: class RemoveFileNodeCreator(NodeCreator): """ A node creator that provides an interface allowing to remove a file """ - def create_node(self, name: str) -> Node: + @staticmethod + def create_node(name: str) -> Node: return Node(Function( function = remove_file, input_names = ['_', 'file_name'], diff --git a/tests/core/test_nodes.py b/tests/core/test_nodes.py index 46c11fff..777095fe 100644 --- a/tests/core/test_nodes.py +++ b/tests/core/test_nodes.py @@ -27,26 +27,13 @@ class TestNodeCreator: def test_create_node(): """ Test the create_node method """ - # It is not possible to create an instance of a NodeCreator - with raises(Exception): - nd.NodeCreator().create_node('node_name') - - # Define a child for NodeCreator - class ErrorNC(nd.NodeCreator): - def random_method(self): - pass - - # Test it cannot be instanciated - with raises(Exception): - ErrorNC().create_node('node_name') - # Define another child for NodeCreator class ValidNC(nd.NodeCreator): - def create_node(self, name: str) -> Node: + def create_node(name: str) -> Node: return Node(Select(), name = name) - # Test it can be instanciated - test_node = ValidNC().create_node('node_name') + # Test it can be instantiated + test_node = ValidNC.create_node('node_name') assert isinstance(test_node, Node) assert isinstance(test_node.interface, Select) assert test_node.name == 'node_name' @@ -59,7 +46,7 @@ class TestRemoveDirectoryNodeCreator: def test_create_node(): """ Test the create_node method """ - test_node = nd.RemoveDirectoryNodeCreator().create_node('node_name') + test_node = nd.RemoveDirectoryNodeCreator.create_node('node_name') assert isinstance(test_node, Node) assert isinstance(test_node.interface, Function) assert test_node.name == 'node_name' @@ -72,7 +59,7 @@ class TestRemoveFileNodeCreator: def test_create_node(): """ Test the create_node method """ - test_node = nd.RemoveFileNodeCreator().create_node('node_name') + test_node = nd.RemoveFileNodeCreator.create_node('node_name') assert isinstance(test_node, Node) assert isinstance(test_node.interface, Function) assert test_node.name == 'node_name'