@@ -645,14 +645,15 @@ def generate_expanded_graph(graph_in):
645
645
key = src_values [0 ]
646
646
else :
647
647
key = tuple (src_values )
648
- # the iterables is a {field: lambda} dictionary, where the
649
- # lambda returns a {source key: iteration list} dictionary
650
- iterables = {}
651
- for field , func in inode .iterables .iteritems ():
652
- # the {source key: iteration list} dictionary
653
- lookup = func ()
654
- if lookup .has_key (key ):
655
- iterables [field ] = lambda : lookup [key ]
648
+ # The itersource iterables is a {field: lookup} dictionary, where the
649
+ # lookup is a {source key: iteration list} dictionary. Look up the
650
+ # current iterable value using the predecessor itersource input values.
651
+ iter_dict = {field : lookup [key ] for field , lookup in inode .iterables
652
+ if lookup .has_key (key )}
653
+ # convert the iterables to the standard {field: function} format
654
+ iter_items = map (lambda (field , value ): (field , lambda : value ),
655
+ iter_dict .iteritems ())
656
+ iterables = dict (iter_items )
656
657
else :
657
658
iterables = inode .iterables .copy ()
658
659
inode .iterables = None
@@ -800,56 +801,82 @@ def _standardize_iterables(node):
800
801
iterables = node .iterables
801
802
# The candidate iterable fields
802
803
fields = set (node .inputs .copyable_trait_names ())
803
-
804
- # Synchronize iterables can be in [fields, value tuples] format
805
- # rather than [(field, value list), (field, value list), ...]
806
- if node .synchronize and len (iterables ) == 2 :
807
- first , last = iterables
808
- if all ((isinstance (item , str ) and item in fields
809
- for item in first )):
810
- iterables = _transpose_iterables (first , last )
811
-
804
+ # Flag indicating whether the iterables are in the alternate
805
+ # synchronize form and are not converted to a standard format.
806
+ synchronize = False
807
+ # A synchronize iterables node without an itersource can be in
808
+ # [fields, value tuples] format rather than
809
+ # [(field, value list), (field, value list), ...]
810
+ if node .synchronize :
811
+ if len (iterables ) == 2 :
812
+ first , last = iterables
813
+ if all ((isinstance (item , str ) and item in fields
814
+ for item in first )):
815
+ iterables = _transpose_iterables (first , last )
816
+
812
817
# Convert a tuple to a list
813
818
if isinstance (iterables , tuple ):
814
819
iterables = [iterables ]
820
+ # Validate the standard [(field, values)] format
821
+ _validate_iterables (node , iterables , fields )
815
822
# Convert a list to a dictionary
816
823
if isinstance (iterables , list ):
817
- # Validate the format
818
- for item in iterables :
819
- try :
820
- if len (item ) != 2 :
821
- raise ValueError ("The %s iterables do not consist of"
822
- " (field, values) pairs" % node .name )
823
- except TypeError , e :
824
- raise TypeError ("The %s iterables is not iterable: %s"
825
- % (node .name , e ))
826
- # Convert the values to functions. This is a legacy Nipype
827
- # requirement with unknown rationale.
828
- iter_items = map (lambda (field , value ): (field , lambda : value ),
829
- iterables )
830
- # Make the iterables dictionary
831
- iterables = dict (iter_items )
832
- elif not isinstance (iterables , dict ):
824
+ # Convert a values list to a function. This is a legacy
825
+ # Nipype requirement with unknown rationale.
826
+ if not node .itersource :
827
+ iter_items = map (lambda (field , value ): (field , lambda : value ),
828
+ iterables )
829
+ iterables = dict (iter_items )
830
+ node .iterables = iterables
831
+
832
+ def _validate_iterables (node , iterables , fields ):
833
+ """
834
+ Raise TypeError if an iterables member is not iterable.
835
+
836
+ Raise ValueError if an iterables member is not a (field, values) pair.
837
+
838
+ Raise ValueError if an iterable field is not in the inputs.
839
+ """
840
+ # The iterables can be a {field: value list} dictionary.
841
+ if isinstance (iterables , dict ):
842
+ iterables = iterables .items ()
843
+ elif not isinstance (iterables , tuple ) and not isinstance (iterables , list ):
833
844
raise ValueError ("The %s iterables type is not a list or a dictionary:"
834
845
" %s" % (node .name , iterables .__class__ ))
835
-
836
- # Validate the iterable fields
837
- for field in iterables .iterkeys ():
846
+ for item in iterables :
847
+ try :
848
+ if len (item ) != 2 :
849
+ raise ValueError ("The %s iterables is not a [(field, values)]"
850
+ " list" % node .name )
851
+ except TypeError , e :
852
+ raise TypeError ("A %s iterables member is not iterable: %s"
853
+ % (node .name , e ))
854
+ field , _ = item
838
855
if field not in fields :
839
856
raise ValueError ("The %s iterables field is unrecognized: %s"
840
857
% (node .name , field ))
841
-
842
- # Assign to the standard form
843
- node .iterables = iterables
844
858
845
859
def _transpose_iterables (fields , values ):
846
860
"""
847
- Converts the given fields and tuple values into a list of
848
- iterable (field: value list) pairs, suitable for setting
849
- a node iterables property.
861
+ Converts the given fields and tuple values into a standardized
862
+ iterables value.
863
+
864
+ If the input values is a synchronize iterables dictionary, then
865
+ the result is a (field, {key: values}) list.
866
+
867
+ Otherwise, the result is a list of (field: value list) pairs.
850
868
"""
851
- return zip (fields , [filter (lambda (v ): v != None , list (transpose ))
852
- for transpose in zip (* values )])
869
+ if isinstance (values , dict ):
870
+ transposed = {field : defaultdict (list ) for field in fields }
871
+ for key , tuples in values .iteritems ():
872
+ for kvals in tuples :
873
+ for idx , val in enumerate (kvals ):
874
+ if val != None :
875
+ transposed [fields [idx ]][key ].append (val )
876
+ return transposed .items ()
877
+ else :
878
+ return zip (fields , [filter (lambda (v ): v != None , list (transpose ))
879
+ for transpose in zip (* values )])
853
880
854
881
def export_graph (graph_in , base_dir = None , show = False , use_execgraph = False ,
855
882
show_connectinfo = False , dotfilename = 'graph.dot' , format = 'png' ,
0 commit comments