diff --git a/CHANGES b/CHANGES
index 15d4824398..09f6537c93 100644
--- a/CHANGES
+++ b/CHANGES
@@ -1,6 +1,7 @@
Next Release
============
+* ENH: New miscelaneous interface: AddCSVRow
* ENH: FUGUE interface has been refactored to use the name_template system, 3 examples
added to doctests, some bugs solved.
* ENH: Added new interfaces (fsl.utils.WarpUtils, ConvertWarp) to fnirtfileutils and convertwarp
diff --git a/nipype/algorithms/misc.py b/nipype/algorithms/misc.py
index 5b9d262203..b71b3a1d2c 100644
--- a/nipype/algorithms/misc.py
+++ b/nipype/algorithms/misc.py
@@ -26,15 +26,16 @@
import itertools
import scipy.stats as stats
-from .. import logging
+from nipype import logging
import warnings
import metrics as nam
from ..interfaces.base import (BaseInterface, traits, TraitedSpec, File,
InputMultiPath, OutputMultiPath,
- BaseInterfaceInputSpec, isdefined)
-from ..utils.filemanip import fname_presuffix, split_filename
+ BaseInterfaceInputSpec, isdefined,
+ DynamicTraitedSpec )
+from nipype.utils.filemanip import fname_presuffix, split_filename
iflogger = logging.getLogger('interface')
@@ -782,6 +783,121 @@ def _list_outputs(self):
return outputs
+class AddCSVRowInputSpec(DynamicTraitedSpec, BaseInterfaceInputSpec):
+ in_file = traits.File(mandatory=True, desc='Input comma-separated value (CSV) files')
+ _outputs = traits.Dict( traits.Any, value={}, usedefault=True )
+
+ def __setattr__(self, key, value):
+ if key not in self.copyable_trait_names():
+ if not isdefined(value):
+ super(AddCSVRowInputSpec, self).__setattr__(key, value)
+ self._outputs[key] = value
+ else:
+ if key in self._outputs:
+ self._outputs[key] = value
+ super(AddCSVRowInputSpec, self).__setattr__(key, value)
+
+class AddCSVRowOutputSpec(TraitedSpec):
+ csv_file = File(desc='Output CSV file containing rows ')
+
+class AddCSVRow(BaseInterface):
+ """Simple interface to add an extra row to a csv file
+
+ .. note:: Requires `pandas `_
+
+ .. warning:: Multi-platform thread-safe execution is possible with
+ `lockfile `_. Please recall that (1)
+ this module is alpha software; and (2) it should be installed for thread-safe writing.
+ If lockfile is not installed, then the interface is not thread-safe.
+
+
+ Example
+ -------
+
+ >>> import nipype.algorithms.misc as misc
+ >>> addrow = misc.AddCSVRow()
+ >>> addrow.inputs.in_file = 'scores.csv'
+ >>> addrow.inputs.si = 0.74
+ >>> addrow.inputs.di = 0.93
+ >>> addrow.subject_id = 'S400'
+ >>> addrow.inputs.list_of_values = [ 0.4, 0.7, 0.3 ]
+ >>> addrow.run() # doctest: +SKIP
+ """
+ input_spec = AddCSVRowInputSpec
+ output_spec = AddCSVRowOutputSpec
+
+ def __init__(self, infields=None, force_run=True, **kwargs):
+ super(AddCSVRow, self).__init__(**kwargs)
+ undefined_traits = {}
+ self._infields = infields
+ self._have_lock = False
+ self._lock = None
+
+ if infields:
+ for key in infields:
+ self.inputs.add_trait(key, traits.Any)
+ self.inputs._outputs[key] = Undefined
+ undefined_traits[key] = Undefined
+ self.inputs.trait_set(trait_change_notify=False, **undefined_traits)
+
+ if force_run:
+ self._always_run = True
+
+ def _run_interface(self, runtime):
+ try:
+ import pandas as pd
+ except ImportError:
+ raise ImportError('This interface requires pandas (http://pandas.pydata.org/) to run.')
+
+ try:
+ import lockfile as pl
+ self._have_lock = True
+ except ImportError:
+ import warnings
+ warnings.warn(('Python module lockfile was not found: AddCSVRow will not be thread-safe '
+ 'in multi-processor execution'))
+
+ input_dict = {}
+ for key, val in self.inputs._outputs.items():
+ # expand lists to several columns
+ if isinstance(val, list):
+ for i,v in enumerate(val):
+ input_dict['%s_%d' % (key,i)]=v
+ else:
+ input_dict[key] = val
+
+ df = pd.DataFrame([input_dict])
+
+ if self._have_lock:
+ self._lock = pl.FileLock(self.inputs.in_file)
+
+ # Acquire lock
+ self._lock.acquire()
+
+ if op.exists(self.inputs.in_file):
+ formerdf = pd.read_csv(self.inputs.in_file, index_col=0)
+ df = pd.concat([formerdf, df], ignore_index=True )
+
+ with open(self.inputs.in_file, 'w') as f:
+ df.to_csv(f)
+
+ if self._have_lock:
+ self._lock.release()
+
+ return runtime
+
+ def _list_outputs(self):
+ outputs = self.output_spec().get()
+ outputs['csv_file'] = self.inputs.in_file
+ return outputs
+
+ def _outputs(self):
+ return self._add_output_traits(super(AddCSVRow, self)._outputs())
+
+ def _add_output_traits(self, base):
+ return base
+
+
class CalculateNormalizedMomentsInputSpec(TraitedSpec):
timeseries_file = File(
exists=True, mandatory=True,
diff --git a/nipype/algorithms/tests/test_auto_AddCSVRow.py b/nipype/algorithms/tests/test_auto_AddCSVRow.py
new file mode 100644
index 0000000000..09b2c58adb
--- /dev/null
+++ b/nipype/algorithms/tests/test_auto_AddCSVRow.py
@@ -0,0 +1,28 @@
+# AUTO-GENERATED by tools/checkspecs.py - DO NOT EDIT
+from nipype.testing import assert_equal
+from nipype.algorithms.misc import AddCSVRow
+
+def test_AddCSVRow_inputs():
+ input_map = dict(_outputs=dict(usedefault=True,
+ ),
+ ignore_exception=dict(nohash=True,
+ usedefault=True,
+ ),
+ in_file=dict(mandatory=True,
+ ),
+ )
+ inputs = AddCSVRow.input_spec()
+
+ for key, metadata in input_map.items():
+ for metakey, value in metadata.items():
+ yield assert_equal, getattr(inputs.traits()[key], metakey), value
+
+def test_AddCSVRow_outputs():
+ output_map = dict(csv_file=dict(),
+ )
+ outputs = AddCSVRow.output_spec()
+
+ for key, metadata in output_map.items():
+ for metakey, value in metadata.items():
+ yield assert_equal, getattr(outputs.traits()[key], metakey), value
+
diff --git a/nipype/pipeline/engine.py b/nipype/pipeline/engine.py
index f32496259a..b92cc482a9 100644
--- a/nipype/pipeline/engine.py
+++ b/nipype/pipeline/engine.py
@@ -190,6 +190,8 @@ def _check_outputs(self, parameter):
return hasattr(self.outputs, parameter)
def _check_inputs(self, parameter):
+ if isinstance(self.inputs, DynamicTraitedSpec):
+ return True
return hasattr(self.inputs, parameter)
def _verify_name(self, name):