Repository: mne-tools/mne-python

Issue:
BUG: parse_folder() with n_jobs>1 for mne.Report causes attribute-error
#### Describe the bug
When changing the n_jobs-parameter for the parse_folder-method of mne.Report to a value greater than 1, an error is thrown.


#### Steps to reproduce
```python
import mne

path = mne.datasets.sample.data_path(verbose=False)
report = mne.Report(verbose=True)
report.parse_folder(path, pattern='*raw.fif', render_bem=False, n_jobs=2)
report.save('test.html', overwrite=True)
```

#### Expected results
No error.


#### Actual results
```python
Traceback (most recent call last):
  File "<decorator-gen-368>", line 22, in parse_folder
  File "C:\Users\marti\PycharmProjects\mne-python\mne\report.py", line 1620, in parse_folder
    r = parallel(p_fun(self, fname, info, cov, baseline, sfreq, on_error,
  File "C:\Users\marti\PycharmProjects\mne-python\mne\parallel.py", line 128, in run
    return fun(*args, **kwargs)
  File "C:\Users\marti\anaconda3\envs\mnedev\lib\site-packages\joblib\parallel.py", line 1054, in __call__
    self.retrieve()
  File "C:\Users\marti\anaconda3\envs\mnedev\lib\site-packages\joblib\parallel.py", line 933, in retrieve
    self._output.extend(job.get(timeout=self.timeout))
  File "C:\Users\marti\anaconda3\envs\mnedev\lib\site-packages\joblib\_parallel_backends.py", line 542, in wrap_future_result
    return future.result(timeout=timeout)
  File "C:\Users\marti\anaconda3\envs\mnedev\lib\concurrent\futures\_base.py", line 440, in result
    return self.__get_result()
  File "C:\Users\marti\anaconda3\envs\mnedev\lib\concurrent\futures\_base.py", line 389, in __get_result
    raise self._exception
AttributeError: 'Report' object has no attribute 'projs'
```


#### Additional information
The error seems to be coming from line 362 in report.py. The report-object doesn't have the _projs_-attribute as if somehow lost attributes when being passed to the new process in __iterate_files_ (line 1620). Other attributes as _data_path_ and _title_ seem to survive.


Discussion:
I can reproduce this locally, but so far I'm not sure where this is coming from … :(
@GuillaumeFavelier and I found a workaround by adding `prefer='threads'` to parallel_func in l. 1619 of report.py.
But this still doesn't explain the disappearing attribute
cc @larsoner 

Resolving patch:
diff --git a/mne/report.py b/mne/report.py
--- a/mne/report.py
+++ b/mne/report.py
@@ -1659,7 +1659,7 @@ def _get_state_params(self):
         # Note: self._fname is not part of the state
         return (['baseline', 'cov_fname', 'fnames', 'html', 'include',
                  'image_format', 'info_fname', 'initial_id', 'raw_psd',
-                 '_sectionlabels', 'sections', '_sectionvars',
+                 '_sectionlabels', 'sections', '_sectionvars', 'projs',
                  '_sort_sections', 'subjects_dir', 'subject', 'title',
                  'verbose'],
                 ['data_path', 'lang', '_sort'])


Test patch:
diff --git a/mne/tests/test_report.py b/mne/tests/test_report.py
--- a/mne/tests/test_report.py
+++ b/mne/tests/test_report.py
@@ -7,6 +7,7 @@
 import base64
 import copy
 import glob
+import pickle
 from io import BytesIO
 import os
 import os.path as op
@@ -625,4 +626,21 @@ def test_split_files(tmpdir, split_naming):
     assert len(report.fnames) == 1
 
 
+def test_survive_pickle(tmpdir):
+    """Testing functionality of Report-Object after pickling."""
+    tempdir = str(tmpdir)
+    raw_fname_new = op.join(tempdir, 'temp_raw.fif')
+    shutil.copyfile(raw_fname, raw_fname_new)
+
+    # Pickle report object to simulate multiprocessing with joblib
+    report = Report(info_fname=raw_fname_new)
+    pickled_report = pickle.dumps(report)
+    report = pickle.loads(pickled_report)
+
+    # Just test if no errors occur
+    report.parse_folder(tempdir, render_bem=False)
+    save_name = op.join(tempdir, 'report.html')
+    report.save(fname=save_name, open_browser=False)
+
+
 run_tests_if_main()