Repository: mne-tools/mne-python

Issue:
BUG: memmap error when buffersize is None but cache dir active
@Eric89GXL @mluessi 

this is uncaught, with a t-test type stat fun this crashes (tries to operate inplace on memmap)

``` Python
, ...], sample_shape=(108, 7829), buffer_size=None, progress_bar=<mne.utils.ProgressBar object>)
    626             # new surrogate data with random sign flip
    627             signs = np.sign(0.5 - rng.rand(n_samp))
    628             signs = signs[:, np.newaxis]
    629
    630         if buffer_size is None:
--> 631             X *= signs
    632             # Recompute statistic on randomized data
    633             T_obs_surr = stat_fun(X)
    634             # Set X back to previous state (trade memory eff. for CPU use)
    635             X *= signs

ValueError: output array is read-only
```



Discussion:

Resolving patch:
diff --git a/mne/parallel.py b/mne/parallel.py
--- a/mne/parallel.py
+++ b/mne/parallel.py
@@ -70,8 +70,8 @@ def parallel_func(func, n_jobs, verbose=None, max_nbytes='auto'):
             return parallel, my_func, n_jobs
 
     # check if joblib is recent enough to support memmaping
-    aspec = inspect.getargspec(Parallel.__init__)
-    joblib_mmap = ('temp_folder' in aspec.args and 'max_nbytes' in aspec.args)
+    p_args = inspect.getargspec(Parallel.__init__).args
+    joblib_mmap = ('temp_folder' in p_args and 'max_nbytes' in p_args)
 
     cache_dir = get_config('MNE_CACHE_DIR', None)
     if isinstance(max_nbytes, string_types) and max_nbytes == 'auto':
diff --git a/mne/stats/cluster_level.py b/mne/stats/cluster_level.py
--- a/mne/stats/cluster_level.py
+++ b/mne/stats/cluster_level.py
@@ -628,11 +628,15 @@ def _do_1samp_permutations(X, slices, threshold, tail, connectivity, stat_fun,
             signs = signs[:, np.newaxis]
 
         if buffer_size is None:
-            X *= signs
-            # Recompute statistic on randomized data
-            T_obs_surr = stat_fun(X)
-            # Set X back to previous state (trade memory eff. for CPU use)
-            X *= signs
+            # be careful about non-writable memmap (GH#1507)
+            if X.flags.writeable:
+                X *= signs
+                # Recompute statistic on randomized data
+                T_obs_surr = stat_fun(X)
+                # Set X back to previous state (trade memory eff. for CPU use)
+                X *= signs
+            else:
+                T_obs_surr = stat_fun(X * signs)
         else:
             # only sign-flip a small data buffer, so we need less memory
             T_obs_surr = np.empty(n_vars, dtype=X.dtype)


Test patch:
diff --git a/mne/stats/tests/test_cluster_level.py b/mne/stats/tests/test_cluster_level.py
--- a/mne/stats/tests/test_cluster_level.py
+++ b/mne/stats/tests/test_cluster_level.py
@@ -1,3 +1,5 @@
+import os
+import os.path as op
 import numpy as np
 from numpy.testing import (assert_equal, assert_array_equal,
                            assert_array_almost_equal)
@@ -11,7 +13,7 @@
                                      spatio_temporal_cluster_test,
                                      spatio_temporal_cluster_1samp_test,
                                      ttest_1samp_no_p, summarize_clusters_stc)
-from mne.utils import run_tests_if_main, slow_test
+from mne.utils import run_tests_if_main, slow_test, _TempDir, set_log_file
 
 warnings.simplefilter('always')  # enable b/c these tests throw warnings
 
@@ -42,6 +44,42 @@ def _get_conditions():
     return condition1_1d, condition2_1d, condition1_2d, condition2_2d
 
 
+def test_cache_dir():
+    """Test use of cache dir
+    """
+    tempdir = _TempDir()
+    orig_dir = os.getenv('MNE_CACHE_DIR', None)
+    orig_size = os.getenv('MNE_MEMMAP_MIN_SIZE', None)
+    rng = np.random.RandomState(0)
+    X = rng.randn(9, 2, 10)
+    log_file = op.join(tempdir, 'log.txt')
+    try:
+        os.environ['MNE_MEMMAP_MIN_SIZE'] = '1K'
+        os.environ['MNE_CACHE_DIR'] = tempdir
+        # Fix error for #1507: in-place when memmapping
+        permutation_cluster_1samp_test(
+            X, buffer_size=None, n_jobs=2, n_permutations=1,
+            seed=0, stat_fun=ttest_1samp_no_p, verbose=False)
+        # ensure that non-independence yields warning
+        stat_fun = partial(ttest_1samp_no_p, sigma=1e-3)
+        set_log_file(log_file)
+        permutation_cluster_1samp_test(
+            X, buffer_size=10, n_jobs=2, n_permutations=1,
+            seed=0, stat_fun=stat_fun, verbose=False)
+        with open(log_file, 'r') as fid:
+            assert_true('independently' in ''.join(fid.readlines()))
+    finally:
+        if orig_dir is not None:
+            os.environ['MNE_CACHE_DIR'] = orig_dir
+        else:
+            del os.environ['MNE_CACHE_DIR']
+        if orig_size is not None:
+            os.environ['MNE_MEMMAP_MIN_SIZE'] = orig_size
+        else:
+            del os.environ['MNE_MEMMAP_MIN_SIZE']
+        set_log_file(None)
+
+
 def test_permutation_step_down_p():
     """Test cluster level permutations with step_down_p
     """