Repository: nilearn/nilearn

Issue:
signal.clean does not accept CSV files delimited by a comma.
Minimal test case:

``` python
import numpy as np
from nilearn import signal


n_samples = 10
n_features = 50
data = np.random.random((n_samples, n_features))

n_confounds = 6
confounds = np.random.random((n_samples, n_confounds))

confounds_file = 'tmp.csv'
np.savetxt(confounds_file, confounds, delimiter=',')

signal.clean(data, confounds=confounds_file)
```

The problem here is that the file is given to np.genfromtxt which interpret each line as a nan and therefore thinks that the first line is a header and remove it. We should use a more clever heuristic and also maybe try to find a more flexible way to load CSV files.



Discussion:
Also discussed with @AlexandreAbraham 


Resolving patch:
diff --git a/nilearn/_utils/numpy_conversions.py b/nilearn/_utils/numpy_conversions.py
--- a/nilearn/_utils/numpy_conversions.py
+++ b/nilearn/_utils/numpy_conversions.py
@@ -4,7 +4,9 @@
 # Author: Gael Varoquaux, Alexandre Abraham, Philippe Gervais
 # License: simplified BSD
 
+import csv
 import numpy as np
+from .compat import _basestring
 
 
 def _asarray(arr, dtype=None, order=None):
@@ -124,3 +126,45 @@ def as_ndarray(arr, copy=False, dtype=None, order='K'):
         raise ValueError("Type not handled: %s" % arr.__class__)
 
     return ret
+
+
+def csv_to_array(csv_path, delimiters=' \t,;', **kwargs):
+    """Read a CSV file by trying to guess its delimiter
+
+    Parameters
+    ----------
+    csv_path: string
+        Path of the CSV file to load.
+
+    delimiters: string
+        Each character of the delimiters string is a potential delimiters for
+        the CSV file.
+
+    kwargs: keyword arguments
+        The additional keyword arguments are passed to numpy.genfromtxt when
+        loading the CSV.
+
+    Returns
+    -------
+    array: numpy.ndarray
+        An array containing the data loaded from the CSV file.
+    """
+    if not isinstance(csv_path, _basestring):
+        raise TypeError('CSV must be a file path. Got a CSV of type: %s' %
+                        type(csv_path))
+    # First, we try genfromtxt which works in most cases.
+    array = np.genfromtxt(csv_path, **kwargs)
+
+    if array.ndim <= 1 and np.all(np.isnan(array)):
+        # If the delimiter is not known genfromtxt generates an array full of
+        # nan. In that case, we try to guess the delimiter
+        try:
+            with open(csv_path, 'r') as csv_file:
+                dialect = csv.Sniffer().sniff(csv_file.readline(), delimiters)
+        except csv.Error as e:
+            raise TypeError(
+                'Could not read CSV file [%s]: %s' % (csv_path, e.args[0]))
+
+        array = np.genfromtxt(csv_path, delimiter=dialect.delimiter, **kwargs)
+
+    return array
diff --git a/nilearn/signal.py b/nilearn/signal.py
--- a/nilearn/signal.py
+++ b/nilearn/signal.py
@@ -17,6 +17,7 @@
 from distutils.version import LooseVersion
 
 from ._utils.compat import _basestring
+from ._utils.numpy_conversions import csv_to_array
 
 np_version = distutils.version.LooseVersion(np.version.short_version).version
 
@@ -423,13 +424,13 @@ def clean(signals, detrend=True, standardize=True, confounds=None,
         for confound in confounds:
             if isinstance(confound, _basestring):
                 filename = confound
-                confound = np.genfromtxt(filename)
+                confound = csv_to_array(filename)
                 if np.isnan(confound.flat[0]):
                     # There may be a header
                     if np_version >= [1, 4, 0]:
-                        confound = np.genfromtxt(filename, skip_header=1)
+                        confound = csv_to_array(filename, skip_header=1)
                     else:
-                        confound = np.genfromtxt(filename, skiprows=1)
+                        confound = csv_to_array(filename, skiprows=1)
                 if confound.shape[0] != signals.shape[0]:
                     raise ValueError("Confound signal has an incorrect length")
 


Test patch:
diff --git a/nilearn/tests/test_numpy_conversions.py b/nilearn/tests/test_numpy_conversions.py
--- a/nilearn/tests/test_numpy_conversions.py
+++ b/nilearn/tests/test_numpy_conversions.py
@@ -1,15 +1,16 @@
 """
 Test the numpy_conversions module
 
-This test file is in nilearn/tests because nosetests seems to ignore modules whose
-name starts with an underscore
+This test file is in nilearn/tests because nosetests seems to ignore modules
+whose name starts with an underscore
 """
 import numpy as np
 import os
+import tempfile
 
 from nose.tools import assert_true, assert_raises
 
-from nilearn._utils.numpy_conversions import as_ndarray
+from nilearn._utils.numpy_conversions import as_ndarray, csv_to_array
 
 
 def are_arrays_identical(arr1, arr2):
@@ -230,3 +231,13 @@ def test_as_ndarray():
     ## Unhandled cases
     assert_raises(ValueError, as_ndarray, "test string")
     assert_raises(ValueError, as_ndarray, [], order="invalid")
+
+
+def test_csv_to_array():
+    # Create a phony CSV file
+    with tempfile.NamedTemporaryFile(suffix='.csv', mode='wt') as csv_file:
+        csv_file.write('1.,2.,3.,4.,5.\n')
+        csv_file.flush()
+        assert_true(np.allclose(csv_to_array(csv_file.name),
+                    np.asarray([1., 2., 3., 4., 5.])))
+        assert_raises(TypeError, csv_to_array, csv_file.name, delimiters='?!')