Repository: nipy/nipype

Issue:
Updates to JoinNode
My use case scenario was this:

I had a class with an inputspec as follows:

``` Python
class JoiningIS(DynamicTraitedSpec):
    subject_id_list = traits.List(trait=traits.Str, imandatory=True, desc="The list of subject ids")

class Joining(BaseInterface):
    input_spec = JoiningIS
    ...
```

This was wrapped in an JoinNode:

``` Python
node = pe.JoinNode(Joining(), joinsource='InfoSource', name='Joining', joinfield=['subject_id_list'])
```

Whenevery I tried to run this I got the following error message:

traits.trait_errors.TraitError: The 'subject_id_listJ1' trait of a DynamicTraitedSpec instance must be an implementor of, or can be adapted to implement, NoneType or None, but a value of '000000642263' <type 'str'> was specified.

I traced this in the code to 

``` Python
trait = self._inputs.trait(field, False, True)
```

in the engine.py on line 1917. This always returned a NoneType. Replacing this with a single call to:

``` Python
trait = traits.Any
```

circumvented this error.

Next, I ran into the problem that my lists were not concatenated. The loop on line 1968 had:

``` Python
for field in self.inputs.copyable_trait_names():
```

This did not iterate over the fields specified by the joinfields. It therefore never concatenated the results. I split the loop into two loops, to make sure it always iterates over the join fields.

The end result is that the JoinNode now works for me as expected. I now get in my interface a list of concatenated inputs.

Please let me know if you need more information about this patch.



Discussion:

Resolving patch:
diff --git a/nipype/pipeline/engine.py b/nipype/pipeline/engine.py
--- a/nipype/pipeline/engine.py
+++ b/nipype/pipeline/engine.py
@@ -14,6 +14,7 @@
 
 from datetime import datetime
 from nipype.utils.misc import flatten, unflatten
+from nipype.interfaces.traits_extension import File
 try:
     from collections import OrderedDict
 except ImportError:
@@ -1947,6 +1948,7 @@ def _override_join_traits(self, basetraits, fields):
             if name in fields and len(trait.inner_traits) == 1:
                 item_trait = trait.inner_traits[0]
                 dyntraits.add_trait(name, item_trait)
+                setattr(dyntraits, name, Undefined)
                 logger.debug("Converted the join node %s field %s"
                              " trait type from %s to %s"
                              % (self, name, trait.trait_type.info(),


Test patch:
diff --git a/nipype/pipeline/tests/test_join.py b/nipype/pipeline/tests/test_join.py
--- a/nipype/pipeline/tests/test_join.py
+++ b/nipype/pipeline/tests/test_join.py
@@ -13,6 +13,27 @@
 import nipype.interfaces.base as nib
 import nipype.pipeline.engine as pe
 from nipype.interfaces.utility import IdentityInterface
+from nipype.interfaces.base import traits, File
+
+class PickFirstSpec(nib.TraitedSpec):
+    in_files = traits.List(File(exists=True), argstr="%s", position=2,
+                           mandatory=True)
+
+class PickFirstOutSpec(nib.TraitedSpec):
+    output1 = File(exists=True)
+
+class PickFirst(nib.BaseInterface):
+    input_spec = PickFirstSpec
+    output_spec = PickFirstOutSpec
+
+    def _run_interface(self, runtime):
+        runtime.returncode = 0
+        return runtime
+
+    def _list_outputs(self):
+        outputs = self._outputs().get()
+        outputs['output1'] = self.inputs.in_files[0]
+        return outputs
 
 
 class IncrementInputSpec(nib.TraitedSpec):
@@ -528,6 +549,32 @@ def test_itersource_two_join_nodes():
 
     os.chdir(cwd)
     rmtree(wd)
+    
+def test_set_join_node_file_input():
+    """Test collecting join inputs to a set."""
+    cwd = os.getcwd()
+    wd = mkdtemp()
+    os.chdir(wd)
+    open('test.nii', 'w+').close()
+    open('test2.nii', 'w+').close()
+
+    # Make the workflow.
+    wf = pe.Workflow(name='test')
+    # the iterated input node
+    inputspec = pe.Node(IdentityInterface(fields=['n']), name='inputspec')
+    inputspec.iterables = [('n', [os.path.join(wd, 'test.nii'), os.path.join(wd, 'test2.nii')])]
+    # a pre-join node in the iterated path
+    pre_join1 = pe.Node(IdentityInterface(fields=['n']), name='pre_join1')
+    wf.connect(inputspec, 'n', pre_join1, 'n')
+    # the set join node
+    join = pe.JoinNode(PickFirst(), joinsource='inputspec',
+        joinfield='in_files', name='join')
+    wf.connect(pre_join1, 'n', join, 'in_files')
+
+    wf.run()
+
+    os.chdir(cwd)
+    rmtree(wd)
 
 
 if __name__ == "__main__":